Published: May 04 2023
Last updated: May 11 2023

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:


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

  1. Create Alert Service
  2. Create Alert Component
  3. Refactor Login Component to use Alert Service
  4. Refactor Register Component to use Alert Service
  5. Update Home Component
  6. 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 to false. 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 which cssClass 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 line 6 to make it available to the component.
  • remove the local error: string and success: string properties as these are no longer needed.
  • add the private alertService: AlertService parameter to the constructor() 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 the onSubmit() method with a call to this.alertService.clear();.
  • replace the statement this.error = error; with this.alertService.error(error); in the onSubmit() 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 line 6 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 the constructor() method so it is injected by the Angular DI system.
  • replace the line below the comment // reset alert on submit in the onSubmit() method with a call to this.alertService.clear();.
  • add a call to alertService.success() on line 55 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; with this.alertService.error(error); in the onSubmit() 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 value accountService.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

On Twitter or RSS.


When I'm not coding...

Me and Tina are on a motorcycle adventure around Australia.
Come along for the ride!


Comments


Supported by