0% found this document useful (0 votes)
11 views31 pages

Awad Unit V

Uploaded by

jaisudhan1408
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
11 views31 pages

Awad Unit V

Uploaded by

jaisudhan1408
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 31

PCA20D01J –ADVANCED WEB APPLICATION DEVELOPMENT UNIT V

BUIDING A SINGLE PAGE APPLICATION WITH ANGULAR : FOUNDATIONS


SETTING THE GROUNDWORK FOR AN ANGULAR SPA
A Single Page Application is a web application that loads a single HTML page and
dynamically updates the content as the user interacts with the application. Instead of
requesting a new page from the server each time the user interacts with the application, a
SPA loads all necessary resources and content up front and uses client-side rendering to
update the page dynamically.
SPAs are important because they provide a faster and more responsive user
experience compared to traditional web applications. By loading all necessary resources up
front, SPAs reduce the amount of time it takes for pages to load and content to appear on
the screen. SPAs also allow for more seamless interactions between the user and the
application, resulting in a more natural and intuitive user experience. The design pattern
used in SPAs is based on client-side rendering, where the majority of the application logic
and rendering occurs in the user’s browser instead of on the server. This allows for faster
and more efficient processing of user interactions, resulting in a smoother user experience.
Working of SPAs: The working of a SPA involves the following steps:
• The initial HTML, CSS, and JavaScript files are loaded into the browser when the user
first accesses the application.
• As the user interacts with the application, the browser sends requests to the server for
data, which is returned as JSON or XML.
• The application uses JavaScript to parse the data and dynamically update the page
without requiring a full page reload.
• The application uses client-side routing to manage the display of different views and
components within the application.
Some of the Main Components used in a SPA:
• Components: Angular components are reusable building blocks that can be combined
to create complex user interfaces. Each component is responsible for rendering a view,
handling user events, and communicating with services to obtain data.
• Services: Angular services provide a way to encapsulate common functionality that can
be reused throughout an application. Services can be used to retrieve data from a server,
handle authentication, or provide utility functions.
• Directives: Angular directives allow developers to extend the HTML syntax to create
reusable components that can be used throughout an application. Directives can be used
to add behavior to existing elements, such as creating a tooltip or adding a custom
attribute to form input.
• Dependency Injection: Angular’s dependency injection system provides a way to
manage the creation and lifecycle of objects in an application. This allows developers to
create modular, testable code by separating concerns and creating reusable services that
can be injected into different components.
Example 1: The below example illustrates the approach to implement & the working of the
Single Page Applications in Angular.
• Install Node.js on your computer if it is not already installed. You can download it from
the Node.js website.
• Open a command prompt or terminal window and run the following command to install
the Angular CLI:
npm install -g @angular/cli
Once the Angular CLI is installed, create a new Angular project by running the following
command:
ng new spa
This will create a new Angular project in a directory named “spa”.

Dr. R.PRIYA Page 1


PCA20D01J –ADVANCED WEB APPLICATION DEVELOPMENT UNIT V

Change into the project directory by running the following command:


cd spa
Create a new component by running the following command:
ng g c home
This will create a new component named “home” in the “src/app” directory.
Edit the “src/app/app.component.html” file to add some content. For example, you can add
the following:
HTML

<h1 style="color: green;">


Welcome to my GFG APP!
</h1>
<h3>
This explains how Single Page
Application works in Angular.
</h3>
<router-outlet></router-outlet>

Edit the “src/app/app-routing.module.ts” file to define a route for the home component. For
example, you can add the following to the routes array:
Javascript

import { NgModule } from '@angular/core';


import { Routes, RouterModule }
from '@angular/router';
import { HomeComponent }
from './home/home.component';

const routes: Routes = [


{ path: '', component: HomeComponent }
];

@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

Edit the “src/app/home/home.component.html” file to add some content. For example, you
can add the following:
HTML

<h4>This is the home page.</h4>

The app.module.ts file will contain the following code:


Javascript

import { NgModule } from '@angular/core';


import { BrowserModule }
from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';

Dr. R.PRIYA Page 2


PCA20D01J –ADVANCED WEB APPLICATION DEVELOPMENT UNIT V

import { AppRoutingModule }
from './app-routing.module';
import { AppComponent }
from './app.component';
import { HomeComponent }
from './home/home.component';

@NgModule({
declarations: [
AppComponent,
HomeComponent
],
imports: [
BrowserModule,
AppRoutingModule,
FormsModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

Start the Angular development server by running the following command:


ng serve -o
This will start the development server and launch the application in your default web
browser.
Open your web browser and navigate to “http://localhost:4200”. You should see the home
page that you created earlier.
Output:

Dr. R.PRIYA Page 3


PCA20D01J –ADVANCED WEB APPLICATION DEVELOPMENT UNIT V

SWITCHING FROM EXPRESS ROUTING TO ANGULAR ROUTING

Routing in Angular allows the users to create a single-page application with multiple views
and allows navigation between them. Users can switch between these views without losing
the application state and properties.
Approach:
Create an Angular app that to be used.
Create the navigation links inside the app component and then provide
the “routerLink” directive to each route and pass the route value to “routerLink” directive.
Then add the routes to the routing.module.ts file and then import the routing.module.ts into
the app.module.ts file.
Syntax:
HTML:
<li><a routerLink="/about" >About Us</a></li>
<router-outlet></router-outlet>
TS:
{ path: 'about', component: AboutComponent }
Example: We are going to create a simple angular application that uses angular routing. So
first, we create an Angular app by running the below command in CLI.
ng new learn-routing
Then we are creating simple navigation that allows us to navigate between the different
components, and we have created some components as well, so users can switch between
these components using routing.
app.component.html

<span>
<ul>
<li><a routerLink="/" >Home</a></li>
<li><a routerLink="/products" >Products</a></li>
<li><a routerLink="/about" >About Us</a></li>
<li><a routerLink="/contact" >Contact Us</a></li>
</ul>
</span>
<router-outlet></router-outlet>

Here the router-outlet is routing functionality that is used by the router to mark wherein a
template, a matched component should be inserted.
Then inside the app-routing.module.ts file, we have provided these routes and let the
angular know about these routes.
app-routing.module.ts

import { NgModule } from '@angular/core';


import { Routes, RouterModule } from '@angular/router';
import { HomeComponent } from './home.component'
import { ProductComponent } from './product.component'
import { AboutComponent } from './about.component'
import { ContactComponent } from './contact.component'

const routes: Routes = [

Dr. R.PRIYA Page 4


PCA20D01J –ADVANCED WEB APPLICATION DEVELOPMENT UNIT V

{ path: '', component: HomeComponent },


{ path: 'products', component: ProductComponent },
{ path: 'about', component: AboutComponent },
{ path: 'contact', component: ContactComponent, },
];

@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
providers: []
})
export class AppRoutingModule { }

And then simply import “AppRouting” module inside the app/module.ts file inside
the @NgModule imports.
app.module.ts

import { NgModule } from '@angular/core';


import { HomeComponent } from './home.component'
import { ProductComponent } from './product.component'
import { AboutComponent } from './about.component'
import { ContactComponent } from './contact.component'
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

@NgModule({
declarations: [
AppComponent,
HomeComponent,
ProductComponent,
AboutComponent,
ContactComponent
],
imports: [
AppRoutingModule,
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

So now run this using “ng serve” in CLI and open localhost://4200 in the browser here you
see your navigation bar, and you can navigate from one component to another without page
reload.
Output:

Dr. R.PRIYA Page 5


PCA20D01J –ADVANCED WEB APPLICATION DEVELOPMENT UNIT V

ADDING THE FIRST VIEWS, CONTROLLERS AND SERVICES

The Model
The model contains the business end of your application. For simple CRUD (Create Read
Update Delete) applications, the model is usually a simple data model. For more complex
applications, the model will naturally reflect that increase in complexity. In the application
you see here, the model will hold a simple array of text notes. Each note has an ID, a title,
and a text. In Angular, the model is coded up in so-called services. The ng command lets
you create a new service.

ng generate service Notes

This will create two new files, src/app/notes.service.ts and src/app/notes.service.spec.ts.


You can ignore the second of these files in this tutorial, just as the other .spec.ts files. These
files are used for unit testing the code. In an application that you want to release for
production, you would write your tests there. Open src/app/notes.service.ts and replace its
contents with the following code.
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observer } from 'rxjs';

export class NoteInfo {


id: number;
title: string;
}

export class Note {


id: number;
title: string;
text: string;
}

@Injectable({
providedIn: 'root'
})

Dr. R.PRIYA Page 6


PCA20D01J –ADVANCED WEB APPLICATION DEVELOPMENT UNIT V

export class NotesService {


private notes: Note[];
private nextId = 0;
private notesSubject = new BehaviorSubject<NoteInfo[]>([]);

constructor() {
this.notes = JSON.parse(localStorage.getItem('notes')) || [];
for (const note of this.notes) {
if (note.id >= this.nextId) this.nextId = note.id+1;
}
this.update();
}

subscribe(observer: Observer<NoteInfo[]>) {
this.notesSubject.subscribe(observer);
}

addNote(title: string, text: string): Note {


const note = {id: this.nextId++, title, text};
this.notes.push(note);
this.update();
return note;
}

getNote(id: number): Note {


const index = this.findIndex(id);
return this.notes[index];
}

updateNote(id: number, title: string, text: string) {


const index = this.findIndex(id);
this.notes[index] = {id, title, text};
this.update();
}

deleteNote(id: number) {
const index = this.findIndex(id);
this.notes.splice(index, 1);
this.update();
}

private update() {
localStorage.setItem('notes', JSON.stringify(this.notes));
this.notesSubject.next(this.notes.map(
note => ({id: note.id, title: note.title})
));
}

private findIndex(id: number): number {


for (let i=0; i<this.notes.length; i++) {

Dr. R.PRIYA Page 7


PCA20D01J –ADVANCED WEB APPLICATION DEVELOPMENT UNIT V

if (this.notes[i].id === id) return i;


}
throw new Error(`Note with id ${id} was not found!`);
}
}
Near the top of the file you can see two class definitions, NoteInfo and Note.
The Note class contains the full information on a note, while NoteInfo only contains
the id and the title. The idea is that NoteInfo is much lighter and can be used in a list,
displaying all note titles. Both Note and NoteInfo are simple data classes, containing no
business logic. The logic is contained in NotesService, which acts as the Model of the
application. It contains a number of properties. The notes property is an array
of Notes objects. This array acts as the source of truth for the model. The
functions addNote, getNote, updateNote, and deleteNote define the CRUD operations on
the model. They all directly act on the notes array, creating, reading, updating, and deleting
elements in the array. The nextId property is used as a unique ID by which a note can be
referenced.
You will notice that, whenever the notes array is modified, the private update method is
called. This method does two things. First, it saves the notes in the local storage. As long as
the browser’s local storage has not been deleted, this will persist the data locally. This
allows users to close the application and open it later on and still have access to their notes.
In a real-world application, the CRUD operations would access a REST API on a different
server, instead of saving the data locally.
The second action performed by update is to emit a new value on
the notesSubject property. notesSubject is a BehaviorSubject from RxJS which contains an
array of the condensed NoteInfo objects. The BehaviorSubject act as an observable to
which any observer can subscribe. This subscription is made possible through
the subscribe method of NotesService. Any observer that has subscribed will be notified
whenever update is called.
The main thing to take away from the implementation of the Model is, that the Model is a
standalone service that has no knowledge of any View or Controller. This is important in
both, the MVC and the MVVM architecture. The Model must not have any dependency on
the other components.
The View
Next, I’d like to turn your attention to the View. In Angular applications, the View lives
inside the .html templates and the .css style sheets. I have already mentioned one of these
templates in the file src/app/app.component.html. Open the file and paste the following
content into it.
<mat-toolbar color="primary" class="expanded-toolbar">
<span>
<button mat-button routerLink="/">{{title}}</button>
<button mat-button routerLink="/"><mat-icon>home</mat-icon></button>
</span>
<button mat-button routerLink="/notes"><mat-icon>note</mat-icon></button>
</mat-toolbar>
<router-outlet></router-outlet>
Why not add a bit of styling too? Open src/app/app.component.css and add the following
style.
.expanded-toolbar {
justify-content: space-between;
align-items: center;

Dr. R.PRIYA Page 8


PCA20D01J –ADVANCED WEB APPLICATION DEVELOPMENT UNIT V

The app.component contains the main page layout, but not any meaningful content. You
will have to add some components that will render any content. Use the ng
generate command again like this.

ng generate component Home


ng generate component Notes

This generates two components. Each component is made up of a .html, .css, and a .ts file.
For now, don’t worry about the .ts file. I’ll get to that in the next section. (Remember, there
is also a .spec.ts file that I am ignoring completely in this tutorial.)
Open src/app/home/home.component.html and change the content to the following.
<h1>Angular Notes</h1>
<h2>A simple app showcasing the MVVM pattern.</h2>

Next, open src/app/notes/notes.component.html and replace the content with the code
below.
<div fxLayout="row" fxLayout.xs="column" fxLayoutAlign="center" class="notes">
<mat-list fxFlex="100%" fxFlex.gt-sm="20%">
<mat-list-item *ngFor='let note of notes'>
<a>
{{note.title}}
</a>
</mat-list-item>
</mat-list>
<mat-divider fxShow="false" fxShow.gt-sm [vertical]="true"></mat-divider>
<mat-divider fxShow="true" fxShow.gt-sm="false" [vertical]="false"></mat-divider>
<div fxFlex="100%" fxFlex.gt-sm="70%" *ngIf="!editNote" class="note-container">
<h3>{{currentNote.title}}</h3>
<p>
{{currentNote.text}}
</p>
<div fxLayout="row" fxLayoutAlign="space-between center" >
<button mat-raised-button color="primary">Edit</button>
<button mat-raised-button color="warn">Delete</button>
<button mat-raised-button color="primary">New Note</button>
</div>
</div>
<div fxFlex="100%" fxFlex.gt-sm="70%" *ngIf="editNote" class="form-container">
<form [formGroup]="editNoteForm">
<mat-form-field class="full-width">
<input matInput placeholder="Title" formControlName="title">
</mat-form-field>

<mat-form-field class="full-width">
<textarea matInput placeholder="Note text" formControlName="text"></textarea>
</mat-form-field>
<button mat-raised-button color="primary">Update</button>
</form>

Dr. R.PRIYA Page 9


PCA20D01J –ADVANCED WEB APPLICATION DEVELOPMENT UNIT V

</div>
</div>

The accompanying src/app/notes/notes.component.css should look like this.


.notes {
padding: 1rem;
}

.notes a {
cursor: pointer;
}

.form-container, .note-container {
padding-left: 2rem;
padding-right: 2rem;
}

.full-width {
width: 80%;
display: block;
}

So far, so good!
Have a look at src/app/notes/notes.component.html which represents the main View of the
application. You will notice placeholders such as {{note.title}} which look like they can be
filled with values. In the version shown above, the View does not seem to refer to any piece
of code in the application.
If you were to follow the MVC pattern, the View would define slots into which the data
could be inserted. It would also provide methods for registering a callback whenever a
button is clicked. In this respect, the View would remain completely ignorant of the
Controller.
The Controller would actively fill the values and register callback methods with the View.
Only the Controller would know about both the View and the Model and link the two
together.
As you will see below, Angular takes a different approach, called the MVVM pattern. Here
the Controller is replaced by a ViewModel. This will be the topic of the next section.
The ViewModel
The ViewModel lives in the .ts files of the components.
Open src/app/notes/notes.component.ts and fill it with the code below.
import { Component, OnInit } from '@angular/core';
import { Note, NoteInfo, NotesService } from '../notes.service';
import { BehaviorSubject } from 'rxjs';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';

@Component({
selector: 'app-notes',
templateUrl: './notes.component.html',
styleUrls: ['./notes.component.css']
})
export class NotesComponent implements OnInit {

Dr. R.PRIYA Page 10


PCA20D01J –ADVANCED WEB APPLICATION DEVELOPMENT UNIT V

notes = new BehaviorSubject<NoteInfo[]>([]);


currentNote: Note = {id:-1, title: '', text:''};
createNote = false;
editNote = false;
editNoteForm: FormGroup;

constructor(private formBuilder: FormBuilder,


private notesModel: NotesService) { }

ngOnInit() {
this.notesModel.subscribe(this.notes);
this.editNoteForm = this.formBuilder.group({
title: ['', Validators.required],
text: ['', Validators.required]
});
}

onSelectNote(id: number) {
this.currentNote = this.notesModel.getNote(id);
}

noteSelected(): boolean {
return this.currentNote.id >= 0;
}

onNewNote() {
this.editNoteForm.reset();
this.createNote = true;
this.editNote = true;
}

onEditNote() {
if (this.currentNote.id < 0) return;
this.editNoteForm.get('title').setValue(this.currentNote.title);
this.editNoteForm.get('text').setValue(this.currentNote.text);
this.createNote = false;
this.editNote = true;
}

onDeleteNote() {
if (this.currentNote.id < 0) return;
this.notesModel.deleteNote(this.currentNote.id);
this.currentNote = {id:-1, title: '', text:''};
this.editNote = false;
}

updateNote() {
if (!this.editNoteForm.valid) return;
const title = this.editNoteForm.get('title').value;
const text = this.editNoteForm.get('text').value;

Dr. R.PRIYA Page 11


PCA20D01J –ADVANCED WEB APPLICATION DEVELOPMENT UNIT V

if (this.createNote) {
this.currentNote = this.notesModel.addNote(title, text);
} else {
const id = this.currentNote.id;
this.notesModel.updateNote(id, title, text);
this.currentNote = {id, title, text};
}
this.editNote = false;
}
}

In the @Component decorator of the class, you can see the reference to the
View .html and .css files. In the rest of the class, on the other hand, there is no reference to
the View whatsoever. Instead, the ViewModel, contained in the NotesComponent class,
exposes properties and methods that can be accessed by the View. This means that,
compared to the MVC architecture, the dependency is reversed. The ViewModel has no
knowledge of the View but provides a Model-like API that can be used by the View. If you
take another look at src/app/notes/notes.component.html you can see that the template
interpolation, such as {{currentNote.text}} directly accesses the properties of
the NotesComponent.
The last step to make your application work is to tell the router which components are
responsible for the different routes. Open src/app/app-routing.module.ts and edit the
content to match the code below.
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { NotesComponent } from './notes/notes.component';

const routes: Routes = [


{ path: '', component: HomeComponent },
{ path: 'notes', component: NotesComponent },
];

@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

This will link the HomeComponent to the default route and the NotesComponent to
the notes route.
For the main application component, I will define a few methods which will be
implemented later on. Open src/app/app.component.ts and update the content to look like
the following.
import { Component } from '@angular/core';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']

Dr. R.PRIYA Page 12


PCA20D01J –ADVANCED WEB APPLICATION DEVELOPMENT UNIT V

})
export class AppComponent {
public title = 'Angular Notes';
public isAuthenticated: boolean;

ngOnInit() {
this.isAuthenticated = false;
}

login() {
}

logout() {
}
}

The component contains two properties title and isAuthenticated. The second one of these
is a flag that indicates whether the user has logged into the application. Right now, it is
simply set to false. Two empty methods act as callbacks to trigger logging in or logging
out. For now, I have left them empty, but you will be filling them in later on.
Complete the View
With this knowledge about the direction of dependency, you can update the View so that
the buttons and forms perform actions on the ViewModel.
Open src/app/notes/notes.component.html again and change the code to look like this.
<div fxLayout="row" fxLayout.xs="column" fxLayoutAlign="center" class="notes">
<mat-list fxFlex="100%" fxFlex.gt-sm="20%">
<mat-list-item *ngFor='let note of notes | async'>
<a (click)="onSelectNote(note.id)">
{{note.title}}
</a>
</mat-list-item>
</mat-list>
<mat-divider fxShow="false" fxShow.gt-sm [vertical]="true"></mat-divider>
<mat-divider fxShow="true" fxShow.gt-sm="false" [vertical]="false"></mat-divider>
<div fxFlex="100%" fxFlex.gt-sm="70%" *ngIf="!editNote" class="note-container">
<h3>{{currentNote.title}}</h3>
<p>
{{currentNote.text}}
</p>
<div fxLayout="row" fxLayoutAlign="space-between center" >
<button mat-raised-button color="primary" (click)="onEditNote()"
*ngIf="noteSelected()">Edit</button>
<button mat-raised-button color="warn" (click)="onDeleteNote()"
*ngIf="noteSelected()">Delete</button>
<button mat-raised-button color="primary" (click)="onNewNote()">New
Note</button>
</div>
</div>
<div fxFlex="100%" fxFlex.gt-sm="70%" *ngIf="editNote" class="form-container">
<form [formGroup]="editNoteForm" (ngSubmit)="updateNote()">

Dr. R.PRIYA Page 13


PCA20D01J –ADVANCED WEB APPLICATION DEVELOPMENT UNIT V

<mat-form-field class="full-width">
<input matInput placeholder="Title" formControlName="title">
</mat-form-field>

<mat-form-field class="full-width">
<textarea matInput placeholder="Note text" formControlName="text"></textarea>
</mat-form-field>
<button mat-raised-button color="primary">Update</button>
</form>
</div>
</div>

INTRODUCTION TO SERVICES AND DEPENDENCY INJECTION


Service is a broad category encompassing any value, function, or feature that an application
needs. A service is typically a class with a narrow, well-defined purpose. It should do
something specific and do it well.
Angular distinguishes components from services to increase modularity and reusability.
Ideally, a component's job is to enable only the user experience. A component should
present properties and methods for data binding to mediate between the view and the
application logic. The view is what the template renders and the application logic is what
includes the notion of a model.
A component should use services for tasks that don't involve the view or application logic.
Services are good for tasks such as fetching data from the server, validating user input, or
logging directly to the console. By defining such processing tasks in an injectable service
class, you make those tasks available to any component. You can also make your
application more adaptable by injecting different providers of the same kind of service, as
appropriate in different circumstances.
Angular doesn't enforce these principles. Instead, Angular helps you follow these principles
by making it easy to factor your application logic into services. In Angular, dependency
injection makes those services available to components.
Service examples
Here's an example of a service class that logs to the browser console.
src/app/logger.service.ts (class)
content_copyexport class Logger {
log(msg: any) { console.log(msg); }
error(msg: any) { console.error(msg); }
warn(msg: any) { console.warn(msg); }
}
Services can depend on other services. For example, here's a HeroService that depends on
the Logger service, and also uses BackendService to get heroes. That service in turn might
depend on the HttpClient service to fetch heroes asynchronously from a server.
src/app/hero.service.ts (class)
content_copyexport class HeroService {
private heroes: Hero[] = [];

constructor(
private backend: BackendService,
private logger: Logger) { }

getHeroes() {

Dr. R.PRIYA Page 14


PCA20D01J –ADVANCED WEB APPLICATION DEVELOPMENT UNIT V

this.backend.getAll(Hero).then( (heroes: Hero[]) => {


this.logger.log(`Fetched ${heroes.length} heroes.`);
this.heroes.push(...heroes); // fill cache
});
return this.heroes;
}
}
Dependency injection (DI)

Dependency injection (DI) is the part of the Angular framework that provides components
with access to services and other resources. Angular provides the ability for you to inject a
service into a component to give that component access to the service.
Add the @Injectable() decorator to a service class so that Angular can inject it into a
component as a dependency; the optional argument tells Angular where to register this class
by default.
content_copy@Injectable({providedIn: 'root'})
export class HeroService {
• Something injectable must be registered with an injector before it can be created
and used.
• Register an injectable with a provider, an object that tells an injector how to obtain
or create a dependency. For a service class, the provider is typically the class itself.
• You don't have to create injectors. Under the hood Angular creates an application-
wide root injector for you during the bootstrap process. It creates additional child
injectors as needed.
An injectable dependency doesn't have to be a class — it could be a function, for
example, or a value.
When Angular creates a new instance of a component class, it determines which services or
other dependencies that component needs by looking at the constructor parameter types.
For example, the constructor of HeroListComponent needs HeroService.
src/app/hero-list.component.ts (constructor)
content_copyconstructor(private service: HeroService) { }
When Angular discovers that a component depends on a service, it first checks if the
injector has any existing instances of that service. If a requested service instance doesn't yet
exist, the injector makes one using the registered provider and adds it to the injector before
returning the service to Angular.
When all requested services have been resolved and returned, Angular can call the
component's constructor with those services as arguments.
The process of HeroService injection looks something like this.

Dr. R.PRIYA Page 15


PCA20D01J –ADVANCED WEB APPLICATION DEVELOPMENT UNIT V

Providing services
You must register at least one provider of any service you are going to use. The provider
can be part of the service's own metadata, making that service available everywhere, or you
can register providers with specific components. You register providers in the metadata of
the service (in the @Injectable() decorator) or @Component() metadata
• By default, the Angular CLI command ng generate service registers a provider with
the root injector for your service by including provider metadata in
the @Injectable() decorator. The tutorial uses this method to register the provider
of HeroService class definition.
hero.service.ts (provide in root)
content_copy@Injectable({providedIn: 'root'})
export class HeroService {
When you provide the service at the root level, Angular creates a single, shared
instance of HeroService and injects it into any class that asks for it. Registering the
provider in the @Injectable() metadata also allows Angular to optimize an app by
removing the service from the compiled application if it isn't used, a process known
as tree-shaking.
• When you register a provider at the component level, you get a new instance of the
service with each new instance of that component. At the component level, register
a service provider in the providers property of the @Component() metadata.
src/app/hero-list.component.ts (component providers)
content_copy@Component({
standalone: true,
selector: 'app-hero-list',
templateUrl: './hero-list.component.html',
imports: [ NgFor, NgIf, HeroDetailComponent ],
providers: [ HeroService ]
})
USING UGLIFY JS TO MINIFY AND CONCATENATE SCRIPTS
UglifyJS is a JavaScript parser, minifier, compressor and beautifier toolkit.
Note:
• uglify-js supports JavaScript and most language features in ECMAScript.
• For more exotic parts of ECMAScript, process your source file with transpilers
like Babel before passing onto uglify-js.
• uglify-js@3 has a simplified API and CLI that is not backwards compatible
with uglify-js@2.
Install

Dr. R.PRIYA Page 16


PCA20D01J –ADVANCED WEB APPLICATION DEVELOPMENT UNIT V

First make sure you have installed the latest version of node.js (You may need to restart
your computer after this step).
From NPM for use as a command line app:
npm install uglify-js -g
From NPM for programmatic use:
npm install uglify-js
Minify options
• annotations — pass false to ignore all comment annotations and elide them from
output. Useful when, for instance, external tools incorrectly
applied /*@__PURE__*/ or /*#__PURE__*/. Pass true to both compress and retain
comment annotations in output to allow for further processing downstream.
• compress (default: {}) — pass false to skip compressing entirely. Pass an object to
specify custom compress options.
• expression (default: false) — parse as a single expression, e.g. JSON.
• ie (default: false) — enable workarounds for Internet Explorer bugs.
• keep_fargs (default: false) — pass true to prevent discarding or mangling of
function arguments.
• keep_fnames (default: false) — pass true to prevent discarding or mangling of
function names. Useful for code relying on Function.prototype.name.
• mangle (default: true) — pass false to skip mangling names, or pass an object to
specify mangle options (see below).
o mangle.properties (default: false) — a subcategory of the mangle option. Pass an
object to specify custom mangle property options.
• module (default: true) — process input as ES module, i.e. implicit "use strict"; and
support for top-level await. When explicitly specified, also enables toplevel.
• nameCache (default: null) — pass an empty object {} or a previously
used nameCache object if you wish to cache mangled variable and property names
across multiple invocations of minify(). Note: this is a read/write
property. minify() will read the name cache state of this object and update it during
minification so that it may be reused or externally persisted by the user.
• output (default: null) — pass an object if you wish to specify additional output
options. The defaults are optimized for best compression.
• parse (default: {}) — pass an object if you wish to specify some additional parse
options.
• sourceMap (default: false) — pass an object if you wish to specify source map
options.
• toplevel (default: false) — set to true if you wish to enable top level variable and
function name mangling and to drop unused variables and functions.
• v8 (default: false) — enable workarounds for Chrome & Node.js bugs.
• warnings (default: false) — pass true to return compressor warnings
in result.warnings. Use the value "verbose" for more detailed warnings.
• webkit (default: false) — enable workarounds for Safari/WebKit bugs. PhantomJS
users should set this option to true.
Minify options structure
{
parse: {
// parse options
},
compress: {
// compress options

Dr. R.PRIYA Page 17


PCA20D01J –ADVANCED WEB APPLICATION DEVELOPMENT UNIT V

},
mangle: {
// mangle options

properties: {
// mangle property options
}
},
output: {
// output options
},
sourceMap: {
// source map options
},
nameCache: null, // or specify a name cache object
toplevel: false,
warnings: false,
}
1. Create a Script to Concatenate and Minify: You can create a simple Node.js script to
concatenate and minify your JavaScript files. Here’s an example:
JavaScript
const fs = require('fs');
const UglifyJS = require('uglify-js');

// List of files to concatenate and minify


const files = ['file1.js', 'file2.js', 'file3.js'];

// Read and concatenate the files


let code = '';
files.forEach(file => {
code += fs.readFileSync(file, 'utf8') + '\n';
});

// Minify the concatenated code


const result = UglifyJS.minify(code);

// Write the minified code to a new file


fs.writeFileSync('output.min.js', result.code);

console.log('Files concatenated and minified successfully!');


AI-generated code. Review and use carefully. More info on FAQ.
2. Run the Script: Save the script to a file, for example, concat-minify.js, and run it using
Node.js:
node concat-minify.js

Dr. R.PRIYA Page 18


PCA20D01J –ADVANCED WEB APPLICATION DEVELOPMENT UNIT V

SAFELY BINDING HTML CONTENT


The current status of the About page in the Angular SPA is that it exists only as a default
skeleton page, as you created it to demonstrate navigation and routing in Angular. In this
section, you’ll complete the page.
Adding the About page content to the app
The About page should be fairly straightforward. You add the content to the component
definition and create the simple markup with the bindings to display it. Easy, right? Start by
adding the content to the component definition. In the following listing,
you can see the class definition in about.component.ts. You’re defining a pageContent
member to hold all the text information, as you’ve done before. We’ve trimmed the text in
the main content area to save ink and trees.
export class AboutComponent implements OnInit {
constructor() { }
ngOnInit() {
}
public pageContent = {
header : {
title : 'About Loc8r',
strapline : ''
},
content : 'Loc8r was created to help people find places to sit
➥down and get a bit of work done.\n\nLorem ipsum dolor sit
➥amet, consectetur adipiscing elit.'
};
}
As components go, this one is simple. No magic is going on here. Note, though, that you’ve
still got the \n characters to denote line breaks.
Next, you need to create the HTML layout. From your original Pug templates, you know
what the markup needs to be; you need a page header and then a couple of <div>s to hold the
content. For the page header, you can reuse the pageHeader component that you created
earlier and pass the data through as you did for the homepage.
There’s not much to the rest of the markup. The entire contents of about .component.html are
shown in the following snippet:
<app-page-header [content]="pageContent.header"></app-page-header>
<div class="row">
<div class="col-12 col-lg-8">{{ pageContent.content }}</div>
</div>
Again, nothing unusual here—only the page header, some HTML, and a standard Angular
binding. If you look at this page in the browser, you’ll see that the content is coming through,
but the line breaks aren’t displaying, as illustrated in figure 9.13.
This situation isn’t ideal. You want your text to be readable and shown as originally intended.
If you can change the way that the distances appear on the homepage by using a pipe, why
not do the same thing to fix the line breaks? Give it a shot, and create a new pipe.
CREATING A PIPE TO TRANSFORM THE LINE BREAKS
You want to create a pipe that takes the provided text and replaces each instance of \n with a
<br/> tag. You’ve already solved this problem in Pug by using a JavaScript replace
command, as shown in the following code snippet:
p !{(content).replace(/\n/g, '<br/>')}
With Angular, you can’t do this inline. Instead, you need to create a pipe and apply it
to the binding.

Dr. R.PRIYA Page 19


PCA20D01J –ADVANCED WEB APPLICATION DEVELOPMENT UNIT V

CREATING AN HTMLLINEBREAKS PIPE


As you’ve already seen, pipes are best created by the Angular CLI, so run the following
command in terminal to generate the files and register the pipe with the application:
$ ng generate pipe html-line-breaks
The pipe itself is fairly straightforward. It needs to accept incoming text as a string value.
Replace each \n with a <br/>, and then return a string value. Update the main content of html-
line-breaks.html to look like the following snippet:
export class HtmlLineBreaksPipe implements PipeTransform {
transform(text: string): string {
return text.replace(/\n/g, '<br/>');
}
}
When you’ve done that, try using it.
APPLYING THE PIPE TO THE BINDING
Applying a pipe to a binding is simple; you’ve already done it a few times. In the HTML, add
the pipe character (|) after the data object being bound, and follow it with the name of the
filter like this:
<div class="col-12 col-lg-8">{{ pageContent.content | htmlLineBreaks }}</div>
Simple, right? But if you try it in the browser, all isn’t quite as you’d hoped. As you can
see in figure 9.14, the line breaks are being replaced by <br/>, but they’re being displayed
as text instead of rendering as HTML.

Dr. R.PRIYA Page 20


PCA20D01J –ADVANCED WEB APPLICATION DEVELOPMENT UNIT V

Hmmmm, this isn’t quite what you wanted, but at least the pipe seems to be working.
There’s a good reason for this output: security. Angular protects you and your application
from malicious attacks by preventing HTML from being injected into a data binding.
Think about when you let visitors write reviews for locations, for example. If they could add
any HTML they wanted to, someone could easily insert a <script> tag and run some
JavaScript, hijacking the page.
But there’s a way to let a subset of HTML tags through into a binding, which you’ll look at
next.
SAFELY BINDING HTML BY USING A PROPERTY BINDING
Angular lets you pass through some HTML tags if you use a property binding instead of the
default bindings you normally use for content. This technique works only for a subset of
HTML tags to prevent XSS hacks, attacks, and weaknesses. Think of property binding as
being “one-way” binding. The component can’t read the data back out and use it, but it can
update it and change the data in the binding.
You used property bindings when you passed data into nested components.
Remember building the About page? There, you were binding data to a property you defined
in the nested component, which you called content. Here, you’re binding to a native property
of a tag—in this case, innerHTML.
Property bindings are denoted by wrapping square brackets around them and then passing the
value. You can remove the content binding in about.component .html and use a property
binding:
<div class="col-12 col-lg-8" [innerHTML]="pageContent.content |
htmlLineBreaks"></div>
Note that you can apply pipes to this type of binding too, so you’re still using your
htmlLineBreaks pipe. Finally, when you view the About page in the browser, you’ll see the
line breaks in place, looking like figure 9.15.
Success! You’ve made a great start toward building Loc8r as an Angular SPA.
You’ve got a couple of pages, some routing and navigation, geolocation, and a great modular
application architecture. Keep on moving!

Dr. R.PRIYA Page 21


PCA20D01J –ADVANCED WEB APPLICATION DEVELOPMENT UNIT V

USING URL PARAMETERS IN COMPONENTS AND SERVICES


The plan is to get the location ID URL parameter and use it in a call to the API to get the
details for a specific location. When the data comes back, you want to display it on the page.
Where’s the best place to put this logic? Any of the components in the routable area could be
configured to get the URL parameter and call the API, but you want to display data in all
three nested components. So you’ll go for the approach of using the “parent” Details page
component to get the data and then pass it through to the three child components. First, you’ll
add a method to your data service to call the API to get a single location by ID.
CREATING THE DATA SERVICE TO CALL THE API
The data service that you created in chapter 8 currently has a single method: get- Locations.
This method retrieves a list of locations when given a pair of coordinates. The new method
you need has a similar construct, so make a copy of this method in loc8r-data.service.ts and
call it getLocationById.
You need to make a few small adjustments to get this method working:
1 Change the expected input parameters to a single locationId of type string.
2 Change the return type to a single Location instance instead of an array.
3 Change the API URL to call, using locationId as a URL parameter.
4 Set the JSON response to a single Location instance.
The following listing shows how this method looks in code, in loc8r-data.service.ts.

Dr. R.PRIYA Page 22


PCA20D01J –ADVANCED WEB APPLICATION DEVELOPMENT UNIT V

With the data service method ready, you can import the service into the Details page
component, ready to use.
IMPORTING THE DATA SERVICE INTO THE COMPONENT
You’ve imported a service into a component before—the data service into the homelist
component—so we won’t dwell on the process too much here. You’ll need to import the data
service into the Details page component, add it to the providers, and then make it available by
declaring it in the class constructor.
While you’re here, you’ll also import the Location class from the home-list component and
empty the default page content. All these updates to details-page.component are shown in the
following listing.

The only real thing to be careful with here is the case of loc8rDataService in the constructor:
the class type definition has an uppercase L, and the local instance is defined with a
lowercase l.Now you’re ready to get the URL parameter into the component.
USING URL PARAMETERS IN A COMPONENT
Given that using URL parameters in an app is a common requirement, the process is
surprisingly complicated. You need three new pieces of functionality:
ActivatedRoute from the Angular router to get you the value of the current route from
inside the component
ParamMap from the Angular router to get you the URL parameters of the active route as
an Observable
Dr. R.PRIYA Page 23
PCA20D01J –ADVANCED WEB APPLICATION DEVELOPMENT UNIT V

switchMap from RxJS to get the values from the ParamMap Observable and use them to
call your API, creating a second Observable
The following snippet shows in bold the additions needed in details-page.component.ts to
import these pieces of functionality:
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, ParamMap } from '@angular/router';
import { Loc8rDataService } from '../loc8r-data.service';
import { Location } from '../home-list/home-list.component';
import { switchMap } from 'rxjs/operators';
You also need to make the activated route available to the component by defining a private
member route of type ActivatedRoute in the constructor:
constructor(
private loc8rDataService: Loc8rDataService,
private route: ActivatedRoute
){}
Now comes the complicated bit. Complete these steps to get a location ID from the URL
parameter and turn it into location data from the API:
1 When the component initializes, use switchMap to subscribe to the paramMap Observable
of the activated route.
2 When the paramMap Observable returns a ParamMap object, get the value of the locationId
URL parameter.
3 Call the getLocationsById method of your data service, passing it the ID.
4 Return the API call so that it returns an Observable.
5 Subscribe to listen for when the Observable returns the data from your API.
The result should be a single object of type Location.
6 Set the content for the page header and sidebar, using the location name returned from the
API.
Phew! That’s a lot of steps for a seemingly simple process. All this takes place in the
ngOnInit lifecycle hook in details-page.component.ts. The next listing shows what the code
looks like.

Dr. R.PRIYA Page 24


PCA20D01J –ADVANCED WEB APPLICATION DEVELOPMENT UNIT V

That’s some fairly dense code; a lot is happening in a few lines and commands. We
recommend reading the plan and the annotated code a few times to piece everything together.
It’s powerful, a little different from what you’ve seen so far, and about as complex as you’ll
see in this book. In particular, note the two chained Observables: first, the route paramMap
being subscribed to by the switchMap, which returns the second. The good news is that when
you’re done, your Details page shows the location name in the page header and the sidebar,
as shown in figure 10.5.

You’re now using the location ID in the URL to query the database and passing a bit of the
returned data to two of the components on the page. Before you build out the main part of the
Details page, make sure that the final component is getting the data it needs.
BUILDING THE DETAILS PAGE VIEW
For the location details, you’ve already got a Pug template with Pug data bindings, and you
need to transform this template into HTML with Angular bindings. You have quite a few
bindings to put in place, as well as some loops, utilizing Angular’s *ngFor construct. You’ll
use the rating-stars component that you created for the challenge at the end of chapter 9 to
show the overall rating and the rating for each review. If you haven’t created this component,
refer to the book’s code repository on GitHub. You’ll also need to allow line breaks in the
review text by using the htmlLineBreaks pipe.

Dr. R.PRIYA Page 25


PCA20D01J –ADVANCED WEB APPLICATION DEVELOPMENT UNIT V

GETTING THE MAIN TEMPLATE IN PLACE


Listing 10.9 shows everything in place, with the bindings in bold. This code should make up
the entire contents of location-details.component.html. We’ve left out some pieces, such as
the opening times, which you’ll fill in when you’ve got this code in place and tested.

Dr. R.PRIYA Page 26


PCA20D01J –ADVANCED WEB APPLICATION DEVELOPMENT UNIT V

That code listing is long, but that’s to be expected, as quite a lot is going on in the Details
page. If you look at the page in the browser now, it looks about right. You have a few things
to fix, but you know about them.
Although the page looks good, if you open the JavaScript console, you’ll see that the page
has thrown a lot of errors along the lines of Cannot read property 'rating' of undefined. These
errors are binding errors, happening because the nested location details component is trying
to bind to data as soon as the page loads, but you don’t have any data until after the API call
has completed.
WORKING WITH FORMS AND HANDLING SUBMITTED DATA
In this section, you’ll create the Add Review page in Angular, and have it submit data to the
API. Rather than navigate to a separate form page when the Add Review button is clicked,
it’ll display a form inline in the page. When the form is submitted, you’ll have Angular
handle the data, submit it to the API, and display the new review at the top of the list. You’ll
start by seeing what’s involved with creating the form in Angular.
Creating the review form in Angular
To create the review form, you’ll get the HTML in place, add data bindings to the input
fields, make sure that they all work as expected, and, finally, ensure that the form is initially
hidden and is displayed only if the button is clicked.
PUTTING THE FORM HTML IN PLACE
Add the inline form to the page just after the Customer reviews<h2> tag, as shown in the
following listing. Much of the layout is taken from the form you used in Express, including
the form input names and IDs.

Dr. R.PRIYA Page 27


PCA20D01J –ADVANCED WEB APPLICATION DEVELOPMENT UNIT V

Right now, you’re not doing anything clever or asking Angular to do anything. You’ve put
raw HTML with some Bootstrap classes in the template. In the browser, this looks like figure
10.7.

Dr. R.PRIYA Page 28


PCA20D01J –ADVANCED WEB APPLICATION DEVELOPMENT UNIT V

ADDING DATA BINDINGS TO FORM INPUTS


In Express, you posted the form to another URL and handled the submitted data there, but
with Angular, you don’t want to change the page at all. With Angular, the approach is to add
data bindings to all the fields in a form so the component can access the values.
To add a data binding to a form field, use a directive with a special syntax like this:
[(ngModel)]="bindingName". (Remembering the order of the brackets can be difficult, so
this has become known as “banana in a boat” to help you remember!)
FormsModule imported into the application in app.module.ts. Add the line import {
FormsModule, ReactiveFormsModule } from '@angular/forms'; to app.module. ts, and add
both of those module names to the imports array in the same file.
In your component, you’ll want to keep all the submitted form data inside a single object so
you can pass it around easily. Define a new public member, newReview, in location-
details.component.html, giving it properties for the author name, rating, and review content.
Each property needs to have a default value, so the definition should
look like this:
public newReview = {
author: '',
rating: 5,
reviewText: ''
};
Now that this newReview object and its properties are defined in the component, you can use
them in the HTML. The following listing shows how to add the bindings to the form in
location-details.component.html.

Dr. R.PRIYA Page 29


PCA20D01J –ADVANCED WEB APPLICATION DEVELOPMENT UNIT V

This looks good and, on the face of it, seems to work. But you want the rating to be a number,
and in a select option, value="5" is a string containing the character 5.
WORKING WITH SELECT VALUES THAT ARE NOT STRINGS
A select option value is by default a string, but your database requires a number for the
rating. Angular has a way to help you get different types of data from a select field.
Instead of using value="STRING VALUE" inside each <option>, use [ngValue]=
"ANGULAR EXPRESSION". When written out, the value of [ngValue] looks like a string,
but it’s an Angular expression. This could be an object or a true Boolean, but you want a
number.
In location-details.component.html, update each of the <option> tags to use [ngValue]
instead of value:
<option [ngValue]="5">5</option>
<option [ngValue]="4">4</option>
<option [ngValue]="3">3</option>
<option [ngValue]="2">2</option>

Dr. R.PRIYA Page 30


PCA20D01J –ADVANCED WEB APPLICATION DEVELOPMENT UNIT V

<option [ngValue]="1">1</option>
Now Angular gets the value of the <select> as a number, not a string. This technique will be
useful when you submit the data to the API. Next, you hide the form by default, as you don’t
want it showing all the time.
SETTING THE VISIBILITY OF THE FORM
You don’t want the Add Review section of the page to be visible when the page loads; you
want the Add Review button to show it, and when the form is displayed, you want the Cancel
button to hide it again.
To show and hide the form, you can use your old friend *ngIf. *ngIf needs a Boolean value to
decide whether to show the element it’s applied to, so you’ll need to define one in the
component.
In location-details.component.ts, define a new public member formVisible of type boolean
with a default value of false:
public formVisible: boolean = false;
You’ve set the default value to false, as you want the form to be hidden by default. To use
this Boolean to set the visibility of the form, locate the <div> surrounding the
<form> in location-details.component.html, and add the *ngIf directive to it like this:
<h2 class="card-title">Customer reviews</h2>
<div *ngIf="formVisible">
<form action="">
Now the form is hidden by default when the page loads.
TOGGLING THE VISIBILITY OF THE FORM
To change the visibility of the form, you need a way to change the value of form-Visible
when the Add Review and Cancel buttons are clicked. Not surprisingly, Angular has an on-
click handler you can use to track clicks of elements and then do something.
Angular’s click handler is accessed by adding (click) to the element and giving it an Angular
expression. This expression could be one that calls a public member in the component class
or any other kind of valid expression. You want to set formVisible to true when the Add
Review button is clicked and false when the Cancel button is clicked.
In location-details.component.html, change the Add Review button from an <a>
tag to a <button>, removing the href attribute and replacing it with a (click) setting
formVisible to be true:
<button (click)="formVisible=true" class="btn btn-primary float-right">Add
review</button>
In a similar way, add a (click) to the Cancel button, setting formVisible to be false:
<button type="button" (click)="formVisible=false" class="btn btn-default
float-right">Cancel</button>
With those click handlers in place, you can use the two buttons to show and hide the review
form, using the formVisible property of the component to keep track of the status. The final
thing you need to do is hook up the form so that when it’s submitted, a review is added.

Dr. R.PRIYA Page 31

You might also like