├── LICENSE
├── README.md
├── part-1
├── .editorconfig
├── .gitignore
├── .vscode
│ ├── extensions.json
│ ├── launch.json
│ └── tasks.json
├── README.md
├── angular.json
├── package-lock.json
├── package.json
├── src
│ ├── app
│ │ ├── app.component.html
│ │ ├── app.component.ts
│ │ └── app.module.ts
│ ├── assets
│ │ └── .gitkeep
│ ├── favicon.ico
│ ├── index.html
│ ├── main.ts
│ └── styles.css
├── tsconfig.app.json
├── tsconfig.json
└── tsconfig.spec.json
├── part-2
├── .editorconfig
├── .gitignore
├── .vscode
│ ├── extensions.json
│ ├── launch.json
│ └── tasks.json
├── README.md
├── angular.json
├── package-lock.json
├── package.json
├── src
│ ├── app
│ │ ├── account
│ │ │ ├── index.ts
│ │ │ ├── login.component.html
│ │ │ ├── login.component.ts
│ │ │ ├── register.component.html
│ │ │ └── register.component.ts
│ │ ├── app-routing.module.ts
│ │ ├── app.component.html
│ │ ├── app.component.ts
│ │ ├── app.module.ts
│ │ └── home
│ │ │ ├── home.component.html
│ │ │ ├── home.component.ts
│ │ │ └── index.ts
│ ├── assets
│ │ └── .gitkeep
│ ├── favicon.ico
│ ├── index.html
│ ├── main.ts
│ └── styles.css
├── tsconfig.app.json
├── tsconfig.json
└── tsconfig.spec.json
├── part-3
├── .editorconfig
├── .gitignore
├── .vscode
│ ├── extensions.json
│ ├── launch.json
│ └── tasks.json
├── README.md
├── angular.json
├── package-lock.json
├── package.json
├── src
│ ├── app
│ │ ├── _helpers
│ │ │ ├── auth.guard.ts
│ │ │ ├── error.interceptor.ts
│ │ │ ├── fake-backend.ts
│ │ │ ├── index.ts
│ │ │ └── jwt.interceptor.ts
│ │ ├── _models
│ │ │ ├── index.ts
│ │ │ └── user.ts
│ │ ├── _services
│ │ │ ├── account.service.ts
│ │ │ └── index.ts
│ │ ├── account
│ │ │ ├── index.ts
│ │ │ ├── login.component.html
│ │ │ ├── login.component.ts
│ │ │ ├── register.component.html
│ │ │ └── register.component.ts
│ │ ├── app-routing.module.ts
│ │ ├── app.component.html
│ │ ├── app.component.ts
│ │ ├── app.module.ts
│ │ └── home
│ │ │ ├── home.component.html
│ │ │ ├── home.component.ts
│ │ │ └── index.ts
│ ├── assets
│ │ └── .gitkeep
│ ├── environments
│ │ └── environment.ts
│ ├── favicon.ico
│ ├── index.html
│ ├── main.ts
│ └── styles.css
├── tsconfig.app.json
├── tsconfig.json
└── tsconfig.spec.json
├── part-4
├── .editorconfig
├── .gitignore
├── .vscode
│ ├── extensions.json
│ ├── launch.json
│ └── tasks.json
├── README.md
├── angular.json
├── package-lock.json
├── package.json
├── src
│ ├── app
│ │ ├── _helpers
│ │ │ ├── auth.guard.ts
│ │ │ ├── error.interceptor.ts
│ │ │ ├── fake-backend.ts
│ │ │ ├── index.ts
│ │ │ └── jwt.interceptor.ts
│ │ ├── _models
│ │ │ ├── index.ts
│ │ │ └── user.ts
│ │ ├── _services
│ │ │ ├── account.service.ts
│ │ │ └── index.ts
│ │ ├── account
│ │ │ ├── index.ts
│ │ │ ├── login.component.html
│ │ │ ├── login.component.ts
│ │ │ ├── register.component.html
│ │ │ └── register.component.ts
│ │ ├── app-routing.module.ts
│ │ ├── app.component.html
│ │ ├── app.component.ts
│ │ ├── app.module.ts
│ │ └── home
│ │ │ ├── home.component.html
│ │ │ ├── home.component.ts
│ │ │ └── index.ts
│ ├── assets
│ │ └── .gitkeep
│ ├── environments
│ │ └── environment.ts
│ ├── favicon.ico
│ ├── index.html
│ ├── main.ts
│ └── styles.css
├── tsconfig.app.json
├── tsconfig.json
└── tsconfig.spec.json
├── part-5
├── .editorconfig
├── .gitignore
├── .vscode
│ ├── extensions.json
│ ├── launch.json
│ └── tasks.json
├── README.md
├── angular.json
├── package-lock.json
├── package.json
├── src
│ ├── app
│ │ ├── _components
│ │ │ ├── alert.component.html
│ │ │ ├── alert.component.ts
│ │ │ └── index.ts
│ │ ├── _helpers
│ │ │ ├── auth.guard.ts
│ │ │ ├── error.interceptor.ts
│ │ │ ├── fake-backend.ts
│ │ │ ├── index.ts
│ │ │ └── jwt.interceptor.ts
│ │ ├── _models
│ │ │ ├── index.ts
│ │ │ └── user.ts
│ │ ├── _services
│ │ │ ├── account.service.ts
│ │ │ ├── alert.service.ts
│ │ │ └── index.ts
│ │ ├── account
│ │ │ ├── index.ts
│ │ │ ├── login.component.html
│ │ │ ├── login.component.ts
│ │ │ ├── register.component.html
│ │ │ └── register.component.ts
│ │ ├── app-routing.module.ts
│ │ ├── app.component.html
│ │ ├── app.component.ts
│ │ ├── app.module.ts
│ │ └── home
│ │ │ ├── home.component.html
│ │ │ ├── home.component.ts
│ │ │ └── index.ts
│ ├── assets
│ │ └── .gitkeep
│ ├── environments
│ │ └── environment.ts
│ ├── favicon.ico
│ ├── index.html
│ ├── main.ts
│ └── styles.css
├── tsconfig.app.json
├── tsconfig.json
└── tsconfig.spec.json
├── part-6
├── .editorconfig
├── .gitignore
├── .vscode
│ ├── extensions.json
│ ├── launch.json
│ └── tasks.json
├── README.md
├── angular.json
├── package-lock.json
├── package.json
├── src
│ ├── app
│ │ ├── _components
│ │ │ ├── alert.component.html
│ │ │ ├── alert.component.ts
│ │ │ └── index.ts
│ │ ├── _helpers
│ │ │ ├── auth.guard.ts
│ │ │ ├── error.interceptor.ts
│ │ │ ├── fake-backend.ts
│ │ │ ├── index.ts
│ │ │ └── jwt.interceptor.ts
│ │ ├── _models
│ │ │ ├── index.ts
│ │ │ └── user.ts
│ │ ├── _services
│ │ │ ├── account.service.ts
│ │ │ ├── alert.service.ts
│ │ │ └── index.ts
│ │ ├── account
│ │ │ ├── index.ts
│ │ │ ├── login.component.html
│ │ │ ├── login.component.ts
│ │ │ ├── register.component.html
│ │ │ └── register.component.ts
│ │ ├── app-routing.module.ts
│ │ ├── app.component.html
│ │ ├── app.component.ts
│ │ ├── app.module.ts
│ │ ├── home
│ │ │ ├── home.component.html
│ │ │ ├── home.component.ts
│ │ │ └── index.ts
│ │ └── users
│ │ │ ├── add-edit.component.html
│ │ │ ├── add-edit.component.ts
│ │ │ ├── list.component.html
│ │ │ ├── list.component.ts
│ │ │ ├── users-routing.module.ts
│ │ │ └── users.module.ts
│ ├── assets
│ │ └── .gitkeep
│ ├── environments
│ │ └── environment.ts
│ ├── favicon.ico
│ ├── index.html
│ ├── main.ts
│ └── styles.css
├── tsconfig.app.json
├── tsconfig.json
└── tsconfig.spec.json
├── part-7
├── .editorconfig
├── .gitignore
├── .vscode
│ ├── extensions.json
│ ├── launch.json
│ └── tasks.json
├── README.md
├── angular.json
├── package-lock.json
├── package.json
├── src
│ ├── app
│ │ ├── _components
│ │ │ ├── alert.component.html
│ │ │ ├── alert.component.ts
│ │ │ └── index.ts
│ │ ├── _helpers
│ │ │ ├── auth.guard.ts
│ │ │ ├── error.interceptor.ts
│ │ │ ├── fake-backend.ts
│ │ │ ├── index.ts
│ │ │ └── jwt.interceptor.ts
│ │ ├── _models
│ │ │ ├── index.ts
│ │ │ └── user.ts
│ │ ├── _services
│ │ │ ├── account.service.ts
│ │ │ ├── alert.service.ts
│ │ │ └── index.ts
│ │ ├── account
│ │ │ ├── index.ts
│ │ │ ├── login.component.html
│ │ │ ├── login.component.ts
│ │ │ ├── register.component.html
│ │ │ └── register.component.ts
│ │ ├── app.component.html
│ │ ├── app.component.ts
│ │ ├── app.routes.ts
│ │ ├── home
│ │ │ ├── home.component.html
│ │ │ ├── home.component.ts
│ │ │ └── index.ts
│ │ └── users
│ │ │ ├── add-edit.component.html
│ │ │ ├── add-edit.component.ts
│ │ │ ├── list.component.html
│ │ │ ├── list.component.ts
│ │ │ └── users.routes.ts
│ ├── assets
│ │ └── .gitkeep
│ ├── environments
│ │ └── environment.ts
│ ├── favicon.ico
│ ├── index.html
│ ├── main.ts
│ └── styles.css
├── tsconfig.app.json
├── tsconfig.json
└── tsconfig.spec.json
└── part-8
├── .dockerignore
├── .editorconfig
├── .gitignore
├── .vscode
├── extensions.json
├── launch.json
└── tasks.json
├── Dockerfile
├── README.md
├── angular.json
├── nginx.conf
├── package-lock.json
├── package.json
├── src
├── app
│ ├── _components
│ │ ├── alert.component.html
│ │ ├── alert.component.ts
│ │ └── index.ts
│ ├── _helpers
│ │ ├── auth.guard.ts
│ │ ├── error.interceptor.ts
│ │ ├── fake-backend.ts
│ │ ├── index.ts
│ │ └── jwt.interceptor.ts
│ ├── _models
│ │ ├── index.ts
│ │ └── user.ts
│ ├── _services
│ │ ├── account.service.ts
│ │ ├── alert.service.ts
│ │ └── index.ts
│ ├── account
│ │ ├── index.ts
│ │ ├── login.component.html
│ │ ├── login.component.ts
│ │ ├── register.component.html
│ │ └── register.component.ts
│ ├── app.component.html
│ ├── app.component.ts
│ ├── app.routes.ts
│ ├── home
│ │ ├── home.component.html
│ │ ├── home.component.ts
│ │ └── index.ts
│ └── users
│ │ ├── add-edit.component.html
│ │ ├── add-edit.component.ts
│ │ ├── list.component.html
│ │ ├── list.component.ts
│ │ └── users.routes.ts
├── assets
│ └── .gitkeep
├── environments
│ └── environment.ts
├── favicon.ico
├── index.html
├── main.ts
└── styles.css
├── tsconfig.app.json
├── tsconfig.json
└── tsconfig.spec.json
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2023 Jason Watmore
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Angular 15/16 Free Course
2 |
3 | In this free step by step Angular course we'll be covering how to implement routing, authentication, registration and CRUD functionality in Angular.
4 |
5 | * [Part 1 - Create Base Project Structure](https://jasonwatmore.com/angular-15-free-course-1-create-base-project-structure)
6 | * [Part 2 - Add Routing & Multiple Pages](https://jasonwatmore.com/angular-15-free-course-2-add-routing-multiple-pages)
7 | * [Part 3 - Login Form, Authentication & Route Guard](https://jasonwatmore.com/angular-15-free-course-2-add-routing-multiple-pages)
8 | * [Part 4 - Registration Form & Service Methods](https://jasonwatmore.com/angular-15-free-course-4-registration-form-service-methods)
9 | * [Part 5 - Alerts & Home Page](https://jasonwatmore.com/angular-15-free-course-5-alerts-home-page)
10 | * [Part 6 - User Management (CRUD) Section](https://jasonwatmore.com/angular-15-free-course-5-alerts-home-page)
11 | * [Part 7 - Migrate to Standalone Components and Functional Interceptors](https://jasonwatmore.com/angular-15-16-free-course-7-migrate-to-standalone-components-and-functional-interceptors)
12 | * [Part 8 - Dockerize App with Nginx](https://jasonwatmore.com/angular-15-16-free-course-8-dockerize-app-with-nginx)
13 |
--------------------------------------------------------------------------------
/part-1/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 |
14 | [*.md]
15 | max_line_length = off
16 | trim_trailing_whitespace = false
17 |
--------------------------------------------------------------------------------
/part-1/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # Compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 | /bazel-out
8 |
9 | # Node
10 | /node_modules
11 | npm-debug.log
12 | yarn-error.log
13 |
14 | # IDEs and editors
15 | .idea/
16 | .project
17 | .classpath
18 | .c9/
19 | *.launch
20 | .settings/
21 | *.sublime-workspace
22 |
23 | # Visual Studio Code
24 | .vscode/*
25 | !.vscode/settings.json
26 | !.vscode/tasks.json
27 | !.vscode/launch.json
28 | !.vscode/extensions.json
29 | .history/*
30 |
31 | # Miscellaneous
32 | /.angular/cache
33 | .sass-cache/
34 | /connect.lock
35 | /coverage
36 | /libpeerconnection.log
37 | testem.log
38 | /typings
39 |
40 | # System files
41 | .DS_Store
42 | Thumbs.db
43 |
--------------------------------------------------------------------------------
/part-1/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
3 | "recommendations": ["angular.ng-template"]
4 | }
5 |
--------------------------------------------------------------------------------
/part-1/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
3 | "version": "0.2.0",
4 | "configurations": [
5 | {
6 | "name": "ng serve",
7 | "type": "chrome",
8 | "request": "launch",
9 | "preLaunchTask": "npm: start",
10 | "url": "http://localhost:4200/"
11 | },
12 | {
13 | "name": "ng test",
14 | "type": "chrome",
15 | "request": "launch",
16 | "preLaunchTask": "npm: test",
17 | "url": "http://localhost:9876/debug.html"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/part-1/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
3 | "version": "2.0.0",
4 | "tasks": [
5 | {
6 | "type": "npm",
7 | "script": "start",
8 | "isBackground": true,
9 | "problemMatcher": {
10 | "owner": "typescript",
11 | "pattern": "$tsc",
12 | "background": {
13 | "activeOnStart": true,
14 | "beginsPattern": {
15 | "regexp": "(.*?)"
16 | },
17 | "endsPattern": {
18 | "regexp": "bundle generation complete"
19 | }
20 | }
21 | }
22 | },
23 | {
24 | "type": "npm",
25 | "script": "test",
26 | "isBackground": true,
27 | "problemMatcher": {
28 | "owner": "typescript",
29 | "pattern": "$tsc",
30 | "background": {
31 | "activeOnStart": true,
32 | "beginsPattern": {
33 | "regexp": "(.*?)"
34 | },
35 | "endsPattern": {
36 | "regexp": "bundle generation complete"
37 | }
38 | }
39 | }
40 | }
41 | ]
42 | }
43 |
--------------------------------------------------------------------------------
/part-1/README.md:
--------------------------------------------------------------------------------
1 | # AngularTutorial
2 |
3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 16.0.0.
4 |
5 | ## Development server
6 |
7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
8 |
9 | ## Code scaffolding
10 |
11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
12 |
13 | ## Build
14 |
15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
16 |
17 | ## Running unit tests
18 |
19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
20 |
21 | ## Running end-to-end tests
22 |
23 | Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
24 |
25 | ## Further help
26 |
27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
28 |
--------------------------------------------------------------------------------
/part-1/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-tutorial",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "ng": "ng",
6 | "start": "ng serve",
7 | "build": "ng build",
8 | "watch": "ng build --watch --configuration development",
9 | "test": "ng test"
10 | },
11 | "private": true,
12 | "dependencies": {
13 | "@angular/animations": "^16.0.0",
14 | "@angular/common": "^16.0.0",
15 | "@angular/compiler": "^16.0.0",
16 | "@angular/core": "^16.0.0",
17 | "@angular/forms": "^16.0.0",
18 | "@angular/platform-browser": "^16.0.0",
19 | "@angular/platform-browser-dynamic": "^16.0.0",
20 | "@angular/router": "^16.0.0",
21 | "rxjs": "~7.8.0",
22 | "tslib": "^2.3.0",
23 | "zone.js": "~0.13.0"
24 | },
25 | "devDependencies": {
26 | "@angular-devkit/build-angular": "^16.0.0",
27 | "@angular/cli": "~16.0.0",
28 | "@angular/compiler-cli": "^16.0.0",
29 | "@types/jasmine": "~4.3.0",
30 | "jasmine-core": "~4.6.0",
31 | "karma": "~6.4.0",
32 | "karma-chrome-launcher": "~3.2.0",
33 | "karma-coverage": "~2.2.0",
34 | "karma-jasmine": "~5.1.0",
35 | "karma-jasmine-html-reporter": "~2.0.0",
36 | "typescript": "~5.0.2"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/part-1/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
Hello Angular!
--------------------------------------------------------------------------------
/part-1/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({ selector: 'app-root', templateUrl: 'app.component.html' })
4 | export class AppComponent { }
--------------------------------------------------------------------------------
/part-1/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { BrowserModule } from '@angular/platform-browser';
3 |
4 | import { AppComponent } from './app.component';
5 |
6 | @NgModule({
7 | imports: [BrowserModule],
8 | declarations: [AppComponent],
9 | bootstrap: [AppComponent]
10 | })
11 | export class AppModule { }
--------------------------------------------------------------------------------
/part-1/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cornflourblue/angular-16-tutorial/a2d771bd813e937d38b1d47ea93bfc270905ee2c/part-1/src/assets/.gitkeep
--------------------------------------------------------------------------------
/part-1/src/favicon.ico:
--------------------------------------------------------------------------------
1 | �PNG
2 |
3 |
IHDR ?�~� pHYs ��~� fIDATH��WKLQ���̔�uG�� e�n�.6qcb�l?���D`�F#�
Ku�F
1Qc�
4 | ��!���� ��C�P�|B?$���ܱ3����I&}��}�̽s�[*�ɀU�A� �K��yx�gY�Ajq��3L Š� ��˫�OD�4��3Ϗ:X�3��o�PJ�ğo#IH�a����,{>1/�2$�R AR]�)w��?�sZw^��q�Y�m_��e���r[8�^�
5 | �&p��-���A}c��- ������!����2_)E�)㊪j���v�m� �ZOi�g�nW�{�n.|�e2�a&�0aŸ����be�̀��C�fˤE%-{�ֺ��C��N��jXi�~c�C,t��T�����r �{� �L)s��V��6%�(�#ᤙ!�]��H�ҐH$R���^R�A�61(?Y舚�>���(Z����Qm�L2�K�ZIc��
6 | ���̧�C��2!⅄ �(����" �Go��>�q��=��$%�z`ѯ��T�&����PHh�Z!=� ��z��O��������,*VVV�1�f*CJ�]EE��K�k��d�#5���`2yT!�}7���߈~�,���zs�����y�T��V������D��C2�G��@%̑72Y�{oJ�"@��^h�~��fĬ�!a�D��6���Ha|�3��� [>�����]7U2п���] �ė
7 | ��PU��.Wejq�in�g��+ p<ߺQH����總j[������.��� Q���p _�K��1(��+��bB8�\ra
8 | �́�v.l���(���ǽ�w���L��w�8�C�� IEND�B`�
--------------------------------------------------------------------------------
/part-1/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Angular Tutorial
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/part-1/src/main.ts:
--------------------------------------------------------------------------------
1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
2 |
3 | import { AppModule } from './app/app.module';
4 |
5 |
6 | platformBrowserDynamic().bootstrapModule(AppModule)
7 | .catch(err => console.error(err));
8 |
--------------------------------------------------------------------------------
/part-1/src/styles.css:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 |
--------------------------------------------------------------------------------
/part-1/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/app",
6 | "types": []
7 | },
8 | "files": [
9 | "src/main.ts"
10 | ],
11 | "include": [
12 | "src/**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/part-1/tsconfig.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "compileOnSave": false,
4 | "compilerOptions": {
5 | "baseUrl": "./",
6 | "outDir": "./dist/out-tsc",
7 | "forceConsistentCasingInFileNames": true,
8 | "strict": true,
9 | "noImplicitOverride": true,
10 | "noPropertyAccessFromIndexSignature": true,
11 | "noImplicitReturns": true,
12 | "noFallthroughCasesInSwitch": true,
13 | "sourceMap": true,
14 | "declaration": false,
15 | "downlevelIteration": true,
16 | "experimentalDecorators": true,
17 | "moduleResolution": "node",
18 | "importHelpers": true,
19 | "target": "ES2022",
20 | "module": "ES2022",
21 | "useDefineForClassFields": false,
22 | "lib": [
23 | "ES2022",
24 | "dom"
25 | ]
26 | },
27 | "angularCompilerOptions": {
28 | "enableI18nLegacyMessageIdFormat": false,
29 | "strictInjectionParameters": true,
30 | "strictInputAccessModifiers": true,
31 | "strictTemplates": true
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/part-1/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/spec",
6 | "types": [
7 | "jasmine"
8 | ]
9 | },
10 | "include": [
11 | "src/**/*.spec.ts",
12 | "src/**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/part-2/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 |
14 | [*.md]
15 | max_line_length = off
16 | trim_trailing_whitespace = false
17 |
--------------------------------------------------------------------------------
/part-2/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # Compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 | /bazel-out
8 |
9 | # Node
10 | /node_modules
11 | npm-debug.log
12 | yarn-error.log
13 |
14 | # IDEs and editors
15 | .idea/
16 | .project
17 | .classpath
18 | .c9/
19 | *.launch
20 | .settings/
21 | *.sublime-workspace
22 |
23 | # Visual Studio Code
24 | .vscode/*
25 | !.vscode/settings.json
26 | !.vscode/tasks.json
27 | !.vscode/launch.json
28 | !.vscode/extensions.json
29 | .history/*
30 |
31 | # Miscellaneous
32 | /.angular/cache
33 | .sass-cache/
34 | /connect.lock
35 | /coverage
36 | /libpeerconnection.log
37 | testem.log
38 | /typings
39 |
40 | # System files
41 | .DS_Store
42 | Thumbs.db
43 |
--------------------------------------------------------------------------------
/part-2/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
3 | "recommendations": ["angular.ng-template"]
4 | }
5 |
--------------------------------------------------------------------------------
/part-2/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
3 | "version": "0.2.0",
4 | "configurations": [
5 | {
6 | "name": "ng serve",
7 | "type": "chrome",
8 | "request": "launch",
9 | "preLaunchTask": "npm: start",
10 | "url": "http://localhost:4200/"
11 | },
12 | {
13 | "name": "ng test",
14 | "type": "chrome",
15 | "request": "launch",
16 | "preLaunchTask": "npm: test",
17 | "url": "http://localhost:9876/debug.html"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/part-2/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
3 | "version": "2.0.0",
4 | "tasks": [
5 | {
6 | "type": "npm",
7 | "script": "start",
8 | "isBackground": true,
9 | "problemMatcher": {
10 | "owner": "typescript",
11 | "pattern": "$tsc",
12 | "background": {
13 | "activeOnStart": true,
14 | "beginsPattern": {
15 | "regexp": "(.*?)"
16 | },
17 | "endsPattern": {
18 | "regexp": "bundle generation complete"
19 | }
20 | }
21 | }
22 | },
23 | {
24 | "type": "npm",
25 | "script": "test",
26 | "isBackground": true,
27 | "problemMatcher": {
28 | "owner": "typescript",
29 | "pattern": "$tsc",
30 | "background": {
31 | "activeOnStart": true,
32 | "beginsPattern": {
33 | "regexp": "(.*?)"
34 | },
35 | "endsPattern": {
36 | "regexp": "bundle generation complete"
37 | }
38 | }
39 | }
40 | }
41 | ]
42 | }
43 |
--------------------------------------------------------------------------------
/part-2/README.md:
--------------------------------------------------------------------------------
1 | # AngularTutorial
2 |
3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 16.0.0.
4 |
5 | ## Development server
6 |
7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
8 |
9 | ## Code scaffolding
10 |
11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
12 |
13 | ## Build
14 |
15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
16 |
17 | ## Running unit tests
18 |
19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
20 |
21 | ## Running end-to-end tests
22 |
23 | Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
24 |
25 | ## Further help
26 |
27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
28 |
--------------------------------------------------------------------------------
/part-2/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-tutorial",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "ng": "ng",
6 | "start": "ng serve",
7 | "build": "ng build",
8 | "watch": "ng build --watch --configuration development",
9 | "test": "ng test"
10 | },
11 | "private": true,
12 | "dependencies": {
13 | "@angular/animations": "^16.0.0",
14 | "@angular/common": "^16.0.0",
15 | "@angular/compiler": "^16.0.0",
16 | "@angular/core": "^16.0.0",
17 | "@angular/forms": "^16.0.0",
18 | "@angular/platform-browser": "^16.0.0",
19 | "@angular/platform-browser-dynamic": "^16.0.0",
20 | "@angular/router": "^16.0.0",
21 | "rxjs": "~7.8.0",
22 | "tslib": "^2.3.0",
23 | "zone.js": "~0.13.0"
24 | },
25 | "devDependencies": {
26 | "@angular-devkit/build-angular": "^16.0.0",
27 | "@angular/cli": "~16.0.0",
28 | "@angular/compiler-cli": "^16.0.0",
29 | "@types/jasmine": "~4.3.0",
30 | "jasmine-core": "~4.6.0",
31 | "karma": "~6.4.0",
32 | "karma-chrome-launcher": "~3.2.0",
33 | "karma-coverage": "~2.2.0",
34 | "karma-jasmine": "~5.1.0",
35 | "karma-jasmine-html-reporter": "~2.0.0",
36 | "typescript": "~5.0.2"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/part-2/src/app/account/index.ts:
--------------------------------------------------------------------------------
1 | export * from './login.component';
2 | export * from './register.component';
--------------------------------------------------------------------------------
/part-2/src/app/account/login.component.html:
--------------------------------------------------------------------------------
1 | Login Page
--------------------------------------------------------------------------------
/part-2/src/app/account/login.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({ templateUrl: 'login.component.html' })
4 | export class LoginComponent { }
--------------------------------------------------------------------------------
/part-2/src/app/account/register.component.html:
--------------------------------------------------------------------------------
1 | Register Page
--------------------------------------------------------------------------------
/part-2/src/app/account/register.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({ templateUrl: 'register.component.html' })
4 | export class RegisterComponent { }
--------------------------------------------------------------------------------
/part-2/src/app/app-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { Routes, RouterModule } from '@angular/router';
3 |
4 | import { HomeComponent } from './home';
5 | import { LoginComponent, RegisterComponent } from './account';
6 |
7 | const routes: Routes = [
8 | { path: '', component: HomeComponent },
9 | { path: 'account/login', component: LoginComponent },
10 | { path: 'account/register', component: RegisterComponent },
11 |
12 | // otherwise redirect to home
13 | { path: '**', redirectTo: '' }
14 | ];
15 |
16 | @NgModule({
17 | imports: [RouterModule.forRoot(routes)],
18 | exports: [RouterModule]
19 | })
20 | export class AppRoutingModule { }
--------------------------------------------------------------------------------
/part-2/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/part-2/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({ selector: 'app-root', templateUrl: 'app.component.html' })
4 | export class AppComponent { }
--------------------------------------------------------------------------------
/part-2/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { BrowserModule } from '@angular/platform-browser';
3 |
4 | import { AppRoutingModule } from './app-routing.module';
5 | import { AppComponent } from './app.component';
6 | import { HomeComponent } from './home';
7 | import { LoginComponent, RegisterComponent } from './account';
8 |
9 | @NgModule({
10 | imports: [
11 | BrowserModule,
12 | AppRoutingModule
13 | ],
14 | declarations: [
15 | AppComponent,
16 | HomeComponent,
17 | LoginComponent,
18 | RegisterComponent
19 | ],
20 | bootstrap: [AppComponent]
21 | })
22 | export class AppModule { }
--------------------------------------------------------------------------------
/part-2/src/app/home/home.component.html:
--------------------------------------------------------------------------------
1 | Home Page
2 |
--------------------------------------------------------------------------------
/part-2/src/app/home/home.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({ templateUrl: 'home.component.html' })
4 | export class HomeComponent { }
--------------------------------------------------------------------------------
/part-2/src/app/home/index.ts:
--------------------------------------------------------------------------------
1 | export * from './home.component';
--------------------------------------------------------------------------------
/part-2/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cornflourblue/angular-16-tutorial/a2d771bd813e937d38b1d47ea93bfc270905ee2c/part-2/src/assets/.gitkeep
--------------------------------------------------------------------------------
/part-2/src/favicon.ico:
--------------------------------------------------------------------------------
1 | �PNG
2 |
3 |
IHDR ?�~� pHYs ��~� fIDATH��WKLQ���̔�uG�� e�n�.6qcb�l?���D`�F#�
Ku�F
1Qc�
4 | ��!���� ��C�P�|B?$���ܱ3����I&}��}�̽s�[*�ɀU�A� �K��yx�gY�Ajq��3L Š� ��˫�OD�4��3Ϗ:X�3��o�PJ�ğo#IH�a����,{>1/�2$�R AR]�)w��?�sZw^��q�Y�m_��e���r[8�^�
5 | �&p��-���A}c��- ������!����2_)E�)㊪j���v�m� �ZOi�g�nW�{�n.|�e2�a&�0aŸ����be�̀��C�fˤE%-{�ֺ��C��N��jXi�~c�C,t��T�����r �{� �L)s��V��6%�(�#ᤙ!�]��H�ҐH$R���^R�A�61(?Y舚�>���(Z����Qm�L2�K�ZIc��
6 | ���̧�C��2!⅄ �(����" �Go��>�q��=��$%�z`ѯ��T�&����PHh�Z!=� ��z��O��������,*VVV�1�f*CJ�]EE��K�k��d�#5���`2yT!�}7���߈~�,���zs�����y�T��V������D��C2�G��@%̑72Y�{oJ�"@��^h�~��fĬ�!a�D��6���Ha|�3��� [>�����]7U2п���] �ė
7 | ��PU��.Wejq�in�g��+ p<ߺQH����總j[������.��� Q���p _�K��1(��+��bB8�\ra
8 | �́�v.l���(���ǽ�w���L��w�8�C�� IEND�B`�
--------------------------------------------------------------------------------
/part-2/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Angular Tutorial
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/part-2/src/main.ts:
--------------------------------------------------------------------------------
1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
2 |
3 | import { AppModule } from './app/app.module';
4 |
5 |
6 | platformBrowserDynamic().bootstrapModule(AppModule)
7 | .catch(err => console.error(err));
8 |
--------------------------------------------------------------------------------
/part-2/src/styles.css:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 |
--------------------------------------------------------------------------------
/part-2/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/app",
6 | "types": []
7 | },
8 | "files": [
9 | "src/main.ts"
10 | ],
11 | "include": [
12 | "src/**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/part-2/tsconfig.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "compileOnSave": false,
4 | "compilerOptions": {
5 | "baseUrl": "./",
6 | "outDir": "./dist/out-tsc",
7 | "forceConsistentCasingInFileNames": true,
8 | "strict": true,
9 | "noImplicitOverride": true,
10 | "noPropertyAccessFromIndexSignature": true,
11 | "noImplicitReturns": true,
12 | "noFallthroughCasesInSwitch": true,
13 | "sourceMap": true,
14 | "declaration": false,
15 | "downlevelIteration": true,
16 | "experimentalDecorators": true,
17 | "moduleResolution": "node",
18 | "importHelpers": true,
19 | "target": "ES2022",
20 | "module": "ES2022",
21 | "useDefineForClassFields": false,
22 | "lib": [
23 | "ES2022",
24 | "dom"
25 | ]
26 | },
27 | "angularCompilerOptions": {
28 | "enableI18nLegacyMessageIdFormat": false,
29 | "strictInjectionParameters": true,
30 | "strictInputAccessModifiers": true,
31 | "strictTemplates": true
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/part-2/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/spec",
6 | "types": [
7 | "jasmine"
8 | ]
9 | },
10 | "include": [
11 | "src/**/*.spec.ts",
12 | "src/**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/part-3/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 |
14 | [*.md]
15 | max_line_length = off
16 | trim_trailing_whitespace = false
17 |
--------------------------------------------------------------------------------
/part-3/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # Compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 | /bazel-out
8 |
9 | # Node
10 | /node_modules
11 | npm-debug.log
12 | yarn-error.log
13 |
14 | # IDEs and editors
15 | .idea/
16 | .project
17 | .classpath
18 | .c9/
19 | *.launch
20 | .settings/
21 | *.sublime-workspace
22 |
23 | # Visual Studio Code
24 | .vscode/*
25 | !.vscode/settings.json
26 | !.vscode/tasks.json
27 | !.vscode/launch.json
28 | !.vscode/extensions.json
29 | .history/*
30 |
31 | # Miscellaneous
32 | /.angular/cache
33 | .sass-cache/
34 | /connect.lock
35 | /coverage
36 | /libpeerconnection.log
37 | testem.log
38 | /typings
39 |
40 | # System files
41 | .DS_Store
42 | Thumbs.db
43 |
--------------------------------------------------------------------------------
/part-3/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
3 | "recommendations": ["angular.ng-template"]
4 | }
5 |
--------------------------------------------------------------------------------
/part-3/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
3 | "version": "0.2.0",
4 | "configurations": [
5 | {
6 | "name": "ng serve",
7 | "type": "chrome",
8 | "request": "launch",
9 | "preLaunchTask": "npm: start",
10 | "url": "http://localhost:4200/"
11 | },
12 | {
13 | "name": "ng test",
14 | "type": "chrome",
15 | "request": "launch",
16 | "preLaunchTask": "npm: test",
17 | "url": "http://localhost:9876/debug.html"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/part-3/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
3 | "version": "2.0.0",
4 | "tasks": [
5 | {
6 | "type": "npm",
7 | "script": "start",
8 | "isBackground": true,
9 | "problemMatcher": {
10 | "owner": "typescript",
11 | "pattern": "$tsc",
12 | "background": {
13 | "activeOnStart": true,
14 | "beginsPattern": {
15 | "regexp": "(.*?)"
16 | },
17 | "endsPattern": {
18 | "regexp": "bundle generation complete"
19 | }
20 | }
21 | }
22 | },
23 | {
24 | "type": "npm",
25 | "script": "test",
26 | "isBackground": true,
27 | "problemMatcher": {
28 | "owner": "typescript",
29 | "pattern": "$tsc",
30 | "background": {
31 | "activeOnStart": true,
32 | "beginsPattern": {
33 | "regexp": "(.*?)"
34 | },
35 | "endsPattern": {
36 | "regexp": "bundle generation complete"
37 | }
38 | }
39 | }
40 | }
41 | ]
42 | }
43 |
--------------------------------------------------------------------------------
/part-3/README.md:
--------------------------------------------------------------------------------
1 | # AngularTutorial
2 |
3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 16.0.0.
4 |
5 | ## Development server
6 |
7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
8 |
9 | ## Code scaffolding
10 |
11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
12 |
13 | ## Build
14 |
15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
16 |
17 | ## Running unit tests
18 |
19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
20 |
21 | ## Running end-to-end tests
22 |
23 | Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
24 |
25 | ## Further help
26 |
27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
28 |
--------------------------------------------------------------------------------
/part-3/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-tutorial",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "ng": "ng",
6 | "start": "ng serve",
7 | "build": "ng build",
8 | "watch": "ng build --watch --configuration development",
9 | "test": "ng test"
10 | },
11 | "private": true,
12 | "dependencies": {
13 | "@angular/animations": "^16.0.0",
14 | "@angular/common": "^16.0.0",
15 | "@angular/compiler": "^16.0.0",
16 | "@angular/core": "^16.0.0",
17 | "@angular/forms": "^16.0.0",
18 | "@angular/platform-browser": "^16.0.0",
19 | "@angular/platform-browser-dynamic": "^16.0.0",
20 | "@angular/router": "^16.0.0",
21 | "rxjs": "~7.8.0",
22 | "tslib": "^2.3.0",
23 | "zone.js": "~0.13.0"
24 | },
25 | "devDependencies": {
26 | "@angular-devkit/build-angular": "^16.0.0",
27 | "@angular/cli": "~16.0.0",
28 | "@angular/compiler-cli": "^16.0.0",
29 | "@types/jasmine": "~4.3.0",
30 | "jasmine-core": "~4.6.0",
31 | "karma": "~6.4.0",
32 | "karma-chrome-launcher": "~3.2.0",
33 | "karma-coverage": "~2.2.0",
34 | "karma-jasmine": "~5.1.0",
35 | "karma-jasmine-html-reporter": "~2.0.0",
36 | "typescript": "~5.0.2"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/part-3/src/app/_helpers/auth.guard.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
3 |
4 | import { AccountService } from '@app/_services';
5 |
6 | @Injectable({ providedIn: 'root' })
7 | export class AuthGuard implements CanActivate {
8 | constructor(
9 | private router: Router,
10 | private accountService: AccountService
11 | ) { }
12 |
13 | canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
14 | const user = this.accountService.userValue;
15 | if (user) {
16 | // authorised so return true
17 | return true;
18 | }
19 |
20 | // not logged in so redirect to login page with the return url
21 | this.router.navigate(['/account/login'], { queryParams: { returnUrl: state.url } });
22 | return false;
23 | }
24 | }
--------------------------------------------------------------------------------
/part-3/src/app/_helpers/error.interceptor.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
3 | import { Observable, throwError } from 'rxjs';
4 | import { catchError } from 'rxjs/operators';
5 |
6 | import { AccountService } from '@app/_services';
7 |
8 | @Injectable()
9 | export class ErrorInterceptor implements HttpInterceptor {
10 | constructor(private accountService: AccountService) { }
11 |
12 | intercept(request: HttpRequest, next: HttpHandler): Observable> {
13 | return next.handle(request).pipe(catchError(err => {
14 | if ([401, 403].includes(err.status) && this.accountService.userValue) {
15 | // auto logout if 401 or 403 response returned from api
16 | this.accountService.logout();
17 | }
18 |
19 | const error = err.error?.message || err.statusText;
20 | console.error(err);
21 | return throwError(() => error);
22 | }))
23 | }
24 | }
--------------------------------------------------------------------------------
/part-3/src/app/_helpers/fake-backend.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { HttpRequest, HttpResponse, HttpHandler, HttpEvent, HttpInterceptor, HTTP_INTERCEPTORS } from '@angular/common/http';
3 | import { Observable, of, throwError } from 'rxjs';
4 | import { delay, materialize, dematerialize } from 'rxjs/operators';
5 |
6 | let users = [{ id: 1, firstName: 'Jason', lastName: 'Watmore', username: 'test', password: 'test' }];
7 |
8 | @Injectable()
9 | export class FakeBackendInterceptor implements HttpInterceptor {
10 | intercept(request: HttpRequest, next: HttpHandler): Observable> {
11 | const { url, method, headers, body } = request;
12 |
13 | return handleRoute();
14 |
15 | function handleRoute() {
16 | switch (true) {
17 | case url.endsWith('/users/authenticate') && method === 'POST':
18 | return authenticate();
19 | default:
20 | // pass through any requests not handled above
21 | return next.handle(request);
22 | }
23 | }
24 |
25 | // route functions
26 |
27 | function authenticate() {
28 | const { username, password } = body;
29 | const user = users.find(x => x.username === username && x.password === password);
30 | if (!user) return error('Username or password is incorrect');
31 | return ok({
32 | ...basicDetails(user),
33 | token: 'fake-jwt-token'
34 | })
35 | }
36 |
37 | // helper functions
38 |
39 | function ok(body?: any) {
40 | return of(new HttpResponse({ status: 200, body }))
41 | .pipe(delay(500)); // delay observable to simulate server api call
42 | }
43 |
44 | function error(message: string) {
45 | return throwError(() => ({ error: { message } }))
46 | .pipe(materialize(), delay(500), dematerialize()); // call materialize and dematerialize to ensure delay even if an error is thrown (https://github.com/Reactive-Extensions/RxJS/issues/648);
47 | }
48 |
49 | function basicDetails(user: any) {
50 | const { id, username, firstName, lastName } = user;
51 | return { id, username, firstName, lastName };
52 | }
53 | }
54 | }
55 |
56 | export const fakeBackendProvider = {
57 | // use fake backend in place of Http service for backend-less development
58 | provide: HTTP_INTERCEPTORS,
59 | useClass: FakeBackendInterceptor,
60 | multi: true
61 | };
--------------------------------------------------------------------------------
/part-3/src/app/_helpers/index.ts:
--------------------------------------------------------------------------------
1 | export * from './auth.guard';
2 | export * from './error.interceptor';
3 | export * from './fake-backend';
4 | export * from './jwt.interceptor';
--------------------------------------------------------------------------------
/part-3/src/app/_helpers/jwt.interceptor.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
3 | import { Observable } from 'rxjs';
4 |
5 | import { environment } from '@environments/environment';
6 | import { AccountService } from '@app/_services';
7 |
8 | @Injectable()
9 | export class JwtInterceptor implements HttpInterceptor {
10 | constructor(private accountService: AccountService) { }
11 |
12 | intercept(request: HttpRequest, next: HttpHandler): Observable> {
13 | // add auth header with jwt if user is logged in and request is to the api url
14 | const user = this.accountService.userValue;
15 | const isLoggedIn = user?.token;
16 | const isApiUrl = request.url.startsWith(environment.apiUrl);
17 | if (isLoggedIn && isApiUrl) {
18 | request = request.clone({
19 | setHeaders: { Authorization: `Bearer ${user.token}` }
20 | });
21 | }
22 |
23 | return next.handle(request);
24 | }
25 | }
--------------------------------------------------------------------------------
/part-3/src/app/_models/index.ts:
--------------------------------------------------------------------------------
1 | export * from './user';
--------------------------------------------------------------------------------
/part-3/src/app/_models/user.ts:
--------------------------------------------------------------------------------
1 | export class User {
2 | id?: string;
3 | username?: string;
4 | password?: string;
5 | firstName?: string;
6 | lastName?: string;
7 | token?: string;
8 | }
--------------------------------------------------------------------------------
/part-3/src/app/_services/account.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Router } from '@angular/router';
3 | import { HttpClient } from '@angular/common/http';
4 | import { BehaviorSubject, Observable } from 'rxjs';
5 | import { map } from 'rxjs/operators';
6 |
7 | import { environment } from '@environments/environment';
8 | import { User } from '@app/_models';
9 |
10 | @Injectable({ providedIn: 'root' })
11 | export class AccountService {
12 | private userSubject: BehaviorSubject;
13 | public user: Observable;
14 |
15 | constructor(
16 | private router: Router,
17 | private http: HttpClient
18 | ) {
19 | this.userSubject = new BehaviorSubject(JSON.parse(localStorage.getItem('user')!));
20 | this.user = this.userSubject.asObservable();
21 | }
22 |
23 | public get userValue() {
24 | return this.userSubject.value;
25 | }
26 |
27 | login(username: string, password: string) {
28 | return this.http.post(`${environment.apiUrl}/users/authenticate`, { username, password })
29 | .pipe(map(user => {
30 | // store user details and jwt token in local storage to keep user logged in between page refreshes
31 | localStorage.setItem('user', JSON.stringify(user));
32 | this.userSubject.next(user);
33 | return user;
34 | }));
35 | }
36 |
37 | logout() {
38 | // remove user from local storage and set current user to null
39 | localStorage.removeItem('user');
40 | this.userSubject.next(null);
41 | this.router.navigate(['/account/login']);
42 | }
43 | }
--------------------------------------------------------------------------------
/part-3/src/app/_services/index.ts:
--------------------------------------------------------------------------------
1 | export * from './account.service';
--------------------------------------------------------------------------------
/part-3/src/app/account/index.ts:
--------------------------------------------------------------------------------
1 | export * from './login.component';
2 | export * from './register.component';
--------------------------------------------------------------------------------
/part-3/src/app/account/login.component.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-3/src/app/account/login.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { Router, ActivatedRoute } from '@angular/router';
3 | import { FormBuilder, FormGroup, Validators } from '@angular/forms';
4 | import { first } from 'rxjs/operators';
5 |
6 | import { AccountService } from '@app/_services'
7 |
8 | @Component({ templateUrl: 'login.component.html' })
9 | export class LoginComponent implements OnInit {
10 | form!: FormGroup;
11 | loading = false;
12 | submitted = false;
13 | error?: string;
14 |
15 | constructor(
16 | private formBuilder: FormBuilder,
17 | private route: ActivatedRoute,
18 | private router: Router,
19 | private accountService: AccountService
20 | ) {
21 | // redirect to home if already logged in
22 | if (this.accountService.userValue) {
23 | this.router.navigate(['/']);
24 | }
25 | }
26 |
27 | ngOnInit() {
28 | this.form = this.formBuilder.group({
29 | username: ['', Validators.required],
30 | password: ['', Validators.required]
31 | });
32 | }
33 |
34 | // convenience getter for easy access to form fields
35 | get f() { return this.form.controls; }
36 |
37 | onSubmit() {
38 | this.submitted = true;
39 |
40 | // reset alert on submit
41 | this.error = '';
42 |
43 | // stop here if form is invalid
44 | if (this.form.invalid) {
45 | return;
46 | }
47 |
48 | this.loading = true;
49 | this.accountService.login(this.f.username.value, this.f.password.value)
50 | .pipe(first())
51 | .subscribe({
52 | next: () => {
53 | // get return url from query parameters or default to home page
54 | const returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';
55 | this.router.navigateByUrl(returnUrl);
56 | },
57 | error: error => {
58 | this.error = error;
59 | this.loading = false;
60 | }
61 | });
62 | }
63 | }
--------------------------------------------------------------------------------
/part-3/src/app/account/register.component.html:
--------------------------------------------------------------------------------
1 | Register Page
--------------------------------------------------------------------------------
/part-3/src/app/account/register.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({ templateUrl: 'register.component.html' })
4 | export class RegisterComponent { }
--------------------------------------------------------------------------------
/part-3/src/app/app-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { Routes, RouterModule } from '@angular/router';
3 |
4 | import { HomeComponent } from './home';
5 | import { LoginComponent, RegisterComponent } from './account';
6 | import { AuthGuard } from './_helpers';
7 |
8 | const routes: Routes = [
9 | { path: '', component: HomeComponent, canActivate: [AuthGuard] },
10 | { path: 'account/login', component: LoginComponent },
11 | { path: 'account/register', component: RegisterComponent },
12 |
13 | // otherwise redirect to home
14 | { path: '**', redirectTo: '' }
15 | ];
16 |
17 | @NgModule({
18 | imports: [RouterModule.forRoot(routes)],
19 | exports: [RouterModule]
20 | })
21 | export class AppRoutingModule { }
--------------------------------------------------------------------------------
/part-3/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/part-3/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | import { AccountService } from './_services';
4 | import { User } from './_models';
5 |
6 | @Component({ selector: 'app-root', templateUrl: 'app.component.html' })
7 | export class AppComponent {
8 | user?: User | null;
9 |
10 | constructor(private accountService: AccountService) {
11 | this.accountService.user.subscribe(x => this.user = x);
12 | }
13 |
14 | logout() {
15 | this.accountService.logout();
16 | }
17 | }
--------------------------------------------------------------------------------
/part-3/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { BrowserModule } from '@angular/platform-browser';
3 | import { ReactiveFormsModule } from '@angular/forms';
4 | import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
5 |
6 | // used to create fake backend
7 | import { fakeBackendProvider } from './_helpers';
8 |
9 | import { AppRoutingModule } from './app-routing.module';
10 | import { JwtInterceptor, ErrorInterceptor } from './_helpers';
11 | import { AppComponent } from './app.component';
12 | import { HomeComponent } from './home';
13 | import { LoginComponent, RegisterComponent } from './account';
14 |
15 | @NgModule({
16 | imports: [
17 | BrowserModule,
18 | ReactiveFormsModule,
19 | HttpClientModule,
20 | AppRoutingModule
21 | ],
22 | declarations: [
23 | AppComponent,
24 | HomeComponent,
25 | LoginComponent,
26 | RegisterComponent
27 | ],
28 | providers: [
29 | { provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true },
30 | { provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true },
31 |
32 | // provider used to create fake backend
33 | fakeBackendProvider
34 | ],
35 | bootstrap: [AppComponent]
36 | })
37 | export class AppModule { }
--------------------------------------------------------------------------------
/part-3/src/app/home/home.component.html:
--------------------------------------------------------------------------------
1 | Home Page
2 |
--------------------------------------------------------------------------------
/part-3/src/app/home/home.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({ templateUrl: 'home.component.html' })
4 | export class HomeComponent { }
--------------------------------------------------------------------------------
/part-3/src/app/home/index.ts:
--------------------------------------------------------------------------------
1 | export * from './home.component';
--------------------------------------------------------------------------------
/part-3/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cornflourblue/angular-16-tutorial/a2d771bd813e937d38b1d47ea93bfc270905ee2c/part-3/src/assets/.gitkeep
--------------------------------------------------------------------------------
/part-3/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | apiUrl: 'http://localhost:4000'
3 | };
--------------------------------------------------------------------------------
/part-3/src/favicon.ico:
--------------------------------------------------------------------------------
1 | �PNG
2 |
3 |
IHDR ?�~� pHYs ��~� fIDATH��WKLQ���̔�uG�� e�n�.6qcb�l?���D`�F#�
Ku�F
1Qc�
4 | ��!���� ��C�P�|B?$���ܱ3����I&}��}�̽s�[*�ɀU�A� �K��yx�gY�Ajq��3L Š� ��˫�OD�4��3Ϗ:X�3��o�PJ�ğo#IH�a����,{>1/�2$�R AR]�)w��?�sZw^��q�Y�m_��e���r[8�^�
5 | �&p��-���A}c��- ������!����2_)E�)㊪j���v�m� �ZOi�g�nW�{�n.|�e2�a&�0aŸ����be�̀��C�fˤE%-{�ֺ��C��N��jXi�~c�C,t��T�����r �{� �L)s��V��6%�(�#ᤙ!�]��H�ҐH$R���^R�A�61(?Y舚�>���(Z����Qm�L2�K�ZIc��
6 | ���̧�C��2!⅄ �(����" �Go��>�q��=��$%�z`ѯ��T�&����PHh�Z!=� ��z��O��������,*VVV�1�f*CJ�]EE��K�k��d�#5���`2yT!�}7���߈~�,���zs�����y�T��V������D��C2�G��@%̑72Y�{oJ�"@��^h�~��fĬ�!a�D��6���Ha|�3��� [>�����]7U2п���] �ė
7 | ��PU��.Wejq�in�g��+ p<ߺQH����總j[������.��� Q���p _�K��1(��+��bB8�\ra
8 | �́�v.l���(���ǽ�w���L��w�8�C�� IEND�B`�
--------------------------------------------------------------------------------
/part-3/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Angular Tutorial
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/part-3/src/main.ts:
--------------------------------------------------------------------------------
1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
2 |
3 | import { AppModule } from './app/app.module';
4 |
5 |
6 | platformBrowserDynamic().bootstrapModule(AppModule)
7 | .catch(err => console.error(err));
8 |
--------------------------------------------------------------------------------
/part-3/src/styles.css:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 |
--------------------------------------------------------------------------------
/part-3/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/app",
6 | "types": []
7 | },
8 | "files": [
9 | "src/main.ts"
10 | ],
11 | "include": [
12 | "src/**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/part-3/tsconfig.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "compileOnSave": false,
4 | "compilerOptions": {
5 | "baseUrl": "./",
6 | "outDir": "./dist/out-tsc",
7 | "forceConsistentCasingInFileNames": true,
8 | "strict": true,
9 | "noImplicitOverride": true,
10 | "noPropertyAccessFromIndexSignature": false,
11 | "noImplicitReturns": true,
12 | "noFallthroughCasesInSwitch": true,
13 | "sourceMap": true,
14 | "declaration": false,
15 | "downlevelIteration": true,
16 | "experimentalDecorators": true,
17 | "moduleResolution": "node",
18 | "importHelpers": true,
19 | "target": "ES2022",
20 | "module": "ES2022",
21 | "useDefineForClassFields": false,
22 | "lib": [
23 | "ES2022",
24 | "dom"
25 | ],
26 | "paths": {
27 | "@app/*": ["src/app/*"],
28 | "@environments/*": ["src/environments/*"]
29 | }
30 | },
31 | "angularCompilerOptions": {
32 | "enableI18nLegacyMessageIdFormat": false,
33 | "strictInjectionParameters": true,
34 | "strictInputAccessModifiers": true,
35 | "strictTemplates": true
36 | }
37 | }
--------------------------------------------------------------------------------
/part-3/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/spec",
6 | "types": [
7 | "jasmine"
8 | ]
9 | },
10 | "include": [
11 | "src/**/*.spec.ts",
12 | "src/**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/part-4/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 |
14 | [*.md]
15 | max_line_length = off
16 | trim_trailing_whitespace = false
17 |
--------------------------------------------------------------------------------
/part-4/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # Compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 | /bazel-out
8 |
9 | # Node
10 | /node_modules
11 | npm-debug.log
12 | yarn-error.log
13 |
14 | # IDEs and editors
15 | .idea/
16 | .project
17 | .classpath
18 | .c9/
19 | *.launch
20 | .settings/
21 | *.sublime-workspace
22 |
23 | # Visual Studio Code
24 | .vscode/*
25 | !.vscode/settings.json
26 | !.vscode/tasks.json
27 | !.vscode/launch.json
28 | !.vscode/extensions.json
29 | .history/*
30 |
31 | # Miscellaneous
32 | /.angular/cache
33 | .sass-cache/
34 | /connect.lock
35 | /coverage
36 | /libpeerconnection.log
37 | testem.log
38 | /typings
39 |
40 | # System files
41 | .DS_Store
42 | Thumbs.db
43 |
--------------------------------------------------------------------------------
/part-4/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
3 | "recommendations": ["angular.ng-template"]
4 | }
5 |
--------------------------------------------------------------------------------
/part-4/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
3 | "version": "0.2.0",
4 | "configurations": [
5 | {
6 | "name": "ng serve",
7 | "type": "chrome",
8 | "request": "launch",
9 | "preLaunchTask": "npm: start",
10 | "url": "http://localhost:4200/"
11 | },
12 | {
13 | "name": "ng test",
14 | "type": "chrome",
15 | "request": "launch",
16 | "preLaunchTask": "npm: test",
17 | "url": "http://localhost:9876/debug.html"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/part-4/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
3 | "version": "2.0.0",
4 | "tasks": [
5 | {
6 | "type": "npm",
7 | "script": "start",
8 | "isBackground": true,
9 | "problemMatcher": {
10 | "owner": "typescript",
11 | "pattern": "$tsc",
12 | "background": {
13 | "activeOnStart": true,
14 | "beginsPattern": {
15 | "regexp": "(.*?)"
16 | },
17 | "endsPattern": {
18 | "regexp": "bundle generation complete"
19 | }
20 | }
21 | }
22 | },
23 | {
24 | "type": "npm",
25 | "script": "test",
26 | "isBackground": true,
27 | "problemMatcher": {
28 | "owner": "typescript",
29 | "pattern": "$tsc",
30 | "background": {
31 | "activeOnStart": true,
32 | "beginsPattern": {
33 | "regexp": "(.*?)"
34 | },
35 | "endsPattern": {
36 | "regexp": "bundle generation complete"
37 | }
38 | }
39 | }
40 | }
41 | ]
42 | }
43 |
--------------------------------------------------------------------------------
/part-4/README.md:
--------------------------------------------------------------------------------
1 | # AngularTutorial
2 |
3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 16.0.0.
4 |
5 | ## Development server
6 |
7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
8 |
9 | ## Code scaffolding
10 |
11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
12 |
13 | ## Build
14 |
15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
16 |
17 | ## Running unit tests
18 |
19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
20 |
21 | ## Running end-to-end tests
22 |
23 | Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
24 |
25 | ## Further help
26 |
27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
28 |
--------------------------------------------------------------------------------
/part-4/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-tutorial",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "ng": "ng",
6 | "start": "ng serve",
7 | "build": "ng build",
8 | "watch": "ng build --watch --configuration development",
9 | "test": "ng test"
10 | },
11 | "private": true,
12 | "dependencies": {
13 | "@angular/animations": "^16.0.0",
14 | "@angular/common": "^16.0.0",
15 | "@angular/compiler": "^16.0.0",
16 | "@angular/core": "^16.0.0",
17 | "@angular/forms": "^16.0.0",
18 | "@angular/platform-browser": "^16.0.0",
19 | "@angular/platform-browser-dynamic": "^16.0.0",
20 | "@angular/router": "^16.0.0",
21 | "rxjs": "~7.8.0",
22 | "tslib": "^2.3.0",
23 | "zone.js": "~0.13.0"
24 | },
25 | "devDependencies": {
26 | "@angular-devkit/build-angular": "^16.0.0",
27 | "@angular/cli": "~16.0.0",
28 | "@angular/compiler-cli": "^16.0.0",
29 | "@types/jasmine": "~4.3.0",
30 | "jasmine-core": "~4.6.0",
31 | "karma": "~6.4.0",
32 | "karma-chrome-launcher": "~3.2.0",
33 | "karma-coverage": "~2.2.0",
34 | "karma-jasmine": "~5.1.0",
35 | "karma-jasmine-html-reporter": "~2.0.0",
36 | "typescript": "~5.0.2"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/part-4/src/app/_helpers/auth.guard.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
3 |
4 | import { AccountService } from '@app/_services';
5 |
6 | @Injectable({ providedIn: 'root' })
7 | export class AuthGuard implements CanActivate {
8 | constructor(
9 | private router: Router,
10 | private accountService: AccountService
11 | ) { }
12 |
13 | canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
14 | const user = this.accountService.userValue;
15 | if (user) {
16 | // authorised so return true
17 | return true;
18 | }
19 |
20 | // not logged in so redirect to login page with the return url
21 | this.router.navigate(['/account/login'], { queryParams: { returnUrl: state.url } });
22 | return false;
23 | }
24 | }
--------------------------------------------------------------------------------
/part-4/src/app/_helpers/error.interceptor.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
3 | import { Observable, throwError } from 'rxjs';
4 | import { catchError } from 'rxjs/operators';
5 |
6 | import { AccountService } from '@app/_services';
7 |
8 | @Injectable()
9 | export class ErrorInterceptor implements HttpInterceptor {
10 | constructor(private accountService: AccountService) { }
11 |
12 | intercept(request: HttpRequest, next: HttpHandler): Observable> {
13 | return next.handle(request).pipe(catchError(err => {
14 | if ([401, 403].includes(err.status) && this.accountService.userValue) {
15 | // auto logout if 401 or 403 response returned from api
16 | this.accountService.logout();
17 | }
18 |
19 | const error = err.error?.message || err.statusText;
20 | console.error(err);
21 | return throwError(() => error);
22 | }))
23 | }
24 | }
--------------------------------------------------------------------------------
/part-4/src/app/_helpers/index.ts:
--------------------------------------------------------------------------------
1 | export * from './auth.guard';
2 | export * from './error.interceptor';
3 | export * from './fake-backend';
4 | export * from './jwt.interceptor';
--------------------------------------------------------------------------------
/part-4/src/app/_helpers/jwt.interceptor.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
3 | import { Observable } from 'rxjs';
4 |
5 | import { environment } from '@environments/environment';
6 | import { AccountService } from '@app/_services';
7 |
8 | @Injectable()
9 | export class JwtInterceptor implements HttpInterceptor {
10 | constructor(private accountService: AccountService) { }
11 |
12 | intercept(request: HttpRequest, next: HttpHandler): Observable> {
13 | // add auth header with jwt if user is logged in and request is to the api url
14 | const user = this.accountService.userValue;
15 | const isLoggedIn = user?.token;
16 | const isApiUrl = request.url.startsWith(environment.apiUrl);
17 | if (isLoggedIn && isApiUrl) {
18 | request = request.clone({
19 | setHeaders: { Authorization: `Bearer ${user.token}` }
20 | });
21 | }
22 |
23 | return next.handle(request);
24 | }
25 | }
--------------------------------------------------------------------------------
/part-4/src/app/_models/index.ts:
--------------------------------------------------------------------------------
1 | export * from './user';
--------------------------------------------------------------------------------
/part-4/src/app/_models/user.ts:
--------------------------------------------------------------------------------
1 | export class User {
2 | id?: string;
3 | username?: string;
4 | password?: string;
5 | firstName?: string;
6 | lastName?: string;
7 | token?: string;
8 | }
--------------------------------------------------------------------------------
/part-4/src/app/_services/account.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Router } from '@angular/router';
3 | import { HttpClient } from '@angular/common/http';
4 | import { BehaviorSubject, Observable } from 'rxjs';
5 | import { map } from 'rxjs/operators';
6 |
7 | import { environment } from '@environments/environment';
8 | import { User } from '@app/_models';
9 |
10 | @Injectable({ providedIn: 'root' })
11 | export class AccountService {
12 | private userSubject: BehaviorSubject;
13 | public user: Observable;
14 |
15 | constructor(
16 | private router: Router,
17 | private http: HttpClient
18 | ) {
19 | this.userSubject = new BehaviorSubject(JSON.parse(localStorage.getItem('user')!));
20 | this.user = this.userSubject.asObservable();
21 | }
22 |
23 | public get userValue() {
24 | return this.userSubject.value;
25 | }
26 |
27 | login(username: string, password: string) {
28 | return this.http.post(`${environment.apiUrl}/users/authenticate`, { username, password })
29 | .pipe(map(user => {
30 | // store user details and jwt token in local storage to keep user logged in between page refreshes
31 | localStorage.setItem('user', JSON.stringify(user));
32 | this.userSubject.next(user);
33 | return user;
34 | }));
35 | }
36 |
37 | logout() {
38 | // remove user from local storage and set current user to null
39 | localStorage.removeItem('user');
40 | this.userSubject.next(null);
41 | this.router.navigate(['/account/login']);
42 | }
43 |
44 | register(user: User) {
45 | return this.http.post(`${environment.apiUrl}/users/register`, user);
46 | }
47 | }
--------------------------------------------------------------------------------
/part-4/src/app/_services/index.ts:
--------------------------------------------------------------------------------
1 | export * from './account.service';
--------------------------------------------------------------------------------
/part-4/src/app/account/index.ts:
--------------------------------------------------------------------------------
1 | export * from './login.component';
2 | export * from './register.component';
--------------------------------------------------------------------------------
/part-4/src/app/account/login.component.html:
--------------------------------------------------------------------------------
1 |
2 |
{{error}}
3 |
{{success}}
4 |
32 |
--------------------------------------------------------------------------------
/part-4/src/app/account/login.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { Router, ActivatedRoute } from '@angular/router';
3 | import { FormBuilder, FormGroup, Validators } from '@angular/forms';
4 | import { first } from 'rxjs/operators';
5 |
6 | import { AccountService } from '@app/_services'
7 |
8 | @Component({ templateUrl: 'login.component.html' })
9 | export class LoginComponent implements OnInit {
10 | form!: FormGroup;
11 | loading = false;
12 | submitted = false;
13 | error?: string;
14 | success?: string;
15 |
16 | constructor(
17 | private formBuilder: FormBuilder,
18 | private route: ActivatedRoute,
19 | private router: Router,
20 | private accountService: AccountService
21 | ) {
22 | // redirect to home if already logged in
23 | if (this.accountService.userValue) {
24 | this.router.navigate(['/']);
25 | }
26 | }
27 |
28 | ngOnInit() {
29 | this.form = this.formBuilder.group({
30 | username: ['', Validators.required],
31 | password: ['', Validators.required]
32 | });
33 |
34 | // show success message after registration
35 | if (this.route.snapshot.queryParams.registered) {
36 | this.success = 'Registration successful';
37 | }
38 | }
39 |
40 | // convenience getter for easy access to form fields
41 | get f() { return this.form.controls; }
42 |
43 | onSubmit() {
44 | this.submitted = true;
45 |
46 | // reset alerts on submit
47 | this.error = '';
48 | this.success = '';
49 |
50 | // stop here if form is invalid
51 | if (this.form.invalid) {
52 | return;
53 | }
54 |
55 | this.loading = true;
56 | this.accountService.login(this.f.username.value, this.f.password.value)
57 | .pipe(first())
58 | .subscribe({
59 | next: () => {
60 | // get return url from query parameters or default to home page
61 | const returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';
62 | this.router.navigateByUrl(returnUrl);
63 | },
64 | error: error => {
65 | this.error = error;
66 | this.loading = false;
67 | }
68 | });
69 | }
70 | }
--------------------------------------------------------------------------------
/part-4/src/app/account/register.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { Router, ActivatedRoute } from '@angular/router';
3 | import { FormBuilder, FormGroup, Validators } from '@angular/forms';
4 | import { first } from 'rxjs/operators';
5 |
6 | import { AccountService } from '@app/_services';
7 |
8 | @Component({ templateUrl: 'register.component.html' })
9 | export class RegisterComponent implements OnInit {
10 | form!: FormGroup;
11 | loading = false;
12 | submitted = false;
13 | error?: string;
14 |
15 | constructor(
16 | private formBuilder: FormBuilder,
17 | private route: ActivatedRoute,
18 | private router: Router,
19 | private accountService: AccountService
20 | ) {
21 | // redirect to home if already logged in
22 | if (this.accountService.userValue) {
23 | this.router.navigate(['/']);
24 | }
25 | }
26 |
27 | ngOnInit() {
28 | this.form = this.formBuilder.group({
29 | firstName: ['', Validators.required],
30 | lastName: ['', Validators.required],
31 | username: ['', Validators.required],
32 | password: ['', [Validators.required, Validators.minLength(6)]]
33 | });
34 | }
35 |
36 | // convenience getter for easy access to form fields
37 | get f() { return this.form.controls; }
38 |
39 | onSubmit() {
40 | this.submitted = true;
41 |
42 | // reset alert on submit
43 | this.error = '';
44 |
45 | // stop here if form is invalid
46 | if (this.form.invalid) {
47 | return;
48 | }
49 |
50 | this.loading = true;
51 | this.accountService.register(this.form.value)
52 | .pipe(first())
53 | .subscribe({
54 | next: () => {
55 | this.router.navigate(['/account/login'], { queryParams: { registered: true }});
56 | },
57 | error: error => {
58 | this.error = error;
59 | this.loading = false;
60 | }
61 | });
62 | }
63 | }
--------------------------------------------------------------------------------
/part-4/src/app/app-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { Routes, RouterModule } from '@angular/router';
3 |
4 | import { HomeComponent } from './home';
5 | import { LoginComponent, RegisterComponent } from './account';
6 | import { AuthGuard } from './_helpers';
7 |
8 | const routes: Routes = [
9 | { path: '', component: HomeComponent, canActivate: [AuthGuard] },
10 | { path: 'account/login', component: LoginComponent },
11 | { path: 'account/register', component: RegisterComponent },
12 |
13 | // otherwise redirect to home
14 | { path: '**', redirectTo: '' }
15 | ];
16 |
17 | @NgModule({
18 | imports: [RouterModule.forRoot(routes)],
19 | exports: [RouterModule]
20 | })
21 | export class AppRoutingModule { }
--------------------------------------------------------------------------------
/part-4/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/part-4/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | import { AccountService } from './_services';
4 | import { User } from './_models';
5 |
6 | @Component({ selector: 'app-root', templateUrl: 'app.component.html' })
7 | export class AppComponent {
8 | user?: User | null;
9 |
10 | constructor(private accountService: AccountService) {
11 | this.accountService.user.subscribe(x => this.user = x);
12 | }
13 |
14 | logout() {
15 | this.accountService.logout();
16 | }
17 | }
--------------------------------------------------------------------------------
/part-4/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { BrowserModule } from '@angular/platform-browser';
3 | import { ReactiveFormsModule } from '@angular/forms';
4 | import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
5 |
6 | // used to create fake backend
7 | import { fakeBackendProvider } from './_helpers';
8 |
9 | import { AppRoutingModule } from './app-routing.module';
10 | import { JwtInterceptor, ErrorInterceptor } from './_helpers';
11 | import { AppComponent } from './app.component';
12 | import { HomeComponent } from './home';
13 | import { LoginComponent, RegisterComponent } from './account';
14 |
15 | @NgModule({
16 | imports: [
17 | BrowserModule,
18 | ReactiveFormsModule,
19 | HttpClientModule,
20 | AppRoutingModule
21 | ],
22 | declarations: [
23 | AppComponent,
24 | HomeComponent,
25 | LoginComponent,
26 | RegisterComponent
27 | ],
28 | providers: [
29 | { provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true },
30 | { provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true },
31 |
32 | // provider used to create fake backend
33 | fakeBackendProvider
34 | ],
35 | bootstrap: [AppComponent]
36 | })
37 | export class AppModule { }
--------------------------------------------------------------------------------
/part-4/src/app/home/home.component.html:
--------------------------------------------------------------------------------
1 | Home Page
2 |
--------------------------------------------------------------------------------
/part-4/src/app/home/home.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({ templateUrl: 'home.component.html' })
4 | export class HomeComponent { }
--------------------------------------------------------------------------------
/part-4/src/app/home/index.ts:
--------------------------------------------------------------------------------
1 | export * from './home.component';
--------------------------------------------------------------------------------
/part-4/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cornflourblue/angular-16-tutorial/a2d771bd813e937d38b1d47ea93bfc270905ee2c/part-4/src/assets/.gitkeep
--------------------------------------------------------------------------------
/part-4/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | apiUrl: 'http://localhost:4000'
3 | };
--------------------------------------------------------------------------------
/part-4/src/favicon.ico:
--------------------------------------------------------------------------------
1 | �PNG
2 |
3 |
IHDR ?�~� pHYs ��~� fIDATH��WKLQ���̔�uG�� e�n�.6qcb�l?���D`�F#�
Ku�F
1Qc�
4 | ��!���� ��C�P�|B?$���ܱ3����I&}��}�̽s�[*�ɀU�A� �K��yx�gY�Ajq��3L Š� ��˫�OD�4��3Ϗ:X�3��o�PJ�ğo#IH�a����,{>1/�2$�R AR]�)w��?�sZw^��q�Y�m_��e���r[8�^�
5 | �&p��-���A}c��- ������!����2_)E�)㊪j���v�m� �ZOi�g�nW�{�n.|�e2�a&�0aŸ����be�̀��C�fˤE%-{�ֺ��C��N��jXi�~c�C,t��T�����r �{� �L)s��V��6%�(�#ᤙ!�]��H�ҐH$R���^R�A�61(?Y舚�>���(Z����Qm�L2�K�ZIc��
6 | ���̧�C��2!⅄ �(����" �Go��>�q��=��$%�z`ѯ��T�&����PHh�Z!=� ��z��O��������,*VVV�1�f*CJ�]EE��K�k��d�#5���`2yT!�}7���߈~�,���zs�����y�T��V������D��C2�G��@%̑72Y�{oJ�"@��^h�~��fĬ�!a�D��6���Ha|�3��� [>�����]7U2п���] �ė
7 | ��PU��.Wejq�in�g��+ p<ߺQH����總j[������.��� Q���p _�K��1(��+��bB8�\ra
8 | �́�v.l���(���ǽ�w���L��w�8�C�� IEND�B`�
--------------------------------------------------------------------------------
/part-4/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Angular Tutorial
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/part-4/src/main.ts:
--------------------------------------------------------------------------------
1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
2 |
3 | import { AppModule } from './app/app.module';
4 |
5 |
6 | platformBrowserDynamic().bootstrapModule(AppModule)
7 | .catch(err => console.error(err));
8 |
--------------------------------------------------------------------------------
/part-4/src/styles.css:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 |
--------------------------------------------------------------------------------
/part-4/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/app",
6 | "types": []
7 | },
8 | "files": [
9 | "src/main.ts"
10 | ],
11 | "include": [
12 | "src/**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/part-4/tsconfig.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "compileOnSave": false,
4 | "compilerOptions": {
5 | "baseUrl": "./",
6 | "outDir": "./dist/out-tsc",
7 | "forceConsistentCasingInFileNames": true,
8 | "strict": true,
9 | "noImplicitOverride": true,
10 | "noPropertyAccessFromIndexSignature": false,
11 | "noImplicitReturns": true,
12 | "noFallthroughCasesInSwitch": true,
13 | "sourceMap": true,
14 | "declaration": false,
15 | "downlevelIteration": true,
16 | "experimentalDecorators": true,
17 | "moduleResolution": "node",
18 | "importHelpers": true,
19 | "target": "ES2022",
20 | "module": "ES2022",
21 | "useDefineForClassFields": false,
22 | "lib": [
23 | "ES2022",
24 | "dom"
25 | ],
26 | "paths": {
27 | "@app/*": ["src/app/*"],
28 | "@environments/*": ["src/environments/*"]
29 | }
30 | },
31 | "angularCompilerOptions": {
32 | "enableI18nLegacyMessageIdFormat": false,
33 | "strictInjectionParameters": true,
34 | "strictInputAccessModifiers": true,
35 | "strictTemplates": true
36 | }
37 | }
--------------------------------------------------------------------------------
/part-4/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/spec",
6 | "types": [
7 | "jasmine"
8 | ]
9 | },
10 | "include": [
11 | "src/**/*.spec.ts",
12 | "src/**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/part-5/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 |
14 | [*.md]
15 | max_line_length = off
16 | trim_trailing_whitespace = false
17 |
--------------------------------------------------------------------------------
/part-5/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # Compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 | /bazel-out
8 |
9 | # Node
10 | /node_modules
11 | npm-debug.log
12 | yarn-error.log
13 |
14 | # IDEs and editors
15 | .idea/
16 | .project
17 | .classpath
18 | .c9/
19 | *.launch
20 | .settings/
21 | *.sublime-workspace
22 |
23 | # Visual Studio Code
24 | .vscode/*
25 | !.vscode/settings.json
26 | !.vscode/tasks.json
27 | !.vscode/launch.json
28 | !.vscode/extensions.json
29 | .history/*
30 |
31 | # Miscellaneous
32 | /.angular/cache
33 | .sass-cache/
34 | /connect.lock
35 | /coverage
36 | /libpeerconnection.log
37 | testem.log
38 | /typings
39 |
40 | # System files
41 | .DS_Store
42 | Thumbs.db
43 |
--------------------------------------------------------------------------------
/part-5/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
3 | "recommendations": ["angular.ng-template"]
4 | }
5 |
--------------------------------------------------------------------------------
/part-5/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
3 | "version": "0.2.0",
4 | "configurations": [
5 | {
6 | "name": "ng serve",
7 | "type": "chrome",
8 | "request": "launch",
9 | "preLaunchTask": "npm: start",
10 | "url": "http://localhost:4200/"
11 | },
12 | {
13 | "name": "ng test",
14 | "type": "chrome",
15 | "request": "launch",
16 | "preLaunchTask": "npm: test",
17 | "url": "http://localhost:9876/debug.html"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/part-5/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
3 | "version": "2.0.0",
4 | "tasks": [
5 | {
6 | "type": "npm",
7 | "script": "start",
8 | "isBackground": true,
9 | "problemMatcher": {
10 | "owner": "typescript",
11 | "pattern": "$tsc",
12 | "background": {
13 | "activeOnStart": true,
14 | "beginsPattern": {
15 | "regexp": "(.*?)"
16 | },
17 | "endsPattern": {
18 | "regexp": "bundle generation complete"
19 | }
20 | }
21 | }
22 | },
23 | {
24 | "type": "npm",
25 | "script": "test",
26 | "isBackground": true,
27 | "problemMatcher": {
28 | "owner": "typescript",
29 | "pattern": "$tsc",
30 | "background": {
31 | "activeOnStart": true,
32 | "beginsPattern": {
33 | "regexp": "(.*?)"
34 | },
35 | "endsPattern": {
36 | "regexp": "bundle generation complete"
37 | }
38 | }
39 | }
40 | }
41 | ]
42 | }
43 |
--------------------------------------------------------------------------------
/part-5/README.md:
--------------------------------------------------------------------------------
1 | # AngularTutorial
2 |
3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 16.0.0.
4 |
5 | ## Development server
6 |
7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
8 |
9 | ## Code scaffolding
10 |
11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
12 |
13 | ## Build
14 |
15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
16 |
17 | ## Running unit tests
18 |
19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
20 |
21 | ## Running end-to-end tests
22 |
23 | Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
24 |
25 | ## Further help
26 |
27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
28 |
--------------------------------------------------------------------------------
/part-5/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-tutorial",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "ng": "ng",
6 | "start": "ng serve",
7 | "build": "ng build",
8 | "watch": "ng build --watch --configuration development",
9 | "test": "ng test"
10 | },
11 | "private": true,
12 | "dependencies": {
13 | "@angular/animations": "^16.0.0",
14 | "@angular/common": "^16.0.0",
15 | "@angular/compiler": "^16.0.0",
16 | "@angular/core": "^16.0.0",
17 | "@angular/forms": "^16.0.0",
18 | "@angular/platform-browser": "^16.0.0",
19 | "@angular/platform-browser-dynamic": "^16.0.0",
20 | "@angular/router": "^16.0.0",
21 | "rxjs": "~7.8.0",
22 | "tslib": "^2.3.0",
23 | "zone.js": "~0.13.0"
24 | },
25 | "devDependencies": {
26 | "@angular-devkit/build-angular": "^16.0.0",
27 | "@angular/cli": "~16.0.0",
28 | "@angular/compiler-cli": "^16.0.0",
29 | "@types/jasmine": "~4.3.0",
30 | "jasmine-core": "~4.6.0",
31 | "karma": "~6.4.0",
32 | "karma-chrome-launcher": "~3.2.0",
33 | "karma-coverage": "~2.2.0",
34 | "karma-jasmine": "~5.1.0",
35 | "karma-jasmine-html-reporter": "~2.0.0",
36 | "typescript": "~5.0.2"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/part-5/src/app/_components/alert.component.html:
--------------------------------------------------------------------------------
1 |
2 |
{{alert.message}}
3 |
--------------------------------------------------------------------------------
/part-5/src/app/_components/alert.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, OnDestroy } from '@angular/core';
2 | import { Subscription } from 'rxjs';
3 |
4 | import { AlertService } from '@app/_services';
5 |
6 | @Component({ selector: 'alert', templateUrl: 'alert.component.html' })
7 | export class AlertComponent implements OnInit, OnDestroy {
8 | private subscription!: Subscription;
9 | alert: any;
10 |
11 | constructor(private alertService: AlertService) { }
12 |
13 | ngOnInit() {
14 | this.subscription = this.alertService.onAlert()
15 | .subscribe(alert => {
16 | switch (alert?.type) {
17 | case 'success':
18 | alert.cssClass = 'alert alert-success';
19 | break;
20 | case 'error':
21 | alert.cssClass = 'alert alert-danger';
22 | break;
23 | }
24 |
25 | this.alert = alert;
26 | });
27 | }
28 |
29 | ngOnDestroy() {
30 | this.subscription.unsubscribe();
31 | }
32 | }
--------------------------------------------------------------------------------
/part-5/src/app/_components/index.ts:
--------------------------------------------------------------------------------
1 | export * from './alert.component';
--------------------------------------------------------------------------------
/part-5/src/app/_helpers/auth.guard.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
3 |
4 | import { AccountService } from '@app/_services';
5 |
6 | @Injectable({ providedIn: 'root' })
7 | export class AuthGuard implements CanActivate {
8 | constructor(
9 | private router: Router,
10 | private accountService: AccountService
11 | ) { }
12 |
13 | canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
14 | const user = this.accountService.userValue;
15 | if (user) {
16 | // authorised so return true
17 | return true;
18 | }
19 |
20 | // not logged in so redirect to login page with the return url
21 | this.router.navigate(['/account/login'], { queryParams: { returnUrl: state.url } });
22 | return false;
23 | }
24 | }
--------------------------------------------------------------------------------
/part-5/src/app/_helpers/error.interceptor.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
3 | import { Observable, throwError } from 'rxjs';
4 | import { catchError } from 'rxjs/operators';
5 |
6 | import { AccountService } from '@app/_services';
7 |
8 | @Injectable()
9 | export class ErrorInterceptor implements HttpInterceptor {
10 | constructor(private accountService: AccountService) { }
11 |
12 | intercept(request: HttpRequest, next: HttpHandler): Observable> {
13 | return next.handle(request).pipe(catchError(err => {
14 | if ([401, 403].includes(err.status) && this.accountService.userValue) {
15 | // auto logout if 401 or 403 response returned from api
16 | this.accountService.logout();
17 | }
18 |
19 | const error = err.error?.message || err.statusText;
20 | console.error(err);
21 | return throwError(() => error);
22 | }))
23 | }
24 | }
--------------------------------------------------------------------------------
/part-5/src/app/_helpers/index.ts:
--------------------------------------------------------------------------------
1 | export * from './auth.guard';
2 | export * from './error.interceptor';
3 | export * from './fake-backend';
4 | export * from './jwt.interceptor';
--------------------------------------------------------------------------------
/part-5/src/app/_helpers/jwt.interceptor.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
3 | import { Observable } from 'rxjs';
4 |
5 | import { environment } from '@environments/environment';
6 | import { AccountService } from '@app/_services';
7 |
8 | @Injectable()
9 | export class JwtInterceptor implements HttpInterceptor {
10 | constructor(private accountService: AccountService) { }
11 |
12 | intercept(request: HttpRequest, next: HttpHandler): Observable> {
13 | // add auth header with jwt if user is logged in and request is to the api url
14 | const user = this.accountService.userValue;
15 | const isLoggedIn = user?.token;
16 | const isApiUrl = request.url.startsWith(environment.apiUrl);
17 | if (isLoggedIn && isApiUrl) {
18 | request = request.clone({
19 | setHeaders: { Authorization: `Bearer ${user.token}` }
20 | });
21 | }
22 |
23 | return next.handle(request);
24 | }
25 | }
--------------------------------------------------------------------------------
/part-5/src/app/_models/index.ts:
--------------------------------------------------------------------------------
1 | export * from './user';
--------------------------------------------------------------------------------
/part-5/src/app/_models/user.ts:
--------------------------------------------------------------------------------
1 | export class User {
2 | id?: string;
3 | username?: string;
4 | password?: string;
5 | firstName?: string;
6 | lastName?: string;
7 | token?: string;
8 | }
--------------------------------------------------------------------------------
/part-5/src/app/_services/account.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Router } from '@angular/router';
3 | import { HttpClient } from '@angular/common/http';
4 | import { BehaviorSubject, Observable } from 'rxjs';
5 | import { map } from 'rxjs/operators';
6 |
7 | import { environment } from '@environments/environment';
8 | import { User } from '@app/_models';
9 |
10 | @Injectable({ providedIn: 'root' })
11 | export class AccountService {
12 | private userSubject: BehaviorSubject;
13 | public user: Observable;
14 |
15 | constructor(
16 | private router: Router,
17 | private http: HttpClient
18 | ) {
19 | this.userSubject = new BehaviorSubject(JSON.parse(localStorage.getItem('user')!));
20 | this.user = this.userSubject.asObservable();
21 | }
22 |
23 | public get userValue() {
24 | return this.userSubject.value;
25 | }
26 |
27 | login(username: string, password: string) {
28 | return this.http.post(`${environment.apiUrl}/users/authenticate`, { username, password })
29 | .pipe(map(user => {
30 | // store user details and jwt token in local storage to keep user logged in between page refreshes
31 | localStorage.setItem('user', JSON.stringify(user));
32 | this.userSubject.next(user);
33 | return user;
34 | }));
35 | }
36 |
37 | logout() {
38 | // remove user from local storage and set current user to null
39 | localStorage.removeItem('user');
40 | this.userSubject.next(null);
41 | this.router.navigate(['/account/login']);
42 | }
43 |
44 | register(user: User) {
45 | return this.http.post(`${environment.apiUrl}/users/register`, user);
46 | }
47 | }
--------------------------------------------------------------------------------
/part-5/src/app/_services/alert.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Router, NavigationStart } from '@angular/router';
3 | import { Observable, Subject } from 'rxjs';
4 |
5 | @Injectable({ providedIn: 'root' })
6 | export class AlertService {
7 | private subject = new Subject();
8 | private showAfterRedirect = false;
9 |
10 | constructor(private router: Router) {
11 | // clear alert messages on route change unless 'showAfterRedirect' flag is true
12 | this.router.events.subscribe(event => {
13 | if (event instanceof NavigationStart) {
14 | if (this.showAfterRedirect) {
15 | // only keep for a single route change
16 | this.showAfterRedirect = false;
17 | } else {
18 | // clear alert message
19 | this.clear();
20 | }
21 | }
22 | });
23 | }
24 |
25 | onAlert(): Observable {
26 | return this.subject.asObservable();
27 | }
28 |
29 | success(message: string, showAfterRedirect = false) {
30 | this.showAfterRedirect = showAfterRedirect;
31 | this.subject.next({ type: 'success', message });
32 | }
33 |
34 | error(message: string, showAfterRedirect = false) {
35 | this.showAfterRedirect = showAfterRedirect;
36 | this.subject.next({ type: 'error', message });
37 | }
38 |
39 | clear() {
40 | // clear by calling subject.next() with null
41 | this.subject.next(null);
42 | }
43 | }
--------------------------------------------------------------------------------
/part-5/src/app/_services/index.ts:
--------------------------------------------------------------------------------
1 | export * from './account.service';
2 | export * from './alert.service';
--------------------------------------------------------------------------------
/part-5/src/app/account/index.ts:
--------------------------------------------------------------------------------
1 | export * from './login.component';
2 | export * from './register.component';
--------------------------------------------------------------------------------
/part-5/src/app/account/login.component.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-5/src/app/account/login.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { Router, ActivatedRoute } from '@angular/router';
3 | import { FormBuilder, FormGroup, Validators } from '@angular/forms';
4 | import { first } from 'rxjs/operators';
5 |
6 | import { AccountService, AlertService } from '@app/_services'
7 |
8 | @Component({ templateUrl: 'login.component.html' })
9 | export class LoginComponent implements OnInit {
10 | form!: FormGroup;
11 | loading = false;
12 | submitted = false;
13 |
14 | constructor(
15 | private formBuilder: FormBuilder,
16 | private route: ActivatedRoute,
17 | private router: Router,
18 | private accountService: AccountService,
19 | private alertService: AlertService
20 | ) {
21 | // redirect to home if already logged in
22 | if (this.accountService.userValue) {
23 | this.router.navigate(['/']);
24 | }
25 | }
26 |
27 | ngOnInit() {
28 | this.form = this.formBuilder.group({
29 | username: ['', Validators.required],
30 | password: ['', Validators.required]
31 | });
32 | }
33 |
34 | // convenience getter for easy access to form fields
35 | get f() { return this.form.controls; }
36 |
37 | onSubmit() {
38 | this.submitted = true;
39 |
40 | // reset alerts on submit
41 | this.alertService.clear();
42 |
43 | // stop here if form is invalid
44 | if (this.form.invalid) {
45 | return;
46 | }
47 |
48 | this.loading = true;
49 | this.accountService.login(this.f.username.value, this.f.password.value)
50 | .pipe(first())
51 | .subscribe({
52 | next: () => {
53 | // get return url from query parameters or default to home page
54 | const returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';
55 | this.router.navigateByUrl(returnUrl);
56 | },
57 | error: error => {
58 | this.alertService.error(error);
59 | this.loading = false;
60 | }
61 | });
62 | }
63 | }
--------------------------------------------------------------------------------
/part-5/src/app/account/register.component.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-5/src/app/account/register.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { Router, ActivatedRoute } from '@angular/router';
3 | import { FormBuilder, FormGroup, Validators } from '@angular/forms';
4 | import { first } from 'rxjs/operators';
5 |
6 | import { AccountService, AlertService } from '@app/_services';
7 |
8 | @Component({ templateUrl: 'register.component.html' })
9 | export class RegisterComponent implements OnInit {
10 | form!: FormGroup;
11 | loading = false;
12 | submitted = false;
13 |
14 | constructor(
15 | private formBuilder: FormBuilder,
16 | private route: ActivatedRoute,
17 | private router: Router,
18 | private accountService: AccountService,
19 | private alertService: AlertService
20 | ) {
21 | // redirect to home if already logged in
22 | if (this.accountService.userValue) {
23 | this.router.navigate(['/']);
24 | }
25 | }
26 |
27 | ngOnInit() {
28 | this.form = this.formBuilder.group({
29 | firstName: ['', Validators.required],
30 | lastName: ['', Validators.required],
31 | username: ['', Validators.required],
32 | password: ['', [Validators.required, Validators.minLength(6)]]
33 | });
34 | }
35 |
36 | // convenience getter for easy access to form fields
37 | get f() { return this.form.controls; }
38 |
39 | onSubmit() {
40 | this.submitted = true;
41 |
42 | // reset alert on submit
43 | this.alertService.clear();
44 |
45 | // stop here if form is invalid
46 | if (this.form.invalid) {
47 | return;
48 | }
49 |
50 | this.loading = true;
51 | this.accountService.register(this.form.value)
52 | .pipe(first())
53 | .subscribe({
54 | next: () => {
55 | this.alertService.success('Registration successful', true);
56 | this.router.navigate(['/account/login'], { queryParams: { registered: true }});
57 | },
58 | error: error => {
59 | this.alertService.error(error);
60 | this.loading = false;
61 | }
62 | });
63 | }
64 | }
--------------------------------------------------------------------------------
/part-5/src/app/app-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { Routes, RouterModule } from '@angular/router';
3 |
4 | import { HomeComponent } from './home';
5 | import { LoginComponent, RegisterComponent } from './account';
6 | import { AuthGuard } from './_helpers';
7 |
8 | const routes: Routes = [
9 | { path: '', component: HomeComponent, canActivate: [AuthGuard] },
10 | { path: 'account/login', component: LoginComponent },
11 | { path: 'account/register', component: RegisterComponent },
12 |
13 | // otherwise redirect to home
14 | { path: '**', redirectTo: '' }
15 | ];
16 |
17 | @NgModule({
18 | imports: [RouterModule.forRoot(routes)],
19 | exports: [RouterModule]
20 | })
21 | export class AppRoutingModule { }
--------------------------------------------------------------------------------
/part-5/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/part-5/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | import { AccountService } from './_services';
4 | import { User } from './_models';
5 |
6 | @Component({ selector: 'app-root', templateUrl: 'app.component.html' })
7 | export class AppComponent {
8 | user?: User | null;
9 |
10 | constructor(private accountService: AccountService) {
11 | this.accountService.user.subscribe(x => this.user = x);
12 | }
13 |
14 | logout() {
15 | this.accountService.logout();
16 | }
17 | }
--------------------------------------------------------------------------------
/part-5/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { BrowserModule } from '@angular/platform-browser';
3 | import { ReactiveFormsModule } from '@angular/forms';
4 | import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
5 |
6 | // used to create fake backend
7 | import { fakeBackendProvider } from './_helpers';
8 |
9 | import { AppRoutingModule } from './app-routing.module';
10 | import { JwtInterceptor, ErrorInterceptor } from './_helpers';
11 | import { AppComponent } from './app.component';
12 | import { HomeComponent } from './home';
13 | import { LoginComponent, RegisterComponent } from './account';
14 | import { AlertComponent } from './_components';
15 |
16 | @NgModule({
17 | imports: [
18 | BrowserModule,
19 | ReactiveFormsModule,
20 | HttpClientModule,
21 | AppRoutingModule
22 | ],
23 | declarations: [
24 | AppComponent,
25 | HomeComponent,
26 | LoginComponent,
27 | RegisterComponent,
28 | AlertComponent
29 | ],
30 | providers: [
31 | { provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true },
32 | { provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true },
33 |
34 | // provider used to create fake backend
35 | fakeBackendProvider
36 | ],
37 | bootstrap: [AppComponent]
38 | })
39 | export class AppModule { }
--------------------------------------------------------------------------------
/part-5/src/app/home/home.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Hi {{user?.firstName}}!
4 |
You're logged in with Angular!!
5 |
6 |
--------------------------------------------------------------------------------
/part-5/src/app/home/home.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | import { User } from '@app/_models';
4 | import { AccountService } from '@app/_services';
5 |
6 | @Component({ templateUrl: 'home.component.html' })
7 | export class HomeComponent {
8 | user: User | null;
9 |
10 | constructor(private accountService: AccountService) {
11 | this.user = this.accountService.userValue;
12 | }
13 | }
--------------------------------------------------------------------------------
/part-5/src/app/home/index.ts:
--------------------------------------------------------------------------------
1 | export * from './home.component';
--------------------------------------------------------------------------------
/part-5/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cornflourblue/angular-16-tutorial/a2d771bd813e937d38b1d47ea93bfc270905ee2c/part-5/src/assets/.gitkeep
--------------------------------------------------------------------------------
/part-5/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | apiUrl: 'http://localhost:4000'
3 | };
--------------------------------------------------------------------------------
/part-5/src/favicon.ico:
--------------------------------------------------------------------------------
1 | �PNG
2 |
3 |
IHDR ?�~� pHYs ��~� fIDATH��WKLQ���̔�uG�� e�n�.6qcb�l?���D`�F#�
Ku�F
1Qc�
4 | ��!���� ��C�P�|B?$���ܱ3����I&}��}�̽s�[*�ɀU�A� �K��yx�gY�Ajq��3L Š� ��˫�OD�4��3Ϗ:X�3��o�PJ�ğo#IH�a����,{>1/�2$�R AR]�)w��?�sZw^��q�Y�m_��e���r[8�^�
5 | �&p��-���A}c��- ������!����2_)E�)㊪j���v�m� �ZOi�g�nW�{�n.|�e2�a&�0aŸ����be�̀��C�fˤE%-{�ֺ��C��N��jXi�~c�C,t��T�����r �{� �L)s��V��6%�(�#ᤙ!�]��H�ҐH$R���^R�A�61(?Y舚�>���(Z����Qm�L2�K�ZIc��
6 | ���̧�C��2!⅄ �(����" �Go��>�q��=��$%�z`ѯ��T�&����PHh�Z!=� ��z��O��������,*VVV�1�f*CJ�]EE��K�k��d�#5���`2yT!�}7���߈~�,���zs�����y�T��V������D��C2�G��@%̑72Y�{oJ�"@��^h�~��fĬ�!a�D��6���Ha|�3��� [>�����]7U2п���] �ė
7 | ��PU��.Wejq�in�g��+ p<ߺQH����總j[������.��� Q���p _�K��1(��+��bB8�\ra
8 | �́�v.l���(���ǽ�w���L��w�8�C�� IEND�B`�
--------------------------------------------------------------------------------
/part-5/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Angular Tutorial
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/part-5/src/main.ts:
--------------------------------------------------------------------------------
1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
2 |
3 | import { AppModule } from './app/app.module';
4 |
5 |
6 | platformBrowserDynamic().bootstrapModule(AppModule)
7 | .catch(err => console.error(err));
8 |
--------------------------------------------------------------------------------
/part-5/src/styles.css:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 |
--------------------------------------------------------------------------------
/part-5/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/app",
6 | "types": []
7 | },
8 | "files": [
9 | "src/main.ts"
10 | ],
11 | "include": [
12 | "src/**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/part-5/tsconfig.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "compileOnSave": false,
4 | "compilerOptions": {
5 | "baseUrl": "./",
6 | "outDir": "./dist/out-tsc",
7 | "forceConsistentCasingInFileNames": true,
8 | "strict": true,
9 | "noImplicitOverride": true,
10 | "noPropertyAccessFromIndexSignature": false,
11 | "noImplicitReturns": true,
12 | "noFallthroughCasesInSwitch": true,
13 | "sourceMap": true,
14 | "declaration": false,
15 | "downlevelIteration": true,
16 | "experimentalDecorators": true,
17 | "moduleResolution": "node",
18 | "importHelpers": true,
19 | "target": "ES2022",
20 | "module": "ES2022",
21 | "useDefineForClassFields": false,
22 | "lib": [
23 | "ES2022",
24 | "dom"
25 | ],
26 | "paths": {
27 | "@app/*": ["src/app/*"],
28 | "@environments/*": ["src/environments/*"]
29 | }
30 | },
31 | "angularCompilerOptions": {
32 | "enableI18nLegacyMessageIdFormat": false,
33 | "strictInjectionParameters": true,
34 | "strictInputAccessModifiers": true,
35 | "strictTemplates": true
36 | }
37 | }
--------------------------------------------------------------------------------
/part-5/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/spec",
6 | "types": [
7 | "jasmine"
8 | ]
9 | },
10 | "include": [
11 | "src/**/*.spec.ts",
12 | "src/**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/part-6/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 |
14 | [*.md]
15 | max_line_length = off
16 | trim_trailing_whitespace = false
17 |
--------------------------------------------------------------------------------
/part-6/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # Compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 | /bazel-out
8 |
9 | # Node
10 | /node_modules
11 | npm-debug.log
12 | yarn-error.log
13 |
14 | # IDEs and editors
15 | .idea/
16 | .project
17 | .classpath
18 | .c9/
19 | *.launch
20 | .settings/
21 | *.sublime-workspace
22 |
23 | # Visual Studio Code
24 | .vscode/*
25 | !.vscode/settings.json
26 | !.vscode/tasks.json
27 | !.vscode/launch.json
28 | !.vscode/extensions.json
29 | .history/*
30 |
31 | # Miscellaneous
32 | /.angular/cache
33 | .sass-cache/
34 | /connect.lock
35 | /coverage
36 | /libpeerconnection.log
37 | testem.log
38 | /typings
39 |
40 | # System files
41 | .DS_Store
42 | Thumbs.db
43 |
--------------------------------------------------------------------------------
/part-6/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
3 | "recommendations": ["angular.ng-template"]
4 | }
5 |
--------------------------------------------------------------------------------
/part-6/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
3 | "version": "0.2.0",
4 | "configurations": [
5 | {
6 | "name": "ng serve",
7 | "type": "chrome",
8 | "request": "launch",
9 | "preLaunchTask": "npm: start",
10 | "url": "http://localhost:4200/"
11 | },
12 | {
13 | "name": "ng test",
14 | "type": "chrome",
15 | "request": "launch",
16 | "preLaunchTask": "npm: test",
17 | "url": "http://localhost:9876/debug.html"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/part-6/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
3 | "version": "2.0.0",
4 | "tasks": [
5 | {
6 | "type": "npm",
7 | "script": "start",
8 | "isBackground": true,
9 | "problemMatcher": {
10 | "owner": "typescript",
11 | "pattern": "$tsc",
12 | "background": {
13 | "activeOnStart": true,
14 | "beginsPattern": {
15 | "regexp": "(.*?)"
16 | },
17 | "endsPattern": {
18 | "regexp": "bundle generation complete"
19 | }
20 | }
21 | }
22 | },
23 | {
24 | "type": "npm",
25 | "script": "test",
26 | "isBackground": true,
27 | "problemMatcher": {
28 | "owner": "typescript",
29 | "pattern": "$tsc",
30 | "background": {
31 | "activeOnStart": true,
32 | "beginsPattern": {
33 | "regexp": "(.*?)"
34 | },
35 | "endsPattern": {
36 | "regexp": "bundle generation complete"
37 | }
38 | }
39 | }
40 | }
41 | ]
42 | }
43 |
--------------------------------------------------------------------------------
/part-6/README.md:
--------------------------------------------------------------------------------
1 | # AngularTutorial
2 |
3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 16.0.0.
4 |
5 | ## Development server
6 |
7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
8 |
9 | ## Code scaffolding
10 |
11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
12 |
13 | ## Build
14 |
15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
16 |
17 | ## Running unit tests
18 |
19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
20 |
21 | ## Running end-to-end tests
22 |
23 | Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
24 |
25 | ## Further help
26 |
27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
28 |
--------------------------------------------------------------------------------
/part-6/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-tutorial",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "ng": "ng",
6 | "start": "ng serve",
7 | "build": "ng build",
8 | "watch": "ng build --watch --configuration development",
9 | "test": "ng test"
10 | },
11 | "private": true,
12 | "dependencies": {
13 | "@angular/animations": "^16.0.0",
14 | "@angular/common": "^16.0.0",
15 | "@angular/compiler": "^16.0.0",
16 | "@angular/core": "^16.0.0",
17 | "@angular/forms": "^16.0.0",
18 | "@angular/platform-browser": "^16.0.0",
19 | "@angular/platform-browser-dynamic": "^16.0.0",
20 | "@angular/router": "^16.0.0",
21 | "rxjs": "~7.8.0",
22 | "tslib": "^2.3.0",
23 | "zone.js": "~0.13.0"
24 | },
25 | "devDependencies": {
26 | "@angular-devkit/build-angular": "^16.0.0",
27 | "@angular/cli": "~16.0.0",
28 | "@angular/compiler-cli": "^16.0.0",
29 | "@types/jasmine": "~4.3.0",
30 | "jasmine-core": "~4.6.0",
31 | "karma": "~6.4.0",
32 | "karma-chrome-launcher": "~3.2.0",
33 | "karma-coverage": "~2.2.0",
34 | "karma-jasmine": "~5.1.0",
35 | "karma-jasmine-html-reporter": "~2.0.0",
36 | "typescript": "~5.0.2"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/part-6/src/app/_components/alert.component.html:
--------------------------------------------------------------------------------
1 |
2 |
{{alert.message}}
3 |
--------------------------------------------------------------------------------
/part-6/src/app/_components/alert.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, OnDestroy } from '@angular/core';
2 | import { Subscription } from 'rxjs';
3 |
4 | import { AlertService } from '@app/_services';
5 |
6 | @Component({ selector: 'alert', templateUrl: 'alert.component.html' })
7 | export class AlertComponent implements OnInit, OnDestroy {
8 | private subscription!: Subscription;
9 | alert: any;
10 |
11 | constructor(private alertService: AlertService) { }
12 |
13 | ngOnInit() {
14 | this.subscription = this.alertService.onAlert()
15 | .subscribe(alert => {
16 | switch (alert?.type) {
17 | case 'success':
18 | alert.cssClass = 'alert alert-success';
19 | break;
20 | case 'error':
21 | alert.cssClass = 'alert alert-danger';
22 | break;
23 | }
24 |
25 | this.alert = alert;
26 | });
27 | }
28 |
29 | ngOnDestroy() {
30 | this.subscription.unsubscribe();
31 | }
32 | }
--------------------------------------------------------------------------------
/part-6/src/app/_components/index.ts:
--------------------------------------------------------------------------------
1 | export * from './alert.component';
--------------------------------------------------------------------------------
/part-6/src/app/_helpers/auth.guard.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
3 |
4 | import { AccountService } from '@app/_services';
5 |
6 | @Injectable({ providedIn: 'root' })
7 | export class AuthGuard implements CanActivate {
8 | constructor(
9 | private router: Router,
10 | private accountService: AccountService
11 | ) { }
12 |
13 | canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
14 | const user = this.accountService.userValue;
15 | if (user) {
16 | // authorised so return true
17 | return true;
18 | }
19 |
20 | // not logged in so redirect to login page with the return url
21 | this.router.navigate(['/account/login'], { queryParams: { returnUrl: state.url } });
22 | return false;
23 | }
24 | }
--------------------------------------------------------------------------------
/part-6/src/app/_helpers/error.interceptor.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
3 | import { Observable, throwError } from 'rxjs';
4 | import { catchError } from 'rxjs/operators';
5 |
6 | import { AccountService } from '@app/_services';
7 |
8 | @Injectable()
9 | export class ErrorInterceptor implements HttpInterceptor {
10 | constructor(private accountService: AccountService) { }
11 |
12 | intercept(request: HttpRequest, next: HttpHandler): Observable> {
13 | return next.handle(request).pipe(catchError(err => {
14 | if ([401, 403].includes(err.status) && this.accountService.userValue) {
15 | // auto logout if 401 or 403 response returned from api
16 | this.accountService.logout();
17 | }
18 |
19 | const error = err.error?.message || err.statusText;
20 | console.error(err);
21 | return throwError(() => error);
22 | }))
23 | }
24 | }
--------------------------------------------------------------------------------
/part-6/src/app/_helpers/index.ts:
--------------------------------------------------------------------------------
1 | export * from './auth.guard';
2 | export * from './error.interceptor';
3 | export * from './fake-backend';
4 | export * from './jwt.interceptor';
--------------------------------------------------------------------------------
/part-6/src/app/_helpers/jwt.interceptor.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
3 | import { Observable } from 'rxjs';
4 |
5 | import { environment } from '@environments/environment';
6 | import { AccountService } from '@app/_services';
7 |
8 | @Injectable()
9 | export class JwtInterceptor implements HttpInterceptor {
10 | constructor(private accountService: AccountService) { }
11 |
12 | intercept(request: HttpRequest, next: HttpHandler): Observable> {
13 | // add auth header with jwt if user is logged in and request is to the api url
14 | const user = this.accountService.userValue;
15 | const isLoggedIn = user?.token;
16 | const isApiUrl = request.url.startsWith(environment.apiUrl);
17 | if (isLoggedIn && isApiUrl) {
18 | request = request.clone({
19 | setHeaders: { Authorization: `Bearer ${user.token}` }
20 | });
21 | }
22 |
23 | return next.handle(request);
24 | }
25 | }
--------------------------------------------------------------------------------
/part-6/src/app/_models/index.ts:
--------------------------------------------------------------------------------
1 | export * from './user';
--------------------------------------------------------------------------------
/part-6/src/app/_models/user.ts:
--------------------------------------------------------------------------------
1 | export class User {
2 | id?: string;
3 | username?: string;
4 | password?: string;
5 | firstName?: string;
6 | lastName?: string;
7 | token?: string;
8 | }
--------------------------------------------------------------------------------
/part-6/src/app/_services/alert.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Router, NavigationStart } from '@angular/router';
3 | import { Observable, Subject } from 'rxjs';
4 |
5 | @Injectable({ providedIn: 'root' })
6 | export class AlertService {
7 | private subject = new Subject();
8 | private showAfterRedirect = false;
9 |
10 | constructor(private router: Router) {
11 | // clear alert messages on route change unless 'showAfterRedirect' flag is true
12 | this.router.events.subscribe(event => {
13 | if (event instanceof NavigationStart) {
14 | if (this.showAfterRedirect) {
15 | // only keep for a single route change
16 | this.showAfterRedirect = false;
17 | } else {
18 | // clear alert message
19 | this.clear();
20 | }
21 | }
22 | });
23 | }
24 |
25 | onAlert(): Observable {
26 | return this.subject.asObservable();
27 | }
28 |
29 | success(message: string, showAfterRedirect = false) {
30 | this.showAfterRedirect = showAfterRedirect;
31 | this.subject.next({ type: 'success', message });
32 | }
33 |
34 | error(message: string, showAfterRedirect = false) {
35 | this.showAfterRedirect = showAfterRedirect;
36 | this.subject.next({ type: 'error', message });
37 | }
38 |
39 | clear() {
40 | // clear by calling subject.next() with null
41 | this.subject.next(null);
42 | }
43 | }
--------------------------------------------------------------------------------
/part-6/src/app/_services/index.ts:
--------------------------------------------------------------------------------
1 | export * from './account.service';
2 | export * from './alert.service';
--------------------------------------------------------------------------------
/part-6/src/app/account/index.ts:
--------------------------------------------------------------------------------
1 | export * from './login.component';
2 | export * from './register.component';
--------------------------------------------------------------------------------
/part-6/src/app/account/login.component.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-6/src/app/account/login.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { Router, ActivatedRoute } from '@angular/router';
3 | import { FormBuilder, FormGroup, Validators } from '@angular/forms';
4 | import { first } from 'rxjs/operators';
5 |
6 | import { AccountService, AlertService } from '@app/_services'
7 |
8 | @Component({ templateUrl: 'login.component.html' })
9 | export class LoginComponent implements OnInit {
10 | form!: FormGroup;
11 | loading = false;
12 | submitted = false;
13 |
14 | constructor(
15 | private formBuilder: FormBuilder,
16 | private route: ActivatedRoute,
17 | private router: Router,
18 | private accountService: AccountService,
19 | private alertService: AlertService
20 | ) {
21 | // redirect to home if already logged in
22 | if (this.accountService.userValue) {
23 | this.router.navigate(['/']);
24 | }
25 | }
26 |
27 | ngOnInit() {
28 | this.form = this.formBuilder.group({
29 | username: ['', Validators.required],
30 | password: ['', Validators.required]
31 | });
32 | }
33 |
34 | // convenience getter for easy access to form fields
35 | get f() { return this.form.controls; }
36 |
37 | onSubmit() {
38 | this.submitted = true;
39 |
40 | // reset alerts on submit
41 | this.alertService.clear();
42 |
43 | // stop here if form is invalid
44 | if (this.form.invalid) {
45 | return;
46 | }
47 |
48 | this.loading = true;
49 | this.accountService.login(this.f.username.value, this.f.password.value)
50 | .pipe(first())
51 | .subscribe({
52 | next: () => {
53 | // get return url from query parameters or default to home page
54 | const returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';
55 | this.router.navigateByUrl(returnUrl);
56 | },
57 | error: error => {
58 | this.alertService.error(error);
59 | this.loading = false;
60 | }
61 | });
62 | }
63 | }
--------------------------------------------------------------------------------
/part-6/src/app/account/register.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { Router, ActivatedRoute } from '@angular/router';
3 | import { FormBuilder, FormGroup, Validators } from '@angular/forms';
4 | import { first } from 'rxjs/operators';
5 |
6 | import { AccountService, AlertService } from '@app/_services';
7 |
8 | @Component({ templateUrl: 'register.component.html' })
9 | export class RegisterComponent implements OnInit {
10 | form!: FormGroup;
11 | loading = false;
12 | submitted = false;
13 |
14 | constructor(
15 | private formBuilder: FormBuilder,
16 | private route: ActivatedRoute,
17 | private router: Router,
18 | private accountService: AccountService,
19 | private alertService: AlertService
20 | ) {
21 | // redirect to home if already logged in
22 | if (this.accountService.userValue) {
23 | this.router.navigate(['/']);
24 | }
25 | }
26 |
27 | ngOnInit() {
28 | this.form = this.formBuilder.group({
29 | firstName: ['', Validators.required],
30 | lastName: ['', Validators.required],
31 | username: ['', Validators.required],
32 | password: ['', [Validators.required, Validators.minLength(6)]]
33 | });
34 | }
35 |
36 | // convenience getter for easy access to form fields
37 | get f() { return this.form.controls; }
38 |
39 | onSubmit() {
40 | this.submitted = true;
41 |
42 | // reset alert on submit
43 | this.alertService.clear();
44 |
45 | // stop here if form is invalid
46 | if (this.form.invalid) {
47 | return;
48 | }
49 |
50 | this.loading = true;
51 | this.accountService.register(this.form.value)
52 | .pipe(first())
53 | .subscribe({
54 | next: () => {
55 | this.alertService.success('Registration successful', true);
56 | this.router.navigate(['/account/login'], { queryParams: { registered: true }});
57 | },
58 | error: error => {
59 | this.alertService.error(error);
60 | this.loading = false;
61 | }
62 | });
63 | }
64 | }
--------------------------------------------------------------------------------
/part-6/src/app/app-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { Routes, RouterModule } from '@angular/router';
3 |
4 | import { HomeComponent } from './home';
5 | import { LoginComponent, RegisterComponent } from './account';
6 | import { AuthGuard } from './_helpers';
7 |
8 | const usersModule = () => import('./users/users.module').then(x => x.UsersModule);
9 |
10 | const routes: Routes = [
11 | { path: '', component: HomeComponent, canActivate: [AuthGuard] },
12 | { path: 'users', loadChildren: usersModule, canActivate: [AuthGuard] },
13 | { path: 'account/login', component: LoginComponent },
14 | { path: 'account/register', component: RegisterComponent },
15 |
16 | // otherwise redirect to home
17 | { path: '**', redirectTo: '' }
18 | ];
19 |
20 | @NgModule({
21 | imports: [RouterModule.forRoot(routes)],
22 | exports: [RouterModule]
23 | })
24 | export class AppRoutingModule { }
--------------------------------------------------------------------------------
/part-6/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/part-6/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | import { AccountService } from './_services';
4 | import { User } from './_models';
5 |
6 | @Component({ selector: 'app-root', templateUrl: 'app.component.html' })
7 | export class AppComponent {
8 | user?: User | null;
9 |
10 | constructor(private accountService: AccountService) {
11 | this.accountService.user.subscribe(x => this.user = x);
12 | }
13 |
14 | logout() {
15 | this.accountService.logout();
16 | }
17 | }
--------------------------------------------------------------------------------
/part-6/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { BrowserModule } from '@angular/platform-browser';
3 | import { ReactiveFormsModule } from '@angular/forms';
4 | import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
5 |
6 | // used to create fake backend
7 | import { fakeBackendProvider } from './_helpers';
8 |
9 | import { AppRoutingModule } from './app-routing.module';
10 | import { JwtInterceptor, ErrorInterceptor } from './_helpers';
11 | import { AppComponent } from './app.component';
12 | import { HomeComponent } from './home';
13 | import { LoginComponent, RegisterComponent } from './account';
14 | import { AlertComponent } from './_components';
15 |
16 | @NgModule({
17 | imports: [
18 | BrowserModule,
19 | ReactiveFormsModule,
20 | HttpClientModule,
21 | AppRoutingModule
22 | ],
23 | declarations: [
24 | AppComponent,
25 | HomeComponent,
26 | LoginComponent,
27 | RegisterComponent,
28 | AlertComponent
29 | ],
30 | providers: [
31 | { provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true },
32 | { provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true },
33 |
34 | // provider used to create fake backend
35 | fakeBackendProvider
36 | ],
37 | bootstrap: [AppComponent]
38 | })
39 | export class AppModule { }
--------------------------------------------------------------------------------
/part-6/src/app/home/home.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Hi {{user?.firstName}}!
4 |
You're logged in with Angular!!
5 |
6 |
--------------------------------------------------------------------------------
/part-6/src/app/home/home.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | import { User } from '@app/_models';
4 | import { AccountService } from '@app/_services';
5 |
6 | @Component({ templateUrl: 'home.component.html' })
7 | export class HomeComponent {
8 | user: User | null;
9 |
10 | constructor(private accountService: AccountService) {
11 | this.user = this.accountService.userValue;
12 | }
13 | }
--------------------------------------------------------------------------------
/part-6/src/app/home/index.ts:
--------------------------------------------------------------------------------
1 | export * from './home.component';
--------------------------------------------------------------------------------
/part-6/src/app/users/list.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Users
4 |
Add User
5 |
6 |
7 |
8 | First Name |
9 | Last Name |
10 | Username |
11 | |
12 |
13 |
14 |
15 |
16 | {{user.firstName}} |
17 | {{user.lastName}} |
18 | {{user.username}} |
19 |
20 | Edit
21 |
25 | |
26 |
27 |
28 |
29 |
30 | |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/part-6/src/app/users/list.component.ts:
--------------------------------------------------------------------------------
1 |
2 | import { Component, OnInit } from '@angular/core';
3 | import { first } from 'rxjs/operators';
4 |
5 | import { AccountService } from '@app/_services';
6 |
7 | @Component({ templateUrl: 'list.component.html' })
8 | export class ListComponent implements OnInit {
9 | users?: any[];
10 |
11 | constructor(private accountService: AccountService) {}
12 |
13 | ngOnInit() {
14 | this.accountService.getAll()
15 | .pipe(first())
16 | .subscribe(users => this.users = users);
17 | }
18 |
19 | deleteUser(id: string) {
20 | const user = this.users!.find(x => x.id === id);
21 | user.isDeleting = true;
22 | this.accountService.delete(id)
23 | .pipe(first())
24 | .subscribe(() => this.users = this.users!.filter(x => x.id !== id));
25 | }
26 | }
--------------------------------------------------------------------------------
/part-6/src/app/users/users-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { Routes, RouterModule } from '@angular/router';
3 |
4 | import { ListComponent } from './list.component';
5 | import { AddEditComponent } from './add-edit.component';
6 |
7 | const routes: Routes = [
8 | { path: '', component: ListComponent },
9 | { path: 'add', component: AddEditComponent },
10 | { path: 'edit/:id', component: AddEditComponent }
11 | ];
12 |
13 | @NgModule({
14 | imports: [RouterModule.forChild(routes)],
15 | exports: [RouterModule]
16 | })
17 | export class UsersRoutingModule { }
--------------------------------------------------------------------------------
/part-6/src/app/users/users.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { ReactiveFormsModule } from '@angular/forms';
3 | import { CommonModule } from '@angular/common';
4 |
5 | import { UsersRoutingModule } from './users-routing.module';
6 | import { ListComponent } from './list.component';
7 | import { AddEditComponent } from './add-edit.component';
8 |
9 | @NgModule({
10 | imports: [
11 | CommonModule,
12 | ReactiveFormsModule,
13 | UsersRoutingModule
14 | ],
15 | declarations: [
16 | ListComponent,
17 | AddEditComponent
18 | ]
19 | })
20 | export class UsersModule { }
--------------------------------------------------------------------------------
/part-6/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cornflourblue/angular-16-tutorial/a2d771bd813e937d38b1d47ea93bfc270905ee2c/part-6/src/assets/.gitkeep
--------------------------------------------------------------------------------
/part-6/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | apiUrl: 'http://localhost:4000'
3 | };
--------------------------------------------------------------------------------
/part-6/src/favicon.ico:
--------------------------------------------------------------------------------
1 | �PNG
2 |
3 |
IHDR ?�~� pHYs ��~� fIDATH��WKLQ���̔�uG�� e�n�.6qcb�l?���D`�F#�
Ku�F
1Qc�
4 | ��!���� ��C�P�|B?$���ܱ3����I&}��}�̽s�[*�ɀU�A� �K��yx�gY�Ajq��3L Š� ��˫�OD�4��3Ϗ:X�3��o�PJ�ğo#IH�a����,{>1/�2$�R AR]�)w��?�sZw^��q�Y�m_��e���r[8�^�
5 | �&p��-���A}c��- ������!����2_)E�)㊪j���v�m� �ZOi�g�nW�{�n.|�e2�a&�0aŸ����be�̀��C�fˤE%-{�ֺ��C��N��jXi�~c�C,t��T�����r �{� �L)s��V��6%�(�#ᤙ!�]��H�ҐH$R���^R�A�61(?Y舚�>���(Z����Qm�L2�K�ZIc��
6 | ���̧�C��2!⅄ �(����" �Go��>�q��=��$%�z`ѯ��T�&����PHh�Z!=� ��z��O��������,*VVV�1�f*CJ�]EE��K�k��d�#5���`2yT!�}7���߈~�,���zs�����y�T��V������D��C2�G��@%̑72Y�{oJ�"@��^h�~��fĬ�!a�D��6���Ha|�3��� [>�����]7U2п���] �ė
7 | ��PU��.Wejq�in�g��+ p<ߺQH����總j[������.��� Q���p _�K��1(��+��bB8�\ra
8 | �́�v.l���(���ǽ�w���L��w�8�C�� IEND�B`�
--------------------------------------------------------------------------------
/part-6/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Angular Tutorial
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/part-6/src/main.ts:
--------------------------------------------------------------------------------
1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
2 |
3 | import { AppModule } from './app/app.module';
4 |
5 |
6 | platformBrowserDynamic().bootstrapModule(AppModule)
7 | .catch(err => console.error(err));
8 |
--------------------------------------------------------------------------------
/part-6/src/styles.css:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 |
--------------------------------------------------------------------------------
/part-6/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/app",
6 | "types": []
7 | },
8 | "files": [
9 | "src/main.ts"
10 | ],
11 | "include": [
12 | "src/**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/part-6/tsconfig.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "compileOnSave": false,
4 | "compilerOptions": {
5 | "baseUrl": "./",
6 | "outDir": "./dist/out-tsc",
7 | "forceConsistentCasingInFileNames": true,
8 | "strict": true,
9 | "noImplicitOverride": true,
10 | "noPropertyAccessFromIndexSignature": false,
11 | "noImplicitReturns": true,
12 | "noFallthroughCasesInSwitch": true,
13 | "sourceMap": true,
14 | "declaration": false,
15 | "downlevelIteration": true,
16 | "experimentalDecorators": true,
17 | "moduleResolution": "node",
18 | "importHelpers": true,
19 | "target": "ES2022",
20 | "module": "ES2022",
21 | "useDefineForClassFields": false,
22 | "lib": [
23 | "ES2022",
24 | "dom"
25 | ],
26 | "paths": {
27 | "@app/*": ["src/app/*"],
28 | "@environments/*": ["src/environments/*"]
29 | }
30 | },
31 | "angularCompilerOptions": {
32 | "enableI18nLegacyMessageIdFormat": false,
33 | "strictInjectionParameters": true,
34 | "strictInputAccessModifiers": true,
35 | "strictTemplates": true
36 | }
37 | }
--------------------------------------------------------------------------------
/part-6/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/spec",
6 | "types": [
7 | "jasmine"
8 | ]
9 | },
10 | "include": [
11 | "src/**/*.spec.ts",
12 | "src/**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/part-7/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 |
14 | [*.md]
15 | max_line_length = off
16 | trim_trailing_whitespace = false
17 |
--------------------------------------------------------------------------------
/part-7/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # Compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 | /bazel-out
8 |
9 | # Node
10 | /node_modules
11 | npm-debug.log
12 | yarn-error.log
13 |
14 | # IDEs and editors
15 | .idea/
16 | .project
17 | .classpath
18 | .c9/
19 | *.launch
20 | .settings/
21 | *.sublime-workspace
22 |
23 | # Visual Studio Code
24 | .vscode/*
25 | !.vscode/settings.json
26 | !.vscode/tasks.json
27 | !.vscode/launch.json
28 | !.vscode/extensions.json
29 | .history/*
30 |
31 | # Miscellaneous
32 | /.angular/cache
33 | .sass-cache/
34 | /connect.lock
35 | /coverage
36 | /libpeerconnection.log
37 | testem.log
38 | /typings
39 |
40 | # System files
41 | .DS_Store
42 | Thumbs.db
43 |
--------------------------------------------------------------------------------
/part-7/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
3 | "recommendations": ["angular.ng-template"]
4 | }
5 |
--------------------------------------------------------------------------------
/part-7/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
3 | "version": "0.2.0",
4 | "configurations": [
5 | {
6 | "name": "ng serve",
7 | "type": "chrome",
8 | "request": "launch",
9 | "preLaunchTask": "npm: start",
10 | "url": "http://localhost:4200/"
11 | },
12 | {
13 | "name": "ng test",
14 | "type": "chrome",
15 | "request": "launch",
16 | "preLaunchTask": "npm: test",
17 | "url": "http://localhost:9876/debug.html"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/part-7/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
3 | "version": "2.0.0",
4 | "tasks": [
5 | {
6 | "type": "npm",
7 | "script": "start",
8 | "isBackground": true,
9 | "problemMatcher": {
10 | "owner": "typescript",
11 | "pattern": "$tsc",
12 | "background": {
13 | "activeOnStart": true,
14 | "beginsPattern": {
15 | "regexp": "(.*?)"
16 | },
17 | "endsPattern": {
18 | "regexp": "bundle generation complete"
19 | }
20 | }
21 | }
22 | },
23 | {
24 | "type": "npm",
25 | "script": "test",
26 | "isBackground": true,
27 | "problemMatcher": {
28 | "owner": "typescript",
29 | "pattern": "$tsc",
30 | "background": {
31 | "activeOnStart": true,
32 | "beginsPattern": {
33 | "regexp": "(.*?)"
34 | },
35 | "endsPattern": {
36 | "regexp": "bundle generation complete"
37 | }
38 | }
39 | }
40 | }
41 | ]
42 | }
43 |
--------------------------------------------------------------------------------
/part-7/README.md:
--------------------------------------------------------------------------------
1 | # AngularTutorial
2 |
3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 16.0.0.
4 |
5 | ## Development server
6 |
7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
8 |
9 | ## Code scaffolding
10 |
11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
12 |
13 | ## Build
14 |
15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
16 |
17 | ## Running unit tests
18 |
19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
20 |
21 | ## Running end-to-end tests
22 |
23 | Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
24 |
25 | ## Further help
26 |
27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
28 |
--------------------------------------------------------------------------------
/part-7/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-tutorial",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "ng": "ng",
6 | "start": "ng serve",
7 | "build": "ng build",
8 | "watch": "ng build --watch --configuration development",
9 | "test": "ng test"
10 | },
11 | "private": true,
12 | "dependencies": {
13 | "@angular/animations": "^16.0.0",
14 | "@angular/common": "^16.0.0",
15 | "@angular/compiler": "^16.0.0",
16 | "@angular/core": "^16.0.0",
17 | "@angular/forms": "^16.0.0",
18 | "@angular/platform-browser": "^16.0.0",
19 | "@angular/platform-browser-dynamic": "^16.0.0",
20 | "@angular/router": "^16.0.0",
21 | "rxjs": "~7.8.0",
22 | "tslib": "^2.3.0",
23 | "zone.js": "~0.13.0"
24 | },
25 | "devDependencies": {
26 | "@angular-devkit/build-angular": "^16.0.0",
27 | "@angular/cli": "~16.0.0",
28 | "@angular/compiler-cli": "^16.0.0",
29 | "@types/jasmine": "~4.3.0",
30 | "jasmine-core": "~4.6.0",
31 | "karma": "~6.4.0",
32 | "karma-chrome-launcher": "~3.2.0",
33 | "karma-coverage": "~2.2.0",
34 | "karma-jasmine": "~5.1.0",
35 | "karma-jasmine-html-reporter": "~2.0.0",
36 | "typescript": "~5.0.2"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/part-7/src/app/_components/alert.component.html:
--------------------------------------------------------------------------------
1 |
2 |
{{alert.message}}
3 |
--------------------------------------------------------------------------------
/part-7/src/app/_components/alert.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, OnDestroy } from '@angular/core';
2 | import { NgIf, NgClass } from '@angular/common';
3 | import { Subscription } from 'rxjs';
4 |
5 | import { AlertService } from '@app/_services';
6 |
7 | @Component({
8 | selector: 'alert', templateUrl: 'alert.component.html',
9 | standalone: true,
10 | imports: [NgIf, NgClass]
11 | })
12 | export class AlertComponent implements OnInit, OnDestroy {
13 | private subscription!: Subscription;
14 | alert: any;
15 |
16 | constructor(private alertService: AlertService) { }
17 |
18 | ngOnInit() {
19 | this.subscription = this.alertService.onAlert()
20 | .subscribe(alert => {
21 | switch (alert?.type) {
22 | case 'success':
23 | alert.cssClass = 'alert alert-success';
24 | break;
25 | case 'error':
26 | alert.cssClass = 'alert alert-danger';
27 | break;
28 | }
29 |
30 | this.alert = alert;
31 | });
32 | }
33 |
34 | ngOnDestroy() {
35 | this.subscription.unsubscribe();
36 | }
37 | }
--------------------------------------------------------------------------------
/part-7/src/app/_components/index.ts:
--------------------------------------------------------------------------------
1 | export * from './alert.component';
--------------------------------------------------------------------------------
/part-7/src/app/_helpers/auth.guard.ts:
--------------------------------------------------------------------------------
1 | import { inject } from '@angular/core';
2 | import { Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
3 |
4 | import { AccountService } from '@app/_services';
5 |
6 | export function authGuard(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
7 | const router = inject(Router);
8 | const accountService = inject(AccountService);
9 | const user = accountService.userValue;
10 | if (user) {
11 | // authorised so return true
12 | return true;
13 | }
14 |
15 | // not logged in so redirect to login page with the return url
16 | router.navigate(['/account/login'], { queryParams: { returnUrl: state.url } });
17 | return false;
18 | }
19 |
--------------------------------------------------------------------------------
/part-7/src/app/_helpers/error.interceptor.ts:
--------------------------------------------------------------------------------
1 | import { inject } from '@angular/core';
2 | import { HttpRequest, HttpHandlerFn } from '@angular/common/http';
3 | import { throwError } from 'rxjs';
4 | import { catchError } from 'rxjs/operators';
5 |
6 | import { AccountService } from '@app/_services';
7 |
8 | export function errorInterceptor(request: HttpRequest, next: HttpHandlerFn) {
9 | const accountService = inject(AccountService);
10 | return next(request).pipe(catchError(err => {
11 | if ([401, 403].includes(err.status) && accountService.userValue) {
12 | // auto logout if 401 or 403 response returned from api
13 | accountService.logout();
14 | }
15 |
16 | const error = err.error?.message || err.statusText;
17 | console.error(err);
18 | return throwError(() => error);
19 | }))
20 | }
21 |
--------------------------------------------------------------------------------
/part-7/src/app/_helpers/index.ts:
--------------------------------------------------------------------------------
1 | export * from './auth.guard';
2 | export * from './error.interceptor';
3 | export * from './fake-backend';
4 | export * from './jwt.interceptor';
--------------------------------------------------------------------------------
/part-7/src/app/_helpers/jwt.interceptor.ts:
--------------------------------------------------------------------------------
1 | import { inject } from '@angular/core';
2 | import { HttpRequest, HttpHandlerFn } from '@angular/common/http';
3 |
4 | import { environment } from '@environments/environment';
5 | import { AccountService } from '@app/_services';
6 |
7 | export function jwtInterceptor(request: HttpRequest, next: HttpHandlerFn) {
8 | // add auth header with jwt if user is logged in and request is to the api url
9 | const accountService = inject(AccountService);
10 | const user = accountService.userValue;
11 | const isLoggedIn = user?.token;
12 | const isApiUrl = request.url.startsWith(environment.apiUrl);
13 | if (isLoggedIn && isApiUrl) {
14 | request = request.clone({
15 | setHeaders: { Authorization: `Bearer ${user.token}` }
16 | });
17 | }
18 |
19 | return next(request);
20 | }
--------------------------------------------------------------------------------
/part-7/src/app/_models/index.ts:
--------------------------------------------------------------------------------
1 | export * from './user';
--------------------------------------------------------------------------------
/part-7/src/app/_models/user.ts:
--------------------------------------------------------------------------------
1 | export class User {
2 | id?: string;
3 | username?: string;
4 | password?: string;
5 | firstName?: string;
6 | lastName?: string;
7 | token?: string;
8 | }
--------------------------------------------------------------------------------
/part-7/src/app/_services/alert.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Router, NavigationStart } from '@angular/router';
3 | import { Observable, Subject } from 'rxjs';
4 |
5 | @Injectable({ providedIn: 'root' })
6 | export class AlertService {
7 | private subject = new Subject();
8 | private showAfterRedirect = false;
9 |
10 | constructor(private router: Router) {
11 | // clear alert messages on route change unless 'showAfterRedirect' flag is true
12 | this.router.events.subscribe(event => {
13 | if (event instanceof NavigationStart) {
14 | if (this.showAfterRedirect) {
15 | // only keep for a single route change
16 | this.showAfterRedirect = false;
17 | } else {
18 | // clear alert message
19 | this.clear();
20 | }
21 | }
22 | });
23 | }
24 |
25 | onAlert(): Observable {
26 | return this.subject.asObservable();
27 | }
28 |
29 | success(message: string, showAfterRedirect = false) {
30 | this.showAfterRedirect = showAfterRedirect;
31 | this.subject.next({ type: 'success', message });
32 | }
33 |
34 | error(message: string, showAfterRedirect = false) {
35 | this.showAfterRedirect = showAfterRedirect;
36 | this.subject.next({ type: 'error', message });
37 | }
38 |
39 | clear() {
40 | // clear by calling subject.next() with null
41 | this.subject.next(null);
42 | }
43 | }
--------------------------------------------------------------------------------
/part-7/src/app/_services/index.ts:
--------------------------------------------------------------------------------
1 | export * from './account.service';
2 | export * from './alert.service';
--------------------------------------------------------------------------------
/part-7/src/app/account/index.ts:
--------------------------------------------------------------------------------
1 | export * from './login.component';
2 | export * from './register.component';
--------------------------------------------------------------------------------
/part-7/src/app/account/login.component.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-7/src/app/account/login.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { NgClass, NgIf } from '@angular/common';
3 | import { Router, ActivatedRoute, RouterLink } from '@angular/router';
4 | import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
5 | import { first } from 'rxjs/operators';
6 |
7 | import { AccountService, AlertService } from '@app/_services'
8 |
9 | @Component({
10 | templateUrl: 'login.component.html',
11 | standalone: true,
12 | imports: [ReactiveFormsModule, NgClass, NgIf, RouterLink]
13 | })
14 | export class LoginComponent implements OnInit {
15 | form!: FormGroup;
16 | loading = false;
17 | submitted = false;
18 |
19 | constructor(
20 | private formBuilder: FormBuilder,
21 | private route: ActivatedRoute,
22 | private router: Router,
23 | private accountService: AccountService,
24 | private alertService: AlertService
25 | ) {
26 | // redirect to home if already logged in
27 | if (this.accountService.userValue) {
28 | this.router.navigate(['/']);
29 | }
30 | }
31 |
32 | ngOnInit() {
33 | this.form = this.formBuilder.group({
34 | username: ['', Validators.required],
35 | password: ['', Validators.required]
36 | });
37 | }
38 |
39 | // convenience getter for easy access to form fields
40 | get f() { return this.form.controls; }
41 |
42 | onSubmit() {
43 | this.submitted = true;
44 |
45 | // reset alerts on submit
46 | this.alertService.clear();
47 |
48 | // stop here if form is invalid
49 | if (this.form.invalid) {
50 | return;
51 | }
52 |
53 | this.loading = true;
54 | this.accountService.login(this.f.username.value, this.f.password.value)
55 | .pipe(first())
56 | .subscribe({
57 | next: () => {
58 | // get return url from query parameters or default to home page
59 | const returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';
60 | this.router.navigateByUrl(returnUrl);
61 | },
62 | error: error => {
63 | this.alertService.error(error);
64 | this.loading = false;
65 | }
66 | });
67 | }
68 | }
--------------------------------------------------------------------------------
/part-7/src/app/account/register.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { NgClass, NgIf } from '@angular/common';
3 | import { Router, ActivatedRoute, RouterLink } from '@angular/router';
4 | import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
5 | import { first } from 'rxjs/operators';
6 |
7 | import { AccountService, AlertService } from '@app/_services';
8 |
9 | @Component({
10 | templateUrl: 'register.component.html',
11 | standalone: true,
12 | imports: [ReactiveFormsModule, NgClass, NgIf, RouterLink]
13 | })
14 | export class RegisterComponent implements OnInit {
15 | form!: FormGroup;
16 | loading = false;
17 | submitted = false;
18 |
19 | constructor(
20 | private formBuilder: FormBuilder,
21 | private route: ActivatedRoute,
22 | private router: Router,
23 | private accountService: AccountService,
24 | private alertService: AlertService
25 | ) {
26 | // redirect to home if already logged in
27 | if (this.accountService.userValue) {
28 | this.router.navigate(['/']);
29 | }
30 | }
31 |
32 | ngOnInit() {
33 | this.form = this.formBuilder.group({
34 | firstName: ['', Validators.required],
35 | lastName: ['', Validators.required],
36 | username: ['', Validators.required],
37 | password: ['', [Validators.required, Validators.minLength(6)]]
38 | });
39 | }
40 |
41 | // convenience getter for easy access to form fields
42 | get f() { return this.form.controls; }
43 |
44 | onSubmit() {
45 | this.submitted = true;
46 |
47 | // reset alert on submit
48 | this.alertService.clear();
49 |
50 | // stop here if form is invalid
51 | if (this.form.invalid) {
52 | return;
53 | }
54 |
55 | this.loading = true;
56 | this.accountService.register(this.form.value)
57 | .pipe(first())
58 | .subscribe({
59 | next: () => {
60 | this.alertService.success('Registration successful', true);
61 | this.router.navigate(['/account/login'], { queryParams: { registered: true }});
62 | },
63 | error: error => {
64 | this.alertService.error(error);
65 | this.loading = false;
66 | }
67 | });
68 | }
69 | }
--------------------------------------------------------------------------------
/part-7/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/part-7/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { NgIf } from '@angular/common';
3 | import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
4 |
5 | import { AccountService } from './_services';
6 | import { User } from './_models';
7 | import { AlertComponent } from './_components/alert.component';
8 |
9 | @Component({
10 | selector: 'app-root', templateUrl: 'app.component.html',
11 | standalone: true,
12 | imports: [NgIf, RouterOutlet, RouterLink, RouterLinkActive, AlertComponent]
13 | })
14 | export class AppComponent {
15 | user?: User | null;
16 |
17 | constructor(private accountService: AccountService) {
18 | this.accountService.user.subscribe(x => this.user = x);
19 | }
20 |
21 | logout() {
22 | this.accountService.logout();
23 | }
24 | }
--------------------------------------------------------------------------------
/part-7/src/app/app.routes.ts:
--------------------------------------------------------------------------------
1 | import { Routes } from "@angular/router";
2 |
3 | import { HomeComponent } from './home';
4 | import { LoginComponent, RegisterComponent } from './account';
5 | import { authGuard } from './_helpers';
6 |
7 | const usersRoutes = () => import('./users/users.routes').then(x => x.USERS_ROUTES);
8 |
9 | export const APP_ROUTES: Routes = [
10 | { path: '', component: HomeComponent, canActivate: [authGuard] },
11 | { path: 'users', loadChildren: usersRoutes, canActivate: [authGuard] },
12 | { path: 'account/login', component: LoginComponent },
13 | { path: 'account/register', component: RegisterComponent },
14 |
15 | // otherwise redirect to home
16 | { path: '**', redirectTo: '' }
17 | ];
18 |
--------------------------------------------------------------------------------
/part-7/src/app/home/home.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Hi {{user?.firstName}}!
4 |
You're logged in with Angular!!
5 |
6 |
--------------------------------------------------------------------------------
/part-7/src/app/home/home.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | import { User } from '@app/_models';
4 | import { AccountService } from '@app/_services';
5 |
6 | @Component({
7 | templateUrl: 'home.component.html',
8 | standalone: true
9 | })
10 | export class HomeComponent {
11 | user: User | null;
12 |
13 | constructor(private accountService: AccountService) {
14 | this.user = this.accountService.userValue;
15 | }
16 | }
--------------------------------------------------------------------------------
/part-7/src/app/home/index.ts:
--------------------------------------------------------------------------------
1 | export * from './home.component';
--------------------------------------------------------------------------------
/part-7/src/app/users/list.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Users
4 |
Add User
5 |
6 |
7 |
8 | First Name |
9 | Last Name |
10 | Username |
11 | |
12 |
13 |
14 |
15 |
16 | {{user.firstName}} |
17 | {{user.lastName}} |
18 | {{user.username}} |
19 |
20 | Edit
21 |
25 | |
26 |
27 |
28 |
29 |
30 | |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/part-7/src/app/users/list.component.ts:
--------------------------------------------------------------------------------
1 |
2 | import { Component, OnInit } from '@angular/core';
3 | import { NgFor, NgIf } from '@angular/common';
4 | import { RouterLink } from '@angular/router';
5 | import { first } from 'rxjs/operators';
6 |
7 | import { AccountService } from '@app/_services';
8 |
9 | @Component({
10 | templateUrl: 'list.component.html',
11 | standalone: true,
12 | imports: [RouterLink, NgFor, NgIf]
13 | })
14 | export class ListComponent implements OnInit {
15 | users?: any[];
16 |
17 | constructor(private accountService: AccountService) {}
18 |
19 | ngOnInit() {
20 | this.accountService.getAll()
21 | .pipe(first())
22 | .subscribe(users => this.users = users);
23 | }
24 |
25 | deleteUser(id: string) {
26 | const user = this.users!.find(x => x.id === id);
27 | user.isDeleting = true;
28 | this.accountService.delete(id)
29 | .pipe(first())
30 | .subscribe(() => this.users = this.users!.filter(x => x.id !== id));
31 | }
32 | }
--------------------------------------------------------------------------------
/part-7/src/app/users/users.routes.ts:
--------------------------------------------------------------------------------
1 | import { Routes } from '@angular/router';
2 |
3 | import { ListComponent } from './list.component';
4 | import { AddEditComponent } from './add-edit.component';
5 |
6 | export const USERS_ROUTES: Routes = [
7 | { path: '', component: ListComponent },
8 | { path: 'add', component: AddEditComponent },
9 | { path: 'edit/:id', component: AddEditComponent }
10 | ];
--------------------------------------------------------------------------------
/part-7/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cornflourblue/angular-16-tutorial/a2d771bd813e937d38b1d47ea93bfc270905ee2c/part-7/src/assets/.gitkeep
--------------------------------------------------------------------------------
/part-7/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | apiUrl: 'http://localhost:4000'
3 | };
--------------------------------------------------------------------------------
/part-7/src/favicon.ico:
--------------------------------------------------------------------------------
1 | �PNG
2 |
3 |
IHDR ?�~� pHYs ��~� fIDATH��WKLQ���̔�uG�� e�n�.6qcb�l?���D`�F#�
Ku�F
1Qc�
4 | ��!���� ��C�P�|B?$���ܱ3����I&}��}�̽s�[*�ɀU�A� �K��yx�gY�Ajq��3L Š� ��˫�OD�4��3Ϗ:X�3��o�PJ�ğo#IH�a����,{>1/�2$�R AR]�)w��?�sZw^��q�Y�m_��e���r[8�^�
5 | �&p��-���A}c��- ������!����2_)E�)㊪j���v�m� �ZOi�g�nW�{�n.|�e2�a&�0aŸ����be�̀��C�fˤE%-{�ֺ��C��N��jXi�~c�C,t��T�����r �{� �L)s��V��6%�(�#ᤙ!�]��H�ҐH$R���^R�A�61(?Y舚�>���(Z����Qm�L2�K�ZIc��
6 | ���̧�C��2!⅄ �(����" �Go��>�q��=��$%�z`ѯ��T�&����PHh�Z!=� ��z��O��������,*VVV�1�f*CJ�]EE��K�k��d�#5���`2yT!�}7���߈~�,���zs�����y�T��V������D��C2�G��@%̑72Y�{oJ�"@��^h�~��fĬ�!a�D��6���Ha|�3��� [>�����]7U2п���] �ė
7 | ��PU��.Wejq�in�g��+ p<ߺQH����總j[������.��� Q���p _�K��1(��+��bB8�\ra
8 | �́�v.l���(���ǽ�w���L��w�8�C�� IEND�B`�
--------------------------------------------------------------------------------
/part-7/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Angular Tutorial
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/part-7/src/main.ts:
--------------------------------------------------------------------------------
1 | import { bootstrapApplication } from '@angular/platform-browser';
2 | import { provideRouter } from '@angular/router';
3 | import { provideHttpClient, withInterceptors } from '@angular/common/http';
4 |
5 | // fake backend
6 | import { fakeBackendInterceptor } from '@app/_helpers';
7 |
8 | import { AppComponent } from '@app/app.component';
9 | import { jwtInterceptor, errorInterceptor } from '@app/_helpers';
10 | import { APP_ROUTES } from '@app/app.routes';
11 |
12 | bootstrapApplication(AppComponent, {
13 | providers: [
14 | provideRouter(APP_ROUTES),
15 | provideHttpClient(
16 | withInterceptors([
17 | jwtInterceptor,
18 | errorInterceptor,
19 |
20 | // fake backend
21 | fakeBackendInterceptor
22 | ])
23 | )
24 | ]
25 | });
26 |
--------------------------------------------------------------------------------
/part-7/src/styles.css:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 |
--------------------------------------------------------------------------------
/part-7/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/app",
6 | "types": []
7 | },
8 | "files": [
9 | "src/main.ts"
10 | ],
11 | "include": [
12 | "src/**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/part-7/tsconfig.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "compileOnSave": false,
4 | "compilerOptions": {
5 | "baseUrl": "./",
6 | "outDir": "./dist/out-tsc",
7 | "forceConsistentCasingInFileNames": true,
8 | "strict": true,
9 | "noImplicitOverride": true,
10 | "noPropertyAccessFromIndexSignature": false,
11 | "noImplicitReturns": true,
12 | "noFallthroughCasesInSwitch": true,
13 | "sourceMap": true,
14 | "declaration": false,
15 | "downlevelIteration": true,
16 | "experimentalDecorators": true,
17 | "moduleResolution": "node",
18 | "importHelpers": true,
19 | "target": "ES2022",
20 | "module": "ES2022",
21 | "useDefineForClassFields": false,
22 | "lib": [
23 | "ES2022",
24 | "dom"
25 | ],
26 | "paths": {
27 | "@app/*": ["src/app/*"],
28 | "@environments/*": ["src/environments/*"]
29 | }
30 | },
31 | "angularCompilerOptions": {
32 | "enableI18nLegacyMessageIdFormat": false,
33 | "strictInjectionParameters": true,
34 | "strictInputAccessModifiers": true,
35 | "strictTemplates": true
36 | }
37 | }
--------------------------------------------------------------------------------
/part-7/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/spec",
6 | "types": [
7 | "jasmine"
8 | ]
9 | },
10 | "include": [
11 | "src/**/*.spec.ts",
12 | "src/**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/part-8/.dockerignore:
--------------------------------------------------------------------------------
1 | # copied from .gitignore
2 |
3 | # Compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 | /bazel-out
8 |
9 | # Node
10 | /node_modules
11 | npm-debug.log
12 | yarn-error.log
13 |
14 | # IDEs and editors
15 | .idea/
16 | .project
17 | .classpath
18 | .c9/
19 | *.launch
20 | .settings/
21 | *.sublime-workspace
22 |
23 | # Visual Studio Code
24 | .vscode/*
25 | !.vscode/settings.json
26 | !.vscode/tasks.json
27 | !.vscode/launch.json
28 | !.vscode/extensions.json
29 | .history/*
30 |
31 | # Miscellaneous
32 | /.angular/cache
33 | .sass-cache/
34 | /connect.lock
35 | /coverage
36 | /libpeerconnection.log
37 | testem.log
38 | /typings
39 |
40 | # System files
41 | .DS_Store
42 | Thumbs.db
43 |
--------------------------------------------------------------------------------
/part-8/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 |
14 | [*.md]
15 | max_line_length = off
16 | trim_trailing_whitespace = false
17 |
--------------------------------------------------------------------------------
/part-8/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # Compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 | /bazel-out
8 |
9 | # Node
10 | /node_modules
11 | npm-debug.log
12 | yarn-error.log
13 |
14 | # IDEs and editors
15 | .idea/
16 | .project
17 | .classpath
18 | .c9/
19 | *.launch
20 | .settings/
21 | *.sublime-workspace
22 |
23 | # Visual Studio Code
24 | .vscode/*
25 | !.vscode/settings.json
26 | !.vscode/tasks.json
27 | !.vscode/launch.json
28 | !.vscode/extensions.json
29 | .history/*
30 |
31 | # Miscellaneous
32 | /.angular/cache
33 | .sass-cache/
34 | /connect.lock
35 | /coverage
36 | /libpeerconnection.log
37 | testem.log
38 | /typings
39 |
40 | # System files
41 | .DS_Store
42 | Thumbs.db
43 |
--------------------------------------------------------------------------------
/part-8/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
3 | "recommendations": ["angular.ng-template"]
4 | }
5 |
--------------------------------------------------------------------------------
/part-8/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
3 | "version": "0.2.0",
4 | "configurations": [
5 | {
6 | "name": "ng serve",
7 | "type": "chrome",
8 | "request": "launch",
9 | "preLaunchTask": "npm: start",
10 | "url": "http://localhost:4200/"
11 | },
12 | {
13 | "name": "ng test",
14 | "type": "chrome",
15 | "request": "launch",
16 | "preLaunchTask": "npm: test",
17 | "url": "http://localhost:9876/debug.html"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/part-8/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
3 | "version": "2.0.0",
4 | "tasks": [
5 | {
6 | "type": "npm",
7 | "script": "start",
8 | "isBackground": true,
9 | "problemMatcher": {
10 | "owner": "typescript",
11 | "pattern": "$tsc",
12 | "background": {
13 | "activeOnStart": true,
14 | "beginsPattern": {
15 | "regexp": "(.*?)"
16 | },
17 | "endsPattern": {
18 | "regexp": "bundle generation complete"
19 | }
20 | }
21 | }
22 | },
23 | {
24 | "type": "npm",
25 | "script": "test",
26 | "isBackground": true,
27 | "problemMatcher": {
28 | "owner": "typescript",
29 | "pattern": "$tsc",
30 | "background": {
31 | "activeOnStart": true,
32 | "beginsPattern": {
33 | "regexp": "(.*?)"
34 | },
35 | "endsPattern": {
36 | "regexp": "bundle generation complete"
37 | }
38 | }
39 | }
40 | }
41 | ]
42 | }
43 |
--------------------------------------------------------------------------------
/part-8/Dockerfile:
--------------------------------------------------------------------------------
1 | # ----------------------------
2 | # build from source
3 | # ----------------------------
4 | FROM node:18 AS build
5 |
6 | WORKDIR /app
7 |
8 | COPY package*.json .
9 | RUN npm install
10 |
11 | COPY . .
12 | RUN npm run build
13 |
14 | # ----------------------------
15 | # run with nginx
16 | # ----------------------------
17 | FROM nginx
18 |
19 | RUN rm /etc/nginx/conf.d/default.conf
20 | COPY nginx.conf /etc/nginx/conf.d
21 | COPY --from=build /app/dist/angular-tutorial /usr/share/nginx/html
22 |
23 | EXPOSE 80
24 |
--------------------------------------------------------------------------------
/part-8/README.md:
--------------------------------------------------------------------------------
1 | # AngularTutorial
2 |
3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 16.0.0.
4 |
5 | ## Development server
6 |
7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
8 |
9 | ## Code scaffolding
10 |
11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
12 |
13 | ## Build
14 |
15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
16 |
17 | ## Running unit tests
18 |
19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
20 |
21 | ## Running end-to-end tests
22 |
23 | Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
24 |
25 | ## Further help
26 |
27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
28 |
--------------------------------------------------------------------------------
/part-8/nginx.conf:
--------------------------------------------------------------------------------
1 | server {
2 | location / {
3 | root /usr/share/nginx/html;
4 | try_files $uri $uri/ /index.html;
5 | }
6 | }
--------------------------------------------------------------------------------
/part-8/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-tutorial",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "ng": "ng",
6 | "start": "ng serve",
7 | "build": "ng build",
8 | "watch": "ng build --watch --configuration development",
9 | "test": "ng test"
10 | },
11 | "private": true,
12 | "dependencies": {
13 | "@angular/animations": "^16.0.0",
14 | "@angular/common": "^16.0.0",
15 | "@angular/compiler": "^16.0.0",
16 | "@angular/core": "^16.0.0",
17 | "@angular/forms": "^16.0.0",
18 | "@angular/platform-browser": "^16.0.0",
19 | "@angular/platform-browser-dynamic": "^16.0.0",
20 | "@angular/router": "^16.0.0",
21 | "rxjs": "~7.8.0",
22 | "tslib": "^2.3.0",
23 | "zone.js": "~0.13.0"
24 | },
25 | "devDependencies": {
26 | "@angular-devkit/build-angular": "^16.0.0",
27 | "@angular/cli": "~16.0.0",
28 | "@angular/compiler-cli": "^16.0.0",
29 | "@types/jasmine": "~4.3.0",
30 | "jasmine-core": "~4.6.0",
31 | "karma": "~6.4.0",
32 | "karma-chrome-launcher": "~3.2.0",
33 | "karma-coverage": "~2.2.0",
34 | "karma-jasmine": "~5.1.0",
35 | "karma-jasmine-html-reporter": "~2.0.0",
36 | "typescript": "~5.0.2"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/part-8/src/app/_components/alert.component.html:
--------------------------------------------------------------------------------
1 |
2 |
{{alert.message}}
3 |
--------------------------------------------------------------------------------
/part-8/src/app/_components/alert.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, OnDestroy } from '@angular/core';
2 | import { NgIf, NgClass } from '@angular/common';
3 | import { Subscription } from 'rxjs';
4 |
5 | import { AlertService } from '@app/_services';
6 |
7 | @Component({
8 | selector: 'alert', templateUrl: 'alert.component.html',
9 | standalone: true,
10 | imports: [NgIf, NgClass]
11 | })
12 | export class AlertComponent implements OnInit, OnDestroy {
13 | private subscription!: Subscription;
14 | alert: any;
15 |
16 | constructor(private alertService: AlertService) { }
17 |
18 | ngOnInit() {
19 | this.subscription = this.alertService.onAlert()
20 | .subscribe(alert => {
21 | switch (alert?.type) {
22 | case 'success':
23 | alert.cssClass = 'alert alert-success';
24 | break;
25 | case 'error':
26 | alert.cssClass = 'alert alert-danger';
27 | break;
28 | }
29 |
30 | this.alert = alert;
31 | });
32 | }
33 |
34 | ngOnDestroy() {
35 | this.subscription.unsubscribe();
36 | }
37 | }
--------------------------------------------------------------------------------
/part-8/src/app/_components/index.ts:
--------------------------------------------------------------------------------
1 | export * from './alert.component';
--------------------------------------------------------------------------------
/part-8/src/app/_helpers/auth.guard.ts:
--------------------------------------------------------------------------------
1 | import { inject } from '@angular/core';
2 | import { Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
3 |
4 | import { AccountService } from '@app/_services';
5 |
6 | export function authGuard(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
7 | const router = inject(Router);
8 | const accountService = inject(AccountService);
9 | const user = accountService.userValue;
10 | if (user) {
11 | // authorised so return true
12 | return true;
13 | }
14 |
15 | // not logged in so redirect to login page with the return url
16 | router.navigate(['/account/login'], { queryParams: { returnUrl: state.url } });
17 | return false;
18 | }
19 |
--------------------------------------------------------------------------------
/part-8/src/app/_helpers/error.interceptor.ts:
--------------------------------------------------------------------------------
1 | import { inject } from '@angular/core';
2 | import { HttpRequest, HttpHandlerFn } from '@angular/common/http';
3 | import { throwError } from 'rxjs';
4 | import { catchError } from 'rxjs/operators';
5 |
6 | import { AccountService } from '@app/_services';
7 |
8 | export function errorInterceptor(request: HttpRequest, next: HttpHandlerFn) {
9 | const accountService = inject(AccountService);
10 | return next(request).pipe(catchError(err => {
11 | if ([401, 403].includes(err.status) && accountService.userValue) {
12 | // auto logout if 401 or 403 response returned from api
13 | accountService.logout();
14 | }
15 |
16 | const error = err.error?.message || err.statusText;
17 | console.error(err);
18 | return throwError(() => error);
19 | }))
20 | }
21 |
--------------------------------------------------------------------------------
/part-8/src/app/_helpers/index.ts:
--------------------------------------------------------------------------------
1 | export * from './auth.guard';
2 | export * from './error.interceptor';
3 | export * from './fake-backend';
4 | export * from './jwt.interceptor';
--------------------------------------------------------------------------------
/part-8/src/app/_helpers/jwt.interceptor.ts:
--------------------------------------------------------------------------------
1 | import { inject } from '@angular/core';
2 | import { HttpRequest, HttpHandlerFn } from '@angular/common/http';
3 |
4 | import { environment } from '@environments/environment';
5 | import { AccountService } from '@app/_services';
6 |
7 | export function jwtInterceptor(request: HttpRequest, next: HttpHandlerFn) {
8 | // add auth header with jwt if user is logged in and request is to the api url
9 | const accountService = inject(AccountService);
10 | const user = accountService.userValue;
11 | const isLoggedIn = user?.token;
12 | const isApiUrl = request.url.startsWith(environment.apiUrl);
13 | if (isLoggedIn && isApiUrl) {
14 | request = request.clone({
15 | setHeaders: { Authorization: `Bearer ${user.token}` }
16 | });
17 | }
18 |
19 | return next(request);
20 | }
--------------------------------------------------------------------------------
/part-8/src/app/_models/index.ts:
--------------------------------------------------------------------------------
1 | export * from './user';
--------------------------------------------------------------------------------
/part-8/src/app/_models/user.ts:
--------------------------------------------------------------------------------
1 | export class User {
2 | id?: string;
3 | username?: string;
4 | password?: string;
5 | firstName?: string;
6 | lastName?: string;
7 | token?: string;
8 | }
--------------------------------------------------------------------------------
/part-8/src/app/_services/alert.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Router, NavigationStart } from '@angular/router';
3 | import { Observable, Subject } from 'rxjs';
4 |
5 | @Injectable({ providedIn: 'root' })
6 | export class AlertService {
7 | private subject = new Subject();
8 | private showAfterRedirect = false;
9 |
10 | constructor(private router: Router) {
11 | // clear alert messages on route change unless 'showAfterRedirect' flag is true
12 | this.router.events.subscribe(event => {
13 | if (event instanceof NavigationStart) {
14 | if (this.showAfterRedirect) {
15 | // only keep for a single route change
16 | this.showAfterRedirect = false;
17 | } else {
18 | // clear alert message
19 | this.clear();
20 | }
21 | }
22 | });
23 | }
24 |
25 | onAlert(): Observable {
26 | return this.subject.asObservable();
27 | }
28 |
29 | success(message: string, showAfterRedirect = false) {
30 | this.showAfterRedirect = showAfterRedirect;
31 | this.subject.next({ type: 'success', message });
32 | }
33 |
34 | error(message: string, showAfterRedirect = false) {
35 | this.showAfterRedirect = showAfterRedirect;
36 | this.subject.next({ type: 'error', message });
37 | }
38 |
39 | clear() {
40 | // clear by calling subject.next() with null
41 | this.subject.next(null);
42 | }
43 | }
--------------------------------------------------------------------------------
/part-8/src/app/_services/index.ts:
--------------------------------------------------------------------------------
1 | export * from './account.service';
2 | export * from './alert.service';
--------------------------------------------------------------------------------
/part-8/src/app/account/index.ts:
--------------------------------------------------------------------------------
1 | export * from './login.component';
2 | export * from './register.component';
--------------------------------------------------------------------------------
/part-8/src/app/account/login.component.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/part-8/src/app/account/login.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { NgClass, NgIf } from '@angular/common';
3 | import { Router, ActivatedRoute, RouterLink } from '@angular/router';
4 | import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
5 | import { first } from 'rxjs/operators';
6 |
7 | import { AccountService, AlertService } from '@app/_services'
8 |
9 | @Component({
10 | templateUrl: 'login.component.html',
11 | standalone: true,
12 | imports: [ReactiveFormsModule, NgClass, NgIf, RouterLink]
13 | })
14 | export class LoginComponent implements OnInit {
15 | form!: FormGroup;
16 | loading = false;
17 | submitted = false;
18 |
19 | constructor(
20 | private formBuilder: FormBuilder,
21 | private route: ActivatedRoute,
22 | private router: Router,
23 | private accountService: AccountService,
24 | private alertService: AlertService
25 | ) {
26 | // redirect to home if already logged in
27 | if (this.accountService.userValue) {
28 | this.router.navigate(['/']);
29 | }
30 | }
31 |
32 | ngOnInit() {
33 | this.form = this.formBuilder.group({
34 | username: ['', Validators.required],
35 | password: ['', Validators.required]
36 | });
37 | }
38 |
39 | // convenience getter for easy access to form fields
40 | get f() { return this.form.controls; }
41 |
42 | onSubmit() {
43 | this.submitted = true;
44 |
45 | // reset alerts on submit
46 | this.alertService.clear();
47 |
48 | // stop here if form is invalid
49 | if (this.form.invalid) {
50 | return;
51 | }
52 |
53 | this.loading = true;
54 | this.accountService.login(this.f.username.value, this.f.password.value)
55 | .pipe(first())
56 | .subscribe({
57 | next: () => {
58 | // get return url from query parameters or default to home page
59 | const returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';
60 | this.router.navigateByUrl(returnUrl);
61 | },
62 | error: error => {
63 | this.alertService.error(error);
64 | this.loading = false;
65 | }
66 | });
67 | }
68 | }
--------------------------------------------------------------------------------
/part-8/src/app/account/register.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { NgClass, NgIf } from '@angular/common';
3 | import { Router, ActivatedRoute, RouterLink } from '@angular/router';
4 | import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
5 | import { first } from 'rxjs/operators';
6 |
7 | import { AccountService, AlertService } from '@app/_services';
8 |
9 | @Component({
10 | templateUrl: 'register.component.html',
11 | standalone: true,
12 | imports: [ReactiveFormsModule, NgClass, NgIf, RouterLink]
13 | })
14 | export class RegisterComponent implements OnInit {
15 | form!: FormGroup;
16 | loading = false;
17 | submitted = false;
18 |
19 | constructor(
20 | private formBuilder: FormBuilder,
21 | private route: ActivatedRoute,
22 | private router: Router,
23 | private accountService: AccountService,
24 | private alertService: AlertService
25 | ) {
26 | // redirect to home if already logged in
27 | if (this.accountService.userValue) {
28 | this.router.navigate(['/']);
29 | }
30 | }
31 |
32 | ngOnInit() {
33 | this.form = this.formBuilder.group({
34 | firstName: ['', Validators.required],
35 | lastName: ['', Validators.required],
36 | username: ['', Validators.required],
37 | password: ['', [Validators.required, Validators.minLength(6)]]
38 | });
39 | }
40 |
41 | // convenience getter for easy access to form fields
42 | get f() { return this.form.controls; }
43 |
44 | onSubmit() {
45 | this.submitted = true;
46 |
47 | // reset alert on submit
48 | this.alertService.clear();
49 |
50 | // stop here if form is invalid
51 | if (this.form.invalid) {
52 | return;
53 | }
54 |
55 | this.loading = true;
56 | this.accountService.register(this.form.value)
57 | .pipe(first())
58 | .subscribe({
59 | next: () => {
60 | this.alertService.success('Registration successful', true);
61 | this.router.navigate(['/account/login'], { queryParams: { registered: true }});
62 | },
63 | error: error => {
64 | this.alertService.error(error);
65 | this.loading = false;
66 | }
67 | });
68 | }
69 | }
--------------------------------------------------------------------------------
/part-8/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/part-8/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { NgIf } from '@angular/common';
3 | import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
4 |
5 | import { AccountService } from './_services';
6 | import { User } from './_models';
7 | import { AlertComponent } from './_components/alert.component';
8 |
9 | @Component({
10 | selector: 'app-root', templateUrl: 'app.component.html',
11 | standalone: true,
12 | imports: [NgIf, RouterOutlet, RouterLink, RouterLinkActive, AlertComponent]
13 | })
14 | export class AppComponent {
15 | user?: User | null;
16 |
17 | constructor(private accountService: AccountService) {
18 | this.accountService.user.subscribe(x => this.user = x);
19 | }
20 |
21 | logout() {
22 | this.accountService.logout();
23 | }
24 | }
--------------------------------------------------------------------------------
/part-8/src/app/app.routes.ts:
--------------------------------------------------------------------------------
1 | import { Routes } from "@angular/router";
2 |
3 | import { HomeComponent } from './home';
4 | import { LoginComponent, RegisterComponent } from './account';
5 | import { authGuard } from './_helpers';
6 |
7 | const usersRoutes = () => import('./users/users.routes').then(x => x.USERS_ROUTES);
8 |
9 | export const APP_ROUTES: Routes = [
10 | { path: '', component: HomeComponent, canActivate: [authGuard] },
11 | { path: 'users', loadChildren: usersRoutes, canActivate: [authGuard] },
12 | { path: 'account/login', component: LoginComponent },
13 | { path: 'account/register', component: RegisterComponent },
14 |
15 | // otherwise redirect to home
16 | { path: '**', redirectTo: '' }
17 | ];
18 |
--------------------------------------------------------------------------------
/part-8/src/app/home/home.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Hi {{user?.firstName}}!
4 |
You're logged in with Angular!!
5 |
6 |
--------------------------------------------------------------------------------
/part-8/src/app/home/home.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | import { User } from '@app/_models';
4 | import { AccountService } from '@app/_services';
5 |
6 | @Component({
7 | templateUrl: 'home.component.html',
8 | standalone: true
9 | })
10 | export class HomeComponent {
11 | user: User | null;
12 |
13 | constructor(private accountService: AccountService) {
14 | this.user = this.accountService.userValue;
15 | }
16 | }
--------------------------------------------------------------------------------
/part-8/src/app/home/index.ts:
--------------------------------------------------------------------------------
1 | export * from './home.component';
--------------------------------------------------------------------------------
/part-8/src/app/users/list.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Users
4 |
Add User
5 |
6 |
7 |
8 | First Name |
9 | Last Name |
10 | Username |
11 | |
12 |
13 |
14 |
15 |
16 | {{user.firstName}} |
17 | {{user.lastName}} |
18 | {{user.username}} |
19 |
20 | Edit
21 |
25 | |
26 |
27 |
28 |
29 |
30 | |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/part-8/src/app/users/list.component.ts:
--------------------------------------------------------------------------------
1 |
2 | import { Component, OnInit } from '@angular/core';
3 | import { NgFor, NgIf } from '@angular/common';
4 | import { RouterLink } from '@angular/router';
5 | import { first } from 'rxjs/operators';
6 |
7 | import { AccountService } from '@app/_services';
8 |
9 | @Component({
10 | templateUrl: 'list.component.html',
11 | standalone: true,
12 | imports: [RouterLink, NgFor, NgIf]
13 | })
14 | export class ListComponent implements OnInit {
15 | users?: any[];
16 |
17 | constructor(private accountService: AccountService) {}
18 |
19 | ngOnInit() {
20 | this.accountService.getAll()
21 | .pipe(first())
22 | .subscribe(users => this.users = users);
23 | }
24 |
25 | deleteUser(id: string) {
26 | const user = this.users!.find(x => x.id === id);
27 | user.isDeleting = true;
28 | this.accountService.delete(id)
29 | .pipe(first())
30 | .subscribe(() => this.users = this.users!.filter(x => x.id !== id));
31 | }
32 | }
--------------------------------------------------------------------------------
/part-8/src/app/users/users.routes.ts:
--------------------------------------------------------------------------------
1 | import { Routes } from '@angular/router';
2 |
3 | import { ListComponent } from './list.component';
4 | import { AddEditComponent } from './add-edit.component';
5 |
6 | export const USERS_ROUTES: Routes = [
7 | { path: '', component: ListComponent },
8 | { path: 'add', component: AddEditComponent },
9 | { path: 'edit/:id', component: AddEditComponent }
10 | ];
--------------------------------------------------------------------------------
/part-8/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cornflourblue/angular-16-tutorial/a2d771bd813e937d38b1d47ea93bfc270905ee2c/part-8/src/assets/.gitkeep
--------------------------------------------------------------------------------
/part-8/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | apiUrl: 'http://localhost:4000'
3 | };
--------------------------------------------------------------------------------
/part-8/src/favicon.ico:
--------------------------------------------------------------------------------
1 | �PNG
2 |
3 |
IHDR ?�~� pHYs ��~� fIDATH��WKLQ���̔�uG�� e�n�.6qcb�l?���D`�F#�
Ku�F
1Qc�
4 | ��!���� ��C�P�|B?$���ܱ3����I&}��}�̽s�[*�ɀU�A� �K��yx�gY�Ajq��3L Š� ��˫�OD�4��3Ϗ:X�3��o�PJ�ğo#IH�a����,{>1/�2$�R AR]�)w��?�sZw^��q�Y�m_��e���r[8�^�
5 | �&p��-���A}c��- ������!����2_)E�)㊪j���v�m� �ZOi�g�nW�{�n.|�e2�a&�0aŸ����be�̀��C�fˤE%-{�ֺ��C��N��jXi�~c�C,t��T�����r �{� �L)s��V��6%�(�#ᤙ!�]��H�ҐH$R���^R�A�61(?Y舚�>���(Z����Qm�L2�K�ZIc��
6 | ���̧�C��2!⅄ �(����" �Go��>�q��=��$%�z`ѯ��T�&����PHh�Z!=� ��z��O��������,*VVV�1�f*CJ�]EE��K�k��d�#5���`2yT!�}7���߈~�,���zs�����y�T��V������D��C2�G��@%̑72Y�{oJ�"@��^h�~��fĬ�!a�D��6���Ha|�3��� [>�����]7U2п���] �ė
7 | ��PU��.Wejq�in�g��+ p<ߺQH����總j[������.��� Q���p _�K��1(��+��bB8�\ra
8 | �́�v.l���(���ǽ�w���L��w�8�C�� IEND�B`�
--------------------------------------------------------------------------------
/part-8/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Angular Tutorial
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/part-8/src/main.ts:
--------------------------------------------------------------------------------
1 | import { bootstrapApplication } from '@angular/platform-browser';
2 | import { provideRouter } from '@angular/router';
3 | import { provideHttpClient, withInterceptors } from '@angular/common/http';
4 |
5 | // fake backend
6 | import { fakeBackendInterceptor } from '@app/_helpers';
7 |
8 | import { AppComponent } from '@app/app.component';
9 | import { jwtInterceptor, errorInterceptor } from '@app/_helpers';
10 | import { APP_ROUTES } from '@app/app.routes';
11 |
12 | bootstrapApplication(AppComponent, {
13 | providers: [
14 | provideRouter(APP_ROUTES),
15 | provideHttpClient(
16 | withInterceptors([
17 | jwtInterceptor,
18 | errorInterceptor,
19 |
20 | // fake backend
21 | fakeBackendInterceptor
22 | ])
23 | )
24 | ]
25 | });
26 |
--------------------------------------------------------------------------------
/part-8/src/styles.css:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 |
--------------------------------------------------------------------------------
/part-8/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/app",
6 | "types": []
7 | },
8 | "files": [
9 | "src/main.ts"
10 | ],
11 | "include": [
12 | "src/**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/part-8/tsconfig.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "compileOnSave": false,
4 | "compilerOptions": {
5 | "baseUrl": "./",
6 | "outDir": "./dist/out-tsc",
7 | "forceConsistentCasingInFileNames": true,
8 | "strict": true,
9 | "noImplicitOverride": true,
10 | "noPropertyAccessFromIndexSignature": false,
11 | "noImplicitReturns": true,
12 | "noFallthroughCasesInSwitch": true,
13 | "sourceMap": true,
14 | "declaration": false,
15 | "downlevelIteration": true,
16 | "experimentalDecorators": true,
17 | "moduleResolution": "node",
18 | "importHelpers": true,
19 | "target": "ES2022",
20 | "module": "ES2022",
21 | "useDefineForClassFields": false,
22 | "lib": [
23 | "ES2022",
24 | "dom"
25 | ],
26 | "paths": {
27 | "@app/*": ["src/app/*"],
28 | "@environments/*": ["src/environments/*"]
29 | }
30 | },
31 | "angularCompilerOptions": {
32 | "enableI18nLegacyMessageIdFormat": false,
33 | "strictInjectionParameters": true,
34 | "strictInputAccessModifiers": true,
35 | "strictTemplates": true
36 | }
37 | }
--------------------------------------------------------------------------------
/part-8/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/spec",
6 | "types": [
7 | "jasmine"
8 | ]
9 | },
10 | "include": [
11 | "src/**/*.spec.ts",
12 | "src/**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------