mirror of
https://github.com/Raghu-Ch/angular-challenges.git
synced 2026-02-10 12:53:03 -05:00
Merge branch 'main' into all-contributors/add-kabrunko-dev
This commit is contained in:
@@ -26,9 +26,9 @@
|
|||||||
"symbol": "🇵🇹",
|
"symbol": "🇵🇹",
|
||||||
"description": "Translate in Portuguese"
|
"description": "Translate in Portuguese"
|
||||||
},
|
},
|
||||||
"translation-pt-BR": {
|
"translation-ru": {
|
||||||
"symbol": "🇧🇷",
|
"symbol": "🇷🇺",
|
||||||
"description": "Translate in Brazilian Portuguese"
|
"description": "Translate in Russian"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"contributors": [
|
"contributors": [
|
||||||
@@ -47,6 +47,38 @@
|
|||||||
"translation-fr"
|
"translation-fr"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"login": "jdegand",
|
||||||
|
"name": "J. Degand",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/70610011?v=4",
|
||||||
|
"profile": "https://github.com/jdegand",
|
||||||
|
"contributions": [
|
||||||
|
"doc",
|
||||||
|
"content",
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "DeveshChau",
|
||||||
|
"name": "Devesh Chaudhari",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/9509673?v=4",
|
||||||
|
"profile": "https://github.com/DeveshChau",
|
||||||
|
"contributions": [
|
||||||
|
"code",
|
||||||
|
"bug",
|
||||||
|
"challenge"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "stillst",
|
||||||
|
"name": "stillst",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/1463098?v=4",
|
||||||
|
"profile": "https://github.com/stillst",
|
||||||
|
"contributions": [
|
||||||
|
"challenge",
|
||||||
|
"translation-ru"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"login": "alan-bio",
|
"login": "alan-bio",
|
||||||
"name": "Alan Dragicevich",
|
"name": "Alan Dragicevich",
|
||||||
@@ -94,26 +126,6 @@
|
|||||||
"code"
|
"code"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"login": "jdegand",
|
|
||||||
"name": "J. Degand",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/70610011?v=4",
|
|
||||||
"profile": "https://github.com/jdegand",
|
|
||||||
"contributions": [
|
|
||||||
"doc"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "DeveshChau",
|
|
||||||
"name": "Devesh Chaudhari",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/9509673?v=4",
|
|
||||||
"profile": "https://github.com/DeveshChau",
|
|
||||||
"contributions": [
|
|
||||||
"code",
|
|
||||||
"bug",
|
|
||||||
"challenge"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"login": "dmmishchenko",
|
"login": "dmmishchenko",
|
||||||
"name": "Dmitriy Mishchenko",
|
"name": "Dmitriy Mishchenko",
|
||||||
@@ -157,9 +169,28 @@
|
|||||||
"avatar_url": "https://avatars.githubusercontent.com/u/142346548?v=4",
|
"avatar_url": "https://avatars.githubusercontent.com/u/142346548?v=4",
|
||||||
"profile": "https://github.com/kabrunko-dev/",
|
"profile": "https://github.com/kabrunko-dev/",
|
||||||
"contributions": [
|
"contributions": [
|
||||||
"translation-pt-BR",
|
"code",
|
||||||
|
"translation-pt",
|
||||||
|
"doc"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "ErickRodrCodes",
|
||||||
|
"name": "Erick Rodriguez",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/1978642?v=4",
|
||||||
|
"profile": "http://www.streamoverlaypro.com",
|
||||||
|
"contributions": [
|
||||||
|
"translation-es"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "eduardoRoth",
|
||||||
|
"name": "Eduardo Roth",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/5419161?v=4",
|
||||||
|
"profile": "https://eduardoroth.dev",
|
||||||
|
"contributions": [
|
||||||
"doc",
|
"doc",
|
||||||
"code"
|
"translation-es"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
2
.github/workflows/close-inactive-pr.yml
vendored
2
.github/workflows/close-inactive-pr.yml
vendored
@@ -16,10 +16,12 @@ jobs:
|
|||||||
days-before-issue-close: -1
|
days-before-issue-close: -1
|
||||||
stale-issue-label: 'stale'
|
stale-issue-label: 'stale'
|
||||||
stale-issue-message: 'This issue is stale because it has been open for 20 days with no activity.'
|
stale-issue-message: 'This issue is stale because it has been open for 20 days with no activity.'
|
||||||
|
exempt-issue-labels: 'long-term'
|
||||||
days-before-pr-stale: 20
|
days-before-pr-stale: 20
|
||||||
days-before-pr-close: 7
|
days-before-pr-close: 7
|
||||||
stale-pr-label: 'stale'
|
stale-pr-label: 'stale'
|
||||||
stale-pr-message: 'This pull request is stale because it has been open for 20 days with no activity.'
|
stale-pr-message: 'This pull request is stale because it has been open for 20 days with no activity.'
|
||||||
close-pr-message: 'This pull request was closed because it has been inactive for 7 days since being marked as stale.'
|
close-pr-message: 'This pull request was closed because it has been inactive for 7 days since being marked as stale.'
|
||||||
only-pr-labels: 'answer'
|
only-pr-labels: 'answer'
|
||||||
|
exempt-pr-labels: 'challenge-creation, long-term'
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -19,7 +19,6 @@ node_modules
|
|||||||
|
|
||||||
# IDE - VSCode
|
# IDE - VSCode
|
||||||
.vscode/*
|
.vscode/*
|
||||||
!.vscode/settings.json
|
|
||||||
!.vscode/tasks.json
|
!.vscode/tasks.json
|
||||||
!.vscode/launch.json
|
!.vscode/launch.json
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
|
|||||||
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@@ -1,3 +1,6 @@
|
|||||||
{
|
{
|
||||||
"eslint.validate": ["json"]
|
"eslint.validate": [
|
||||||
|
"json"
|
||||||
|
],
|
||||||
|
"cSpell.language": "en,es-ES"
|
||||||
}
|
}
|
||||||
|
|||||||
17
README.md
17
README.md
@@ -24,7 +24,7 @@ If you would like to propose a challenge, this project is open source, so feel f
|
|||||||
|
|
||||||
## Challenges
|
## Challenges
|
||||||
|
|
||||||
Check [all 40 challenges](https://angular-challenges.vercel.app/)
|
Check [all 43 challenges](https://angular-challenges.vercel.app/)
|
||||||
|
|
||||||
## Contributors ✨
|
## Contributors ✨
|
||||||
|
|
||||||
@@ -35,20 +35,25 @@ Check [all 40 challenges](https://angular-challenges.vercel.app/)
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://thomaslaforge.dev/home"><img src="https://avatars.githubusercontent.com/u/30832608?v=4?s=100" width="100px;" alt="Laforge Thomas"/><br /><sub><b>Laforge Thomas</b></sub></a><br /><a href="#challenge-tomalaforge" title="Create a challenge">🧩</a> <a href="https://github.com/tomalaforge/angular-challenges/commits?author=tomalaforge" title="Code">💻</a> <a href="https://github.com/tomalaforge/angular-challenges/commits?author=tomalaforge" title="Documentation">📖</a> <a href="#content-tomalaforge" title="Content">🖋</a> <a href="#ideas-tomalaforge" title="Ideas, Planning, & Feedback">🤔</a> <a href="#design-tomalaforge" title="Design">🎨</a> <a href="#translation-fr-tomalaforge" title="Translate in French">🇫🇷</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://thomaslaforge.dev/home"><img src="https://avatars.githubusercontent.com/u/30832608?v=4?s=100" width="100px;" alt="Laforge Thomas"/><br /><sub><b>Laforge Thomas</b></sub></a><br /><a href="#challenge-tomalaforge" title="Create a challenge">🧩</a> <a href="https://github.com/tomalaforge/angular-challenges/commits?author=tomalaforge" title="Code">💻</a> <a href="https://github.com/tomalaforge/angular-challenges/commits?author=tomalaforge" title="Documentation">📖</a> <a href="#content-tomalaforge" title="Content">🖋</a> <a href="#ideas-tomalaforge" title="Ideas, Planning, & Feedback">🤔</a> <a href="#design-tomalaforge" title="Design">🎨</a> <a href="#translation-fr-tomalaforge" title="Translate in French">🇫🇷</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jdegand"><img src="https://avatars.githubusercontent.com/u/70610011?v=4?s=100" width="100px;" alt="J. Degand"/><br /><sub><b>J. Degand</b></sub></a><br /><a href="https://github.com/tomalaforge/angular-challenges/commits?author=jdegand" title="Documentation">📖</a> <a href="#content-jdegand" title="Content">🖋</a> <a href="https://github.com/tomalaforge/angular-challenges/commits?author=jdegand" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/DeveshChau"><img src="https://avatars.githubusercontent.com/u/9509673?v=4?s=100" width="100px;" alt="Devesh Chaudhari"/><br /><sub><b>Devesh Chaudhari</b></sub></a><br /><a href="https://github.com/tomalaforge/angular-challenges/commits?author=DeveshChau" title="Code">💻</a> <a href="https://github.com/tomalaforge/angular-challenges/issues?q=author%3ADeveshChau" title="Bug reports">🐛</a> <a href="#challenge-DeveshChau" title="Create a challenge">🧩</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/stillst"><img src="https://avatars.githubusercontent.com/u/1463098?v=4?s=100" width="100px;" alt="stillst"/><br /><sub><b>stillst</b></sub></a><br /><a href="#challenge-stillst" title="Create a challenge">🧩</a> <a href="#translation-ru-stillst" title="Translate in Russian">🇷🇺</a></td>
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/alan-bio"><img src="https://avatars.githubusercontent.com/u/31838230?v=4?s=100" width="100px;" alt="Alan Dragicevich"/><br /><sub><b>Alan Dragicevich</b></sub></a><br /><a href="https://github.com/tomalaforge/angular-challenges/commits?author=alan-bio" title="Documentation">📖</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/alan-bio"><img src="https://avatars.githubusercontent.com/u/31838230?v=4?s=100" width="100px;" alt="Alan Dragicevich"/><br /><sub><b>Alan Dragicevich</b></sub></a><br /><a href="https://github.com/tomalaforge/angular-challenges/commits?author=alan-bio" title="Documentation">📖</a></td>
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/edimitchel"><img src="https://avatars.githubusercontent.com/u/2922851?v=4?s=100" width="100px;" alt="Michel EDIGHOFFER"/><br /><sub><b>Michel EDIGHOFFER</b></sub></a><br /><a href="https://github.com/tomalaforge/angular-challenges/commits?author=edimitchel" title="Documentation">📖</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/edimitchel"><img src="https://avatars.githubusercontent.com/u/2922851?v=4?s=100" width="100px;" alt="Michel EDIGHOFFER"/><br /><sub><b>Michel EDIGHOFFER</b></sub></a><br /><a href="https://github.com/tomalaforge/angular-challenges/commits?author=edimitchel" title="Documentation">📖</a></td>
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/gsgonzalez88"><img src="https://avatars.githubusercontent.com/u/39884678?v=4?s=100" width="100px;" alt="Gerardo Sebastian Gonzalez"/><br /><sub><b>Gerardo Sebastian Gonzalez</b></sub></a><br /><a href="https://github.com/tomalaforge/angular-challenges/commits?author=gsgonzalez88" title="Documentation">📖</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/gsgonzalez88"><img src="https://avatars.githubusercontent.com/u/39884678?v=4?s=100" width="100px;" alt="Gerardo Sebastian Gonzalez"/><br /><sub><b>Gerardo Sebastian Gonzalez</b></sub></a><br /><a href="https://github.com/tomalaforge/angular-challenges/commits?author=gsgonzalez88" title="Documentation">📖</a></td>
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/marryday"><img src="https://avatars.githubusercontent.com/u/57489315?v=4?s=100" width="100px;" alt="Evseev Yuriy"/><br /><sub><b>Evseev Yuriy</b></sub></a><br /><a href="https://github.com/tomalaforge/angular-challenges/issues?q=author%3Amarryday" title="Bug reports">🐛</a></td>
|
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/tomer953"><img src="https://avatars.githubusercontent.com/u/1807493?v=4?s=100" width="100px;" alt="Tomer953"/><br /><sub><b>Tomer953</b></sub></a><br /><a href="https://github.com/tomalaforge/angular-challenges/issues?q=author%3Atomer953" title="Bug reports">🐛</a> <a href="https://github.com/tomalaforge/angular-challenges/commits?author=tomer953" title="Documentation">📖</a> <a href="https://github.com/tomalaforge/angular-challenges/commits?author=tomer953" title="Code">💻</a></td>
|
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jdegand"><img src="https://avatars.githubusercontent.com/u/70610011?v=4?s=100" width="100px;" alt="J. Degand"/><br /><sub><b>J. Degand</b></sub></a><br /><a href="https://github.com/tomalaforge/angular-challenges/commits?author=jdegand" title="Documentation">📖</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/DeveshChau"><img src="https://avatars.githubusercontent.com/u/9509673?v=4?s=100" width="100px;" alt="Devesh Chaudhari"/><br /><sub><b>Devesh Chaudhari</b></sub></a><br /><a href="https://github.com/tomalaforge/angular-challenges/commits?author=DeveshChau" title="Code">💻</a> <a href="https://github.com/tomalaforge/angular-challenges/issues?q=author%3ADeveshChau" title="Bug reports">🐛</a> <a href="#challenge-DeveshChau" title="Create a challenge">🧩</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/marryday"><img src="https://avatars.githubusercontent.com/u/57489315?v=4?s=100" width="100px;" alt="Evseev Yuriy"/><br /><sub><b>Evseev Yuriy</b></sub></a><br /><a href="https://github.com/tomalaforge/angular-challenges/issues?q=author%3Amarryday" title="Bug reports">🐛</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/tomer953"><img src="https://avatars.githubusercontent.com/u/1807493?v=4?s=100" width="100px;" alt="Tomer953"/><br /><sub><b>Tomer953</b></sub></a><br /><a href="https://github.com/tomalaforge/angular-challenges/issues?q=author%3Atomer953" title="Bug reports">🐛</a> <a href="https://github.com/tomalaforge/angular-challenges/commits?author=tomer953" title="Documentation">📖</a> <a href="https://github.com/tomalaforge/angular-challenges/commits?author=tomer953" title="Code">💻</a></td>
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/dmmishchenko"><img src="https://avatars.githubusercontent.com/u/51910160?v=4?s=100" width="100px;" alt="Dmitriy Mishchenko"/><br /><sub><b>Dmitriy Mishchenko</b></sub></a><br /><a href="https://github.com/tomalaforge/angular-challenges/commits?author=dmmishchenko" title="Documentation">📖</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/dmmishchenko"><img src="https://avatars.githubusercontent.com/u/51910160?v=4?s=100" width="100px;" alt="Dmitriy Mishchenko"/><br /><sub><b>Dmitriy Mishchenko</b></sub></a><br /><a href="https://github.com/tomalaforge/angular-challenges/commits?author=dmmishchenko" title="Documentation">📖</a></td>
|
||||||
<td align="center" valign="top" width="14.28%"><a href="http://www.sagardev.com.np"><img src="https://avatars.githubusercontent.com/u/30800393?v=4?s=100" width="100px;" alt="Sagar Devkota"/><br /><sub><b>Sagar Devkota</b></sub></a><br /><a href="https://github.com/tomalaforge/angular-challenges/commits?author=Sagardevkota" title="Documentation">📖</a> <a href="https://github.com/tomalaforge/angular-challenges/commits?author=Sagardevkota" title="Code">💻</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="http://www.sagardev.com.np"><img src="https://avatars.githubusercontent.com/u/30800393?v=4?s=100" width="100px;" alt="Sagar Devkota"/><br /><sub><b>Sagar Devkota</b></sub></a><br /><a href="https://github.com/tomalaforge/angular-challenges/commits?author=Sagardevkota" title="Documentation">📖</a> <a href="https://github.com/tomalaforge/angular-challenges/commits?author=Sagardevkota" title="Code">💻</a></td>
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://nelsonguti.dev/"><img src="https://avatars.githubusercontent.com/u/62297014?v=4?s=100" width="100px;" alt="Nelson Gutierrez"/><br /><sub><b>Nelson Gutierrez</b></sub></a><br /><a href="#translation-es-nelsongutidev" title="Translate in Spanish">🇪🇸</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://nelsonguti.dev/"><img src="https://avatars.githubusercontent.com/u/62297014?v=4?s=100" width="100px;" alt="Nelson Gutierrez"/><br /><sub><b>Nelson Gutierrez</b></sub></a><br /><a href="#translation-es-nelsongutidev" title="Translate in Spanish">🇪🇸</a></td>
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ho-ssain"><img src="https://avatars.githubusercontent.com/u/61125174?v=4?s=100" width="100px;" alt="Hossain K. M."/><br /><sub><b>Hossain K. M.</b></sub></a><br /><a href="https://github.com/tomalaforge/angular-challenges/commits?author=ho-ssain" title="Documentation">📖</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ho-ssain"><img src="https://avatars.githubusercontent.com/u/61125174?v=4?s=100" width="100px;" alt="Hossain K. M."/><br /><sub><b>Hossain K. M.</b></sub></a><br /><a href="https://github.com/tomalaforge/angular-challenges/commits?author=ho-ssain" title="Documentation">📖</a></td>
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/kabrunko-dev/"><img src="https://avatars.githubusercontent.com/u/142346548?v=4?s=100" width="100px;" alt="Diogo Nishikawa"/><br /><sub><b>Diogo Nishikawa</b></sub></a><br /><a href="#translation-pt-BR-kabrunko-dev" title="Translate in Brazilian Portuguese">🇧🇷</a> <a href="https://github.com/tomalaforge/angular-challenges/commits?author=kabrunko-dev" title="Documentation">📖</a> <a href="https://github.com/tomalaforge/angular-challenges/commits?author=kabrunko-dev" title="Code">💻</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/kabrunko-dev/"><img src="https://avatars.githubusercontent.com/u/142346548?v=4?s=100" width="100px;" alt="Diogo Nishikawa"/><br /><sub><b>Diogo Nishikawa</b></sub></a><br /><a href="#translation-pt-kabrunko-dev" title="Translate in Portuguese">🇵🇹</a> <a href="https://github.com/tomalaforge/angular-challenges/commits?author=kabrunko-dev" title="Documentation">📖</a> <a href="https://github.com/tomalaforge/angular-challenges/commits?author=kabrunko-dev" title="Code">💻</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="http://www.streamoverlaypro.com"><img src="https://avatars.githubusercontent.com/u/1978642?v=4?s=100" width="100px;" alt="Erick Rodriguez"/><br /><sub><b>Erick Rodriguez</b></sub></a><br /><a href="#translation-es-ErickRodrCodes" title="Translate in Spanish">🇪🇸</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://eduardoroth.dev"><img src="https://avatars.githubusercontent.com/u/5419161?v=4?s=100" width="100px;" alt="Eduardo Roth"/><br /><sub><b>Eduardo Roth</b></sub></a><br /><a href="https://github.com/tomalaforge/angular-challenges/commits?author=eduardoRoth" title="Documentation">📖</a> <a href="#translation-es-eduardoRoth" title="Translate in Spanish">🇪🇸</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
<tfoot>
|
<tfoot>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
export default {
|
export default {
|
||||||
displayName: 'angular-anchor-scrolling',
|
displayName: 'angular-anchor-scrolling',
|
||||||
preset: '../../../jest.preset.js',
|
preset: '../../../jest.preset.js',
|
||||||
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
|
setupFilesAfterEnv: ['<rootDir>/src/subscription-setup.ts'],
|
||||||
coverageDirectory: '../../../coverage/apps/angular/anchor-scrolling',
|
coverageDirectory: '../../../coverage/apps/angular/anchor-scrolling',
|
||||||
transform: {
|
transform: {
|
||||||
'^.+\\.(ts|mjs|js|html)$': [
|
'^.+\\.(ts|mjs|js|html)$': [
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { RouterOutlet } from '@angular/router';
|
|||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [RouterOutlet],
|
imports: [RouterOutlet],
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
template: ` <router-outlet></router-outlet> `,
|
template: `
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
`,
|
||||||
})
|
})
|
||||||
export class AppComponent {}
|
export class AppComponent {}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { NavButtonComponent } from './nav-button.component';
|
|||||||
selector: 'app-foo',
|
selector: 'app-foo',
|
||||||
template: `
|
template: `
|
||||||
Welcome to foo page
|
Welcome to foo page
|
||||||
<nav-button href="home" class="fixed top-3 left-1/2">Home Page</nav-button>
|
<nav-button href="home" class="fixed left-1/2 top-3">Home Page</nav-button>
|
||||||
<div class="h-screen bg-blue-200">section 1</div>
|
<div class="h-screen bg-blue-200">section 1</div>
|
||||||
<div class="h-screen bg-red-200">section 2</div>
|
<div class="h-screen bg-red-200">section 2</div>
|
||||||
`,
|
`,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { NavButtonComponent } from './nav-button.component';
|
|||||||
imports: [NavButtonComponent],
|
imports: [NavButtonComponent],
|
||||||
selector: 'app-home',
|
selector: 'app-home',
|
||||||
template: `
|
template: `
|
||||||
<nav-button href="/foo" class="fixed top-3 left-1/2">Foo Page</nav-button>
|
<nav-button href="/foo" class="fixed left-1/2 top-3">Foo Page</nav-button>
|
||||||
<div id="top" class="h-screen bg-gray-500">
|
<div id="top" class="h-screen bg-gray-500">
|
||||||
Empty
|
Empty
|
||||||
<nav-button href="#bottom">Scroll Bottom</nav-button>
|
<nav-button href="#bottom">Scroll Bottom</nav-button>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { appConfig } from './app/app.config';
|
|
||||||
import { bootstrapApplication } from '@angular/platform-browser';
|
import { bootstrapApplication } from '@angular/platform-browser';
|
||||||
|
import { appConfig } from './app/app.config';
|
||||||
|
|
||||||
import { AppComponent } from './app/app.component';
|
import { AppComponent } from './app/app.component';
|
||||||
|
|
||||||
bootstrapApplication(AppComponent, appConfig).catch((err) =>
|
bootstrapApplication(AppComponent, appConfig).catch((err) =>
|
||||||
console.error(err)
|
console.error(err),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
export default {
|
export default {
|
||||||
displayName: 'angular-bug-cd',
|
displayName: 'angular-bug-cd',
|
||||||
preset: '../../../jest.preset.js',
|
preset: '../../../jest.preset.js',
|
||||||
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
|
setupFilesAfterEnv: ['<rootDir>/src/subscription-setup.ts'],
|
||||||
coverageDirectory: '../../../coverage/apps/angular/bug-cd',
|
coverageDirectory: '../../../coverage/apps/angular/bug-cd',
|
||||||
transform: {
|
transform: {
|
||||||
'^.+\\.(ts|mjs|js|html)$': [
|
'^.+\\.(ts|mjs|js|html)$': [
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { RouterOutlet } from '@angular/router';
|
|||||||
imports: [RouterOutlet],
|
imports: [RouterOutlet],
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
template: `
|
template: `
|
||||||
<h1 class="text-xl px-4 py-2">My Application</h1>
|
<h1 class="px-4 py-2 text-xl">My Application</h1>
|
||||||
<section class="flex">
|
<section class="flex">
|
||||||
<router-outlet name="side" />
|
<router-outlet name="side" />
|
||||||
<div class="border p-4">
|
<div class="border p-4">
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { Component } from '@angular/core';
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'app-bar',
|
selector: 'app-bar',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
template: ` BarComponent `,
|
template: `
|
||||||
|
BarComponent
|
||||||
|
`,
|
||||||
})
|
})
|
||||||
export class BarComponent {}
|
export class BarComponent {}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { Component } from '@angular/core';
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'app-foo',
|
selector: 'app-foo',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
template: `Foo Component `,
|
template: `
|
||||||
|
Foo Component
|
||||||
|
`,
|
||||||
})
|
})
|
||||||
export class FooComponent {}
|
export class FooComponent {}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ interface MenuItem {
|
|||||||
template: `
|
template: `
|
||||||
<ng-container *ngFor="let menu of menus">
|
<ng-container *ngFor="let menu of menus">
|
||||||
<a
|
<a
|
||||||
class="border px-4 py-2 rounded-md"
|
class="rounded-md border px-4 py-2"
|
||||||
[routerLink]="menu.path"
|
[routerLink]="menu.path"
|
||||||
routerLinkActive="isSelected">
|
routerLinkActive="isSelected">
|
||||||
{{ menu.name }}
|
{{ menu.name }}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { bootstrapApplication } from '@angular/platform-browser';
|
import { bootstrapApplication } from '@angular/platform-browser';
|
||||||
import { appConfig } from './app/app.config';
|
|
||||||
import { AppComponent } from './app/app.component';
|
import { AppComponent } from './app/app.component';
|
||||||
|
import { appConfig } from './app/app.config';
|
||||||
|
|
||||||
bootstrapApplication(AppComponent, appConfig).catch((err) =>
|
bootstrapApplication(AppComponent, appConfig).catch((err) =>
|
||||||
console.error(err)
|
console.error(err),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -17,11 +17,10 @@ import {
|
|||||||
*ngTemplateOutlet="
|
*ngTemplateOutlet="
|
||||||
listTemplateRef || emptyRef;
|
listTemplateRef || emptyRef;
|
||||||
context: { $implicit: item, appList: item, index: i }
|
context: { $implicit: item, appList: item, index: i }
|
||||||
">
|
"></ng-container>
|
||||||
</ng-container>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-template #emptyRef> No Template </ng-template>
|
<ng-template #emptyRef>No Template</ng-template>
|
||||||
`,
|
`,
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ interface Person {
|
|||||||
context: { $implicit: person.name, age: person.age }
|
context: { $implicit: person.name, age: person.age }
|
||||||
"></ng-container>
|
"></ng-container>
|
||||||
|
|
||||||
<ng-template #emptyRef> No Template </ng-template>
|
<ng-template #emptyRef>No Template</ng-template>
|
||||||
`,
|
`,
|
||||||
})
|
})
|
||||||
export class PersonComponent {
|
export class PersonComponent {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
export default {
|
export default {
|
||||||
displayName: 'angular-crud',
|
displayName: 'angular-crud',
|
||||||
preset: '../../../jest.preset.js',
|
preset: '../../../jest.preset.js',
|
||||||
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
|
setupFilesAfterEnv: ['<rootDir>/src/subscription-setup.ts'],
|
||||||
globals: {},
|
globals: {},
|
||||||
coverageDirectory: '../../../coverage/apps/angular/crud',
|
coverageDirectory: '../../../coverage/apps/angular/crud',
|
||||||
transform: {
|
transform: {
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export class AppComponent implements OnInit {
|
|||||||
headers: {
|
headers: {
|
||||||
'Content-type': 'application/json; charset=UTF-8',
|
'Content-type': 'application/json; charset=UTF-8',
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
.subscribe((todoUpdated: any) => {
|
.subscribe((todoUpdated: any) => {
|
||||||
this.todos[todoUpdated.id - 1] = todoUpdated;
|
this.todos[todoUpdated.id - 1] = todoUpdated;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { ApplicationConfig } from '@angular/core';
|
|
||||||
import { enableProdMode, importProvidersFrom } from '@angular/core';
|
|
||||||
import { HttpClientModule } from '@angular/common/http';
|
import { HttpClientModule } from '@angular/common/http';
|
||||||
|
import { ApplicationConfig, importProvidersFrom } from '@angular/core';
|
||||||
export const appConfig: ApplicationConfig = {
|
export const appConfig: ApplicationConfig = {
|
||||||
providers: [importProvidersFrom(HttpClientModule)],
|
providers: [importProvidersFrom(HttpClientModule)],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
|
|||||||
@@ -4,5 +4,5 @@ import { bootstrapApplication } from '@angular/platform-browser';
|
|||||||
import { AppComponent } from './app/app.component';
|
import { AppComponent } from './app/app.component';
|
||||||
|
|
||||||
bootstrapApplication(AppComponent, appConfig).catch((err) =>
|
bootstrapApplication(AppComponent, appConfig).catch((err) =>
|
||||||
console.error(err)
|
console.error(err),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import { Component } from '@angular/core';
|
|||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [BtnDisabledDirective, BtnHelmetDirective],
|
imports: [BtnDisabledDirective, BtnHelmetDirective],
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
template: ` <button btnDisabled hlm>Coucou</button> `,
|
template: `
|
||||||
|
<button btnDisabled hlm>Coucou</button>
|
||||||
|
`,
|
||||||
})
|
})
|
||||||
export class AppComponent {}
|
export class AppComponent {}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ interface ProductContext {
|
|||||||
export class ProductDirective {
|
export class ProductDirective {
|
||||||
static ngTemplateContextGuard(
|
static ngTemplateContextGuard(
|
||||||
dir: ProductDirective,
|
dir: ProductDirective,
|
||||||
ctx: unknown
|
ctx: unknown,
|
||||||
): ctx is ProductContext {
|
): ctx is ProductContext {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export class CurrencyPipe implements PipeTransform {
|
|||||||
|
|
||||||
transform(price: number) {
|
transform(price: number) {
|
||||||
return this.currencyService.symbol$.pipe(
|
return this.currencyService.symbol$.pipe(
|
||||||
map((s) => `${String(price)}${s}`)
|
map((s) => `${String(price)}${s}`),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export const currency: Currency[] = [
|
|||||||
export class CurrencyService extends ComponentStore<{ code: string }> {
|
export class CurrencyService extends ComponentStore<{ code: string }> {
|
||||||
readonly code$ = this.select((state) => state.code);
|
readonly code$ = this.select((state) => state.code);
|
||||||
readonly symbol$ = this.code$.pipe(
|
readonly symbol$ = this.code$.pipe(
|
||||||
map((code) => currency.find((c) => c.code === code)?.symbol ?? code)
|
map((code) => currency.find((c) => c.code === code)?.symbol ?? code),
|
||||||
);
|
);
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
export default {
|
export default {
|
||||||
displayName: 'angular-injection-token',
|
displayName: 'angular-injection-token',
|
||||||
preset: '../../../jest.preset.js',
|
preset: '../../../jest.preset.js',
|
||||||
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
|
setupFilesAfterEnv: ['<rootDir>/src/subscription-setup.ts'],
|
||||||
coverageDirectory: '../../../coverage/apps/angular/injection-token',
|
coverageDirectory: '../../../coverage/apps/angular/injection-token',
|
||||||
transform: {
|
transform: {
|
||||||
'^.+\\.(ts|mjs|js|html)$': [
|
'^.+\\.(ts|mjs|js|html)$': [
|
||||||
|
|||||||
@@ -5,15 +5,17 @@ import { RouterLink, RouterOutlet } from '@angular/router';
|
|||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [RouterOutlet, RouterLink],
|
imports: [RouterOutlet, RouterLink],
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
template: ` <div class="flex gap-4 mb-5">
|
template: `
|
||||||
<button class="border rounded-md px-4 py-2" routerLink="video">
|
<div class="mb-5 flex gap-4">
|
||||||
|
<button class="rounded-md border px-4 py-2" routerLink="video">
|
||||||
Video
|
Video
|
||||||
</button>
|
</button>
|
||||||
<button class="border rounded-md px-4 py-2" routerLink="phone">
|
<button class="rounded-md border px-4 py-2" routerLink="phone">
|
||||||
Phone
|
Phone
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<router-outlet />`,
|
<router-outlet />
|
||||||
|
`,
|
||||||
host: {
|
host: {
|
||||||
class: 'p-10 flex flex-col',
|
class: 'p-10 flex flex-col',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,10 +5,12 @@ import { TimerContainerComponent } from './timer-container.component';
|
|||||||
selector: 'app-phone',
|
selector: 'app-phone',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [TimerContainerComponent],
|
imports: [TimerContainerComponent],
|
||||||
template: `<div class="flex gap-2">
|
template: `
|
||||||
|
<div class="flex gap-2">
|
||||||
Phone Call Timer:
|
Phone Call Timer:
|
||||||
<p class="italic">(should be 2000s)</p>
|
<p class="italic">(should be 2000s)</p>
|
||||||
</div>
|
</div>
|
||||||
<timer-container />`,
|
<timer-container />
|
||||||
|
`,
|
||||||
})
|
})
|
||||||
export default class PhoneComponent {}
|
export default class PhoneComponent {}
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ import { DEFAULT_TIMER } from './data';
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'timer',
|
selector: 'timer',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
template: ` Timer running {{ timer() }} `,
|
template: `
|
||||||
|
Timer running {{ timer() }}
|
||||||
|
`,
|
||||||
})
|
})
|
||||||
export class TimerComponent {
|
export class TimerComponent {
|
||||||
timer = toSignal(interval(DEFAULT_TIMER));
|
timer = toSignal(interval(DEFAULT_TIMER));
|
||||||
|
|||||||
@@ -5,10 +5,12 @@ import { TimerContainerComponent } from './timer-container.component';
|
|||||||
selector: 'app-video',
|
selector: 'app-video',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [TimerContainerComponent],
|
imports: [TimerContainerComponent],
|
||||||
template: `<div class="flex gap-2">
|
template: `
|
||||||
|
<div class="flex gap-2">
|
||||||
Video Call Timer:
|
Video Call Timer:
|
||||||
<p class="italic">(should be the default 1000s)</p>
|
<p class="italic">(should be the default 1000s)</p>
|
||||||
</div>
|
</div>
|
||||||
<timer-container />`,
|
<timer-container />
|
||||||
|
`,
|
||||||
})
|
})
|
||||||
export default class VideoComponent {}
|
export default class VideoComponent {}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { bootstrapApplication } from '@angular/platform-browser';
|
import { bootstrapApplication } from '@angular/platform-browser';
|
||||||
import { appConfig } from './app/app.config';
|
|
||||||
import { AppComponent } from './app/app.component';
|
import { AppComponent } from './app/app.component';
|
||||||
|
import { appConfig } from './app/app.config';
|
||||||
|
|
||||||
bootstrapApplication(AppComponent, appConfig).catch((err) =>
|
bootstrapApplication(AppComponent, appConfig).catch((err) =>
|
||||||
console.error(err)
|
console.error(err),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
export default {
|
export default {
|
||||||
displayName: 'angular-interop-rxjs-signal',
|
displayName: 'angular-interop-rxjs-signal',
|
||||||
preset: '../../../jest.preset.js',
|
preset: '../../../jest.preset.js',
|
||||||
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
|
setupFilesAfterEnv: ['<rootDir>/src/subscription-setup.ts'],
|
||||||
coverageDirectory: '../../../coverage/apps/angular/interop-rxjs-signal',
|
coverageDirectory: '../../../coverage/apps/angular/interop-rxjs-signal',
|
||||||
transform: {
|
transform: {
|
||||||
'^.+\\.(ts|mjs|js|html)$': [
|
'^.+\\.(ts|mjs|js|html)$': [
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ import { RouterOutlet } from '@angular/router';
|
|||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [RouterOutlet],
|
imports: [RouterOutlet],
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
template: `<router-outlet />`,
|
template: `
|
||||||
|
<router-outlet />
|
||||||
|
`,
|
||||||
styles: [''],
|
styles: [''],
|
||||||
})
|
})
|
||||||
export class AppComponent {}
|
export class AppComponent {}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export const appConfig: ApplicationConfig = {
|
|||||||
redirectTo: '',
|
redirectTo: '',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
withComponentInputBinding()
|
withComponentInputBinding(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,13 +9,25 @@ import { Photo } from '../photo.model';
|
|||||||
imports: [DatePipe, RouterLink],
|
imports: [DatePipe, RouterLink],
|
||||||
template: `
|
template: `
|
||||||
<img src="{{ photo.url_m }}" alt="{{ photo.title }}" class="image" />
|
<img src="{{ photo.url_m }}" alt="{{ photo.title }}" class="image" />
|
||||||
<p><span class="font-bold">Title:</span> {{ photo.title }}</p>
|
<p>
|
||||||
<p><span class="font-bold">Owner:</span> {{ photo.ownername }}</p>
|
<span class="font-bold">Title:</span>
|
||||||
<p><span class="font-bold">Date:</span> {{ photo.datetaken | date }}</p>
|
{{ photo.title }}
|
||||||
<p><span class="font-bold">Tags:</span> {{ photo.tags }}</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
<span class="font-bold">Owner:</span>
|
||||||
|
{{ photo.ownername }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span class="font-bold">Date:</span>
|
||||||
|
{{ photo.datetaken | date }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span class="font-bold">Tags:</span>
|
||||||
|
{{ photo.tags }}
|
||||||
|
</p>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="border border-black rounded-md px-4 py-2 mt-10"
|
class="mt-10 rounded-md border border-black px-4 py-2"
|
||||||
routerLink="">
|
routerLink="">
|
||||||
Back
|
Back
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import { PhotoStore } from './photos.store';
|
|||||||
RouterLinkWithHref,
|
RouterLinkWithHref,
|
||||||
],
|
],
|
||||||
template: `
|
template: `
|
||||||
<h2 class="text-xl mb-2">Photos</h2>
|
<h2 class="mb-2 text-xl">Photos</h2>
|
||||||
|
|
||||||
<mat-form-field appearance="fill">
|
<mat-form-field appearance="fill">
|
||||||
<mat-label>Search</mat-label>
|
<mat-label>Search</mat-label>
|
||||||
@@ -33,23 +33,23 @@ import { PhotoStore } from './photos.store';
|
|||||||
type="text"
|
type="text"
|
||||||
matInput
|
matInput
|
||||||
[formControl]="search"
|
[formControl]="search"
|
||||||
placeholder="write an article" />
|
placeholder="find a photo" />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<ng-container *ngrxLet="vm$ as vm">
|
<ng-container *ngrxLet="vm$ as vm">
|
||||||
<section class="flex flex-col">
|
<section class="flex flex-col">
|
||||||
<section class="flex gap-3 items-center">
|
<section class="flex items-center gap-3">
|
||||||
<button
|
<button
|
||||||
[disabled]="vm.page === 1"
|
[disabled]="vm.page === 1"
|
||||||
[class.bg-gray-400]="vm.page === 1"
|
[class.bg-gray-400]="vm.page === 1"
|
||||||
class="text-xl border rounded-md p-3"
|
class="rounded-md border p-3 text-xl"
|
||||||
(click)="store.previousPage()">
|
(click)="store.previousPage()">
|
||||||
<
|
<
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
[disabled]="vm.endOfPage"
|
[disabled]="vm.endOfPage"
|
||||||
[class.bg-gray-400]="vm.endOfPage"
|
[class.bg-gray-400]="vm.endOfPage"
|
||||||
class="text-xl border rounded-md p-3"
|
class="rounded-md border p-3 text-xl"
|
||||||
(click)="store.nextPage()">
|
(click)="store.nextPage()">
|
||||||
>
|
>
|
||||||
</button>
|
</button>
|
||||||
@@ -93,7 +93,7 @@ export default class PhotosComponent implements OnInit {
|
|||||||
this.search.setValue(search);
|
this.search.setValue(search);
|
||||||
this.formInit = true;
|
this.formInit = true;
|
||||||
}
|
}
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
private formInit = false;
|
private formInit = false;
|
||||||
@@ -104,8 +104,8 @@ export default class PhotosComponent implements OnInit {
|
|||||||
this.search.valueChanges.pipe(
|
this.search.valueChanges.pipe(
|
||||||
skipWhile(() => !this.formInit),
|
skipWhile(() => !this.formInit),
|
||||||
debounceTime(300),
|
debounceTime(300),
|
||||||
distinctUntilChanged()
|
distinctUntilChanged(),
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export class PhotoStore
|
|||||||
private readonly endOfPage$ = this.select(
|
private readonly endOfPage$ = this.select(
|
||||||
this.page$,
|
this.page$,
|
||||||
this.pages$,
|
this.pages$,
|
||||||
(page, pages) => page === pages
|
(page, pages) => page === pages,
|
||||||
);
|
);
|
||||||
|
|
||||||
readonly vm$ = this.select(
|
readonly vm$ = this.select(
|
||||||
@@ -60,7 +60,7 @@ export class PhotoStore
|
|||||||
loading: this.loading$,
|
loading: this.loading$,
|
||||||
error: this.error$,
|
error: this.error$,
|
||||||
},
|
},
|
||||||
{ debounce: true }
|
{ debounce: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
ngrxOnStoreInit() {
|
ngrxOnStoreInit() {
|
||||||
@@ -82,7 +82,7 @@ export class PhotoStore
|
|||||||
this.select({
|
this.select({
|
||||||
search: this.search$,
|
search: this.search$,
|
||||||
page: this.page$,
|
page: this.page$,
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,21 +91,21 @@ export class PhotoStore
|
|||||||
...state,
|
...state,
|
||||||
search,
|
search,
|
||||||
page: 1,
|
page: 1,
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
readonly nextPage = this.updater(
|
readonly nextPage = this.updater(
|
||||||
(state): PhotoState => ({
|
(state): PhotoState => ({
|
||||||
...state,
|
...state,
|
||||||
page: state.page + 1,
|
page: state.page + 1,
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
readonly previousPage = this.updater(
|
readonly previousPage = this.updater(
|
||||||
(state): PhotoState => ({
|
(state): PhotoState => ({
|
||||||
...state,
|
...state,
|
||||||
page: state.page - 1,
|
page: state.page - 1,
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
readonly searchPhotos = this.effect<{ search: string; page: number }>(
|
readonly searchPhotos = this.effect<{ search: string; page: number }>(
|
||||||
@@ -123,13 +123,13 @@ export class PhotoStore
|
|||||||
});
|
});
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
PHOTO_STATE_KEY,
|
PHOTO_STATE_KEY,
|
||||||
JSON.stringify({ search, page })
|
JSON.stringify({ search, page }),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
(error: unknown) => this.patchState({ error, loading: false })
|
(error: unknown) => this.patchState({ error, loading: false }),
|
||||||
)
|
),
|
||||||
)
|
),
|
||||||
)
|
),
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,22 @@
|
|||||||
export interface Photo {
|
export interface Photo {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
|
||||||
tags: string;
|
|
||||||
owner: string;
|
owner: string;
|
||||||
ownername: string;
|
secret: string;
|
||||||
|
server: string;
|
||||||
|
farm: number;
|
||||||
|
title: string;
|
||||||
|
ispublic: number;
|
||||||
|
isfriend: number;
|
||||||
|
isfamily: number;
|
||||||
datetaken: string;
|
datetaken: string;
|
||||||
|
datetakengranularity: number;
|
||||||
|
datetakenunknown: string;
|
||||||
|
ownername: string;
|
||||||
|
tags: string;
|
||||||
url_q: string;
|
url_q: string;
|
||||||
|
height_q: number;
|
||||||
|
width_q: number;
|
||||||
url_m: string;
|
url_m: string;
|
||||||
|
height_m: number;
|
||||||
|
width_m: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,13 @@ import { Photo } from './photo.model';
|
|||||||
|
|
||||||
export interface FlickrAPIResponse {
|
export interface FlickrAPIResponse {
|
||||||
photos: {
|
photos: {
|
||||||
|
page: number;
|
||||||
pages: number;
|
pages: number;
|
||||||
|
perpage: number;
|
||||||
|
total: number;
|
||||||
photo: Photo[];
|
photo: Photo[];
|
||||||
};
|
};
|
||||||
|
stat: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
@@ -16,7 +20,7 @@ export class PhotoService {
|
|||||||
|
|
||||||
public searchPublicPhotos(
|
public searchPublicPhotos(
|
||||||
searchTerm: string,
|
searchTerm: string,
|
||||||
page: number
|
page: number,
|
||||||
): Observable<FlickrAPIResponse> {
|
): Observable<FlickrAPIResponse> {
|
||||||
return this.http.get<FlickrAPIResponse>(
|
return this.http.get<FlickrAPIResponse>(
|
||||||
'https://www.flickr.com/services/rest/',
|
'https://www.flickr.com/services/rest/',
|
||||||
@@ -33,7 +37,7 @@ export class PhotoService {
|
|||||||
extras: 'tags,date_taken,owner_name,url_q,url_m',
|
extras: 'tags,date_taken,owner_name,url_q,url_m',
|
||||||
api_key: 'c3050d39a5bb308d9921bef0e15c437d',
|
api_key: 'c3050d39a5bb308d9921bef0e15c437d',
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { bootstrapApplication } from '@angular/platform-browser';
|
import { bootstrapApplication } from '@angular/platform-browser';
|
||||||
import { appConfig } from './app/app.config';
|
|
||||||
import { AppComponent } from './app/app.component';
|
import { AppComponent } from './app/app.component';
|
||||||
|
import { appConfig } from './app/app.config';
|
||||||
|
|
||||||
bootstrapApplication(AppComponent, appConfig).catch((err) =>
|
bootstrapApplication(AppComponent, appConfig).catch((err) =>
|
||||||
console.error(err)
|
console.error(err),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,24 +2,26 @@ import { Component } from '@angular/core';
|
|||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
template: ` <div class="flex gap-2">
|
template: `
|
||||||
|
<div class="flex gap-2">
|
||||||
<button
|
<button
|
||||||
routerLink="home"
|
routerLink="home"
|
||||||
class="border px-4 py-2 border-blue-400 rounded-md">
|
class="rounded-md border border-blue-400 px-4 py-2">
|
||||||
Home
|
Home
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
routerLink="admin"
|
routerLink="admin"
|
||||||
class="border px-4 py-2 border-blue-400 rounded-md">
|
class="rounded-md border border-blue-400 px-4 py-2">
|
||||||
Admin
|
Admin
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
routerLink="user"
|
routerLink="user"
|
||||||
class="border px-4 py-2 border-blue-400 rounded-md">
|
class="rounded-md border border-blue-400 px-4 py-2">
|
||||||
User
|
User
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<router-outlet></router-outlet>`,
|
<router-outlet></router-outlet>
|
||||||
|
`,
|
||||||
host: {
|
host: {
|
||||||
class: 'flex flex-col p-4 gap-3',
|
class: 'flex flex-col p-4 gap-3',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ import { RouterOutlet } from '@angular/router';
|
|||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [RouterOutlet],
|
imports: [RouterOutlet],
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
template: ` <router-outlet></router-outlet> `,
|
template: `
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
`,
|
||||||
styles: [],
|
styles: [],
|
||||||
})
|
})
|
||||||
export class AppComponent {}
|
export class AppComponent {}
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
|
|||||||
@Component({
|
@Component({
|
||||||
standalone: true,
|
standalone: true,
|
||||||
selector: 'button[app-button]',
|
selector: 'button[app-button]',
|
||||||
template: `<ng-content></ng-content>`,
|
template: `
|
||||||
|
<ng-content></ng-content>
|
||||||
|
`,
|
||||||
host: {
|
host: {
|
||||||
class: 'border border-blue-700 bg-blue-400 p-2 rounded-sm text-white',
|
class: 'border border-blue-700 bg-blue-400 p-2 rounded-sm text-white',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { UserStore } from './user.store';
|
|||||||
imports: [InformationComponent, RouterLink, ButtonComponent],
|
imports: [InformationComponent, RouterLink, ButtonComponent],
|
||||||
selector: 'app-login',
|
selector: 'app-login',
|
||||||
template: `
|
template: `
|
||||||
<header class="flex gap-3 items-center">
|
<header class="flex items-center gap-3">
|
||||||
Log as :
|
Log as :
|
||||||
<button app-button (click)="admin()">Admin</button>
|
<button app-button (click)="admin()">Admin</button>
|
||||||
<button app-button (click)="manager()">Manager</button>
|
<button app-button (click)="manager()">Manager</button>
|
||||||
@@ -26,12 +26,12 @@ import { UserStore } from './user.store';
|
|||||||
<button app-button (click)="writer()">Writer</button>
|
<button app-button (click)="writer()">Writer</button>
|
||||||
<button app-button (click)="readerWriter()">Reader and Writer</button>
|
<button app-button (click)="readerWriter()">Reader and Writer</button>
|
||||||
<button app-button (click)="client()">Client</button>
|
<button app-button (click)="client()">Client</button>
|
||||||
<button app-button (click)="everyone()">Client</button>
|
<button app-button (click)="everyone()">Everyone</button>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<app-information></app-information>
|
<app-information></app-information>
|
||||||
|
|
||||||
<button app-button class=" mt-10" routerLink="enter">
|
<button app-button class="mt-10" routerLink="enter">
|
||||||
Enter application
|
Enter application
|
||||||
</button>
|
</button>
|
||||||
`,
|
`,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export const APP_ROUTES = [
|
|||||||
path: 'enter',
|
path: 'enter',
|
||||||
loadComponent: () =>
|
loadComponent: () =>
|
||||||
import('./dashboard/admin.component').then(
|
import('./dashboard/admin.component').then(
|
||||||
(m) => m.AdminDashboardComponent
|
(m) => m.AdminDashboardComponent,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
|
|||||||
@@ -4,5 +4,5 @@ import { appConfig } from './app/app.config';
|
|||||||
import { AppComponent } from './app/app.component';
|
import { AppComponent } from './app/app.component';
|
||||||
|
|
||||||
bootstrapApplication(AppComponent, appConfig).catch((err) =>
|
bootstrapApplication(AppComponent, appConfig).catch((err) =>
|
||||||
console.error(err)
|
console.error(err),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
export default {
|
export default {
|
||||||
displayName: 'angular-projection',
|
displayName: 'angular-projection',
|
||||||
preset: '../../../jest.preset.js',
|
preset: '../../../jest.preset.js',
|
||||||
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
|
setupFilesAfterEnv: ['<rootDir>/src/subscription-setup.ts'],
|
||||||
globals: {},
|
globals: {},
|
||||||
coverageDirectory: '../../../coverage/apps/angular/projection',
|
coverageDirectory: '../../../coverage/apps/angular/projection',
|
||||||
transform: {
|
transform: {
|
||||||
|
|||||||
@@ -7,10 +7,12 @@ import { CardComponent } from '../../ui/card/card.component';
|
|||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-student-card',
|
selector: 'app-student-card',
|
||||||
template: `<app-card
|
template: `
|
||||||
[list]="students"
|
<app-card
|
||||||
[type]="cardType"
|
[list]="students"
|
||||||
customClass="bg-light-green"></app-card>`,
|
[type]="cardType"
|
||||||
|
customClass="bg-light-green"></app-card>
|
||||||
|
`,
|
||||||
standalone: true,
|
standalone: true,
|
||||||
styles: [
|
styles: [
|
||||||
`
|
`
|
||||||
@@ -25,7 +27,10 @@ export class StudentCardComponent implements OnInit {
|
|||||||
students: Student[] = [];
|
students: Student[] = [];
|
||||||
cardType = CardType.STUDENT;
|
cardType = CardType.STUDENT;
|
||||||
|
|
||||||
constructor(private http: FakeHttpService, private store: StudentStore) {}
|
constructor(
|
||||||
|
private http: FakeHttpService,
|
||||||
|
private store: StudentStore,
|
||||||
|
) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.http.fetchStudents$.subscribe((s) => this.store.addAll(s));
|
this.http.fetchStudents$.subscribe((s) => this.store.addAll(s));
|
||||||
|
|||||||
@@ -7,10 +7,12 @@ import { CardComponent } from '../../ui/card/card.component';
|
|||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-teacher-card',
|
selector: 'app-teacher-card',
|
||||||
template: `<app-card
|
template: `
|
||||||
[list]="teachers"
|
<app-card
|
||||||
[type]="cardType"
|
[list]="teachers"
|
||||||
customClass="bg-light-red"></app-card>`,
|
[type]="cardType"
|
||||||
|
customClass="bg-light-red"></app-card>
|
||||||
|
`,
|
||||||
styles: [
|
styles: [
|
||||||
`
|
`
|
||||||
::ng-deep .bg-light-red {
|
::ng-deep .bg-light-red {
|
||||||
@@ -25,7 +27,10 @@ export class TeacherCardComponent implements OnInit {
|
|||||||
teachers: Teacher[] = [];
|
teachers: Teacher[] = [];
|
||||||
cardType = CardType.TEACHER;
|
cardType = CardType.TEACHER;
|
||||||
|
|
||||||
constructor(private http: FakeHttpService, private store: TeacherStore) {}
|
constructor(
|
||||||
|
private http: FakeHttpService,
|
||||||
|
private store: TeacherStore,
|
||||||
|
) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.http.fetchTeachers$.subscribe((t) => this.store.addAll(t));
|
this.http.fetchTeachers$.subscribe((t) => this.store.addAll(t));
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { CardType } from '../../model/card.model';
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'app-list-item',
|
selector: 'app-list-item',
|
||||||
template: `
|
template: `
|
||||||
<div class="border border-grey-300 py-1 px-2 flex justify-between">
|
<div class="border-grey-300 flex justify-between border px-2 py-1">
|
||||||
{{ name }}
|
{{ name }}
|
||||||
<button (click)="delete(id)">
|
<button (click)="delete(id)">
|
||||||
<img class="h-5" src="assets/svg/trash.svg" />
|
<img class="h-5" src="assets/svg/trash.svg" />
|
||||||
@@ -22,7 +22,7 @@ export class ListItemComponent {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private teacherStore: TeacherStore,
|
private teacherStore: TeacherStore,
|
||||||
private studentStore: StudentStore
|
private studentStore: StudentStore,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
delete(id: number) {
|
delete(id: number) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
|
|||||||
@@ -6,17 +6,19 @@ import { RouterLink, RouterModule } from '@angular/router';
|
|||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [RouterLink, RouterModule, ReactiveFormsModule],
|
imports: [RouterLink, RouterModule, ReactiveFormsModule],
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
template: ` <label for="userName">UserName</label>
|
template: `
|
||||||
|
<label for="userName">UserName</label>
|
||||||
<input id="userName" type="text" [formControl]="userName" />
|
<input id="userName" type="text" [formControl]="userName" />
|
||||||
<label for="testId">TestId</label>
|
<label for="testId">TestId</label>
|
||||||
<input id="testId" type="number" [formControl]="testId" />
|
<input id="testId" type="number" [formControl]="testId" />
|
||||||
<button
|
<button
|
||||||
[routerLink]="'test/' + testId.value"
|
[routerLink]="'subscription/' + testId.value"
|
||||||
[queryParams]="{ user: userName.value }">
|
[queryParams]="{ user: userName.value }">
|
||||||
Test
|
Test
|
||||||
</button>
|
</button>
|
||||||
<button routerLink="/">HOME</button>
|
<button routerLink="/">HOME</button>
|
||||||
<router-outlet></router-outlet>`,
|
<router-outlet></router-outlet>
|
||||||
|
`,
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent {
|
||||||
userName = new FormControl();
|
userName = new FormControl();
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export const appRoutes: Route[] = [
|
|||||||
loadComponent: () => import('./home.component'),
|
loadComponent: () => import('./home.component'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'test/:testId',
|
path: 'subscription/:testId',
|
||||||
loadComponent: () => import('./test.component'),
|
loadComponent: () => import('./test.component'),
|
||||||
data: {
|
data: {
|
||||||
permission: 'admin',
|
permission: 'admin',
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { Component } from '@angular/core';
|
|||||||
selector: 'app-home',
|
selector: 'app-home',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [],
|
imports: [],
|
||||||
template: `<div>Home</div>`,
|
template: `
|
||||||
|
<div>Home</div>
|
||||||
|
`,
|
||||||
})
|
})
|
||||||
export default class HomeComponent {}
|
export default class HomeComponent {}
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ import { AsyncPipe } from '@angular/common';
|
|||||||
import { Component, inject } from '@angular/core';
|
import { Component, inject } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { map } from 'rxjs';
|
import { map } from 'rxjs';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-test',
|
selector: 'app-subscription',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [AsyncPipe],
|
imports: [AsyncPipe],
|
||||||
template: `
|
template: `
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { bootstrapApplication } from '@angular/platform-browser';
|
import { bootstrapApplication } from '@angular/platform-browser';
|
||||||
import { appConfig } from './app/app.config';
|
|
||||||
import { AppComponent } from './app/app.component';
|
import { AppComponent } from './app/app.component';
|
||||||
|
import { appConfig } from './app/app.config';
|
||||||
|
|
||||||
bootstrapApplication(AppComponent, appConfig).catch((err) =>
|
bootstrapApplication(AppComponent, appConfig).catch((err) =>
|
||||||
console.error(err)
|
console.error(err),
|
||||||
);
|
);
|
||||||
|
|||||||
36
apps/angular/signal-input/.eslintrc.json
Normal file
36
apps/angular/signal-input/.eslintrc.json
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"extends": ["../../../.eslintrc.json"],
|
||||||
|
"ignorePatterns": ["!**/*"],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["*.ts"],
|
||||||
|
"extends": [
|
||||||
|
"plugin:@nx/angular",
|
||||||
|
"plugin:@angular-eslint/template/process-inline-templates"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"@angular-eslint/directive-selector": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"type": "attribute",
|
||||||
|
"prefix": "app",
|
||||||
|
"style": "camelCase"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@angular-eslint/component-selector": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"prefix": "app",
|
||||||
|
"style": "kebab-case"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["*.html"],
|
||||||
|
"extends": ["plugin:@nx/angular-template"],
|
||||||
|
"rules": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
13
apps/angular/signal-input/README.md
Normal file
13
apps/angular/signal-input/README.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Signal Input
|
||||||
|
|
||||||
|
> author: thomas-laforge
|
||||||
|
|
||||||
|
### Run Application
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx nx serve angular-signal-input
|
||||||
|
```
|
||||||
|
|
||||||
|
### Documentation and Instruction
|
||||||
|
|
||||||
|
Challenge documentation is [here](https://angular-challenges.vercel.app/challenges/angular/43-signal-input/).
|
||||||
73
apps/angular/signal-input/project.json
Normal file
73
apps/angular/signal-input/project.json
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
{
|
||||||
|
"name": "angular-signal-input",
|
||||||
|
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
|
||||||
|
"projectType": "application",
|
||||||
|
"prefix": "app",
|
||||||
|
"sourceRoot": "apps/angular/signal-input/src",
|
||||||
|
"tags": [],
|
||||||
|
"targets": {
|
||||||
|
"build": {
|
||||||
|
"executor": "@angular-devkit/build-angular:application",
|
||||||
|
"outputs": ["{options.outputPath}"],
|
||||||
|
"options": {
|
||||||
|
"outputPath": "dist/apps/angular/signal-input",
|
||||||
|
"index": "apps/angular/signal-input/src/index.html",
|
||||||
|
"browser": "apps/angular/signal-input/src/main.ts",
|
||||||
|
"polyfills": ["zone.js"],
|
||||||
|
"tsConfig": "apps/angular/signal-input/tsconfig.app.json",
|
||||||
|
"inlineStyleLanguage": "scss",
|
||||||
|
"assets": [
|
||||||
|
"apps/angular/signal-input/src/favicon.ico",
|
||||||
|
"apps/angular/signal-input/src/assets"
|
||||||
|
],
|
||||||
|
"styles": ["apps/angular/signal-input/src/styles.scss"],
|
||||||
|
"scripts": []
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"budgets": [
|
||||||
|
{
|
||||||
|
"type": "initial",
|
||||||
|
"maximumWarning": "500kb",
|
||||||
|
"maximumError": "1mb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "anyComponentStyle",
|
||||||
|
"maximumWarning": "2kb",
|
||||||
|
"maximumError": "4kb"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputHashing": "all"
|
||||||
|
},
|
||||||
|
"development": {
|
||||||
|
"optimization": false,
|
||||||
|
"extractLicenses": false,
|
||||||
|
"sourceMap": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "production"
|
||||||
|
},
|
||||||
|
"serve": {
|
||||||
|
"executor": "@angular-devkit/build-angular:dev-server",
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"buildTarget": "angular-signal-input:build:production"
|
||||||
|
},
|
||||||
|
"development": {
|
||||||
|
"buildTarget": "angular-signal-input:build:development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "development"
|
||||||
|
},
|
||||||
|
"extract-i18n": {
|
||||||
|
"executor": "@angular-devkit/build-angular:extract-i18n",
|
||||||
|
"options": {
|
||||||
|
"buildTarget": "angular-signal-input:build"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"executor": "@nx/eslint:lint",
|
||||||
|
"outputs": ["{options.outputFile}"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
45
apps/angular/signal-input/src/app/app.component.ts
Normal file
45
apps/angular/signal-input/src/app/app.component.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { JsonPipe } from '@angular/common';
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { UserComponent } from './user.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
imports: [UserComponent, JsonPipe],
|
||||||
|
selector: 'app-root',
|
||||||
|
template: `
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
<div class="flex gap-2 ">
|
||||||
|
Name:
|
||||||
|
<input #name class="border" />
|
||||||
|
@if (showUser && !name.value) {
|
||||||
|
<div class="text-sm text-red-500">name required</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2 ">
|
||||||
|
LastName:
|
||||||
|
<input #lastName class="border" />
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2 ">
|
||||||
|
Age:
|
||||||
|
<input type="number" #age class="border" />
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
(click)="showUser = true"
|
||||||
|
class="w-fit rounded-md border border-blue-500 bg-blue-200 px-4 py-2">
|
||||||
|
Submit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
@if (showUser && !!name.value) {
|
||||||
|
<app-user
|
||||||
|
[name]="name.value"
|
||||||
|
[lastName]="lastName.value"
|
||||||
|
[age]="age.value" />
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
host: {
|
||||||
|
class: 'p-10 block flex flex-col gap-10',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export class AppComponent {
|
||||||
|
showUser = false;
|
||||||
|
}
|
||||||
5
apps/angular/signal-input/src/app/app.config.ts
Normal file
5
apps/angular/signal-input/src/app/app.config.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { ApplicationConfig } from '@angular/core';
|
||||||
|
|
||||||
|
export const appConfig: ApplicationConfig = {
|
||||||
|
providers: [],
|
||||||
|
};
|
||||||
41
apps/angular/signal-input/src/app/user.component.ts
Normal file
41
apps/angular/signal-input/src/app/user.component.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { TitleCasePipe } from '@angular/common';
|
||||||
|
import {
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
Component,
|
||||||
|
Input,
|
||||||
|
OnChanges,
|
||||||
|
} from '@angular/core';
|
||||||
|
|
||||||
|
type Category = 'Youth' | 'Junior' | 'Open' | 'Senior';
|
||||||
|
const ageToCategory = (age: number): Category => {
|
||||||
|
if (age < 10) return 'Youth';
|
||||||
|
else if (age < 18) return 'Junior';
|
||||||
|
else if (age < 35) return 'Open';
|
||||||
|
return 'Senior';
|
||||||
|
};
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-user',
|
||||||
|
standalone: true,
|
||||||
|
imports: [TitleCasePipe],
|
||||||
|
template: `
|
||||||
|
{{ fullName | titlecase }} plays tennis in the {{ category }} category!!
|
||||||
|
`,
|
||||||
|
host: {
|
||||||
|
class: 'text-xl text-green-800',
|
||||||
|
},
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class UserComponent implements OnChanges {
|
||||||
|
@Input({ required: true }) name!: string;
|
||||||
|
@Input() lastName?: string;
|
||||||
|
@Input() age?: string;
|
||||||
|
|
||||||
|
fullName = '';
|
||||||
|
category: Category = 'Junior';
|
||||||
|
|
||||||
|
ngOnChanges(): void {
|
||||||
|
this.fullName = `${this.name} ${this.lastName ?? ''}`;
|
||||||
|
this.category = ageToCategory(Number(this.age) ?? 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
0
apps/angular/signal-input/src/assets/.gitkeep
Normal file
0
apps/angular/signal-input/src/assets/.gitkeep
Normal file
BIN
apps/angular/signal-input/src/favicon.ico
Normal file
BIN
apps/angular/signal-input/src/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
13
apps/angular/signal-input/src/index.html
Normal file
13
apps/angular/signal-input/src/index.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>angular-signal-input</title>
|
||||||
|
<base href="/" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<app-root></app-root>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
7
apps/angular/signal-input/src/main.ts
Normal file
7
apps/angular/signal-input/src/main.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { bootstrapApplication } from '@angular/platform-browser';
|
||||||
|
import { AppComponent } from './app/app.component';
|
||||||
|
import { appConfig } from './app/app.config';
|
||||||
|
|
||||||
|
bootstrapApplication(AppComponent, appConfig).catch((err) =>
|
||||||
|
console.error(err),
|
||||||
|
);
|
||||||
5
apps/angular/signal-input/src/styles.scss
Normal file
5
apps/angular/signal-input/src/styles.scss
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
/* You can add global styles to this file, and also import other style files */
|
||||||
14
apps/angular/signal-input/tailwind.config.js
Normal file
14
apps/angular/signal-input/tailwind.config.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
const { createGlobPatternsForDependencies } = require('@nx/angular/tailwind');
|
||||||
|
const { join } = require('path');
|
||||||
|
|
||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
module.exports = {
|
||||||
|
content: [
|
||||||
|
join(__dirname, 'src/**/!(*.stories|*.spec).{ts,html}'),
|
||||||
|
...createGlobPatternsForDependencies(__dirname),
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
};
|
||||||
10
apps/angular/signal-input/tsconfig.app.json
Normal file
10
apps/angular/signal-input/tsconfig.app.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../../../dist/out-tsc",
|
||||||
|
"types": []
|
||||||
|
},
|
||||||
|
"files": ["src/main.ts"],
|
||||||
|
"include": ["src/**/*.d.ts"],
|
||||||
|
"exclude": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts"]
|
||||||
|
}
|
||||||
7
apps/angular/signal-input/tsconfig.editor.json
Normal file
7
apps/angular/signal-input/tsconfig.editor.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"include": ["src/**/*.ts"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"types": []
|
||||||
|
}
|
||||||
|
}
|
||||||
30
apps/angular/signal-input/tsconfig.json
Normal file
30
apps/angular/signal-input/tsconfig.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es2022",
|
||||||
|
"useDefineForClassFields": false,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"strict": true,
|
||||||
|
"noImplicitOverride": true,
|
||||||
|
"noPropertyAccessFromIndexSignature": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"files": [],
|
||||||
|
"include": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.app.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.editor.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"extends": "../../../tsconfig.base.json",
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"enableI18nLegacyMessageIdFormat": false,
|
||||||
|
"strictInjectionParameters": true,
|
||||||
|
"strictInputAccessModifiers": true,
|
||||||
|
"strictTemplates": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ import { TextComponent } from './text.component';
|
|||||||
<static-text></static-text>
|
<static-text></static-text>
|
||||||
<static-text type="error"></static-text>
|
<static-text type="error"></static-text>
|
||||||
<static-text type="warning"></static-text>
|
<static-text type="warning"></static-text>
|
||||||
<text [font]="15" color="blue">This a a blue text</text>
|
<text [font]="15" color="blue">This is a blue text</text>
|
||||||
`,
|
`,
|
||||||
})
|
})
|
||||||
export class PageComponent {}
|
export class PageComponent {}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
|
|||||||
36
apps/forms/control-value-accessor/.eslintrc.json
Normal file
36
apps/forms/control-value-accessor/.eslintrc.json
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"extends": ["../../../.eslintrc.json"],
|
||||||
|
"ignorePatterns": ["!**/*"],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["*.ts"],
|
||||||
|
"extends": [
|
||||||
|
"plugin:@nx/angular",
|
||||||
|
"plugin:@angular-eslint/template/process-inline-templates"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"@angular-eslint/directive-selector": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"type": "attribute",
|
||||||
|
"prefix": "app",
|
||||||
|
"style": "camelCase"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@angular-eslint/component-selector": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"prefix": "app",
|
||||||
|
"style": "kebab-case"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["*.html"],
|
||||||
|
"extends": ["plugin:@nx/angular-template"],
|
||||||
|
"rules": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
13
apps/forms/control-value-accessor/README.md
Normal file
13
apps/forms/control-value-accessor/README.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Control Value Accessor
|
||||||
|
|
||||||
|
> author: stanislav-gavrilov
|
||||||
|
|
||||||
|
### Run Application
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx nx serve forms-control-value-accessor
|
||||||
|
```
|
||||||
|
|
||||||
|
### Documentation and Instruction
|
||||||
|
|
||||||
|
Challenge documentation is [here](https://angular-challenges.vercel.app/challenges/forms/41-control-value-accessor/).
|
||||||
22
apps/forms/control-value-accessor/jest.config.ts
Normal file
22
apps/forms/control-value-accessor/jest.config.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
export default {
|
||||||
|
displayName: 'forms-control-value-accessor',
|
||||||
|
preset: '../../../jest.preset.js',
|
||||||
|
setupFilesAfterEnv: ['<rootDir>/src/subscription-setup.ts'],
|
||||||
|
coverageDirectory: '../../../coverage/apps/forms/control-value-accessor',
|
||||||
|
transform: {
|
||||||
|
'^.+\\.(ts|mjs|js|html)$': [
|
||||||
|
'jest-preset-angular',
|
||||||
|
{
|
||||||
|
tsconfig: '<rootDir>/tsconfig.spec.json',
|
||||||
|
stringifyContentPathRegex: '\\.(html|svg)$',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
|
||||||
|
snapshotSerializers: [
|
||||||
|
'jest-preset-angular/build/serializers/no-ng-attributes',
|
||||||
|
'jest-preset-angular/build/serializers/ng-snapshot',
|
||||||
|
'jest-preset-angular/build/serializers/html-comment',
|
||||||
|
],
|
||||||
|
};
|
||||||
86
apps/forms/control-value-accessor/project.json
Normal file
86
apps/forms/control-value-accessor/project.json
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
{
|
||||||
|
"name": "forms-control-value-accessor",
|
||||||
|
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
|
||||||
|
"projectType": "application",
|
||||||
|
"prefix": "app",
|
||||||
|
"sourceRoot": "apps/forms/control-value-accessor/src",
|
||||||
|
"tags": [],
|
||||||
|
"targets": {
|
||||||
|
"build": {
|
||||||
|
"executor": "@angular-devkit/build-angular:application",
|
||||||
|
"outputs": ["{options.outputPath}"],
|
||||||
|
"options": {
|
||||||
|
"outputPath": "dist/apps/forms/control-value-accessor",
|
||||||
|
"index": "apps/forms/control-value-accessor/src/index.html",
|
||||||
|
"browser": "apps/forms/control-value-accessor/src/main.ts",
|
||||||
|
"polyfills": ["zone.js"],
|
||||||
|
"tsConfig": "apps/forms/control-value-accessor/tsconfig.app.json",
|
||||||
|
"inlineStyleLanguage": "scss",
|
||||||
|
"assets": [
|
||||||
|
"apps/forms/control-value-accessor/src/favicon.ico",
|
||||||
|
"apps/forms/control-value-accessor/src/assets"
|
||||||
|
],
|
||||||
|
"styles": ["apps/forms/control-value-accessor/src/styles.scss"],
|
||||||
|
"scripts": []
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"budgets": [
|
||||||
|
{
|
||||||
|
"type": "initial",
|
||||||
|
"maximumWarning": "500kb",
|
||||||
|
"maximumError": "1mb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "anyComponentStyle",
|
||||||
|
"maximumWarning": "2kb",
|
||||||
|
"maximumError": "4kb"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputHashing": "all"
|
||||||
|
},
|
||||||
|
"development": {
|
||||||
|
"optimization": false,
|
||||||
|
"extractLicenses": false,
|
||||||
|
"sourceMap": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "production"
|
||||||
|
},
|
||||||
|
"serve": {
|
||||||
|
"executor": "@angular-devkit/build-angular:dev-server",
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"buildTarget": "forms-control-value-accessor:build:production"
|
||||||
|
},
|
||||||
|
"development": {
|
||||||
|
"buildTarget": "forms-control-value-accessor:build:development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "development"
|
||||||
|
},
|
||||||
|
"extract-i18n": {
|
||||||
|
"executor": "@angular-devkit/build-angular:extract-i18n",
|
||||||
|
"options": {
|
||||||
|
"buildTarget": "forms-control-value-accessor:build"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"executor": "@nx/eslint:lint",
|
||||||
|
"outputs": ["{options.outputFile}"],
|
||||||
|
"options": {
|
||||||
|
"lintFilePatterns": [
|
||||||
|
"apps/forms/control-value-accessor/**/*.ts",
|
||||||
|
"apps/forms/control-value-accessor/**/*.html"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"executor": "@nx/jest:jest",
|
||||||
|
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
||||||
|
"options": {
|
||||||
|
"jestConfig": "apps/forms/control-value-accessor/jest.config.ts"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
apps/forms/control-value-accessor/src/app/app.component.ts
Normal file
16
apps/forms/control-value-accessor/src/app/app.component.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { FeedbackFormComponent } from './feedback-form/feedback-form.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
imports: [FeedbackFormComponent],
|
||||||
|
selector: 'app-root',
|
||||||
|
template: `
|
||||||
|
<app-feedback-form (feedBackSubmit)="apiCall($event)"></app-feedback-form>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
export class AppComponent {
|
||||||
|
apiCall(event: Record<string, string | null>): void {
|
||||||
|
console.log(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<form
|
||||||
|
[formGroup]="feedbackForm"
|
||||||
|
class="feedback-form"
|
||||||
|
(ngSubmit)="submitForm()">
|
||||||
|
<legend class="feedback-form-title">Tell us what you think</legend>
|
||||||
|
<input
|
||||||
|
class="feedback-form-control"
|
||||||
|
[formControl]="feedbackForm.controls.name"
|
||||||
|
placeholder="Name"
|
||||||
|
type="text" />
|
||||||
|
<input
|
||||||
|
class="feedback-form-control"
|
||||||
|
[formControl]="feedbackForm.controls.email"
|
||||||
|
placeholder="Email"
|
||||||
|
type="email" />
|
||||||
|
<app-rating-control (ratingUpdated)="rating = $event"></app-rating-control>
|
||||||
|
<textarea
|
||||||
|
class="feedback-form-control"
|
||||||
|
[formControl]="feedbackForm.controls.comment"
|
||||||
|
placeholder="Сomment text"></textarea>
|
||||||
|
<button
|
||||||
|
class="feedback-form-submit"
|
||||||
|
type="submit"
|
||||||
|
[disabled]="
|
||||||
|
!feedbackForm.touched || rating === null || feedbackForm.invalid
|
||||||
|
">
|
||||||
|
Submit
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback-form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 500px;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback-form-title {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback-form-control {
|
||||||
|
max-height: 200px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding: 12px 12px 12px 20px;
|
||||||
|
border-radius: 0;
|
||||||
|
background-color: #fbfbfb;
|
||||||
|
color: #3c3c3c;
|
||||||
|
font-size: 18px;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
padding: 10px 10px 10px 18px;
|
||||||
|
border: 2px solid #054ada;
|
||||||
|
outline: none;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback-form-submit {
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #054ada;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 18px;
|
||||||
|
|
||||||
|
&[disabled] {
|
||||||
|
background-color: #cccccc;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { Component, EventEmitter, Output } from '@angular/core';
|
||||||
|
import {
|
||||||
|
FormControl,
|
||||||
|
FormGroup,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
Validators,
|
||||||
|
} from '@angular/forms';
|
||||||
|
import { RatingControlComponent } from '../rating-control/rating-control.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
imports: [RatingControlComponent, ReactiveFormsModule],
|
||||||
|
selector: 'app-feedback-form',
|
||||||
|
templateUrl: 'feedback-form.component.html',
|
||||||
|
styleUrls: ['feedback-form.component.scss'],
|
||||||
|
})
|
||||||
|
export class FeedbackFormComponent {
|
||||||
|
@Output()
|
||||||
|
readonly feedBackSubmit: EventEmitter<Record<string, string | null>> =
|
||||||
|
new EventEmitter<Record<string, string | null>>();
|
||||||
|
|
||||||
|
readonly feedbackForm = new FormGroup({
|
||||||
|
name: new FormControl('', {
|
||||||
|
validators: Validators.required,
|
||||||
|
}),
|
||||||
|
email: new FormControl('', {
|
||||||
|
validators: Validators.required,
|
||||||
|
}),
|
||||||
|
comment: new FormControl(),
|
||||||
|
});
|
||||||
|
|
||||||
|
rating: string | null = null;
|
||||||
|
|
||||||
|
submitForm(): void {
|
||||||
|
this.feedBackSubmit.emit({
|
||||||
|
...this.feedbackForm.value,
|
||||||
|
rating: this.rating,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.feedbackForm.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<svg width="0" height="0" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<symbol id="star" width="50" height="50" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
d="M31.77 11.857H19.74L15.99.5l-3.782 11.357H0l9.885 6.903-3.692 11.21 9.736-7.05 9.796 6.962-3.722-11.18 9.766-6.845z" />
|
||||||
|
</symbol>
|
||||||
|
</svg>
|
||||||
|
<div class="rating">
|
||||||
|
@for (item of [].constructor(5); track item) {
|
||||||
|
<svg
|
||||||
|
class="star"
|
||||||
|
[class.star-active]="isStarActive($index, value)"
|
||||||
|
(click)="setRating($index)">
|
||||||
|
<use xlink:href="#star"></use>
|
||||||
|
</svg>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
After Width: | Height: | Size: 547 B |
@@ -0,0 +1,26 @@
|
|||||||
|
.rating {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0 10px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.star {
|
||||||
|
fill: #ffd055;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.star {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
fill: #cccccc;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover ~ .star {
|
||||||
|
fill: #d8d8d8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.star-active {
|
||||||
|
fill: #ffd055 !important;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { Component, EventEmitter, Output } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
selector: 'app-rating-control',
|
||||||
|
templateUrl: 'rating-control.component.html',
|
||||||
|
styleUrls: ['rating-control.component.scss'],
|
||||||
|
})
|
||||||
|
export class RatingControlComponent {
|
||||||
|
@Output()
|
||||||
|
readonly ratingUpdated: EventEmitter<string> = new EventEmitter<string>();
|
||||||
|
|
||||||
|
value: number | null = null;
|
||||||
|
|
||||||
|
setRating(index: number): void {
|
||||||
|
this.value = index + 1;
|
||||||
|
this.ratingUpdated.emit(`${this.value}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
isStarActive(index: number, value: number | null): boolean {
|
||||||
|
return value ? index < value : false;
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user