May 31 2019

Angular 7 Tutorial Part 6 - Home Page & Alert Component

Other parts available in Angular 7 tutorial series:


Angular 7 Tutorial Part 6

In part 6 of this Angular 7 tutorial series we're going to update the home page to demonstrate fetching, displaying and deleting users from secure api endpoints. We'll also simplify the way alert messages are displayed by creating a centralized alert service and alert component.

The complete source code for this part of the tutorial is available on github at https://github.com/cornflourblue/angular-7-tutorial in the part-6 folder. If you haven't completed Part 5 (Registration Form & User Service) but want to follow the steps in this part of the tutorial series you can start with the code in the part-5 folder of the github repo.

Steps:

  1. Add "Get Users" and "Delete User" Routes to Fake Backend
  2. Add Logic to Home Component
  3. Add HTML to Home Component Template
  4. Create Alert Service
  5. Create Alert Component
  6. Refactor Login Component to use Alert Service
  7. Refactor Register Component to use Alert Service
  8. Start Angular 7 Application!


Add "Get Users" and "Delete User" Routes to Fake Backend

To demonstrate accessing secure api routes from the app after logging in, the home page component will be updated in the next steps to fetch and display a list of all users from a secure route, and enable deleting a user with another secure route. These routes have been added to the fake backend so we can test the Angular 7 application without a real backend.

The secure get users route (/users) has been added to the handleRoute() function to enable fetching all users, requests to the get users route are handled by the new getUsers() function.

The getUsers() function:

  • checks if the user is logged in by calling the new isLoggedIn() helper function. If the user is not logged in a 401 Unauthorized response is returned by calling the new unauthorized() helper function.
  • returns an ok() response with the whole users array in the response body.

The secure delete user route (/users/{id}) has been added to the handleRoute() function to enable deleting a user, requests to the delete user route are handled by the new deleteUser() function. The route uses the regular expression /\/users\/\d+$/ to matches urls that end with /users/ followed by any number for the user id.

The deleteUser() function:

  • checks if the user is logged in by calling the new isLoggedIn() helper function. If the user is not logged in a 401 Unauthorized response is returned by calling the new unauthorized() helper function.
  • filters the users array to remove the user with the id from the request url. The id from the url is returned from the new idFromUrl() helper function.
  • saves the updated users array to browser local storage.
  • returns an ok() response.

A few new helper functions have been added that are used by the new routes: unauthorized(), isLoggedIn() and idFromUrl().

The unauthorized() function returns a 401 Unauthorized response.

The isLoggedIn() function checks if the HTTP Authorization header contains the correct token (fake-jwt-token).

The idFromUrl() function splits the url into parts with url.split('/') and converts the last part (the user id) from a string to an integer with parseInt(urlParts[urlParts.length - 1]).

Update Fake Backend

This is how the fake-backend.ts file should look after the changes, the new lines are 27-30, 66-77 and 89-100.

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, mergeMap, materialize, dematerialize } from 'rxjs/operators';

// array in local storage for registered users
let users = JSON.parse(localStorage.getItem('users')) || [];

@Injectable()
export class FakeBackendInterceptor implements HttpInterceptor {
    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        const { url, method, headers, body } = request;

        // wrap in delayed observable to simulate server api call
        return of(null)
            .pipe(mergeMap(handleRoute))
            .pipe(materialize()) // call materialize and dematerialize to ensure delay even if an error is thrown (https://github.com/Reactive-Extensions/RxJS/issues/648)
            .pipe(delay(500))
            .pipe(dematerialize());

        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 === '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({
                id: user.id,
                username: user.username,
                firstName: user.firstName,
                lastName: user.lastName,
                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('users', JSON.stringify(users));

            return ok();
        }

        function getUsers() {
            if (!isLoggedIn()) return unauthorized();
            return ok(users);
        }

        function deleteUser() {
            if (!isLoggedIn()) return unauthorized();

            users = users.filter(x => x.id !== idFromUrl());
            localStorage.setItem('users', JSON.stringify(users));
            return ok();
        }

        // helper functions

        function ok(body?) {
            return of(new HttpResponse({ status: 200, body }))
        }

        function error(message) {
            return throwError({ error: { message } });
        }

        function unauthorized() {
            return throwError({ status: 401, error: { message: 'Unauthorised' } });
        }

        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
};


Add Logic to Home Component

The home component contains logic for displaying the current user, a list of all users and enables the deletion of users.

The constructor() method:

  • specifies the dependencies that are required by the component as parameters, these are automatically injected by the Angular Dependency Injection (DI) system when the component is created.
  • assigns the currentUser property with the value authenticationService.currentUserValue so the current user can be displayed in the home component template.

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.
  • calls the this.loadAllUsers() method to load users so they can be displayed in the home component template.

The deleteUser() method:

  • calls the userService.delete() method with the user id to delete. The user service returns an Observable that we .subscribe() to for the results of the deletion. On success the users list is refreshed by calling this.loadAllUsers().
    The call to .pipe(first()) unsubscribes from the observable immediately after the first value is emitted.

The loadAllUsers() method:

  • calls the userService.getAll() method and assigns the result to the this.users property so the users can be displayed in the home component template.

Update Home Component

Open the home.component.ts file and add the following TypeScript code to it:

import { Component, OnInit } from '@angular/core';
import { first } from 'rxjs/operators';

import { UserService, AuthenticationService } from '../_services';

@Component({ templateUrl: 'home.component.html' })
export class HomeComponent implements OnInit {
    currentUser: any;
    users = [];

    constructor(
        private authenticationService: AuthenticationService,
        private userService: UserService
    ) {
        this.currentUser = this.authenticationService.currentUserValue;
    }

    ngOnInit() {
        this.loadAllUsers();
    }

    deleteUser(id: number) {
        this.userService.delete(id)
            .pipe(first())
            .subscribe(() => this.loadAllUsers());
    }

    private loadAllUsers() {
        this.userService.getAll()
            .pipe(first())
            .subscribe(users => this.users = users);
    }
}


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, a list of all users, and enables users to be deleted.

The {{currentUser.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.

The *ngFor="let user of users" directive renders an <li> for each user containing the user details and a link enabling the user to be deleted. The (click)="deleteUser(user.id)" event binding attribute binds the click event to the deleteUser() method in the home component.

Update Home Component Template

Open the home.component.html file and add the following HTML code to it:

<h1>Hi {{currentUser.firstName}}!</h1>
<p>You're logged in with Angular 7!!</p>
<h3>All registered users:</h3>
<ul>
    <li *ngFor="let user of users">
        {{user.username}} ({{user.firstName}} {{user.lastName}})
        - <a (click)="deleteUser(user.id)" class="text-danger">Delete</a>
    </li>
</ul>


Create Alert Service

The alert service provides a centralized / global way to display alert notifications from anywhere in the Angular 7 application.

The constructor() method clears the alert message on route change, unless the keepAfterRouteChange property is true, in which case the keepAfterRouteChange 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 getAlert() 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 getAlert() 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 (keepAfterRouteChange) 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 message to all subscribers by calling subject.next(). It wraps the message text in an object so it can include the type: 'success' property 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 message type to 'error' to tell the alert component to display it as a red error message.

The clear() method publishes an empty (undefined) value to all subscribers by calling subject.next() without parameters, which 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 keepAfterRouteChange = false;

    constructor(private router: Router) {
        // clear alert messages on route change unless 'keepAfterRouteChange' flag is true
        this.router.events.subscribe(event => {
            if (event instanceof NavigationStart) {
                if (this.keepAfterRouteChange) {
                    // only keep for a single route change
                    this.keepAfterRouteChange = false;
                } else {
                    // clear alert message
                    this.clear();
                }
            }
        });
    }

    getAlert(): Observable<any> {
        return this.subject.asObservable();
    }

    success(message: string, keepAfterRouteChange = false) {
        this.keepAfterRouteChange = keepAfterRouteChange;
        this.subject.next({ type: 'success', text: message });
    }

    error(message: string, keepAfterRouteChange = false) {
        this.keepAfterRouteChange = keepAfterRouteChange;
        this.subject.next({ type: 'error', text: message });
    }

    clear() {
        // clear by calling subject.next() without parameters
        this.subject.next();
    }
}

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 user 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 './authentication.service';
export * from './user.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 alert messages from the alert service by calling alertService.getAlert().subscribe().
  • checks the message.type to determine which cssClass to set on the message. The css classes are provided by Bootstrap 4 and display success messages as green alerts and error messages as red alerts.
  • assigns the message to the this.message 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, login, register), 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 '../_services';

@Component({ selector: 'alert', templateUrl: 'alert.component.html' })
export class AlertComponent implements OnInit, OnDestroy {
    private subscription: Subscription;
    message: any;

    constructor(private alertService: AlertService) { }

    ngOnInit() {
        this.subscription = this.alertService.getAlert()
            .subscribe(message => {
                switch (message && message.type) {
                    case 'success':
                        message.cssClass = 'alert alert-success';
                        break;
                    case 'error':
                        message.cssClass = 'alert alert-danger';
                        break;
                }

                this.message = message;
            });
    }

    ngOnDestroy() {
        this.subscription.unsubscribe();
    }
}

Create Alert Component Template

The alert component template displays an alert message when there is one available.

The *ngIf="message" directive renders the alert message if the message is not null / undefined.

The [ngClass]="message.cssClass" attribute directive binds the element class attribute to the cssClass property of the message set in the alert component above.

The {{message.text}} 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/template-syntax#interpolation-and-template-expressions.

Create a file named alert.component.html in the _components folder and add the following HTML code to it:

<div *ngIf="message" [ngClass]="message.cssClass">{{message.text}}</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 15 and 29.

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';
import { JwtInterceptor, ErrorInterceptor } from './_helpers';
import { AppComponent } from './app.component';
import { HomeComponent } from './home';
import { LoginComponent } from './login';
import { RegisterComponent } from './register';
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 7 app.

This is how the app.component.html file should look after the changes, the new line is 14.

<!-- nav -->
<nav class="navbar navbar-expand navbar-dark bg-dark" *ngIf="currentUser">
    <div class="navbar-nav">
        <a class="nav-item nav-link" routerLink="/">Home</a>
        <a class="nav-item nav-link" (click)="logout()">Logout</a>
    </div>
</nav>

<!-- main content container -->
<div class="jumbotron">
    <div class="container">
        <div class="row">
            <div class="col-sm-8 offset-sm-2">
                <alert></alert>
                <router-outlet></router-outlet>
            </div>
        </div>
    </div>
</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 { AuthenticationService, AlertService } from '../_services'

@Component({templateUrl: 'login.component.html'})
export class LoginComponent implements OnInit {
    loginForm: FormGroup;
    loading = false;
    submitted = false;
    returnUrl: string;

    constructor(
        private formBuilder: FormBuilder,
        private route: ActivatedRoute,
        private router: Router,
        private authenticationService: AuthenticationService,
        private alertService: AlertService
    ) {
        // redirect to home if already logged in
        if (this.authenticationService.currentUserValue) { 
            this.router.navigate(['/']);
        }
    }

    ngOnInit() {
        this.loginForm = this.formBuilder.group({
            username: ['', Validators.required],
            password: ['', Validators.required]
        });

        // get return url from route parameters or default to '/'
        this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';
    }

    // convenience getter for easy access to form fields
    get f() { return this.loginForm.controls; }

    onSubmit() {
        this.submitted = true;

        // reset alerts on submit
        this.alertService.clear();
        
        // stop here if form is invalid
        if (this.loginForm.invalid) {
            return;
        }

        this.loading = true;
        this.authenticationService.login(this.f.username.value, this.f.password.value)
            .pipe(first())
            .subscribe(
                data => {
                    this.router.navigate([this.returnUrl]);
                },
                error => {
                    this.alertService.error(error);
                    this.loading = false;
                });
    }
}

Remove Alert HTML from Login Component Template

Open the login.component.html file and remove the top 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:

<h2>Login</h2>
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
    <div class="form-group">
        <label for="username">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="form-group">
        <label for="password">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 class="form-group">
        <button [disabled]="loading" class="btn btn-primary">
            <span *ngIf="loading" class="spinner-border spinner-border-sm mr-1"></span>
            Login
        </button>
        <a routerLink="/register" class="btn btn-link">Register</a>
    </div>
</form>


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.
  • add the statement below the comment // reset alerts on submit in the onSubmit() method to clear any alert messages that are displayed when the form is submitted (see lines 42-43).
  • 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 keep the message after a single route change so it will be displayed after redirecting the to 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 } from '@angular/router';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { first } from 'rxjs/operators';

import { UserService, AuthenticationService, AlertService } from '../_services';

@Component({ templateUrl: 'register.component.html' })
export class RegisterComponent implements OnInit {
    registerForm: FormGroup;
    loading = false;
    submitted = false;

    constructor(
        private formBuilder: FormBuilder,
        private router: Router,
        private authenticationService: AuthenticationService,
        private userService: UserService,
        private alertService: AlertService
    ) {
        // redirect to home if already logged in
        if (this.authenticationService.currentUserValue) {
            this.router.navigate(['/']);
        }
    }

    ngOnInit() {
        this.registerForm = 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.registerForm.controls; }

    onSubmit() {
        this.submitted = true;

        // reset alerts on submit
        this.alertService.clear();

        // stop here if form is invalid
        if (this.registerForm.invalid) {
            return;
        }

        this.loading = true;
        this.userService.register(this.registerForm.value)
            .pipe(first())
            .subscribe(
                data => {
                    this.alertService.success('Registration successful', true);
                    this.router.navigate(['/login'], { queryParams: { registered: true }});
                },
                error => {
                    this.alertService.error(error);
                    this.loading = false;
                });
    }
}

Remove Alert HTML from Register Component Template

Open the register.component.html file and remove the top 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:

<h2>Register</h2>
<form [formGroup]="registerForm" (ngSubmit)="onSubmit()">
    <div class="form-group">
        <label for="firstName">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="form-group">
        <label for="lastName">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="form-group">
        <label for="username">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="form-group">
        <label for="password">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 class="form-group">
        <button [disabled]="loading" class="btn btn-primary">
            <span *ngIf="loading" class="spinner-border spinner-border-sm mr-1"></span>
            Register
        </button>
        <a routerLink="/login" class="btn btn-link">Cancel</a>
    </div>
</form>


Start Angular 7 Application!

Run the command npm start from the project root folder (where the package.json is located) to launch the Angular 7 application.

 

Subscribe or Follow Me For Updates

Subscribe to my YouTube channel or follow me on Twitter or GitHub to be notified when I post new content.

 


Supported by