Angular 15/16 Free Course #5 - Alerts & Home Page
Built and tested with Angular 15 and Angular 16
In this free step by step Angular course we'll be covering how to implement routing, authentication, registration and CRUD functionality in Angular.
Other parts available in this Angular course:
- Part 1 - Create Base Project Structure
- Part 2 - Add Routing & Multiple Pages
- Part 3 - Login Form, Authentication & Route Guard
- Part 4 - Registration Form & Service Methods
- Part 5 - Alerts & Home Page
- Part 6 - User Management (CRUD) Section
- Part 7 - Migrate to Standalone Components and Functional Interceptors
- Part 8 - Dockerize App with Nginx
Angular Tutorial Part 5
In part 5 of this Angular tutorial series we're going to simplify the way alert messages are displayed by creating a centralized alert service and alert component, and add a personalised welcome message to the home page.
Code on GitHub
The complete source code for this part of the tutorial is available on GitHub at https://github.com/cornflourblue/angular-16-tutorial in the part-5
folder. If you haven't completed Part 4 (Registration Form & Service Methods) but want to follow the steps in this part of the course you can start with the code in the part-4
folder of the GitHub repo.
Tutorial Steps
- Create Alert Service
- Create Alert Component
- Refactor Login Component to use Alert Service
- Refactor Register Component to use Alert Service
- Update Home Component
- Start Angular Application!
Create Alert Service
The alert service provides a centralized / global way to display alert notifications from anywhere in the Angular application.
The constructor()
method clears the alert message on route change, unless the showAfterRedirect
property is true
, in which case the showAfterRedirect
property is reset to false
so the alert will be cleared on the following route change. The service subscribes to Angular router events by calling router.events.subscribe()
and checks the event is a route change by checking event instanceof NavigationStart
.
The onAlert()
method returns an Observable
that any component can subscribe to to be notified when there is a new alert message. The alert component in the next step will subscribe to onAlert()
so it can display the alert message.
The success()
method:
- has a parameter for the message text (
message: string
), and an optional parameter to continue displaying the message after one route change (showAfterRedirect
) which defaults tofalse
. This parameter is useful when you want to display an alert message after a redirect, like when a new user registers and is redirected to the login page. - publishes a new alert to all subscribers by calling
subject.next()
. The alert object includes the message text and type (type: 'success'
) which will be used by the alert component to display a green success message.
The error()
method does the same as the success()
method, except it sets the alert type to 'error'
to tell the alert component to display it as a red error message.
The clear()
method publishes a null
value to all subscribers by calling subject.next(null)
, this tells the alert component to remove the alert message from the UI.
Create Alert Service
Create a file named alert.service.ts
in the _services folder and add the following TypeScript code to it:
import { Injectable } from '@angular/core';
import { Router, NavigationStart } from '@angular/router';
import { Observable, Subject } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class AlertService {
private subject = new Subject<any>();
private showAfterRedirect = false;
constructor(private router: Router) {
// clear alert messages on route change unless 'showAfterRedirect' flag is true
this.router.events.subscribe(event => {
if (event instanceof NavigationStart) {
if (this.showAfterRedirect) {
// only keep for a single route change
this.showAfterRedirect = false;
} else {
// clear alert message
this.clear();
}
}
});
}
onAlert(): Observable<any> {
return this.subject.asObservable();
}
success(message: string, showAfterRedirect = false) {
this.showAfterRedirect = showAfterRedirect;
this.subject.next({ type: 'success', message });
}
error(message: string, showAfterRedirect = false) {
this.showAfterRedirect = showAfterRedirect;
this.subject.next({ type: 'error', message });
}
clear() {
// clear by calling subject.next() with null
this.subject.next(null);
}
}
Add Alert Service to Services Barrel File
Open the services barrel file (/src/app/_services/index.ts
) and add the line export * from './alert.service';
, this enables the alert service to be imported using only the folder path (e.g. import { AlertService } from './_services'
).
This is how the services barrel file should look after the update:
export * from './account.service';
export * from './alert.service';
Create Alert Component & Template
The alert component uses the alert service to subscribe to messages and display them in the UI.
The @Component
decorator contains two parameters, the selector: 'alert'
parameter tells Angular to inject an instance of this component wherever it finds the <alert></alert>
HTML tag. The templateUrl: 'alert.component.html'
parameter tells Angular where to find the HTML template for this component.
The constructor()
method specifies the AlertService
as a dependency which is automatically injected by the Angular Dependency Injection (DI) system.
The ngOnInit()
method:
- is an Angular lifecycle hook that runs once after the component is created. For more info on Angular lifecycle hooks see https://angular.io/guide/lifecycle-hooks.
- subscribes to new alerts from the alert service by calling
alertService.onAlert().subscribe()
. - checks the
alert.type
to determine whichcssClass
to set on the message. The css classes are provided by Bootstrap 5 and display success messages as green alerts and error messages as red alerts. - assigns the alert to the
this.alert
property of the component to make it accessible to the component template.
The ngOnDestroy()
method:
- is an Angular lifecycle hook that runs once just before Angular destroys the component. For more info on Angular lifecycle hooks see https://angular.io/guide/lifecycle-hooks.
- unsubscribes from the alert service by calling
subscription.unsubscribe()
to avoid memory leaks.
Create Components Folder
Create a folder named _components
in the /src/app
folder.
The _components
folder contains shared Angular components that can be used by different parts of the application. The underscore "_" prefix is used to easily differentiate between shared code (e.g. _services, _components, _helpers etc) and feature specific code (e.g. home, account), the prefix also groups shared code folders together at the top of the folder structure in VS Code.
Create Alert Component
Create a file named alert.component.ts
in the _components
folder and add the following TypeScript code to it:
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';
import { AlertService } from '@app/_services';
@Component({ selector: 'alert', templateUrl: 'alert.component.html' })
export class AlertComponent implements OnInit, OnDestroy {
private subscription!: Subscription;
alert: any;
constructor(private alertService: AlertService) { }
ngOnInit() {
this.subscription = this.alertService.onAlert()
.subscribe(alert => {
switch (alert?.type) {
case 'success':
alert.cssClass = 'alert alert-success';
break;
case 'error':
alert.cssClass = 'alert alert-danger';
break;
}
this.alert = alert;
});
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
Create Alert Component Template
The alert component template renders the alert sent via the alert service.
The *ngIf="alert"
directive renders the alert message if the alert is not null / undefined.
The [ngClass]="alert.cssClass"
attribute directive binds the element class
attribute to the cssClass
property of the alert set in the alert component above.
The {{alert.messag}}
uses Angular interpolation ({{...}}
) to render the message text in the alert div. For more info on Angular interpolation and template syntax see https://angular.io/guide/interpolation.
Create a file named alert.component.html
in the _components
folder and add the following HTML code to it:
<div *ngIf="alert" class="mt-4">
<div [ngClass]="alert.cssClass">{{alert.message}}</div>
</div>
Create Barrel File in Components Folder
Create a file named index.ts
inside the _components
folder.
The index.ts
file is a barrel file that re-exports components from the _components
folder so they can be imported in other files using only the folder path (e.g. './_components'
) instead of the full path to the component (e.g. './_components/alert.component'
).
Add the following TypeScript code to the barrel file:
export * from './alert.component';
Add Alert Component to App Module
Open the app module (/src/app/app.module.ts
) and add the AlertComponent
to the declarations
array, this will make the alert component available to the other components in the App Module.
This is how the app.module.ts
file should look after the changes, the new lines are 14
and 28
.
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 { HomeComponent } from './home';
import { LoginComponent, RegisterComponent } from './account';
import { AlertComponent } from './_components';
@NgModule({
imports: [
BrowserModule,
ReactiveFormsModule,
HttpClientModule,
AppRoutingModule
],
declarations: [
AppComponent,
HomeComponent,
LoginComponent,
RegisterComponent,
AlertComponent
],
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 { }
Add Alert Component to App Component Template
Open the app component template (/src/app/app.component.html
) and add the <alert></alert>
element to display alert messages sent from anywhere in the Angular app.
This is how the app.component.html
file should look after the changes, the new line is 11
.
<!-- 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="/">Home</a>
<button class="btn btn-link nav-item nav-link" (click)="logout()">Logout</button>
</div>
</nav>
<!-- main app container -->
<div class="container">
<alert></alert>
<router-outlet></router-outlet>
</div>
Refactor Login Component to use Alert Service
Now that we have a global alert service we can refactor the login component to use the alert service and remove the local error
and success
alert properties.
Refactor Login Component Logic
Open the login.component.ts
file and:
- add the
AlertService
to the service imports on line6
to make it available to the component. - remove the local
error: string
andsuccess: string
properties as these are no longer needed. - add the
private alertService: AlertService
parameter to theconstructor()
method so it is injected by the Angular DI system. - remove the logic that sets the success message below the comment
// show success message on registration
, the register component will be refactored in the next step to set the'Registration successful'
message with the alert service. - replace the two lines below the comment
// reset alerts on submit
in theonSubmit()
method with a call tothis.alertService.clear();
. - replace the statement
this.error = error;
withthis.alertService.error(error);
in theonSubmit()
method.
This is how the login component should look after the updates:
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
) {
// redirect to home if already logged in
if (this.accountService.userValue) {
this.router.navigate(['/']);
}
}
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;
}
});
}
}
Remove Alert HTML from Login Component Template
Open the login.component.html
file and remove the two elements that display the {{error}}
and {{success}}
alert messages. These elements are no longer needed since the alert component will display messages in the app component.
This is how the login component template should look after the alert elements have been removed:
<div class="container col-md-6 offset-md-3 mt-5">
<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>
</div>
Refactor Register Component to use Alert Service
Just as we did with the login component above, we can refactor the register component to use the alert service and remove the local error
alert property.
Refactor Register Component Logic
Open the register.component.ts
file and:
- add the
AlertService
to the service imports on line6
to make it available to the component. - remove the local
error: string
property as it is no longer needed. - add the
private alertService: AlertService
parameter to theconstructor()
method so it is injected by the Angular DI system. - replace the line below the comment
// reset alert on submit
in theonSubmit()
method with a call tothis.alertService.clear();
. - add a call to
alertService.success()
on line55
to display a green success alert on successful registration. The first parameter sets the message to'Registration successful'
, the second parameter tells the alert service to show the alert after redirecting to the login page. - replace the statement
this.error = error;
withthis.alertService.error(error);
in theonSubmit()
method.
This is how the register component should look after the updates:
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
) {
// redirect to home if already logged in
if (this.accountService.userValue) {
this.router.navigate(['/']);
}
}
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 alert 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', true);
this.router.navigate(['/account/login'], { queryParams: { registered: true }});
},
error: error => {
this.alertService.error(error);
this.loading = false;
}
});
}
}
Remove Alert HTML from Register Component Template
Open the register.component.html
file and remove the element that displays the {{error}}
alert message, it is no longer needed since the alert component will display messages in the app component.
This is how the register component template should look after the alert element has been removed:
<div class="container col-md-6 offset-md-3 mt-5">
<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>
</div>
Add Logic to Home Component
The home component contains logic for displaying a personalised welcome message to the current user.
The constructor()
method:
- specifies the
AlertService
as a dependency which is automatically injected by the Angular Dependency Injection (DI) system. - assigns the
user
property with the valueaccountService.userValue
so the current user's name can be displayed in the home component template.
Update Home Component
Open the home.component.ts
file and update it with the following TypeScript:
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;
}
}
Add HTML to Home Component Template
The home component template contains the HTML and Angular template syntax for displaying a welcome message with the current user name.
The {{user?.firstName}}
uses Angular interpolation ({{...}}
) to display the first name property of the currentUser
object from the home component above. For more info on Angular interpolation and template syntax see https://angular.io/guide/template-syntax#interpolation-and-template-expressions.
Update Home Component Template
Open the home.component.html
file and update it with the following HTML:
<div class="p-4">
<div class="container">
<h1>Hi {{user?.firstName}}!</h1>
<p>You're logged in with Angular!!</p>
</div>
</div>
Start Angular Application!
Run the command npm start
from the project root folder (where the package.json is located) to start the Angular application, then open a browser tab to the URL http://localhost:4200.
The command output should look something like this:
PS C:\Projects\angular-tutorial> npm start
> [email protected] start
> ng serve
✔ Browser application bundle generation complete.
Initial Chunk Files | Names | Raw Size
vendor.js | vendor | 2.44 MB |
polyfills.js | polyfills | 314.29 kB |
styles.css, styles.js | styles | 209.42 kB |
main.js | main | 60.68 kB |
runtime.js | runtime | 6.54 kB |
| Initial Total | 3.01 MB
Build at: 2023-05-04T02:12:20.005Z - Hash: 8d8b895a8d06797e - Time: 8821ms
** Angular Live Development Server is listening on localhost:4200, open your browser on http://localhost:4200/ **
√ Compiled successfully.
Need Some Angular 16 Help?
Search fiverr for freelance Angular 16 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!