Angular 14 - User Registration and Login Example & Tutorial
Tutorial built with Angular 14.2.12
Other versions available:
- Angular: Angular 15/16, 10, 9, 8, 7, 6, 2/5
- React: React 18 + Redux, React 17 + Recoil, React 16 Hooks + Redux, React 16 + Redux
- Next.js: Next.js 13 (App Router) + MongoDB, Next.js 13 (Pages Router) + MongoDB, Next.js 13 (Pages Router) + MySQL, Next.js 11
- Vue: Vue 3 + Pinia, Vue 2 + Vuex
- AngularJS: AngularJS
- .NET: Blazor WebAssembly
In this tutorial we'll go through an example of how to build a simple user registration, login and user management (CRUD) application with Angular 14.
Example Angular 14 App
The example app contains the following pages to demonstrate login, registration and CRUD functionality:
- Login (
/account/login
) - a simple login form with username and password fields. - Register (
/account/register
) - a form to register a new account. - Home (
/
) - the home page with a welcome message displayed after successful login. - User List (
/users
) - the default page of the users section for performing CRUD operations. Displays a list of all users with buttons to add, edit and delete. - Add User (
/users/add
) - a form to create a new user. - Edit User (
/users/edit/:id
) - a form to update the user with the specified:id
.
For an extended version that includes email verification, role based authorization and forgot password functionality see Angular 10 Boilerplate - Email Sign Up with Verification, Authentication & Forgot Password.
Forms built with Angular Reactive Forms
The forms in the example are built with the Reactive Forms library that comes as part of the Angular framework (in @angular/forms
). It uses a model-driven approach to build, validate and handle forms in Angular. For more info see https://angular.io/guide/reactive-forms.
Shared State managed with RxJS
RxJS Subjects and Observables are used to manage shared state in the Angular application. For example the current logged in user is managed by the account service with an RxJS BehaviorSubject
and exposed to the rest of the app with an RxJS Observable
that any Angular component can subscribe to and be notified when the user logs in or out.
Fake Backend API
The Angular app runs with a fake backend by default to enable it to run completely in the browser without a real backend api (backend-less), to switch to a real api simply remove or comment out the line below the comment // provider used to create fake backend
located in the app module (/src/app/app.module.ts
).
You can build your own API or hook it up with a .NET API or Node.js API available below.
Styled with Bootstrap 5
The example login app is styled with the CSS from Bootstrap 5.2, for more info about Bootstrap see https://getbootstrap.com/docs/5.2/getting-started/introduction/.
Code on GitHub
The project is available on GitHub at https://github.com/cornflourblue/angular-14-registration-login-example.
Here it is in action: (See on StackBlitz at https://stackblitz.com/edit/angular-14-registration-login-example)
Run the Angular 14 Login Tutorial Example Locally
- Install Node.js and npm from https://nodejs.org.
- Download or clone the Angular project source code from https://github.com/cornflourblue/angular-14-registration-login-example
- Install all required npm packages by running
npm install
ornpm i
from the command line in the project root folder (where the package.json is located). - Start the app by running
npm start
from the command line in the project root folder, this will compile the Angular app and automatically launch it in the browser on the URLhttp://localhost:4200
.
NOTE: You can also start the app with the Angular CLI command ng serve --open
. To do this first install the Angular CLI globally on your system with the command npm install -g @angular/cli
.
Connect the Angular App with a .NET 6.0 API
For full details about the example .NET 6 API see the post .NET 6.0 - User Registration and Login Tutorial with Example API. But to get up and running quickly just follow the below steps.
- Install the .NET Core SDK from https://dotnet.microsoft.com/download.
- Download or clone the project source code from https://github.com/cornflourblue/dotnet-6-registration-login-api
- Start the api by running
dotnet run
from the command line in the project root folder (where the WebApi.csproj file is located), you should see the messageNow listening on: http://localhost:4000
. - Back in the Angular app, remove or comment out the line below the comment
// provider used to create fake backend
located in the/src/app/app.module.ts
file, then start the Angular app and it should now be hooked up with the .NET API.
Connect the Angular App with a Node.js + SQL Server API
For full details about the example Node.js + MSSQL API see the post Node.js + MS SQL Server - Simple API for Authentication, Registration and User Management. But to get up and running quickly just follow the below steps.
- Install MS SQL Server - you'll need access to running SQL Server instance for the API to connect to, it can be remote (e.g. Azure, AWS etc) or on your local machine. The Express edition of SQL Server is available for free at https://www.microsoft.com/sql-server/sql-server-downloads. You can also run it in a Docker container, the official docker images for SQL Server are available at https://hub.docker.com/_/microsoft-mssql-server.
- Download or clone the project source code from https://github.com/cornflourblue/node-mssql-registration-login-api
- Install all required npm packages by running
npm install
ornpm i
from the command line in the project root folder (where the package.json is located). - Update the database credentials in
/config.json
to connect to your MS SQL Server instance, and ensure MSSQL server is running. - Start the API by running
npm start
(ornpm run dev
to start with nodemon) from the command line in the project root folder, you should see the messageServer listening on port 4000
. - Back in the Angular app, remove or comment out the line below the comment
// provider used to create fake backend
located in the/src/app/app.module.ts
file, then start the Angular app and it should now be hooked up with the Node + MSSQL API.
Node.js API with MySQL or MongoDB database
The following versions of the Node API are also available: Node + MySQL, Node + MongoDB.
Angular 14 Project Structure
The Angular CLI was used to generate the base project structure with the ng new <project name>
command, the CLI is also used to build and serve the application. For more info about the Angular CLI see https://angular.io/cli.
The app and code structure of the tutorial mostly follow the best practice recommendations in the official Angular Style Guide, with a few of my own tweaks here and there.
Folder Structure
Each feature has it's own folder (account, home & users), other shared/common code such as components, services, models, helpers etc are placed in folders prefixed with an underscore _
to easily differentiate them from features and group them together at the top of the folder structure.
Barrel Files
The index.ts
files in some folders are barrel files that group the exported modules from that folder together so they can be imported using only the folder path instead of the full module path, and to enable importing multiple modules in a single import (e.g. import { AccountService, AlertService } from '@app/_services'
).
Angular Feature Modules
The account
and users
features are organised into self contained feature modules that manage their own layout, routes and components, and are hooked into the main app inside the app routing module with lazy loading.
TypeScript Path Aliases
Path aliases @app
and @environments
have been configured in tsconfig.json that map to the /src/app
and /src/environments
directories. This allows imports to be relative to the app and environments folders by prefixing import paths with aliases instead of having to use long relative paths (e.g. import MyComponent from '../../../MyComponent'
).
Here are the main project files that contain the application logic, I left out some files that were generated by Angular CLI ng new
command that I didn't change.
- src
- app
- _components
- alert.component.html
- alert.component.ts
- index.ts
- _helpers
- _models
- _services
- account.service.ts
- alert.service.ts
- index.ts
- account
- home
- home.component.html
- home.component.ts
- index.ts
- users
- app-routing.module.ts
- app.component.html
- app.component.ts
- app.module.ts
- _components
- environments
- index.html
- main.ts
- polyfills.ts
- styles.less
- app
- package.json
- tsconfig.json
Alert Component Template
The alert component template contains the html for displaying alert messages, it renders a notification for each item in the alerts
array using the Angular *ngFor
directive.
<div *ngFor="let alert of alerts" class="{{cssClass(alert)}}">
<span [innerHTML]="alert.message"></span>
<button class="btn-close" (click)="removeAlert(alert)"></button>
</div>
Alert Component
The alert component controls the adding & removing of alerts in the UI, it maintains an array of alerts that are rendered by the component template.
The ngOnInit
method subscribes to the observable returned from the alertService.onAlert()
method, this enables the alert component to be notified whenever an alert message is sent to the alert service and add it to the alerts
array for display. Alerts are cleared when an alert with an empty message is received from the alert service. The ngOnInit
method also calls router.events.subscribe()
to subscribe to route change events so it can automatically clear alerts on route changes.
The ngOnDestroy()
method unsubscribes from the alert service and router when the component is destroyed to prevent memory leaks from orphaned subscriptions.
The removeAlert()
method removes the specified alert
object from the array, which allows individual alerts to be closed in the UI.
The cssClass()
method returns a corresponding bootstrap alert class for each of the alert types, if you're using something other than bootstrap you can change the CSS classes returned to suit your application.
For more info about Angular alerts see the tutorial Angular 14 - Alert (Toaster) Notifications Tutorial & Example.
import { Component, OnInit, OnDestroy, Input } from '@angular/core';
import { Router, NavigationStart } from '@angular/router';
import { Subscription } from 'rxjs';
import { Alert, AlertType } from '@app/_models';
import { AlertService } from '@app/_services';
@Component({ selector: 'alert', templateUrl: 'alert.component.html' })
export class AlertComponent implements OnInit, OnDestroy {
@Input() id = 'default-alert';
@Input() fade = true;
alerts: Alert[] = [];
alertSubscription!: Subscription;
routeSubscription!: Subscription;
constructor(private router: Router, private alertService: AlertService) { }
ngOnInit() {
// subscribe to new alert notifications
this.alertSubscription = this.alertService.onAlert(this.id)
.subscribe(alert => {
// clear alerts when an empty alert is received
if (!alert.message) {
// filter out alerts without 'keepAfterRouteChange' flag
this.alerts = this.alerts.filter(x => x.keepAfterRouteChange);
// remove 'keepAfterRouteChange' flag on the rest
this.alerts.forEach(x => delete x.keepAfterRouteChange);
return;
}
// add alert to array
this.alerts.push(alert);
// auto close alert if required
if (alert.autoClose) {
setTimeout(() => this.removeAlert(alert), 3000);
}
});
// clear alerts on location change
this.routeSubscription = this.router.events.subscribe(event => {
if (event instanceof NavigationStart) {
this.alertService.clear(this.id);
}
});
}
ngOnDestroy() {
// unsubscribe to avoid memory leaks
this.alertSubscription.unsubscribe();
this.routeSubscription.unsubscribe();
}
removeAlert(alert: Alert) {
// check if already removed to prevent error on auto close
if (!this.alerts.includes(alert)) return;
if (this.fade) {
// fade out alert
alert.fade = true;
// remove alert after faded out
setTimeout(() => {
this.alerts = this.alerts.filter(x => x !== alert);
}, 250);
} else {
// remove alert
this.alerts = this.alerts.filter(x => x !== alert);
}
}
cssClass(alert: Alert) {
if (!alert) return;
const classes = ['alert', 'alert-dismissible', 'mt-4', 'container'];
const alertTypeClass = {
[AlertType.Success]: 'alert-success',
[AlertType.Error]: 'alert-danger',
[AlertType.Info]: 'alert-info',
[AlertType.Warning]: 'alert-warning'
}
if (alert.type !== undefined) {
classes.push(alertTypeClass[alert.type]);
}
if (alert.fade) {
classes.push('fade');
}
return classes.join(' ');
}
}
Auth Guard
The auth guard is an angular route guard that's used to prevent unauthenticated users from accessing restricted routes, it does this by implementing the CanActivate
interface which allows the guard to decide if a route can be activated with the canActivate()
method. If the method returns true
the route is activated (allowed to proceed), otherwise if the method returns false
the route is blocked.
The auth guard uses the account service to check if the user is logged in, if they are logged in it returns true
from the canActivate()
method, otherwise it returns false
and redirects the user to the login page along with the returnUrl
in the query parameters.
Angular route guards are attached to routes in the router config, this auth guard is used in app-routing.module.ts to protect the home page and /users
routes.
import { Injectable } from '@angular/core';
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { AccountService } from '@app/_services';
@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
constructor(
private router: Router,
private accountService: AccountService
) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const user = this.accountService.userValue;
if (user) {
// authorised so return true
return true;
}
// not logged in so redirect to login page with the return url
this.router.navigate(['/account/login'], { queryParams: { returnUrl: state.url }});
return false;
}
}
Error Interceptor
The Error Interceptor intercepts http responses from the api to check if there were any errors. If there is a 401 Unauthorized
or 403 Forbidden
response the user is automatically logged out of the application, all other errors are re-thrown up to the calling service so an alert with the error can be displayed on the screen.
It's implemented using the Angular HttpInterceptor
interface included in the HttpClientModule
, by implementing the HttpInterceptor interface you can create a custom interceptor to catch all error responses from the server in a single location.
Http interceptors are added to the request pipeline in the providers section of the app.module.ts file.
import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { AccountService } from '@app/_services';
@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
constructor(private accountService: AccountService) {}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(request).pipe(catchError(err => {
if ([401, 403].includes(err.status) && this.accountService.userValue) {
// auto logout if 401 or 403 response returned from api
this.accountService.logout();
}
const error = err.error?.message || err.statusText;
console.error(err);
return throwError(() => error);
}))
}
}
Fake Backend Provider
In order to run and test the Angular application without a real backend API, the example uses a fake backend that intercepts the HTTP requests from the Angular app and sends back "fake" responses. This is done by a class that implements the Angular HttpInterceptor
interface, for more information on Angular HTTP Interceptors see https://angular.io/api/common/http/HttpInterceptor or this article.
The fake backend contains a handleRoute
function that checks if the request matches one of the faked routes in the switch statement, at the moment this includes requests for handling registration, authentication and user CRUD operations. Matching requests are intercepted and handled by one of the below // route functions
, non-matching requests are sent through to the real backend by calling next.handle(request);
. Below the route functions there are // helper functions
for returning different response types and performing small tasks.
For more info see the tutorial Angular 14 - Fake Backend API to Intercept HTTP Requests in Development.
import { Injectable } from '@angular/core';
import { HttpRequest, HttpResponse, HttpHandler, HttpEvent, HttpInterceptor, HTTP_INTERCEPTORS } from '@angular/common/http';
import { Observable, of, throwError } from 'rxjs';
import { delay, materialize, dematerialize } from 'rxjs/operators';
// array in local storage for registered users
const usersKey = 'angular-14-registration-login-example-users';
let users: any[] = JSON.parse(localStorage.getItem(usersKey)!) || [];
@Injectable()
export class FakeBackendInterceptor implements HttpInterceptor {
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const { url, method, headers, body } = request;
return handleRoute();
function handleRoute() {
switch (true) {
case url.endsWith('/users/authenticate') && method === 'POST':
return authenticate();
case url.endsWith('/users/register') && method === 'POST':
return register();
case url.endsWith('/users') && method === 'GET':
return getUsers();
case url.match(/\/users\/\d+$/) && method === 'GET':
return getUserById();
case url.match(/\/users\/\d+$/) && method === 'PUT':
return updateUser();
case url.match(/\/users\/\d+$/) && method === 'DELETE':
return deleteUser();
default:
// pass through any requests not handled above
return next.handle(request);
}
}
// route functions
function authenticate() {
const { username, password } = body;
const user = users.find(x => x.username === username && x.password === password);
if (!user) return error('Username or password is incorrect');
return ok({
...basicDetails(user),
token: 'fake-jwt-token'
})
}
function register() {
const user = body
if (users.find(x => x.username === user.username)) {
return error('Username "' + user.username + '" is already taken')
}
user.id = users.length ? Math.max(...users.map(x => x.id)) + 1 : 1;
users.push(user);
localStorage.setItem(usersKey, JSON.stringify(users));
return ok();
}
function getUsers() {
if (!isLoggedIn()) return unauthorized();
return ok(users.map(x => basicDetails(x)));
}
function getUserById() {
if (!isLoggedIn()) return unauthorized();
const user = users.find(x => x.id === idFromUrl());
return ok(basicDetails(user));
}
function updateUser() {
if (!isLoggedIn()) return unauthorized();
let params = body;
let user = users.find(x => x.id === idFromUrl());
// only update password if entered
if (!params.password) {
delete params.password;
}
// update and save user
Object.assign(user, params);
localStorage.setItem(usersKey, JSON.stringify(users));
return ok();
}
function deleteUser() {
if (!isLoggedIn()) return unauthorized();
users = users.filter(x => x.id !== idFromUrl());
localStorage.setItem(usersKey, JSON.stringify(users));
return ok();
}
// helper functions
function ok(body?: any) {
return of(new HttpResponse({ status: 200, body }))
.pipe(delay(500)); // delay observable to simulate server api call
}
function error(message: string) {
return throwError(() => ({ error: { message } }))
.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);
}
function unauthorized() {
return throwError(() => ({ status: 401, error: { message: 'Unauthorized' } }))
.pipe(materialize(), delay(500), dematerialize());
}
function basicDetails(user: any) {
const { id, username, firstName, lastName } = user;
return { id, username, firstName, lastName };
}
function isLoggedIn() {
return headers.get('Authorization') === 'Bearer fake-jwt-token';
}
function idFromUrl() {
const urlParts = url.split('/');
return parseInt(urlParts[urlParts.length - 1]);
}
}
}
export const fakeBackendProvider = {
// use fake backend in place of Http service for backend-less development
provide: HTTP_INTERCEPTORS,
useClass: FakeBackendInterceptor,
multi: true
};
JWT Interceptor
The JWT Interceptor intercepts http requests from the application to add a JWT auth token to the Authorization header if the user is logged in and the request is to the application api url (environment.apiUrl
).
It's implemented using the HttpInterceptor
interface included in the HttpClientModule
, by implementing the HttpInterceptor interface you can create a custom interceptor to modify http requests before they get sent to the server.
Http interceptors are added to the request pipeline in the providers section of the app.module.ts file.
import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
import { Observable } from 'rxjs';
import { environment } from '@environments/environment';
import { AccountService } from '@app/_services';
@Injectable()
export class JwtInterceptor implements HttpInterceptor {
constructor(private accountService: AccountService) { }
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// add auth header with jwt if user is logged in and request is to the api url
const user = this.accountService.userValue;
const isLoggedIn = user && user.token;
const isApiUrl = request.url.startsWith(environment.apiUrl);
if (isLoggedIn && isApiUrl) {
request = request.clone({
setHeaders: {
Authorization: `Bearer ${user.token}`
}
});
}
return next.handle(request);
}
}
Alert Models
The alert model file contains the Alert
, AlertType
and AlertOptions
models.
Alert
defines the properties of each alert object.AlertType
is an enumeration containing the types of alerts.AlertOptions
defines the options available when sending an alert to the alert service.
export class Alert {
id?: string;
type?: AlertType;
message?: string;
autoClose?: boolean;
keepAfterRouteChange?: boolean;
fade?: boolean;
constructor(init?:Partial<Alert>) {
Object.assign(this, init);
}
}
export enum AlertType {
Success,
Error,
Info,
Warning
}
export class AlertOptions {
id?: string;
autoClose?: boolean;
keepAfterRouteChange?: boolean;
}
User Model
The user model is a small class that defines the properties of a user.
export class User {
id?: string;
username?: string;
password?: string;
firstName?: string;
lastName?: string;
token?: string;
}
Account Service
The account service handles communication between the Angular app and the backend api for everything related to accounts. It contains methods for the login, logout and registration, as well as standard CRUD methods for retrieving, modifying and deleting user data.
On successful login the returned user
is stored in browser local storage to keep the user logged in between page refreshes and browser sessions, if you prefer not to use local storage you can simply remove it from the account service and the application will continue to work correctly, except for staying logged in between page refreshes.
The user
property exposes an RxJS observable (Observable<User>
) so any component can subscribe to be notified when a user logs in, logs out or updates their profile. The notification is triggered by the call to this.userSubject.next()
from each of those methods. For more info on component communication with RxJS see the tutorial Angular 14 - Communicating Between Components with Observable & Subject.
The userValue
getter allows other components to easily get the current value of the logged in user without having to subscribe to the user
observable.
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { environment } from '@environments/environment';
import { User } from '@app/_models';
@Injectable({ providedIn: 'root' })
export class AccountService {
private userSubject: BehaviorSubject<User | null>;
public user: Observable<User | null>;
constructor(
private router: Router,
private http: HttpClient
) {
this.userSubject = new BehaviorSubject(JSON.parse(localStorage.getItem('user')!));
this.user = this.userSubject.asObservable();
}
public get userValue() {
return this.userSubject.value;
}
login(username: string, password: string) {
return this.http.post<User>(`${environment.apiUrl}/users/authenticate`, { username, password })
.pipe(map(user => {
// store user details and jwt token in local storage to keep user logged in between page refreshes
localStorage.setItem('user', JSON.stringify(user));
this.userSubject.next(user);
return user;
}));
}
logout() {
// remove user from local storage and set current user to null
localStorage.removeItem('user');
this.userSubject.next(null);
this.router.navigate(['/account/login']);
}
register(user: User) {
return this.http.post(`${environment.apiUrl}/users/register`, user);
}
getAll() {
return this.http.get<User[]>(`${environment.apiUrl}/users`);
}
getById(id: string) {
return this.http.get<User>(`${environment.apiUrl}/users/${id}`);
}
update(id: string, params: any) {
return this.http.put(`${environment.apiUrl}/users/${id}`, params)
.pipe(map(x => {
// update stored user if the logged in user updated their own record
if (id == this.userValue?.id) {
// update local storage
const user = { ...this.userValue, ...params };
localStorage.setItem('user', JSON.stringify(user));
// publish updated user to subscribers
this.userSubject.next(user);
}
return x;
}));
}
delete(id: string) {
return this.http.delete(`${environment.apiUrl}/users/${id}`)
.pipe(map(x => {
// auto logout if the logged in user deleted their own record
if (id == this.userValue?.id) {
this.logout();
}
return x;
}));
}
}
Alert Service
The alert service acts as the bridge between any component in an Angular application and the alert component that actually displays the alert messages. It contains methods for sending, clearing and subscribing to alert messages.
The service uses the RxJS Observable and Subject classes to enable communication with other components, for more information on how this works see the tutorial Angular 14 - Communicating Between Components with Observable & Subject.
import { Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import { filter } from 'rxjs/operators';
import { Alert, AlertType, AlertOptions } from '@app/_models';
@Injectable({ providedIn: 'root' })
export class AlertService {
private subject = new Subject<Alert>();
private defaultId = 'default-alert';
// enable subscribing to alerts observable
onAlert(id = this.defaultId): Observable<Alert> {
return this.subject.asObservable().pipe(filter(x => x && x.id === id));
}
// convenience methods
success(message: string, options?: AlertOptions) {
this.alert(new Alert({ ...options, type: AlertType.Success, message }));
}
error(message: string, options?: AlertOptions) {
this.alert(new Alert({ ...options, type: AlertType.Error, message }));
}
info(message: string, options?: AlertOptions) {
this.alert(new Alert({ ...options, type: AlertType.Info, message }));
}
warn(message: string, options?: AlertOptions) {
this.alert(new Alert({ ...options, type: AlertType.Warning, message }));
}
// main alert method
alert(alert: Alert) {
alert.id = alert.id || this.defaultId;
this.subject.next(alert);
}
// clear alerts
clear(id = this.defaultId) {
this.subject.next(new Alert({ id }));
}
}
Account Routing Module
The account routing module defines the routes for the account feature module. It includes routes for user login and registration, and a parent route for the layout component which contains the common layout code for all pages in the account section.
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { LayoutComponent } from './layout.component';
import { LoginComponent } from './login.component';
import { RegisterComponent } from './register.component';
const routes: Routes = [
{
path: '', component: LayoutComponent,
children: [
{ path: 'login', component: LoginComponent },
{ path: 'register', component: RegisterComponent }
]
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class AccountRoutingModule { }
Account Module
The account module defines the feature module for the account section of the tutorial application along with metadata about the module. The imports
specify which other angular modules are required by this module, and the declarations
state which components belong to this module. For more info about Angular modules see https://angular.io/docs/ts/latest/guide/ngmodule.html.
The account module is hooked into the main app inside the app routing module with lazy loading.
import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { AccountRoutingModule } from './account-routing.module';
import { LayoutComponent } from './layout.component';
import { LoginComponent } from './login.component';
import { RegisterComponent } from './register.component';
@NgModule({
imports: [
CommonModule,
ReactiveFormsModule,
AccountRoutingModule
],
declarations: [
LayoutComponent,
LoginComponent,
RegisterComponent
]
})
export class AccountModule { }
Account Layout Component Template
The account layout component template is the root template of the account feature / section of the app, it contains the outer HTML for all account pages and a <router-outlet>
that renders the component for the current route.
<div class="container col-md-6 offset-md-3 mt-5">
<router-outlet></router-outlet>
</div>
Account Layout Component
The account layout component is the root component of the account feature / section of the app, it binds the component to the account layout template with the templateUrl
property of the angular @Component
decorator. It automatically redirects the user to the home page if they are already logged in to prevent an authenticated user from accessing /account/login
or /account/register
.
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { AccountService } from '@app/_services';
@Component({ templateUrl: 'layout.component.html' })
export class LayoutComponent {
constructor(
private router: Router,
private accountService: AccountService
) {
// redirect to home if already logged in
if (this.accountService.userValue) {
this.router.navigate(['/']);
}
}
}
Login Component Template
The login component template contains a login form with username and password fields. It displays validation messages for invalid fields when the submit button is clicked. The form submit event is bound to the onSubmit()
method of the login component.
The component uses reactive form validation to validate the input fields, for more information about angular reactive form validation see the tutorial Angular 14 - Reactive Forms Validation Example.
<div class="card">
<h4 class="card-header">Login</h4>
<div class="card-body">
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<div class="mb-3">
<label class="form-label">Username</label>
<input type="text" formControlName="username" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.username.errors }" />
<div *ngIf="submitted && f.username.errors" class="invalid-feedback">
<div *ngIf="f.username.errors.required">Username is required</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">Password</label>
<input type="password" formControlName="password" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.password.errors }" />
<div *ngIf="submitted && f.password.errors" class="invalid-feedback">
<div *ngIf="f.password.errors.required">Password is required</div>
</div>
</div>
<div>
<button [disabled]="loading" class="btn btn-primary">
<span *ngIf="loading" class="spinner-border spinner-border-sm me-1"></span>
Login
</button>
<a routerLink="../register" class="btn btn-link">Register</a>
</div>
</form>
</div>
</div>
Login Component
The login component uses the account service to login to the application on form submit. It creates the form fields and validators using an Angular FormBuilder
to create an instance of a FormGroup
that is stored in the form
property. The form
is then bound to the <form>
element in the login component template above using the [formGroup]
directive.
The component contains a convenience getter property f
to make it a bit easier to access form controls, for example you can access the password field in the template using f.password
instead of form.controls.password
.
import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { first } from 'rxjs/operators';
import { AccountService, AlertService } from '@app/_services';
@Component({ templateUrl: 'login.component.html' })
export class LoginComponent implements OnInit {
form!: FormGroup;
loading = false;
submitted = false;
constructor(
private formBuilder: FormBuilder,
private route: ActivatedRoute,
private router: Router,
private accountService: AccountService,
private alertService: AlertService
) { }
ngOnInit() {
this.form = this.formBuilder.group({
username: ['', Validators.required],
password: ['', Validators.required]
});
}
// convenience getter for easy access to form fields
get f() { return this.form.controls; }
onSubmit() {
this.submitted = true;
// reset alerts on submit
this.alertService.clear();
// stop here if form is invalid
if (this.form.invalid) {
return;
}
this.loading = true;
this.accountService.login(this.f.username.value, this.f.password.value)
.pipe(first())
.subscribe({
next: () => {
// get return url from query parameters or default to home page
const returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';
this.router.navigateByUrl(returnUrl);
},
error: error => {
this.alertService.error(error);
this.loading = false;
}
});
}
}
Register Component Template
The register component template contains a simple registration form with fields for first name, last name, username and password. It displays validation messages for invalid fields when the submit button is clicked. The form element uses the [formGroup]
directive to bind to the form
FormGroup in the register component below, and it binds the form submit event to the onSubmit()
handler in the register component using the angular event binding (ngSubmit)="onSubmit()"
.
The component uses reactive form validation to validate the input fields, for more information about angular reactive form validation see the tutorial Angular 14 - Reactive Forms Validation Example.
<div class="card">
<h4 class="card-header">Register</h4>
<div class="card-body">
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<div class="mb-3">
<label class="form-label">First Name</label>
<input type="text" formControlName="firstName" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.firstName.errors }" />
<div *ngIf="submitted && f.firstName.errors" class="invalid-feedback">
<div *ngIf="f.firstName.errors.required">First Name is required</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">Last Name</label>
<input type="text" formControlName="lastName" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.lastName.errors }" />
<div *ngIf="submitted && f.lastName.errors" class="invalid-feedback">
<div *ngIf="f.lastName.errors.required">Last Name is required</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">Username</label>
<input type="text" formControlName="username" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.username.errors }" />
<div *ngIf="submitted && f.username.errors" class="invalid-feedback">
<div *ngIf="f.username.errors.required">Username is required</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">Password</label>
<input type="password" formControlName="password" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.password.errors }" />
<div *ngIf="submitted && f.password.errors" class="invalid-feedback">
<div *ngIf="f.password.errors.required">Password is required</div>
<div *ngIf="f.password.errors.minlength">Password must be at least 6 characters</div>
</div>
</div>
<div>
<button [disabled]="loading" class="btn btn-primary">
<span *ngIf="loading" class="spinner-border spinner-border-sm me-1"></span>
Register
</button>
<a routerLink="../login" class="btn btn-link">Cancel</a>
</div>
</form>
</div>
</div>
Register Component
The register component creates a new user with the account service when the register form is valid and submitted.
It creates the form fields and validators using an Angular FormBuilder
to create an instance of a FormGroup
that is stored in the form
property. The form
is then bound to the <form>
element in the register component template above using the [formGroup]
directive.
The component contains a convenience getter property f
to make it a bit easier to access form controls, for example you can access the password field in the template using f.password
instead of form.controls.password
.
import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { first } from 'rxjs/operators';
import { AccountService, AlertService } from '@app/_services';
@Component({ templateUrl: 'register.component.html' })
export class RegisterComponent implements OnInit {
form!: FormGroup;
loading = false;
submitted = false;
constructor(
private formBuilder: FormBuilder,
private route: ActivatedRoute,
private router: Router,
private accountService: AccountService,
private alertService: AlertService
) { }
ngOnInit() {
this.form = this.formBuilder.group({
firstName: ['', Validators.required],
lastName: ['', Validators.required],
username: ['', Validators.required],
password: ['', [Validators.required, Validators.minLength(6)]]
});
}
// convenience getter for easy access to form fields
get f() { return this.form.controls; }
onSubmit() {
this.submitted = true;
// reset alerts on submit
this.alertService.clear();
// stop here if form is invalid
if (this.form.invalid) {
return;
}
this.loading = true;
this.accountService.register(this.form.value)
.pipe(first())
.subscribe({
next: () => {
this.alertService.success('Registration successful', { keepAfterRouteChange: true });
this.router.navigate(['../login'], { relativeTo: this.route });
},
error: error => {
this.alertService.error(error);
this.loading = false;
}
});
}
}
Home Component Template
The home component template contains html and angular 14 template syntax for displaying a simple welcome message and a link to the users section.
<div class="p-4">
<div class="container">
<h1>Hi {{user?.firstName}}!</h1>
<p>You're logged in with Angular 14!!</p>
<p><a routerLink="/users">Manage Users</a></p>
</div>
</div>
Home Component
The home component defines an angular component that gets the current logged in user from the account service and makes it available to the template via the user
property.
import { Component } from '@angular/core';
import { User } from '@app/_models';
import { AccountService } from '@app/_services';
@Component({ templateUrl: 'home.component.html' })
export class HomeComponent {
user: User | null;
constructor(private accountService: AccountService) {
this.user = this.accountService.userValue;
}
}
Users Add/Edit Component Template
The users add/edit component template contains a dynamic form that supports both adding and editing users. The form is in edit mode when there a user id
property in the current route, otherwise it is in add mode.
In edit mode the form is pre-populated with user details fetched from the API and the password field is optional. The dynamic behaviour is implemented in the users add/edit component.
<h1>{{title}}</h1>
<form *ngIf="!loading" [formGroup]="form" (ngSubmit)="onSubmit()">
<div class="row">
<div class="mb-3 col">
<label class="form-label">First Name</label>
<input type="text" formControlName="firstName" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.firstName.errors }" />
<div *ngIf="submitted && f.firstName.errors" class="invalid-feedback">
<div *ngIf="f.firstName.errors.required">First Name is required</div>
</div>
</div>
<div class="mb-3 col">
<label class="form-label">Last Name</label>
<input type="text" formControlName="lastName" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.lastName.errors }" />
<div *ngIf="submitted && f.lastName.errors" class="invalid-feedback">
<div *ngIf="f.lastName.errors.required">Last Name is required</div>
</div>
</div>
</div>
<div class="row">
<div class="mb-3 col">
<label class="form-label">Username</label>
<input type="text" formControlName="username" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.username.errors }" />
<div *ngIf="submitted && f.username.errors" class="invalid-feedback">
<div *ngIf="f.username.errors.required">Username is required</div>
</div>
</div>
<div class="mb-3 col">
<label class="form-label">
Password
<em *ngIf="id">(Leave blank to keep the same password)</em>
</label>
<input type="password" formControlName="password" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.password.errors }" />
<div *ngIf="submitted && f.password.errors" class="invalid-feedback">
<div *ngIf="f.password.errors.required">Password is required</div>
<div *ngIf="f.password.errors.minlength">Password must be at least 6 characters</div>
</div>
</div>
</div>
<div class="mb-3">
<button [disabled]="submitting" class="btn btn-primary">
<span *ngIf="submitting" class="spinner-border spinner-border-sm me-1"></span>
Save
</button>
<a routerLink="/users" class="btn btn-link">Cancel</a>
</div>
</form>
<div *ngIf="loading" class="text-center m-5">
<span class="spinner-border spinner-border-lg align-center"></span>
</div>
Users Add/Edit Component
The users add/edit component is used for both adding and editing users in the angular tutorial app, the component is in edit mode when there a user id
route parameter, otherwise it is in add mode.
In add mode the password field is required and the form fields are empty by default. In edit mode the password field is optional and the form is pre-populated with the specified user details, which are fetched from the API with the account service.
On submit a user is either created or updated by calling the account service, and on success you are redirected back to the users list page with a success message.
import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { first } from 'rxjs/operators';
import { AccountService, AlertService } from '@app/_services';
@Component({ templateUrl: 'add-edit.component.html' })
export class AddEditComponent implements OnInit {
form!: FormGroup;
id?: string;
title!: string;
loading = false;
submitting = false;
submitted = false;
constructor(
private formBuilder: FormBuilder,
private route: ActivatedRoute,
private router: Router,
private accountService: AccountService,
private alertService: AlertService
) { }
ngOnInit() {
this.id = this.route.snapshot.params['id'];
// form with validation rules
this.form = this.formBuilder.group({
firstName: ['', Validators.required],
lastName: ['', Validators.required],
username: ['', Validators.required],
// password only required in add mode
password: ['', [Validators.minLength(6), ...(!this.id ? [Validators.required] : [])]]
});
this.title = 'Add User';
if (this.id) {
// edit mode
this.title = 'Edit User';
this.loading = true;
this.accountService.getById(this.id)
.pipe(first())
.subscribe(x => {
this.form.patchValue(x);
this.loading = false;
});
}
}
// convenience getter for easy access to form fields
get f() { return this.form.controls; }
onSubmit() {
this.submitted = true;
// reset alerts on submit
this.alertService.clear();
// stop here if form is invalid
if (this.form.invalid) {
return;
}
this.submitting = true;
this.saveUser()
.pipe(first())
.subscribe({
next: () => {
this.alertService.success('User saved', { keepAfterRouteChange: true });
this.router.navigateByUrl('/users');
},
error: error => {
this.alertService.error(error);
this.submitting = false;
}
})
}
private saveUser() {
// create or update user based on id param
return this.id
? this.accountService.update(this.id!, this.form.value)
: this.accountService.register(this.form.value);
}
}
Users Layout Component Template
The users layout component template is the root template of the users feature / section of the app, it contains the outer HTML for all /users
pages and a <router-outlet>
for rendering the currently routed component.
<div class="p-4">
<div class="container">
<router-outlet></router-outlet>
</div>
</div>
Users Layout Component
The users layout component is the root component of the users feature / section of the app, it binds the component to the users layout template with the templateUrl
property of the angular @Component
decorator.
import { Component } from '@angular/core';
@Component({ templateUrl: 'layout.component.html' })
export class LayoutComponent { }
Users List Component Template
The users list component template displays a list of all users and contains buttons for adding, editing and deleting users.
<h1>Users</h1>
<a routerLink="add" class="btn btn-sm btn-success mb-2">Add User</a>
<table class="table table-striped">
<thead>
<tr>
<th style="width: 30%">First Name</th>
<th style="width: 30%">Last Name</th>
<th style="width: 30%">Username</th>
<th style="width: 10%"></th>
</tr>
</thead>
<tbody>
<tr *ngFor="let user of users">
<td>{{user.firstName}}</td>
<td>{{user.lastName}}</td>
<td>{{user.username}}</td>
<td style="white-space: nowrap">
<a routerLink="edit/{{user.id}}" class="btn btn-sm btn-primary me-1">Edit</a>
<button (click)="deleteUser(user.id)" class="btn btn-sm btn-danger btn-delete-user" [disabled]="user.isDeleting">
<span *ngIf="user.isDeleting" class="spinner-border spinner-border-sm"></span>
<span *ngIf="!user.isDeleting">Delete</span>
</button>
</td>
</tr>
<tr *ngIf="!users">
<td colspan="4" class="text-center">
<span class="spinner-border spinner-border-lg align-center"></span>
</td>
</tr>
</tbody>
</table>
Users List Component
The users list component gets all users from the account service in the ngOnInit()
angular lifecycle method and makes them available to the users list template via the users
property.
The deleteUser()
method first sets the property user.isDeleting = true
so the template displays a spinner on the delete button, it then calls this.accountService.delete()
to delete the user and removes the deleted user from component users
array so it is removed from the UI.
import { Component, OnInit } from '@angular/core';
import { first } from 'rxjs/operators';
import { AccountService } from '@app/_services';
@Component({ templateUrl: 'list.component.html' })
export class ListComponent implements OnInit {
users?: any[];
constructor(private accountService: AccountService) {}
ngOnInit() {
this.accountService.getAll()
.pipe(first())
.subscribe(users => this.users = users);
}
deleteUser(id: string) {
const user = this.users!.find(x => x.id === id);
user.isDeleting = true;
this.accountService.delete(id)
.pipe(first())
.subscribe(() => this.users = this.users!.filter(x => x.id !== id));
}
}
Users Routing Module
The users routing module defines the routes for the users feature module. It includes routes for listing, adding and editing users, and a parent route for the layout component which contains the common layout code for all pages in the users section.
The add and edit routes are different but both load the same component (AddEditComponent
) which modifies its behaviour based on the route.
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { LayoutComponent } from './layout.component';
import { ListComponent } from './list.component';
import { AddEditComponent } from './add-edit.component';
const routes: Routes = [
{
path: '', component: LayoutComponent,
children: [
{ path: '', component: ListComponent },
{ path: 'add', component: AddEditComponent },
{ path: 'edit/:id', component: AddEditComponent }
]
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class UsersRoutingModule { }
Users Module
The users module defines the feature module for the users section of the tutorial application along with metadata about the module. The imports
specify which other angular modules are required by this module, and the declarations
state which components belong to this module. For more info about angular modules see https://angular.io/docs/ts/latest/guide/ngmodule.html.
The users module is hooked into the main app inside the app routing module with lazy loading.
import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { UsersRoutingModule } from './users-routing.module';
import { LayoutComponent } from './layout.component';
import { ListComponent } from './list.component';
import { AddEditComponent } from './add-edit.component';
@NgModule({
imports: [
CommonModule,
ReactiveFormsModule,
UsersRoutingModule
],
declarations: [
LayoutComponent,
ListComponent,
AddEditComponent
]
})
export class UsersModule { }
App Routing Module
The app routing module defines the top level routes for the angular application and generates a root routing module by passing the array of routes
to the RouterModule.forRoot()
method. The module is imported into the main app module below.
The home route maps the root path of the app to the home component, the users route maps to the users module and the account route maps to the account module, both feature module routes (/users
and /account
) are lazy loaded. The home and users routes are secured by passing the auth guard to the canActivate
property of each route.
For more information on angular routing see https://angular.io/guide/router.
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HomeComponent } from './home';
import { AuthGuard } from './_helpers';
const accountModule = () => import('./account/account.module').then(x => x.AccountModule);
const usersModule = () => import('./users/users.module').then(x => x.UsersModule);
const routes: Routes = [
{ path: '', component: HomeComponent, canActivate: [AuthGuard] },
{ path: 'users', loadChildren: usersModule, canActivate: [AuthGuard] },
{ path: 'account', loadChildren: accountModule },
// otherwise redirect to home
{ path: '**', redirectTo: '' }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
App Component Template
The app component template is the root component template of the application, it contains the main nav bar which is only displayed for authenticated users, a global alert component, and a router-outlet
component for displaying the contents of each view based on the current route / path.
<!-- nav -->
<nav class="navbar navbar-expand navbar-dark bg-dark px-3" *ngIf="user">
<div class="navbar-nav">
<a class="nav-item nav-link" routerLink="/" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">Home</a>
<a class="nav-item nav-link" routerLink="/users" routerLinkActive="active">Users</a>
<button class="btn btn-link nav-item nav-link" (click)="logout()">Logout</button>
</div>
</nav>
<!-- main app container -->
<div class="app-container" [ngClass]="{ 'bg-light': user }">
<alert></alert>
<router-outlet></router-outlet>
</div>
App Component
The app component is the root component of the application, it defines the root tag of the app as <app-root></app-root>
with the selector property of the @Component()
decorator.
It subscribes to the user
observable of the account service so it can reactively show/hide the main navigation bar when the user logs in/out of the application. Usually it's best practice to unsubscribe when the component is destroyed to avoid memory leaks. This isn't necessary in the root app component because it will only be destroyed when the entire app is closed.
The logout()
method is called from the logout link in the main nav bar above to log the user out and redirect them to the login page.
import { Component } from '@angular/core';
import { AccountService } from './_services';
import { User } from './_models';
@Component({ selector: 'app-root', templateUrl: 'app.component.html' })
export class AppComponent {
user?: User | null;
constructor(private accountService: AccountService) {
this.accountService.user.subscribe(x => this.user = x);
}
logout() {
this.accountService.logout();
}
}
App Module
The app module defines the root module of the application along with metadata about the module. For more info about angular modules see https://angular.io/docs/ts/latest/guide/ngmodule.html.
This is where the fake backend provider is added to the application, to switch to a real backend simply remove the fakeBackendProvider
located below the comment // provider used to create fake backend
.
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
// used to create fake backend
import { fakeBackendProvider } from './_helpers';
import { AppRoutingModule } from './app-routing.module';
import { JwtInterceptor, ErrorInterceptor } from './_helpers';
import { AppComponent } from './app.component';
import { AlertComponent } from './_components';
import { HomeComponent } from './home';
@NgModule({
imports: [
BrowserModule,
ReactiveFormsModule,
HttpClientModule,
AppRoutingModule
],
declarations: [
AppComponent,
AlertComponent,
HomeComponent
],
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true },
{ provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true },
// provider used to create fake backend
fakeBackendProvider
],
bootstrap: [AppComponent]
})
export class AppModule { };
Production Environment Config
The production environment config contains variables required to run the application in production. This enables you to build the application with a different configuration for each different environment (e.g. production & development) without updating the app code.
When you build the application for production with the command ng build --configuration production
, the output environment.ts
is replaced with environment.prod.ts
.
export const environment = {
production: true,
apiUrl: 'http://localhost:4000'
};
Development Environment Config
The development environment config contains variables required to run the application in development.
Environment config is accessed by importing the environment object into any Angular service of component with the line import { environment } from '@environments/environment'
and accessing properties on the environment
object, see the account service for an example.
export const environment = {
production: false,
apiUrl: 'http://localhost:4000'
};
Main Index Html File
The main index.html file is the initial page loaded by the browser that kicks everything off. The Angular CLI (with Webpack under the hood) bundles all of the compiled javascript files together and injects them into the body of the index.html page so the scripts can be loaded and executed by the browser.
<!DOCTYPE html>
<html>
<head>
<base href="/" />
<title>Angular 14 - User Registration and Login Example</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- bootstrap css -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous">
</head>
<body>
<app-root></app-root>
</body>
</html>
Main (Bootstrap) File
The main file is the entry point used by angular to launch and bootstrap the application.
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));
Polyfills
Some features used by Angular 14 are not yet supported natively by all major browsers, polyfills are used to add support for features where necessary so the Angular tutorial application works across all major browsers.
This file is generated by the Angular CLI when creating a new project with the ng new
command, I've excluded the comments in the file for brevity.
import 'zone.js';
Global LESS/CSS Styles
The global styles file contains LESS/CSS styles that are applied globally throughout the application.
/* You can add global styles to this file, and also import other style files */
.app-container {
min-height: 320px;
overflow: hidden;
}
.btn-delete-user {
width: 40px;
text-align: center;
box-sizing: content-box;
}
npm package.json
The package.json file contains project configuration information including package dependencies that get installed when you run npm install
and scripts that are executed when you run npm start
or npm run build
etc. Full documentation is available at https://docs.npmjs.com/files/package.json.
{
"name": "angular-registration-login-example",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve --open",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test"
},
"private": true,
"dependencies": {
"@angular/animations": "^14.2.0",
"@angular/common": "^14.2.0",
"@angular/compiler": "^14.2.0",
"@angular/core": "^14.2.0",
"@angular/forms": "^14.2.0",
"@angular/platform-browser": "^14.2.0",
"@angular/platform-browser-dynamic": "^14.2.0",
"@angular/router": "^14.2.0",
"rxjs": "~7.5.0",
"tslib": "^2.3.0",
"zone.js": "~0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "^14.2.8",
"@angular/cli": "~14.2.8",
"@angular/compiler-cli": "^14.2.0",
"@types/jasmine": "~4.0.0",
"jasmine-core": "~4.3.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.0.0",
"typescript": "~4.7.2"
}
}
TypeScript tsconfig.json
The tsconfig.json file contains the base TypeScript compiler configuration for all projects in the Angular workspace, it configures how the TypeScript code will be compiled / transpiled into JavaScript that is understood by the browser. For more info see https://angular.io/config/tsconfig.
Most of the file is unchanged from when it was generated by the Angular CLI, only the paths
property has been added to map the @app
and @environments
aliases to the /src/app
and /src/environments
directories. This allows imports to be relative to the app and environments folders by prefixing import paths with aliases instead of having to use long relative paths (e.g. import MyComponent from '@app/MyComponent'
instead of import MyComponent from '../../../MyComponent'
).
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,
"target": "es2020",
"module": "es2020",
"lib": [
"es2020",
"dom"
],
"paths": {
"@app/*": ["src/app/*"],
"@environments/*": ["src/environments/*"]
}
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}
Need Some Angular 14 Help?
Search fiverr for freelance Angular 14 developers.
Follow me for updates
When I'm not coding...
Me and Tina are on a motorcycle adventure around Australia.
Come along for the ride!