Angular 7 Tutorial Part 6 - Home Page & Alert Component
Other parts available in Angular 7 tutorial series:
- Part 1 (Windows) - Setup Development Machine on Windows
- Part 1 (Mac) - Setup Development Machine on Mac
- Part 2 - Create Base Project Structure & Webpack Config
- Part 3 - Add Routing & Multiple Pages
- Part 4 - Login Form, Authentication Service & Route Guard
- Part 5 - Registration Form & User Service
- Part 7 (Optional) - Migrating to an Angular CLI Project
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:
- Add "Get Users" and "Delete User" Routes to Fake Backend
- Add Logic to Home Component
- Add HTML to Home Component Template
- Create Alert Service
- Create Alert Component
- Refactor Login Component to use Alert Service
- Refactor Register Component to use Alert Service
- 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 a401 Unauthorized
response is returned by calling the newunauthorized()
helper function. - returns an
ok()
response with the wholeusers
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 a401 Unauthorized
response is returned by calling the newunauthorized()
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 newidFromUrl()
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 valueauthenticationService.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 anObservable
that we.subscribe()
to for the results of the deletion. On success the users list is refreshed by callingthis.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 thethis.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 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 message to all subscribers by calling
subject.next()
. It wraps the message text in an object so it can include thetype: '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 whichcssClass
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 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 { 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 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. - add the statement below the comment
// reset alerts on submit
in theonSubmit()
method to clear any alert messages that are displayed when the form is submitted (see lines42-43
). - 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 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;
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 } 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.
Need Some Angular 7 Help?
Search fiverr for freelance Angular 7 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!