Build Your First PWA with Angular

Build Your First PWA with Angular


During the last two years, everybody started talking about Progressive Web Applications, or PWAs for short. But what is this new type of application, and how can it make your life as an Angular developer better? To understand what PWAs are all about, and how you can build them in Angular, let’s consider the following scenario. You are out and about in an area with little or no network reception. You are using a cool web app to search for a good book to read. Traditional web applications only work while you are online. Every time you lose the network the application will stall. What’s more, a typical app will load all of its scripts before starting up. This means that you might have to wait a minute or more for the first page to load. In conditions like this, you will quickly give up and abandon the application altogether.

This is where progressive web applications come into play. PWAs leverage a number of current browser technologies in order to provide a smooth user experience even in situations with little or no network connection. They use service workers which act a little like a proxy to intercept network requests and cache the responses. They allow the complete application to be installed in the client’s browser. This means that the user can use the application when they’re offline.

In this tutorial, I will show you how to create a complete progressive web app in Angular 7. The application will allow the user to search for book titles using the OpenLibrary service. I will be using a Material Design library, Angular Material, to give the application layout a professional appearance and make it responsive.


Create Your Single Page Application with Angular

Start by creating a single page application with Angular 7. I will assume that you have Node installed on your system. To begin you will need to install the Angular command line tool. Open a shell and enter the following command.

<pre class="ql-syntax" spellcheck="false">npm install -g @angular/[email protected]

</pre>

This will install the ng command on your system. Depending on your system settings you might need to run this command using sudo. Once npm has finished installing, you’ll be ready to create a new Angular project. In the shell, navigate to the directory in which you want to create your application and type the following command.

<pre class="ql-syntax" spellcheck="false">ng new AngularBooksPWA

</pre>

This will create a new directory called AngularBooksPWA and create an Angular application in it. The script will ask you two questions. When you are asked if you want to use the Router in your project, answer Yes. The router will allow you to navigate between different application components using the browser’s URL. Next, you are going to be prompted for the CSS technology that you wish to use. In this simple project, I will be using plain CSS. For larger projects, you should switch this to one of the other technologies. Once you have answered the questions ng will install all the necessary packages into the newly created application directory and create a number of files to help you get started quickly.


Add Angular Material

Next, navigate into your project’s directory and run the following command.

<pre class="ql-syntax" spellcheck="false">npm install @angular/[email protected] @angular/[email protected] @angular/[email protected] @angular/[email protected]

</pre>

This command will install all the necessary packages for using Material Design. Material design uses an icon font to display icons. This font is hosted on Google’s CDN. To include the icon font, open the src/index.html file and add the following line inside the <head> tags.

<pre class="ql-syntax" spellcheck="false"><link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">

</pre>

The src/app/app.module.ts contains the imports for the modules which will be available throughout the application. In order to import the Angular Material modules that you will be using, open the file and update it to match the following.

<pre class="ql-syntax" spellcheck="false">import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';

import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { FlexLayoutModule } from "@angular/flex-layout";

import { MatToolbarModule,
MatMenuModule,
MatIconModule,
MatCardModule,
MatButtonModule,
MatTableModule,
MatDividerModule,
MatProgressSpinnerModule } from '@angular/material';

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

@NgModule({
declarations: [
AppComponent,
],
imports: [
BrowserModule,
HttpClientModule,
BrowserAnimationsModule,
FlexLayoutModule,
FormsModule,
ReactiveFormsModule,
MatToolbarModule,
MatMenuModule,
MatIconModule,
MatCardModule,
MatButtonModule,
MatTableModule,
MatDividerModule,
MatProgressSpinnerModule,
AppRoutingModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

</pre>

The template for the main component of the application lives in the src/app/app.component.html file. Open this file and replace the contents with the following code.

<pre class="ql-syntax" spellcheck="false"><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>
<div fxLayout="row" fxShow="false" fxShow.gt-sm>
<form [formGroup]="searchForm" (ngSubmit)="onSearch()">
<div class="input-group">
<input class="input-group-field" type="search" value="" placeholder="Search" formControlName="search">
<div class="input-group-button"><button mat-flat-button color="accent"><mat-icon>search</mat-icon></button></div>
</div>
</form>
</div>
<button mat-button [mat-menu-trigger-for]="menu" fxHide="false" fxHide.gt-sm>
<mat-icon>menu</mat-icon>
</button>
</mat-toolbar>
<mat-menu x-position="before" #menu="matMenu">
<button mat-menu-item routerLink="/"><mat-icon>home</mat-icon> Home</button>

&lt;form [formGroup]="searchForm" (ngSubmit)="onSearch()"&gt;
  &lt;div class="input-group"&gt;
    &lt;input class="input-group-field" type="search" value="" placeholder="Search" formControlName="search"&gt;
    &lt;div class="input-group-button"&gt;&lt;button mat-button routerLink="/"&gt;&lt;mat-icon&gt;magnify&lt;/mat-icon&gt;&lt;/button&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/form&gt;

</mat-menu>
<router-outlet></router-outlet>

</pre>

You might notice the routerLink attributes used in various places. These refer to components that will be added later in this tutorial. Also, note the HTML <form> tag and the formGroup attribute. This is the search form that will allow you to search for book titles. I will be referring to this when implementing the application component.

Next, I will add a bit of styling. Angular separates the style sheets into a single global style sheet and local style sheets for each component. First, open the global style sheet in src/style.css and paste the following content into it.

<pre class="ql-syntax" spellcheck="false">@import "[email protected]/material/prebuilt-themes/deeppurple-amber.css";

body {
margin: 0;
font-family: sans-serif;
}

h1, h2 {
text-align: center;
}

.input-group {
display: flex;
align-items: stretch;
}

.input-group-field {
margin-right: 0;
}

.input-group .input-group-button {
margin-left: 0;
border: none;
}

.input-group .mat-flat-button {
border-radius: 0;
}

</pre>

The first line in this style sheet in necessary to apply the correct styles to any Angular Material components. The local style for the main application component is found in src/app/app.component.css. Add the toolbar styling here.

<pre class="ql-syntax" spellcheck="false">.expanded-toolbar {
justify-content: space-between;
align-items: center;
}

</pre>

Add a Search Feature with Angular

Now, you are finally ready to implement the main application component. Open src/app/app.component.ts and replace its content with the following.

<pre class="ql-syntax" spellcheck="false">import { Component } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { Router } from "@angular/router";

import { BooksService } from './books/books.service';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'AngularBooksPWA';
searchForm: FormGroup;

constructor(private formBuilder: FormBuilder,
private router: Router) {
}

ngOnInit() {
this.searchForm = this.formBuilder.group({
search: ['', Validators.required],
});
}

onSearch() {
if (!this.searchForm.valid) return;
this.router.navigate(['search'], { queryParams: {query: this.searchForm.get('search').value}});
}
}

</pre>

There are two things to note about this code. The searchForm attribute is a FormGroup which is created using the FormBuilder. The builder allows the creation of form elements that can be associated with validators to allow easy validation of any user input. When the user submits the form, the onSearch() function is called. This checks for valid user input and then simply forwards the call to the router. Note how the query string is passed to the router. This will append the query to the URL and make it available to the search route. The router will pick the appropriate component and the book search is handled within that component. This means that the responsibility for performing the search request is encapsulated in the local scope of a single component. When building larger applications, this separation of responsibilities is an important technique to keep the code simple and maintainable.


Create a BookService to talk to the OpenLibrary API

Next, create a service that will provide a high-level interface to the OpenLibrary API. To have Angular create the service, open the shell again in the application root directory and run the following command.

<pre class="ql-syntax" spellcheck="false">ng generate service books/books

</pre>

This will create two files in the src/app/books directory. Open the books.service.ts file and replace its contents with the following.

<pre class="ql-syntax" spellcheck="false">import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';

const baseUrl = 'http://openlibrary.org';

@Injectable({
providedIn: 'root'
})
export class BooksService {

constructor(private http: HttpClient) { }

async get(route: string, data?: any) {
const url = baseUrl+route;
let params = new HttpParams();

if (data!==undefined) {
  Object.getOwnPropertyNames(data).forEach(key =&gt; {
    params = params.set(key, data[key]);
  });
}

const result = this.http.get(url, {
  responseType: 'json',
  params: params
});

return new Promise&lt;any&gt;((resolve, reject) =&gt; {
  result.subscribe(resolve as any, reject as any);
});

}

searchBooks(query: string) {
return this.get('/search.json', {title: query});
}
}

</pre>

To keep things simple, you can use a single route into the OpenLibrary API. The search.json route takes a search request and returns a list of books together with some information about them. Note how the functions return a Promise object. This will make it easier to use them later on in using the async/await technique.


Generate Angular Components for Your PWA using Angular CLI

Now it’s time to turn your attention to the components that make up the books search application. There will be three components in total. The Home component displays the splash screen, Search lists the book search results and Details displays detailed information about a single book. To create these components, open the shell and execute the following commands.

<pre class="ql-syntax" spellcheck="false">ng generate component home
ng generate component search
ng generate component details

</pre>

After having created these three components, you have to link them to specific routes using the Router. Open src/app/app-routing.module.ts and add routes for the components you just created.

<pre class="ql-syntax" spellcheck="false">import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { SearchComponent } from './search/search.component';
import { DetailsComponent } from './details/details.component';

const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'search', component: SearchComponent },
{ path: 'details', component: DetailsComponent },
];

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

</pre>

Start with the Home component. This component will consist only of two simple headings. Open src/app/home/home.component.html and enter the lines below.

<pre class="ql-syntax" spellcheck="false"><h1>Angular Books PWA</h1>
<h2>A simple progressive web application</h2>

</pre>

Next, implement the search component by changing the code in src/app/search/search.component.ts to look like the following.

<pre class="ql-syntax" spellcheck="false">import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { MatTableDataSource } from '@angular/material';
import { BooksService } from '../books/books.service';
import { Subscription } from 'rxjs';

@Component({
selector: 'app-search',
templateUrl: './search.component.html',
styleUrls: ['./search.component.css']
})
export class SearchComponent implements OnInit {
private subscription: Subscription;

displayedColumns: string[] = ['title', 'author', 'publication', 'details'];
books = new MatTableDataSource<any>();

constructor(private route: ActivatedRoute,
private router: Router,
private bookService: BooksService) { }

ngOnInit() {
this.subscription = this.route.queryParams.subscribe(params => {
this.searchBooks(params['query']);
});
}

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

async searchBooks(query: string) {
const results = await this.bookService.searchBooks(query);

this.books.data = results.docs;

}

viewDetails(book) {
console.log(book);
this.router.navigate(['details'], { queryParams: {
title: book.title,
authors: book.author_name && book.author_name.join(', '),
year: book.first_publish_year,
cover_id: book.cover_edition_key
}});
}
}

</pre>

There are a few things going on here. During initialization of the component, the search query is obtained by subscribing to the ActivatedRoute.queryParams Observable. Whenever the value changes, this will call the searchBooks method. Inside this method, the BooksService, which you implemented earlier, is used to obtain a list of books. The result is passed to a MatTableDataSource object that allows displaying beautiful tables with the Angular Material library.

Take a look at the src/app/search/search.component.html and update its HTML to match the template below.

<pre class="ql-syntax" spellcheck="false"><h1 class="h1">Search Results</h1>
<div fxLayout="row" fxLayout.xs="column" fxLayoutAlign="center" class="products">
<table mat-table fxFlex="100%" fxFlex.gt-sm="66%" [dataSource]="books" class="mat-elevation-z1">
<ng-container matColumnDef="title">
<th mat-header-cell *matHeaderCellDef>Title</th>
<td mat-cell *matCellDef="let book"> {{book.title}} </td>
</ng-container>
<ng-container matColumnDef="author">
<th mat-header-cell *matHeaderCellDef>Author</th>
<td mat-cell *matCellDef="let book"> {{book.author_name && book.author_name.join(', ')}} </td>
</ng-container>
<ng-container matColumnDef="publication">
<th mat-header-cell *matHeaderCellDef>Pub. Year</th>
<td mat-cell *matCellDef="let book"> {{book.first_publish_year}} </td>
</ng-container>
<ng-container matColumnDef="details">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let book">
<button mat-icon-button (click)="viewDetails(book)"><mat-icon>visibility</mat-icon></button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</div>

</pre>

This table uses the data source to display the search results. The last component displays the details of the book, including its cover image. Just like the Search component, the data is obtained through subscribing to the route parameters. Open src/app/details/details.component.ts and update the content to the following.

<pre class="ql-syntax" spellcheck="false">import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs';

@Component({
selector: 'app-details',
templateUrl: './details.component.html',
styleUrls: ['./details.component.css']
})
export class DetailsComponent implements OnInit {
private subscription: Subscription;
book: any;

constructor(private route: ActivatedRoute) { }

ngOnInit() {
this.subscription = this.route.queryParams.subscribe(params => {
this.updateDetails(params);
});
}

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

updateDetails(book) {
this.book = book;
}
}

</pre>

The template simply shows some of the fields in the book’s data structure. Copy the following into src/app/details/details.component.html.

<pre class="ql-syntax" spellcheck="false"><h1 class="h1">Book Details</h1>
<div fxLayout="row" fxLayout.xs="column" fxLayoutAlign="center" class="products">
<mat-card fxFlex="100%" fxFlex.gt-sm="66%" class="mat-elevation-z1">
<h3>{{book.title}}</h3>
<img src="http://covers.openlibrary.org/b/OLID/{{book.cover_id}}-M.jpg" />
<h4>Authors</h4>
<p>
{{book.authors}}
</p>
<h4>Published</h4>
<p>
{{book.year}}
</p>
</mat-card>
</div>

</pre>

Run Your Angular PWA

The application is now complete. You can now start up and test the application. When building a PWA, you should not use the ng serve command to run your application. This is OK during development but it will disable a number of features that are necessary for the performance of PWAs. Instead, you need to build the application in production mode and the serve it using the http-server-spa command. Run the following commands.

<pre class="ql-syntax" spellcheck="false">npm install -g [email protected]
ng build --prod --source-map
http-server-spa dist/AngularBooksPWA/ index.html 8080

</pre>

You need to run the first command only once to install the http-server-spa command. The second line builds your Angular app. With the --source-map option you will generate source maps that help you debugging in the browser. The last command starts the HTTP server. Open your browser, navigate to http://localhost:8080, and enter a book title in the search bar. You should see a list of books, somewhat like this.


Add Authentication to your Angular PWA

A complete application will have to have some user authentication to restrict access to some of the information contained within the application. Okta allows you to implement authentication in a quick, easy, and safe way. In this section I will show you how to implement authentication using the Okta libraries for Angular. If you haven’t done so already, register a developer account with Okta.

Open your browser and navigate to developer.okta.com. Click on Create Free Account. On the next screen enter your details and click Get Started. You will be taken to your Okta developer dashboard. Each application that uses the Okta authentication service needs to be registered in the dashboard. Click on Add Application to create a new application.

The PWA that you are creating falls under the single page application category. Choose Single Page App and click Next.

On the next page, you will be shown the settings for the application. You can leave the default settings untouched and click on Done. On the following screen you will be presented with a Client ID. This is needed in your application.

To add authentication to your PWA, first install the Okta library for Angular.

<pre class="ql-syntax" spellcheck="false">npm install @okta/[email protected] --save-exact

</pre>

Open app.module.ts and import the OktaAuthModule.

<pre class="ql-syntax" spellcheck="false">import { OktaAuthModule } from '@okta/okta-angular';

</pre>

Add the OktaAuthModule to the list of imports of the app.

<pre class="ql-syntax" spellcheck="false">OktaAuthModule.initAuth({
issuer: 'https://{yourOktaDomain}/oauth2/default',
redirectUri: 'http://localhost:8080/implicit/callback',
clientId: '{yourClientId}'
})

</pre>

The {yourClientId} has to be replaced by the client ID that you obtained when registering your application. Next, open app.component.ts, and import the service.

<pre class="ql-syntax" spellcheck="false">import { OktaAuthService } from '@okta/okta-angular';

</pre>

Create an isAuthenticated field as a property of the AppComponent.

<pre class="ql-syntax" spellcheck="false">isAuthenticated: boolean;

</pre>

Then, modify the constructor to inject the service and subscribe to it.

<pre class="ql-syntax" spellcheck="false">constructor(private formBuilder: FormBuilder,
private router: Router,
public oktaAuth: OktaAuthService) {
this.oktaAuth.$authenticationState.subscribe(
(isAuthenticated: boolean) => this.isAuthenticated = isAuthenticated
);
}

</pre>

Whenever the authentication status changes this will be reflected in the isAuthenticated property. You will still need to initialize it when the component is loaded. In the ngOnInit() method add the line

<pre class="ql-syntax" spellcheck="false">this.oktaAuth.isAuthenticated().then((auth) => {this.isAuthenticated = auth});

</pre>

You want the application to be able to react to login and logout requests. To do this, implement the login() and logout() methods as follows.

<pre class="ql-syntax" spellcheck="false">login() {
this.oktaAuth.loginRedirect();
}

logout() {
this.oktaAuth.logout('/');
}

</pre>

Open app.component.html and add the following lines to the top bar before <div fxLayout="row" fxShow="false" fxShow.gt-sm>.

<pre class="ql-syntax" spellcheck="false"><button mat-button *ngIf="!isAuthenticated" (click)="login()"> Login </button>
<button mat-button *ngIf="isAuthenticated" (click)="logout()"> Logout </button>

</pre>

Finally, you need to register the route that will be used for the login request. Open app-routing.module.ts and add the following imports.

<pre class="ql-syntax" spellcheck="false">import { OktaCallbackComponent, OktaAuthGuard } from '@okta/okta-angular';

</pre>

Add the implicit/callback route to the routes array.

<pre class="ql-syntax" spellcheck="false">{ path: 'implicit/callback', component: OktaCallbackComponent }

</pre>

This is the route that the Okta authorization service will return to, once authentication is completed. The next step is to protect the search and the details routes from unauthorized access, add the following setting to both routes.

<pre class="ql-syntax" spellcheck="false">canActivate: [OktaAuthGuard]

</pre>

Your routes array should look as follows after these changes.

<pre class="ql-syntax" spellcheck="false">const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'search', component: SearchComponent, canActivate: [OktaAuthGuard] },
{ path: 'details', component: DetailsComponent, canActivate: [OktaAuthGuard] },
{ path: 'implicit/callback', component: OktaCallbackComponent }
];

</pre>

This is it. Whenever a user tries to access the Search or Details view of the application, they will be redirected to the Okta login page. Once logged on, the user will be redirected back to the view that they wanted to see in the first place. As before, you can build and start your application by running the commands:

<pre class="ql-syntax" spellcheck="false">ng build --prod --source-map
http-server-spa dist/AngularBooksPWA/ index.html 8080

</pre>

Create a Progressive Web App with Angular

The application runs and works, but it is not a Progressive Web Application. To see how your application performs, I will be using the Lighthouse extension. If you don’t want to install Lighthouse, you can also use the audit tool built into Google Chrome. This is a slightly less up-to-date version of Lighthouse accessible through Developer Tools > Audits. Install the Lighthouse extension for the Chrome browser. This extension allows you to analyze the performance and compatibility of web pages and applications.

After installation, open your application, click on the small Lighthouse logo and run the test. At the moment, your Books application likely rates poorly on the PWA scale, achieving only 46%.

Over the last year, the folks developing Angular have made it very easy to turn your regular application into a PWA. Shut down the server and run the following command.

<pre class="ql-syntax" spellcheck="false">ng add @angular/pwa --project AngularBooksPWA

</pre>

Rebuild your application, start the server, and run Lighthouse again. I tried and I got a score of 92%. The only reason that the application is not achieving 100% is due to the fact that it is not served via https protocol.

What did adding PWA support do? The most important improvement is the addition of a service worker. A service worker can intercept requests to the server and returns cached results wherever possible. This means that the application should work when you’re offline. To test this, open the developer console, open the network tab, and tick the offline check box. When you now click the reload button, the page should still work and show some content.

Adding PWA support also created application icons of various sizes (in the src/assets/icons/ directory). Naturally, you will want to replace them with your own icons. Use any regular image manipulation software to create some cool logos. Finally, a web app manifest was added to the file src/manifest.json. The manifest provides the browser with information that it needs to install the application locally on the user’s device.

Does that mean that you are done turning your application into a PWA? Not at all! There are numerous other features that are not tested by Lighthouse, but still make for a good Progressive Web Application. Check Google’s Progressive Web App Checklist for a list of features that make a good PWA.


Cache Recent Requests and Responses

Start the Books application in your browser and search for a book. Now click on the eye icon of one of the books to view its details. After the details page has loaded, open the developer console and switch to offline mode (Network tab > check Offline). In this mode click the back button in your browser. You will notice that the content has disappeared. The application is trying to request resources from the OpenLibrary API again. Ideally, you would like to keep some search results in the cache. Also, it would be nice for the user to know that they are using the application in offline mode.

I will start with the cache. The following code is adapted from Tamas Piros’ great article about caching HTTP requests. Start by creating two new services.

<pre class="ql-syntax" spellcheck="false">ng generate service cache/request-cache
ng generate service cache/caching-interceptor

</pre>

Now change the content of the src/app/cache/request-cache.service.ts file to mirror the code below.

<pre class="ql-syntax" spellcheck="false">import { Injectable } from '@angular/core';
import { HttpRequest, HttpResponse } from '@angular/common/http';

const maxAge = 30000;
@Injectable({
providedIn: 'root'
})
export class RequestCache {

cache = new Map();

get(req: HttpRequest<any>): HttpResponse<any> | undefined {
const url = req.urlWithParams;
const cached = this.cache.get(url);

if (!cached) return undefined;

const isExpired = cached.lastRead &lt; (Date.now() - maxAge);
const expired = isExpired ? 'expired ' : '';
return cached.response;

}

put(req: HttpRequest<any>, response: HttpResponse<any>): void {
const url = req.urlWithParams;
const entry = { url, response, lastRead: Date.now() };
this.cache.set(url, entry);

const expired = Date.now() - maxAge;
this.cache.forEach(expiredEntry =&gt; {
  if (expiredEntry.lastRead &lt; expired) {
    this.cache.delete(expiredEntry.url);
  }
});

}
}

</pre>

The RequestCache service acts as a cache in memory. The put and get methods will store and retrieve HttpResponses based on the request data. Now replace the contents of src/app/cache/caching-interceptor.service.ts with the following.

<pre class="ql-syntax" spellcheck="false">import { Injectable } from '@angular/core';
import { HttpEvent, HttpRequest, HttpResponse, HttpInterceptor, HttpHandler } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { tap } from 'rxjs/operators';
import { RequestCache } from './request-cache.service';

@Injectable()
export class CachingInterceptor implements HttpInterceptor {
constructor(private cache: RequestCache) {}

intercept(req: HttpRequest<any>, next: HttpHandler) {
const cachedResponse = this.cache.get(req);
return cachedResponse ? of(cachedResponse) : this.sendRequest(req, next, this.cache);
}

sendRequest(req: HttpRequest<any>, next: HttpHandler,
cache: RequestCache): Observable<HttpEvent<any>> {
return next.handle(req).pipe(
tap(event => {
if (event instanceof HttpResponse) {
cache.put(req, event);
}
})
);
}
}

</pre>

The CachingInterceptor can intercept any HttpRequest. It uses the RequestCache service to look for already stored data and returns it if possible. To set up the interceptor, open src/app/app.module.ts and add the following imports.

<pre class="ql-syntax" spellcheck="false">import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { RequestCache } from './cache/request-cache.service';
import { CachingInterceptor } from './cache/caching-interceptor.service';

</pre>

Update the providers section to include the services

<pre class="ql-syntax" spellcheck="false">providers: [{ provide: HTTP_INTERCEPTORS, useClass: CachingInterceptor, multi: true }],

</pre>

With these changes, the application will cache the most recent requests and their responses. This means that you can navigate back from the details page and still see the search results in offline mode.

NOTE: In this version, I am keeping the cache in memory and not persisting it in the browsers localStorage. This means that you will lose the search results when you force a reload on the application. If you wanted to store the responses persistently, you would have to modify the RequestCache accordingly.

Remember not to use the ng serve command to test your PWA. Always build your project first, then start the server with:

<pre class="ql-syntax" spellcheck="false">ng build --prod --source-map
http-server-spa dist/AngularBooksPWA/ index.html 8080

</pre>

Monitor Your Network’s Status

Open src/app/app.component.ts and add the following property and method.

<pre class="ql-syntax" spellcheck="false">offline: boolean;

onNetworkStatusChange() {
this.offline = !navigator.onLine;
console.log('offline ' + this.offline);
}

</pre>

Then edit the ngOnInit method and add the following lines.

<pre class="ql-syntax" spellcheck="false">window.addEventListener('online', this.onNetworkStatusChange.bind(this));
window.addEventListener('offline', this.onNetworkStatusChange.bind(this));

</pre>

Finally, add a notice to the top-bar in src/app/app.component.html, before the div containing the search form.

<pre class="ql-syntax" spellcheck="false"><div *ngIf="offline">offline</div>

</pre>

The application will now show an offline message in the top bar when the network is not available.

Pretty cool, don’t you think?!


Learn More About PWAs and Angular

In this tutorial, I’ve shown you how to create a progressive web app using Angular 7. Due to the effort put in by the Angular developers, it is easier than ever to achieve a perfect score for your PWA. With just a single command all the necessary resources and infrastructure is put into place to make your app offline-ready. In order to create a really outstanding PWA, there are many more improvements you can apply to your application. I have shown you how to implement a cache for HTTP requests as well as an indicator telling the users when they are offline.

The complete code for this project can be found at https://github.com/oktadeveloper/okta-angular-pwa-example.

In order to further improve the application, you might consider the following. You could add placeholder images for the book covers when the user is in offline mode. You should also make sure that delayed loading of images does not make the page jump around. Because PWAs are mainly useful for mobile devices, it is important to check that you can always scroll form inputs into view, even if the on-screen keyboard is open.


Learn More

Angular 7 (formerly Angular 2) - The Complete Guide

Learn and Understand AngularJS

Angular Crash Course for Busy Developers

The Complete Angular Course: Beginner to Advanced

Angular (Angular 2+) & NodeJS - The MEAN Stack Guide

Become a JavaScript developer - Learn (React, Node,Angular)

Angular (Full App) with Angular Material, Angularfire & NgRx

The Web Developer Bootcamp

Angular 7 CRUD with Nodejs and MySQL Example

Angular 7 CRUD with Nodejs and MySQL Example

<span class="ql-cursor"></span>Below are the requirements for creating the CRUD on MEAN

  • Node.js
  • Angular CLI
  • Angular 7
  • Mysql
  • IDE or Text Editor

We assume that you have already available the above tools/frameworks and you are familiar with all the above that what individually actually does.

So now we will proceed step by step to achieve the task.

1. Update Angular CLI and Create Angular 7 Application

At first, We have to update the Angular CLI to the latest version. Open the terminal then go to the project folder and then type the below command to update the Angular CLI

<pre class="ql-syntax" spellcheck="false">sudo npm install -g @angular/cli </pre>

Once the above task finishes, Next task is to create new angular application with below command. So go to your project folder and then type below command:

<pre class="ql-syntax" spellcheck="false">ng new angular7-crud </pre>

then go to the newly created folder of angular application with **cd /angular7-crud ** and type **ng serve. **Now, open the browser then go to <a href="http://localhost:4200" title="" target="_blank">http://localhost:4200</a> you should see this page.

2. Create a server with node.js express and Mysql for REST APIs

create a separate folder named server for server-side stuff, Then move inside folder and create server.js by typing touch server.js

Let’s have a look on the server.js file

<pre class="ql-syntax" spellcheck="false">let app = require('express')(), server = require('http').Server(app), bodyParser = require('body-parser') express = require('express'), cors = require('cors'), http = require('http'), path = require('path');   let articleRoute = require('./Routes/article'), util = require('./Utilities/util');   app.use(bodyParser.json()); app.use(bodyParser.urlencoded({extended: false }));   app.use(cors());   app.use(function(err, req, res, next) { return res.send({ "statusCode": util.statusCode.ONE, "statusMessage":util.statusMessage.SOMETHING_WENT_WRONG }); });   app.use('/article', articleRoute);   // catch 404 and forward to error handler app.use(function(req, res, next) { next(); });   /*first API to check if server is running*/ app.get('*', (req, res) => { res.sendFile(path.join(__dirname, '../server/client/dist/index.html')); })     server.listen(3000,function(){ console.log('app listening on port: 3000'); }); </pre>

In the above file we can see, at the top, there are required packages for the app. Below that body parsing, middleware and routing is done.

The next task is to create routes and create a file article.js . So creating a folder name ‘Routes’ and adding article.js within it.

Add the below code for routing in article.js inside routing folder

<pre class="ql-syntax" spellcheck="false">let express = require('express'), router = express.Router(), util = require('../Utilities/util'), articleService = require('../Services/article');   /**Api to create article */ router.post('/create-article', (req, res) => { articleService.createArticle(req.body, (data) => { res.send(data); }); });   // /**Api to update article */ router.put('/update-article', (req, res) => { articleService.updateArticle(req.body, (data) => { res.send(data); }); });   // /**Api to delete the article */ router.delete('/delete-article', (req, res) => { articleService.deleteArticle(req.query, (data) => { res.send(data); }); });   /**Api to get the list of article */ router.get('/get-article', (req, res) => { documentService.getArticle(req.query, (data) => { res.send(data); }); });   // /**API to get the article by id... */ router.get('/get-article-by-id', (req, res) => { articleService.getArticleById(req.query, (data) => { res.send(data); }); });   module.exports = router; </pre>


Now create a folder named Utilities for all config, common methods and mysql connection config.

Now I am adding config values in a file named config.js

<pre class="ql-syntax" spellcheck="false">let environment = "dev";   let serverURLs = { "dev": { "NODE_SERVER": "http://localhost", "NODE_SERVER_PORT": "3000", "MYSQL_HOST": 'localhost', "MYSQL_USER": 'root', "MYSQL_PASSWORD": 'password', 'MYSQL_DATABASE': 'demo_angular7_crud', } }   let config = { "DB_URL_MYSQL": { "host": `${serverURLs[environment].MYSQL_HOST}`, "user": `${serverURLs[environment].MYSQL_USER}`, "password": `${serverURLs[environment].MYSQL_PASSWORD}`, "database": `${serverURLs[environment].MYSQL_DATABASE}` }, "NODE_SERVER_PORT": { "port": `${serverURLs[environment].NODE_SERVER_PORT}` }, "NODE_SERVER_URL": { "url": `${serverURLs[environment].NODE_SERVER}` } };   module.exports = { config: config }; </pre>

Now configure mysql connection. So I am writing the connection with database in a separate file. So creating a file named mysqkConfig.js under Utilities folder and adding the below line of code for mysql connection:

 

<pre class="ql-syntax" spellcheck="false">var config = require("../Utilities/config").config; var mysql = require('mysql'); var connection = mysql.createConnection({ host: config.DB_URL_MYSQL.host, user: config.DB_URL_MYSQL.user, password: config.DB_URL_MYSQL.password, database: config.DB_URL_MYSQL.database, });   connection.connect(() => { require('../Models/Article').initialize(); });   let getDB = () => { return connection; }   module.exports = { getDB: getDB } </pre>

Now I am creating separate file name util.js to save common methods and common status code/message:

<pre class="ql-syntax" spellcheck="false">// Define Error Codes let statusCode = { OK: 200, FOUR_ZERO_FOUR: 404, FOUR_ZERO_THREE: 403, FOUR_ZERO_ONE: 401, FIVE_ZERO_ZERO: 500 };   // Define Error Messages let statusMessage = { SERVER_BUSY : 'Our Servers are busy. Please try again later.', DATA_UPDATED: 'Data updated successfully.', DELETE_DATA : 'Delete data successfully',   };   module.exports = { statusCode: statusCode, statusMessage: statusMessage } </pre>

Now the next part is model, So create a folder named Models and create a file Article.js and add the below code in it:

<pre class="ql-syntax" spellcheck="false">let mysqlConfig = require("../Utilities/mysqlConfig");   let initialize = () => { mysqlConfig.getDB().query("create table IF NOT EXISTS article (id INT auto_increment primary key, category VARCHAR(30), title VARCHAR(24))");   }   module.exports = { initialize: initialize } </pre>

Now create DAO folder and add a file articleDAO.js for writting the mysql queries common functions:

<pre class="ql-syntax" spellcheck="false">let dbConfig = require("../Utilities/mysqlConfig");

 
let getArticle = (criteria, callback) => {
//criteria.aricle_id ? conditions += and aricle_id = '${criteria.aricle_id}' : true;
dbConfig.getDB().query(select * from article where 1,criteria, callback);
}
 
let getArticleDetail = (criteria, callback) => {
    let conditions = "";
criteria.id ? conditions +=  and id = '${criteria.id}' : true;
dbConfig.getDB().query(select * from article where 1&nbsp;${conditions}, callback);
}
 
let createArticle = (dataToSet, callback) => {
console.log("insert into article set ? ", dataToSet,'pankaj')
dbConfig.getDB().query("insert into article set ? ", dataToSet, callback);
}
 
let deleteArticle = (criteria, callback) => {
let conditions = "";
criteria.id ? conditions +=  and id = '${criteria.id}' : true;
console.log(delete from article where 1&nbsp;${conditions});
dbConfig.getDB().query(delete from article where 1&nbsp;${conditions}, callback);
 
}
 
let updateArticle = (criteria,dataToSet,callback) => {
    let conditions = "";
let setData = "";
criteria.id ? conditions +=  and id = '${criteria.id}' : true;
dataToSet.category ? setData += category = '${dataToSet.category}' : true;
dataToSet.title ? setData += , title = '${dataToSet.title}' : true;
console.log(UPDATE article SET&nbsp;${setData}&nbsp;where 1&nbsp;${conditions});
dbConfig.getDB().query(UPDATE article SET&nbsp;${setData}&nbsp;where 1&nbsp;${conditions}, callback);
}
module.exports = {
getArticle : getArticle,
createArticle : createArticle,
deleteArticle : deleteArticle,
updateArticle : updateArticle,
getArticleDetail : getArticleDetail
}
</pre>

Now one create Services folder and add a file article.js for all the logic of API

<pre class="ql-syntax" spellcheck="false"> let async = require('async'),
parseString = require('xml2js').parseString;
 
let util = require('../Utilities/util'),
articleDAO = require('../DAO/articleDAO');
//config = require("../Utilities/config").config;
 
 
/**API to create the atricle */
let createArticle = (data, callback) => {
async.auto({
article: (cb) => {
var dataToSet = {
"category":data.category?data.category:'',
"title":data.title,
}
console.log(dataToSet);
articleDAO.createArticle(dataToSet, (err, dbData) => {
if (err) {
cb(null, { "statusCode": util.statusCode.FOUR_ZERO_ONE, "statusMessage":util.statusMessage.SERVER_BUSY });
return;
}
 
cb(null, { "statusCode": util.statusCode.OK, "statusMessage":util.statusMessage.DATA_UPDATED,"result":dataToSet });
});
}
//]
}, (err, response) => {
callback(response.article);
});
}
 
/**API to update the article */
let updateArticle = (data,callback) => {
async.auto({
articleUpdate :(cb) =>{
if (!data.id) {
cb(null, { "statusCode": util.statusCode.FOUR_ZERO_ONE, "statusMessage":util.statusMessage.PARAMS_MISSING })
return;
}
console.log('phase 1');
var criteria = {
id : data.id,
}
var dataToSet={
"category": data.category,
"title":data.title,
}
console.log(criteria,'test',dataToSet);
                    articleDAO.updateArticle(criteria, dataToSet, (err, dbData)=>{
                        if(err){
cb(null,{"statusCode":util.statusCode.FOUR_ZERO_ONE,"statusMessage":util.statusMessage.SERVER_BUSY});
                        return; 
                        }
                        else{
cb(null, { "statusCode": util.statusCode.OK, "statusMessage":util.statusMessage.DATA_UPDATED,"result":dataToSet });                        
                        }
                    });
}
}, (err,response) => {
callback(response.articleUpdate);
});
}
 
/**API to delete the subject */
let deleteArticle = (data,callback) => {
console.log(data,'data to set')
async.auto({
removeArticle :(cb) =>{
if (!data.id) {
cb(null, { "statusCode": util.statusCode.FOUR_ZERO_ONE, "statusMessage":util.statusMessage.PARAMS_MISSING })
return;
}
var criteria = {
id : data.id,
}
articleDAO.deleteArticle(criteria,(err,dbData) => {
if (err) {
console.log(err);
cb(null, { "statusCode": util.statusCode.FOUR_ZERO_ONE, "statusMessage":util.statusMessage.SERVER_BUSY });
return;
}
cb(null, { "statusCode": util.statusCode.OK, "statusMessage": util.statusMessage.DELETE_DATA });
});
}
}, (err,response) => {
callback(response.removeArticle);
});
}
 
/***API to get the article list */
let getArticle = (data, callback) => {
async.auto({
article: (cb) => {
articleDAO.getArticle({},(err, data) => {
if (err) {
cb(null, {"errorCode": util.statusCode.INTERNAL_SERVER_ERROR,"statusMessage":util.statusMessage.SERVER_BUSY});
return;
}
cb(null, data);
return;
});
}
}, (err, response) => {
callback(response.article);
})
}
 
/***API to get the article detail by id */
let getArticleById = (data, callback) => {
async.auto({
article: (cb) => {
let criteria = {
"id":data.id
}
articleDAO.getArticleDetail(criteria,(err, data) => {
if (err) {
console.log(err,'error----');
cb(null, {"errorCode": util.statusCode.INTERNAL_SERVER_ERROR,"statusMessage":util.statusMessage.SERVER_BUSY});
return;
}
cb(null, data[0]);
return;
});
}
}, (err, response) => {
callback(response.article);
})
}
 
module.exports = {
createArticle : createArticle,
updateArticle : updateArticle,
deleteArticle : deleteArticle,
getArticle : getArticle,
getArticleById : getArticleById
};
</pre>

3. Create angular component for performing CRUD task of article

<pre class="ql-syntax" spellcheck="false">ng g component article
</pre>

Above command will generate all required files for build article component and also automatically added this component to app.module.ts.

<pre class="ql-syntax" spellcheck="false">create src/app/article/article.component.css (0 bytes)
create src/app/article/article.component.html (23 bytes)
create src/app/article/article.component.spec.ts (614 bytes)
create src/app/article/article.component.ts (321 bytes)
update src/app/app.module.ts (390 bytes)
</pre>

Now we need to add HttpClientModule to app.module.ts. Open and edit src/app/app.module.ts then add this import. And add it to @NgModule imports after BrowserModule. Now our app.module.ts will have following code:

<pre class="ql-syntax" spellcheck="false"> import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReactiveFormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
 
import { AppComponent } from './app.component';
import { ArticleComponent } from './article.component';
import { ArticleService } from './article.service';
 
@NgModule({
imports: [
BrowserModule,
HttpModule,
ReactiveFormsModule
],
declarations: [
AppComponent,
ArticleComponent
],
providers: [
ArticleService
],
bootstrap: [
AppComponent
]
})
export class AppModule { }
</pre>

Now create a service file where we will make all the request to the server for CRUD operation. Command for creating service is ng g service artcle , for now I have just created a file named it article.service.ts. Let's have a look in the code inside this file.

<pre class="ql-syntax" spellcheck="false">import { Injectable } from '@angular/core';
import { Http, Response, Headers, URLSearchParams, RequestOptions } from '@angular/http';
import { Observable } from 'rxjs';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
 
import { Article } from './article';
 
@Injectable()
export class ArticleService {
//URL for CRUD operations
    articleUrl = "http://localhost:3000/article";
    //Create constructor to get Http instance
    constructor(private http:Http) {
    }
    
    //Fetch all articles
getAllArticles(): Observable<Article[]> {
return this.http.get(this.articleUrl+"/get-article")
              .map(this.extractData)
         .catch(this.handleError);
 
}
    //Create article
createArticle(article: Article):Observable<number> {
     let cpHeaders = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: cpHeaders });
return this.http.post(this.articleUrl+"/create-article", article, options)
.map(success => success.status)
.catch(this.handleError);
}
    //Fetch article by id
getArticleById(articleId: string): Observable<Article> {
        let cpHeaders = new Headers({ 'Content-Type': 'application/json' });
        let options = new RequestOptions({ headers: cpHeaders });
        console.log(this.articleUrl +"/get-article-by-id?id="+ articleId);
        return this.http.get(this.articleUrl +"/get-article-by-id?id="+ articleId)
             .map(this.extractData)
             .catch(this.handleError);
}   
    //Update article
updateArticle(article: Article):Observable<number> {
     let cpHeaders = new Headers({ 'Content-Type': 'application/json' });
        let options = new RequestOptions({ headers: cpHeaders });
return this.http.put(this.articleUrl +"/update-article", article, options)
.map(success => success.status)
.catch(this.handleError);
}
//Delete article    
deleteArticleById(articleId: string): Observable<number> {
        let cpHeaders = new Headers({ 'Content-Type': 'application/json' });
        let options = new RequestOptions({ headers: cpHeaders });
        return this.http.delete(this.articleUrl +"/delete-article?id="+ articleId)
             .map(success => success.status)
             .catch(this.handleError);
}   
    private extractData(res: Response) {
        let body = res.json();
return body;
}
private handleError (error: Response | any) {
        console.error(error.message || error);
        return Observable.throw(error.status);
}
}
</pre>

In the above file we have made all the http request for the CRUD operation. Observables of rxjs library has been used to handle the data fetching from http request.

 

Now let's move to the next file, article.component.ts. Here we have all the login part of the app. Let's have a look code inside this file:

<pre class="ql-syntax" spellcheck="false">import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
 
import { ArticleService } from './article.service';
import { Article } from './article';
 
@Component({
selector: 'app-article',
templateUrl: './article.component.html',
styleUrls: ['./article.component.css']
})
export class ArticleComponent implements OnInit {
//Component properties
allArticles: Article[];
statusCode: number;
requestProcessing = false;
articleIdToUpdate = null;
processValidation = false;
//Create form
articleForm = new FormGroup({
title: new FormControl('', Validators.required),
category: new FormControl('', Validators.required)   
});
//Create constructor to get service instance
constructor(private articleService: ArticleService) {
}
//Create ngOnInit() and and load articles
ngOnInit(): void {
     this.getAllArticles();
}
//Fetch all articles
 
getAllArticles() {
        this.articleService.getAllArticles()
         .subscribe(
data => this.allArticles = data,
                errorCode => this.statusCode = errorCode);
                
}
//Handle create and update article
onArticleFormSubmit() {
     this.processValidation = true;
     if (this.articleForm.invalid) {
     return; //Validation failed, exit from method.
     }
     //Form is valid, now perform create or update
this.preProcessConfigurations();
     let article = this.articleForm.value;
     if (this.articleIdToUpdate === null) {
     //Generate article id then create article
this.articleService.getAllArticles()
     .subscribe(articles => {
            
         //Generate article id    
         let maxIndex = articles.length - 1;
         let articleWithMaxIndex = articles[maxIndex];
         let articleId = articleWithMaxIndex.id + 1;
         article.id = articleId;
         console.log(article,'this is form data---');
         //Create article
    this.articleService.createArticle(article)
             .subscribe(successCode => {
                    this.statusCode = successCode;
                    this.getAllArticles();  
                    this.backToCreateArticle();
                 },
                 errorCode => this.statusCode = errorCode
             );
         });        
     } else {
  //Handle update article
article.id = this.articleIdToUpdate;        
     this.articleService.updateArticle(article)
     .subscribe(successCode => {
         this.statusCode = successCode;
                 this.getAllArticles();  
                    this.backToCreateArticle();
             },
         errorCode => this.statusCode = errorCode);  
     }
}
//Load article by id to edit
loadArticleToEdit(articleId: string) {
this.preProcessConfigurations();
this.articleService.getArticleById(articleId)
     .subscribe(article => {
            console.log(article,'poiuytre');
         this.articleIdToUpdate = article.id;
                    this.articleForm.setValue({ title: article.title, category: article.category });
                    this.processValidation = true;
                    this.requestProcessing = false;
         },
         errorCode => this.statusCode = errorCode);
}
//Delete article
deleteArticle(articleId: string) {
this.preProcessConfigurations();
this.articleService.deleteArticleById(articleId)
     .subscribe(successCode => {
         //this.statusCode = successCode;
                    //Expecting success code 204 from server
                    this.statusCode = 204;
                 this.getAllArticles();  
                 this.backToCreateArticle();
             },
         errorCode => this.statusCode = errorCode);
}
//Perform preliminary processing configurations
preProcessConfigurations() {
this.statusCode = null;
     this.requestProcessing = true;
}
//Go back from update to create
backToCreateArticle() {
this.articleIdToUpdate = null;
this.articleForm.reset(); 
     this.processValidation = false;
}
}
</pre>

Now we have to show the task over browser, So lets have a look inside article.component.html file.

<pre class="ql-syntax" spellcheck="false"><h1 class="text-center">Angular 7 CRUD Demo App</h1>
<h3 class="text-center" *ngIf="articleIdToUpdate; else create">
Update Article for Id: {{articleIdToUpdate}}
</h3>
<ng-template #create>
<h3 class="text-center"> Create New Article </h3>
</ng-template>
<div>
<form [formGroup]="articleForm" (ngSubmit)="onArticleFormSubmit()">
<table class="table-striped" style="margin:0 auto;">
<tr><td>Enter Title</td><td><input formControlName="title">
   <label *ngIf="articleForm.get('title').invalid && processValidation" [ngClass] = "'error'"> Title is required. </label>
 </td></tr>
<tr><td>Enter Category</td><td><input formControlName="category">
   <label *ngIf="articleForm.get('category').invalid && processValidation" [ngClass] = "'error'">Category is required. </label>
  </td></tr>  
<tr><td colspan="2">
   <button class="btn btn-default" *ngIf="!articleIdToUpdate">CREATE</button>
    <button class="btn btn-default" *ngIf="articleIdToUpdate">UPDATE</button>
   <button (click)="backToCreateArticle()" *ngIf="articleIdToUpdate">Go Back</button>
  </td></tr>
</table>
</form>
<br/>
<div class="text-center" *ngIf="statusCode; else processing">
<div *ngIf="statusCode === 201" [ngClass] = "'success'">
   Article added successfully.
</div>
<div *ngIf="statusCode === 409" [ngClass] = "'success'">
Article already exists.
</div>   
<div *ngIf="statusCode === 200" [ngClass] = "'success'">
Article updated successfully.
</div>   
<div *ngIf="statusCode === 204" [ngClass] = "'success'">
Article deleted successfully.
</div>   
<div *ngIf="statusCode === 500" [ngClass] = "'error'">
Internal Server Error.
</div> 
</div>
<ng-template #processing>
  <img *ngIf="requestProcessing" src="assets/images/loading.gif">
</ng-template>
</div>
<h3 class="text-center">Article List</h3>
<table class="table-striped" style="margin:0 auto;" *ngIf="allArticles">
<tr><th> Id</th> <th>Title</th><th>Category</th><th></th><th></th></tr>
<tr *ngFor="let article of allArticles" >
<td>{{article.id}}</td> <td>{{article.title}}</td> <td>{{article.category}}</td>
  <td><button class="btn btn-default" type="button"(click)="loadArticleToEdit(article.id)">Edit</button> </td>
  <td><button class="btn btn-default" type="button"(click)="deleteArticle(article.id)">Delete</button></td>
</tr>
</table>
</pre>

Now since I have created server and client two separate folder for nodejs and angular task. So will run both the apps with npm start over two tabs of terminal.

On the browser, over link <a href="http://localhost:4200." target="_blank">http://localhost:4200.</a> App will look like below

 

That’s all for now. Thank you for reading and I hope this post will be very helpful for creating CRUD operations with angular7,node.js & mysql.



================================================

Thanks for reading :heart: If you liked this post, share it with all of your programming buddies! Follow me on <a href="https://l.morioh.com/b0a3f595aa?r=https://www.facebook.com/angular4u" title="" target="_blank">Facebook</a> | <a href="https://l.morioh.com/b0a3f595aa?r=https://twitter.com/codek_tv" title="" target="_blank">Twitter</a>

Learn More

☞ <a href="https://l.morioh.com/b0a3f595aa?r=http://learnstartup.net/p/H1jE_tD3l" title="" target="_blank">Angular 8 (formerly Angular 2) - The Complete Guide</a>

☞ <a href="https://l.morioh.com/b0a3f595aa?r=http://learnstartup.net/p/HJigQzgZx" title="" target="_blank">Learn and Understand AngularJS</a>

☞ <a href="https://l.morioh.com/b0a3f595aa?r=http://learnstartup.net/p/ry7Ey9yKW" title="" target="_blank">The Complete Angular Course: Beginner to Advanced</a>

☞ <a href="https://l.morioh.com/b0a3f595aa?r=http://learnstartup.net/p/rkjpGfa5W" title="" target="_blank">Angular Crash Course for Busy Developers</a>

☞ <a href="https://l.morioh.com/b0a3f595aa?r=http://learnstartup.net/p/SkdU19JFZ" title="" target="_blank">Angular Essentials (Angular 2+ with TypeScript)</a>

☞ <a href="https://l.morioh.com/b0a3f595aa?r=http://learnstartup.net/p/B1oHaY8cM" title="" target="_blank">Angular (Full App) with Angular Material, Angularfire & NgRx</a>

☞ <a href="https://l.morioh.com/b0a3f595aa?r=http://learnstartup.net/p/Skf7ILFw3l" title="" target="_blank">Angular & NodeJS - The MEAN Stack Guide</a>



Angular 8 Forms Tutorial - Reactive Forms Validation Example

Angular 8 Forms Tutorial - Reactive Forms Validation Example

The example is a simple registration form with pretty standard fields for title, first name, last name, email, password, confirm password and an accept Ts & Cs checkbox. All fields are required including the checkbox, the email field must be a valid email address and the password field must have a min length of 6. There's also a custom validator called MustMatch which is used to validate that the confirm password and password fields match.

I've setup the form to validate on submit rather than as soon as each field is changed, this is implemented with a submitted property in the app component that is set to true when the form is submitted for the first time, and reset to false if the cancel button is clicked.

Styling of the example is all done with Bootstrap 4.3 CSS.

See on StackBlitz at https://stackblitz.com/edit/angular-8-reactive-form-validation

Reactive Forms Validation App Component

The app component defines the form fields and validators for our registration form using an Angular FormBuilder to create an instance of a FormGroup that is stored in the registerForm property. The registerForm is then bound to the form in the app template below using the [formGroup] directive.

I also added a getter f as a convenience property to make it easier to access form controls from the template. So for example you can access the confirmPassword field in the template using f.confirmPassword instead of registerForm.controls.confirmPassword.

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';

// import custom validator to validate that password and confirm password fields match
import { MustMatch } from './_helpers/must-match.validator';

@Component({ selector: 'app', templateUrl: 'app.component.html' })
export class AppComponent implements OnInit {
    registerForm: FormGroup;
    submitted = false;

    constructor(private formBuilder: FormBuilder) { }

    ngOnInit() {
        this.registerForm = this.formBuilder.group({
            title: ['', Validators.required],
            firstName: ['', Validators.required],
            lastName: ['', Validators.required],
            email: ['', [Validators.required, Validators.email]],
            password: ['', [Validators.required, Validators.minLength(6)]],
            confirmPassword: ['', Validators.required],
            acceptTerms: [false, Validators.requiredTrue]
        }, {
            validator: MustMatch('password', 'confirmPassword')
        });
    }

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

    onSubmit() {
        this.submitted = true;

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

        // display form values on success
        alert('SUCCESS!! :-)\n\n' + JSON.stringify(this.registerForm.value, null, 4));
    }

    onReset() {
        this.submitted = false;
        this.registerForm.reset();
    }
}

Reactive Forms Validation App Template

The app component template contains all the html markup for displaying the example registration form in your browser. The form element uses the [formGroup] directive to bind to the registerForm FormGroup in the app component above.

The form binds the form submit event to the onSubmit() handler in the app component using the Angular event binding (ngSubmit)="onSubmit()". Validation messages are displayed only after the user attempts to submit the form for the first time, this is controlled with the submitted property of the app component.

The cancel button click event is bound to the onReset() handler in the app component using the Angular event binding (click)="onReset()".

<!-- main app container -->
<div class="card m-3">
    <h5 class="card-header">Angular 8 Reactive Form Validation</h5>
    <div class="card-body">
        <form [formGroup]="registerForm" (ngSubmit)="onSubmit()">
            <div class="form-row">
                <div class="form-group col">
                    <label>Title</label>
                    <select formControlName="title" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.title.errors }">
                        <option value=""></option>
                        <option value="Mr">Mr</option>
                        <option value="Mrs">Mrs</option>
                        <option value="Miss">Miss</option>
                        <option value="Ms">Ms</option>
                    </select>
                    <div *ngIf="submitted && f.title.errors" class="invalid-feedback">
                        <div *ngIf="f.title.errors.required">Title is required</div>
                    </div>
                </div>
                <div class="form-group col-5">
                    <label>First Name</label>
                    <input type="text" formControlName="firstName" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.firstName.errors }" />
                    <div *ngIf="submitted && f.firstName.errors" class="invalid-feedback">
                        <div *ngIf="f.firstName.errors.required">First Name is required</div>
                    </div>
                </div>
                <div class="form-group col-5">
                    <label>Last Name</label>
                    <input type="text" formControlName="lastName" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.lastName.errors }" />
                    <div *ngIf="submitted && f.lastName.errors" class="invalid-feedback">
                        <div *ngIf="f.lastName.errors.required">Last Name is required</div>
                    </div>
                </div>
            </div>
            <div class="form-group">
                <label>Email</label>
                <input type="text" formControlName="email" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.email.errors }" />
                <div *ngIf="submitted && f.email.errors" class="invalid-feedback">
                    <div *ngIf="f.email.errors.required">Email is required</div>
                    <div *ngIf="f.email.errors.email">Email must be a valid email address</div>
                </div>
            </div>
            <div class="form-row">
                <div class="form-group col">
                    <label>Password</label>
                    <input type="password" formControlName="password" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.password.errors }" />
                    <div *ngIf="submitted && f.password.errors" class="invalid-feedback">
                        <div *ngIf="f.password.errors.required">Password is required</div>
                        <div *ngIf="f.password.errors.minlength">Password must be at least 6 characters</div>
                    </div>
                </div>
                <div class="form-group col">
                    <label>Confirm Password</label>
                    <input type="password" formControlName="confirmPassword" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.confirmPassword.errors }" />
                    <div *ngIf="submitted && f.confirmPassword.errors" class="invalid-feedback">
                        <div *ngIf="f.confirmPassword.errors.required">Confirm Password is required</div>
                        <div *ngIf="f.confirmPassword.errors.mustMatch">Passwords must match</div>
                    </div>
                </div>
            </div>
            <div class="form-group form-check">
                <input type="checkbox" formControlName="acceptTerms" id="acceptTerms" class="form-check-input" [ngClass]="{ 'is-invalid': submitted && f.acceptTerms.errors }" />
                <label for="acceptTerms" class="form-check-label">Accept Terms & Conditions</label>
                <div *ngIf="submitted && f.acceptTerms.errors" class="invalid-feedback">Accept Ts & Cs is required</div>
            </div>
            <div class="text-center">
                <button class="btn btn-primary mr-1">Register</button>
                <button class="btn btn-secondary" type="reset" (click)="onReset()">Cancel</button>
            </div>
        </form>
    </div>
</div>

Reactive Forms Custom "Must Match" Validator

The custom MustMatch validator is used in this example to validate that both of the password fields - password and confirmPassword - are matching. However it can be used to validate that any pair of fields is matching (e.g. email and confirm email fields).

It works slightly differently than a typical custom validator because I'm setting the error on the second field instead of returning it to be set on the formGroup. I did it this way because I think it makes the template a bit cleaner and more intuitive, the mustMatch validation error is displayed below the confirmPassword field so I think it makes sense that the error is attached the the confirmPassword form control.

import { FormGroup } from '@angular/forms';

// custom validator to check that two fields match
export function MustMatch(controlName: string, matchingControlName: string) {
    return (formGroup: FormGroup) => {
        const control = formGroup.controls[controlName];
        const matchingControl = formGroup.controls[matchingControlName];

        if (matchingControl.errors && !matchingControl.errors.mustMatch) {
            // return if another validator has already found an error on the matchingControl
            return;
        }

        // set error on matchingControl if validation fails
        if (control.value !== matchingControl.value) {
            matchingControl.setErrors({ mustMatch: true });
        } else {
            matchingControl.setErrors(null);
        }
    }
}

Reactive Forms Validation App Module

There isn't much going on in the app module other than the standard stuff, the main thing you need to remember for using reactive forms in Angular is to import the ReactiveFormsModule from '@angular/forms' and include it in the imports array of the @NgModule decorator.

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReactiveFormsModule } from '@angular/forms';

import { AppComponent } from './app.component';

@NgModule({
    imports: [
        BrowserModule,
        ReactiveFormsModule
    ],
    declarations: [
        AppComponent
    ],
    bootstrap: [AppComponent]
})
export class AppModule { }

Thanks for reading

If you liked this post, share it with all of your programming buddies!

Follow us on Facebook | Twitter

Further reading

Angular 8 (formerly Angular 2) - The Complete Guide

Angular & NodeJS - The MEAN Stack Guide

The Web Developer Bootcamp

Angular 8 is coming

MEAN Stack Angular 8 CRUD Web Application

Angular 8 + Spring Boot 2.2: Build a CRUD App Today!

Best 17 Angular Libraries Every Angular Developers Should Know in 2019

React vs Angular vs Vue.js by Example

Best JavaScript Frameworks, Libraries and Tools to Use in 2019

Angular Authentication with JWT 

Angular 8 Pagination Example and Tutorial

Angular 8 Pagination Example and Tutorial

For example, when you search something that returns a large number of records which cannot be shown on a single web page therefore, those records are part into number of pages that can be accessed through links via pagination structure.

So today in this demo we will discuss the simple pagination in Angular 8.

Step 1: Create a basic app with angular cli

<pre class="ql-syntax" spellcheck="false">ng new angular8-simple-pagination-example </pre>

By typing the above command we will see a basic angular app created on the current folder. So move to the created folder by typing cd angular8-simple-pagination-example/. You can check the newly created app by typing <a href="http://localhost:4200" title="" target="_blank">http://localhost:4200</a> on the browser.

Step 2: install ngx-pagination pagination dependency from terminal

So run the below command over terminal

<pre class="ql-syntax" spellcheck="false">npm install ngx-pagination --save </pre>

Step 3: Create dummy records for pagination

Now we will create static data to show the pagination. So lets have a look on the code under file app.component.ts 

<pre class="ql-syntax" spellcheck="false">import { Component } from '@angular/core'; import {NgxPaginationModule} from 'ngx-pagination'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { title = 'simple pagination demo'; collection = []; constructor(){ for(let i=1;i<=100;i++){ let Obj = {'name': `Employee Name ${i}`,'code': `EMP00 ${i}`} this.collection.push(Obj); } } } </pre>

In the above file, we can see that inside constructor we have created a loop for created dummy record for 100 employees having employee name & code for showing pagination.

Step 4: Import dependency in app.module.ts

Now let's have a look on the code inside app.module.ts where the ngx-pagination module has been imported

<pre class="ql-syntax" spellcheck="false">import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core';   import { NgxPaginationModule } from 'ngx-pagination'; import { AppComponent } from './app.component';   @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, NgxPaginationModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { } </pre>

Step 5: Update view from app.component.html

Now one last step needed to do is, add the below code anywhere inside app.component.html

<pre class="ql-syntax" spellcheck="false"><ul> <li> Emp Name | Emp code</li> <li *ngFor="let item of collection | paginate:{itemsPerPage: 5, currentPage:p}"> {{item.name}} | {{item.code}}</li>  </ul> <pagination-controls (pageChange)="p=$event"></pagination-controls> </pre>

Now, we are done with all the needed steps for the pagination in our angular application. 

 

Step 6: Run the app

Run the app over the terminal with npm start and check the app after typing the url <a href="http://localhost:4200/." target="_blank">http://localhost:4200/.</a> A page will open like below:

Conclusion

By following these easy steps we can easily achieve the client side pagination in Angular 8 application. If you want to impliment server side pagination in angular8 <a href="https://jsonworld.com/demo/server-side-pagination-in-angular-example-and-tutorial" title="" target="_blank">Server Side Pagination in Angular Example and Tutorial</a> . You can also find other demos of<a href="https://jsonworld.com/demo/angular-sample-projects" title="" target="_blank"> Angular Sample Application</a> here to start working on enterprise level application. <a href="https://www.npmjs.com/package/ngx-pagination" title="" target="_blank">Click here</a> to view more about the pagination package over npm.

Originally published by Suraj Roy at jsonworld.com

==========================================

Thanks for reading :heart: If you liked this post, share it with all of your programming buddies! Follow me on <a href="https://www.facebook.com/angular4u" title="" target="_blank">Facebook</a> | <a href="https://twitter.com/codek_tv" title="" target="_blank">Twitter</a>

Learn More

☞ <a href="http://learnstartup.net/p/H1jE_tD3l" title="" target="_blank">Angular 8 (formerly Angular 2) - The Complete Guide</a>

☞ <a href="http://learnstartup.net/p/HJigQzgZx" title="" target="_blank">Learn and Understand AngularJS</a>

☞ <a href="http://learnstartup.net/p/ry7Ey9yKW" title="" target="_blank">The Complete Angular Course: Beginner to Advanced</a>

☞ <a href="http://learnstartup.net/p/rkjpGfa5W" title="" target="_blank">Angular Crash Course for Busy Developers</a>

☞ <a href="http://learnstartup.net/p/SkdU19JFZ" title="" target="_blank">Angular Essentials (Angular 2+ with TypeScript)</a>

☞ <a href="http://learnstartup.net/p/B1oHaY8cM" title="" target="_blank">Angular (Full App) with Angular Material, Angularfire & NgRx</a>

☞ <a href="https://l.morioh.com/b0a3f595aa?r=http://learnstartup.net/p/Skf7ILFw3l" title="" target="_blank">Angular & NodeJS - The MEAN Stack Guide</a>

Angular Architecture Patterns and Best Practices (that help to scale)

Angular Architecture Patterns and Best Practices (that help to scale)
<p class="ql-align-center">Originally published by Bartosz Pietrucha at angular-academy.com</p><p> In order to deal with mentioned factors to maintain a high quality of delivery and prevent technical debt, robust and well-grounded architecture is necessary. Angular itself is a quite opinionated framework, forcing developers to do things the proper way, yet there are a lot of places where things can go wrong. In this article, I will present high-level recommendations of well-designed Angular application architecture based on best practices and battle-proven patterns. Our ultimate goal in this article is to learn how to design Angular application in order to maintain sustainable development speed and ease of adding new features in the long run. To achieve these goals, we will apply:</p>
    <li>proper abstractions between application layers,</li><li>unidirectional data flow,</li><li>reactive state management,</li><li>modular design,</li><li>smart and dumb components pattern.</li>
<p></p>

Problems of scalability in front-end

<p>Let's think about problems in terms of scalability we can face in the development of modern front-end applications. Today, front-end applications are not "just displaying" data and accepting user inputs. Single Page Applications (SPAs) are providing users with rich interactions and use backend mostly as a data persistence layer. This means, far more responsibility has been moved to the front-end part of software systems. This leads to a growing complexity of front-end logic, we need to deal with. Not only the number of requirements grows over time, but the amount of data we load into the application is increasing. On top of that, we need to maintain application performance, which can easily be hurt. Finally, our development teams are growing (or at least rotating - people come and go) and it is important for new-comers to get up to speed as fast as possible.</p><p></p><p>One of the solutions to the problems described above is solid system architecture. But, this comes with the cost, the cost of investing in that architecture from day one. It can be very tempting for us developers, to deliver new features very quickly, when the system is still very small. At this stage, everything is easy and understandable, so development goes really fast. But, unless we care about the architecture, after a few developers rotations, tricky features, refactorings, a couple of new modules, the speed of development slows down radically. Below diagram presents how it usually looked like in my development career. This is not any scientifical study, it's just how I see it.</p><p></p>

Software architecture

<p>To discuss architecture best practices and patterns, we need to answer a question, what the software architecture is, in the first place. Martin Fowlerdefines architecture as "highest-level breakdown of a system into its parts". On top of that, I would say that software architecture describes how the software is composed of its parts and what are the rules and constraints of the communication between those parts. Usually, the architectural decisions that we make in our system development, are hard to change as the system grows over time. That's why it is very important to pay attention to those decisions from the very beginning of our project, especially if the software we build is supposed to be running in production for many years. Robert C. Martin once said: the true cost of software is its maintenance. Having well-grounded architecture helps to reduce the costs of the system's maintenance.</p>
Software architecture is the way the software is composed of its parts and the rules and constraints of the communication between those parts

High-level abstraction layers

<p>The first way, we will be decomposing our system, is through the abstraction layers. Below diagram depicts the general concept of this decomposition. The idea is to place the proper responsibility into the proper layer of the system: coreabstraction or presentation layer. We will be looking at each layer independently and analyzing its responsibility. This division of the system also dictates communication rules. For example, the presentation layer can talk to the core layer only through the abstractionlayer. Later, we will learn what are the benefits of this kind of constraint.</p><p></p>

Presentation layer

<p>Let's start analyzing our system break-down from the presentation layer. This is the place where all our Angular components live. The only responsibilities of this layer are to present and to delegate. In other words, it presents the UI and delegates user's actions to the core layer, through the abstraction layer. It knows what to display and what to do, but it does not know how the user's interactions should be handled.</p><p>Below code snippet contains CategoriesComponent using SettingsFacadeinstance from abstraction layer to delegate user's interaction (via addCategory() and updateCategory()) and present some state in its template (via isUpdating$).</p><pre class="ql-syntax" spellcheck="false">@Component({ selector: 'categories', templateUrl: './categories.component.html', styleUrls: ['./categories.component.scss'] }) export class CategoriesComponent implements OnInit {

@Input() cashflowCategories$: CashflowCategory[];
newCategory: CashflowCategory = new CashflowCategory();
isUpdating$: Observable<boolean>;

constructor(private settingsFacade: SettingsFacade) {
this.isUpdating$ = settingsFacade.isUpdating$();
}

ngOnInit() {
this.settingsFacade.loadCashflowCategories();
}

addCategory(category: CashflowCategory) {
this.settingsFacade.addCashflowCategory(category);
}

updateCategory(category: CashflowCategory) {
this.settingsFacade.updateCashflowCategory(category);
}

}
</pre>

Abstraction layer

<p>The abstraction layer decouples the presentation layer from the core layer and also has it's very own defined responsibilities. This layer exposes the streams of state and interface for the components in the presentation layer, playing the role of the facade. This kind of facade sandboxes what components can see and do in the system. We can implement facades by simply using Angular class providers. The classes here may be named with Facade postfix, for example SettingsFacade. Below, you can find an example of such a facade.</p><pre class="ql-syntax" spellcheck="false">@Injectable()
export class SettingsFacade {

constructor(private cashflowCategoryApi: CashflowCategoryApi, private settingsState: SettingsState) { }

isUpdating$(): Observable<boolean> {
return this.settingsState.isUpdating$();
}

getCashflowCategories$(): Observable<CashflowCategory[]> {
// here we just pass the state without any projections
// it may happen that it is necessary to combine two or more streams and expose to the components
return this.settingsState.getCashflowCategories$();
}

loadCashflowCategories() {
return this.cashflowCategoryApi.getCashflowCategories()
.pipe(tap(categories => this.settingsState.setCashflowCategories(categories)));
}

// optimistic update
// 1. update UI state
// 2. call API
addCashflowCategory(category: CashflowCategory) {
this.settingsState.addCashflowCategory(category);
this.cashflowCategoryApi.createCashflowCategory(category)
.subscribe(
(addedCategoryWithId: CashflowCategory) => {
// success callback - we have id generated by the server, let's update the state
this.settingsState.updateCashflowCategoryId(category, addedCategoryWithId)
},
(error: any) => {
// error callback - we need to rollback the state change
this.settingsState.removeCashflowCategory(category);
console.log(error);
}
);
}

// pessimistic update
// 1. call API
// 2. update UI state
updateCashflowCategory(category: CashflowCategory) {
this.settingsState.setUpdating(true);
this.cashflowCategoryApi.updateCashflowCategory(category)
.subscribe(
() => this.settingsState.updateCashflowCategory(category),
(error) => console.log(error),
() => this.settingsState.setUpdating(false)
);
}
}
</pre>

Abstraction interface

<p>We already know the main responsibilities for this layer; to expose streams of state and interface for the components. Let's start with the interface. Public methods loadCashflowCategories()addCashflowCategory() and updateCashflowCategory() abstract away the details of state management and the external API calls from the components. We are not using API providers (like CashflowCategoryApi) in components directly, as they live in the core layer. Also, how the state changes is not a concern of the components. The presentation layer should not care about how things are done and components should just call the methods from the abstraction layer when necessary (delegate). Looking at the public methods in our abstraction layer should give us a quick insight about high-level use casesin this part of the system.</p><p>But we should remember that the abstraction layer is not a place to implement business logic. Here we just want to connect the presentation layer to our business logic, abstracting the way it is connected.</p>

State

<p>When it comes to the state, the abstraction layer makes our components independent of the state management solution. Components are given Observables with data to display on the templates (usually with async pipe) and don't care how and where this data comes from. To manage our state we can pick any state management library that supports RxJS (like NgRx) or simple use BehaviorSubjects to model our state. In the example above we are using state object that internally uses BehaviorSubjects (state object is a part of our core layer). In the case of NgRx, we would dispatch actions for the store.</p><p>Having this kind abstraction gives us a lot of flexibility and allows to change the way we manage state not even touching the presentation layer. It's even possible to seamlessly migrate to a real-time backend like Firebase, making our application real-time. I personally like to start with BehaviorSubjects to manage the state. If later, at some point in the development of the system, there is a need to use something else, with this kind of architecture, it is very easy to refactor.</p>

Synchronization strategy

<p>Now, let's take a closer look at the other important aspect of the abstraction layer. Regardless of the state management solution we choose, we can implement UI updates in either optimistic or pessimistic fashion. Imagine we want to create a new record in the collection of some entities. This collection was fetched from the backend and displayed in the DOM. In a pessimistic approach, we first try to update the state on the backend side (for example with HTTP request) and in case of success we update the state in the frontend application. On the other hand, in an optimistic approach, we do it in a different order. First, we assume that the backend update will succeed and update frontend state immediately. Then we send request to update server state. In case of success, we don't need to do anything, but in case of failure, we need to rollback the change in our frontend application and inform the user about this situation.</p>
Optimistic update changes the UI state first and attempts to update the backend state. This provides a user with a better experience, as he does not see any delays, because of network latency. If backend update fails, then UI change has to be rolled back.
Pessimistic update changes the backend state first and only in case of success updates the UI state. Usually, it is necessary to show some kind of spinner or loading bar during the execution of backend request, because of network latency.

Caching

<p>Sometimes, we may decide that the data we fetch from the backend will not be a part of our application state. This may be useful for read-only data that we don't want to manipulate at all and just pass (via abstraction layer) to the components. In this case, we can apply data caching in our facade. The easiest way to achieve it is to use shareReplay() RxJS operator that will replay the last value in the stream for each new subscriber. Take a look at the code snippet below with RecordsFacade using RecordsApi to fetch, cache and filter the data for the components.</p><pre class="ql-syntax" spellcheck="false">@Injectable()
export class RecordsFacade {

private records$: Observable<Record[]>;

constructor(private recordApi: RecordApi) {
this.records$ = this.recordApi
.getRecords()
.pipe(shareReplay(1)); // cache the data
}

getRecords() {
return this.records$;
}

// project the cached data for the component
getRecordsFromPeriod(period?: Period): Observable<Record[]> {
return this.records$
.pipe(map(records => records.filter(record => record.inPeriod(period))));
}

searchRecords(search: string): Observable<Record[]> {
return this.recordApi.searchRecords(search);
}
}
</pre><p>To sum up, what we can do in the abstraction layer is to:</p>

    <li>expose methods for the components in which we:</li><li class="ql-indent-1">delegate logic execution to the core layer,</li><li class="ql-indent-1">decide about data synchronization strategy (optimistic vs. pessimistic),</li><li>expose streams of state for the components:</li><li class="ql-indent-1">pick one or more streams of UI state (and combine them if necessary),</li><li class="ql-indent-1">cache data from external API.</li>
<p>As we see, the abstraction layer plays an important role in our layered architecture. It has clearly defined responsibilities what helps to better understand and reason about the system. Depending on your particular case, you can create one facade per Angular module or one per each entity. For example, the SettingsModule may have a single SettingsFacade, if it's not too bloated. But sometimes it may be better to create more-granular abstraction facades for each entity individually, like UserFacade for Userentity.</p>

Core layer

<p>The last layer is the core layer. Here is where core application logic is implemented. All data manipulation and outside world communicationhappen here. If for state management, we were using a solution like NgRx, here is a place to put our state definition, actions and reducers. Since in our examples we are modeling state with BehaviorSubjects, we can encapsulate it in a convenient state class. Below, you can find SettingsState example from the core layer.</p><pre class="ql-syntax" spellcheck="false">@Injectable()
export class SettingsState {

private updating$ = new BehaviorSubject<boolean>(false);
private cashflowCategories$ = new BehaviorSubject<CashflowCategory[]>(null);

isUpdating$() {
return this.updating$.asObservable();
}

setUpdating(isUpdating: boolean) {
this.updating$.next(isUpdating);
}

getCashflowCategories$() {
return this.cashflowCategories$.asObservable();
}

setCashflowCategories(categories: CashflowCategory[]) {
this.cashflowCategories$.next(categories);
}

addCashflowCategory(category: CashflowCategory) {
const currentValue = this.cashflowCategories$.getValue();
this.cashflowCategories$.next([...currentValue, category]);
}

updateCashflowCategory(updatedCategory: CashflowCategory) {
const categories = this.cashflowCategories$.getValue();
const indexOfUpdated = categories.findIndex(category => category.id === updatedCategory.id);
categories[indexOfUpdated] = updatedCategory;
this.cashflowCategories$.next([...categories]);
}

updateCashflowCategoryId(categoryToReplace: CashflowCategory, addedCategoryWithId: CashflowCategory) {
const categories = this.cashflowCategories$.getValue();
const updatedCategoryIndex = categories.findIndex(category => category === categoryToReplace);
categories[updatedCategoryIndex] = addedCategoryWithId;
this.cashflowCategories$.next([...categories]);
}

removeCashflowCategory(categoryRemove: CashflowCategory) {
const currentValue = this.cashflowCategories$.getValue();
this.cashflowCategories$.next(currentValue.filter(category => category !== categoryRemove));
}
}
</pre><p>In the core layer, we also implement HTTP queries in the form of class providers. This kind of class could have Api or Service name postfix. API services have only one responsibility - it is just to communicate with API endpoints and nothing else. We should avoid any caching, logic or data manipulation here. A simple example of API service can be found below.</p><pre class="ql-syntax" spellcheck="false">@Injectable()
export class CashflowCategoryApi {

readonly API = '/api/cashflowCategories';

constructor(private http: HttpClient) {}

getCashflowCategories(): Observable<CashflowCategory[]> {
return this.http.get<CashflowCategory[]>(this.API);
}

createCashflowCategory(category: CashflowCategory): Observable<any> {
return this.http.post(this.API, category);
}

updateCashflowCategory(category: CashflowCategory): Observable<any> {
return this.http.put(${this.API}/${category.id}, category);
}

}
</pre><p>In this layer, we could also place any validators, mappers or more advanced use-cases that require manipulating many slices of our UI state.</p><p>We have covered the topic of the abstraction layers in our frontend application. Every layer has it's well-defined boundaries and responsibilities. We also defined the strict rules of communication between layers. This all helps to better understand and reason about the system over time as it becomes more and more complex.</p><p></p><p>If you need help with your project, check out Angualar Academy Workshops or write an email to [email protected].</p>

Unidirectional data flow and reactive state management

<p>The next principle we want to introduce in our system is about the data flow and propagation of change. Angular itself uses unidirectional data flow on presentation level (via input bindings), but we will impose a similar restriction on the application level. Together with reactive state management (based on streams), it will give us the very important property of the system - data consistency. Below diagram presents the general idea of unidirectional data flow.</p><p></p><p>Whenever any model value change in our application, Angular change detection system takes care of the propagation of that change. It does it via input property bindings from the top to bottom of the whole component tree. It means that a child component can only depend on its parent and never vice versa. This is why we call it unidirectional data flow. This allows Angular to traverse the components tree only once (as there are no cycles in the tree structure) to achieve a stable state, which means that every value in the bindings is propagated.</p><p>As we know from previous chapters, there is the core layer above the presentation layer, where our application logic is implemented. There are the services and providers that operate on our data. What if we apply the same principle of data manipulation on that level? We can place the application data (the state) in one place "above" the components and propagate the values down to the components via Observable streams (Redux and NgRx call this place a store). The state can be propagated to multiple components and displayed in multiple places, but never modified locally. The change may come only "from above" and the components below only "reflect" the current state of the system. This gives us the important system's property mentioned before - data consistency - and the state object becomes the single source of truth. Practically speaking, we can display the same data in multiple places and not be afraid that the values would differ.</p><p>Our state object exposes the methods for the services in our core layer to manipulate the state. Whenever there is a need to change the state, it can happen only by calling a method on the state object (or dispatching an action in case of using NgRx). Then, the change is propagated "down", via streams, the to presentation layer (or any other service). This way, our state management is reactive. Moreover, with this approach, we also increase the level of predictability in our system, because of strict rules of manipulating and sharing the application state. Below you can find a code snippet modeling the state with BehaviorSubjects.</p><pre class="ql-syntax" spellcheck="false">@Injectable()
export class SettingsState {

private updating$ = new BehaviorSubject<boolean>(false);
private cashflowCategories$ = new BehaviorSubject<CashflowCategory[]>(null);

isUpdating$() {
return this.updating$.asObservable();
}

setUpdating(isUpdating: boolean) {
this.updating$.next(isUpdating);
}

getCashflowCategories$() {
return this.cashflowCategories$.asObservable();
}

setCashflowCategories(categories: CashflowCategory[]) {
this.cashflowCategories$.next(categories);
}

addCashflowCategory(category: CashflowCategory) {
const currentValue = this.cashflowCategories$.getValue();
this.cashflowCategories$.next([...currentValue, category]);
}

updateCashflowCategory(updatedCategory: CashflowCategory) {
const categories = this.cashflowCategories$.getValue();
const indexOfUpdated = categories.findIndex(category => category.id === updatedCategory.id);
categories[indexOfUpdated] = updatedCategory;
this.cashflowCategories$.next([...categories]);
}

updateCashflowCategoryId(categoryToReplace: CashflowCategory, addedCategoryWithId: CashflowCategory) {
const categories = this.cashflowCategories$.getValue();
const updatedCategoryIndex = categories.findIndex(category => category === categoryToReplace);
categories[updatedCategoryIndex] = addedCategoryWithId;
this.cashflowCategories$.next([...categories]);
}

removeCashflowCategory(categoryRemove: CashflowCategory) {
const currentValue = this.cashflowCategories$.getValue();
this.cashflowCategories$.next(currentValue.filter(category => category !== categoryRemove));
}
}

</pre><p>Let's recap the steps of handling the user interaction, having in mind all the principles we have already introduced. First, let's imagine that there is some event in the presentation layer (for example button click). The component delegates the execution to the abstraction layer, calling the method on the facade settingsFacade.addCategory(). Then, the facade calls the methods on the services in the core layer - categoryApi.create() and settingsState.addCategory(). The order of invocation of those two methods depends on synchronization strategy we choose (pessimistic or optimistic). Finally, the application state is propagated down to the presentation layer via the observable streams. This process is well-defined.</p><p></p>

Modular design

<p>We have covered the horizontal division in our system and the communication patterns across it. Now we are going to introduce a vertical separation into feature modules. The idea is to slice the application into feature modules representing different business functionalities. This is yet another step to deconstruct the system into smaller pieces for better maintainability. Each of the features modules share the same horizontal separation of the core, abstraction, and presentation layer. It is important to note, that these modules could be lazily loaded (and preloaded) into the browser increasing the initial load time of the application. Below you can find a diagram illustrating features modules separation.</p><p></p><p>Our application has also two additional modules for more technical reasons. We have a CoreModule that defines our singleton services, single-instance components, configuration, and export any third-party modules needed in AppModule. This module is imported only once in AppModule. The second module is SharedModule that contains common components/pipes/directives and also export commonly used Angular modules (like CommonModule). SharedModule can be imported by any feature module. The diagram below presents the imports structure.</p><p></p>

Module directory structure

<p>Below diagram presents how we can place all the pieces of our SettingsModule inside the directories. We can put the files inside of the folders with a name representing their function.</p><p></p>

Smart and dumb components

<p>The final architectural pattern we introduce in this article is about components themselves. We want to divide components into two categories, depending on their responsibilities. First, are the smart components (aka containers). These components usually:</p>
    <li>have facade/s and other services injected,</li><li>communicate with the core layer,</li><li>pass data to the dumb components,</li><li>react to the events from dumb components,</li><li>are top-level routable components (but not always!).</li>
<p>Previously presented CategoriesComponent is smart. It has SettingsFacadeinjected and uses it to communicate with the core layer of our application.</p><p>In the second category, there are dumb components (aka presentational). Their only responsibilities are to present UI element and to delegate user interaction "up" to the smart components via events. Think of a native HTML element like <button>Click me</button>. That element does not have any particular logic implemented. We can think of the text 'Click me' as an input for this component. It also has some events that can be subscribed to, like click event. Below you can find a code snippet of a simple presentational component with one input and no output events.</p><pre class="ql-syntax" spellcheck="false">@Component({
selector: 'budget-progress',
templateUrl: './budget-progress.component.html',
styleUrls: ['./budget-progress.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class BudgetProgressComponent {

@Input()
budget: Budget;
today: string;

}
</pre>

Summary

<p>We have covered a couple of ideas on how to design the architecture of an Angular application. These principles, if applied wisely, can help to maintain sustainable development speed over time, and allow new features to be delivered easily. Please don't treat them as some strict rules, but rather recommendations that could be employed when they make sense.</p><p>We have taken a close look at the abstractions layers, unidirectional data flow, and reactive state management, modular design, and smart/dumb components pattern. I hope that these concepts will be helpful in your projects and, as always, if you have any questions, I am more than happy to chat with you.</p><p class="ql-align-center">Originally published by Bartosz Pietrucha at angular-academy.com</p><p>==========================================</p><p>Thanks for reading :heart: If you liked this post, share it with all of your programming buddies! Follow me on Facebook | Twitter</p>

Learn More

<p>☞ Angular 8 (formerly Angular 2) - The Complete Guide</p><p>☞ Complete Angular 8 from Zero to Hero | Get Hired</p><p>☞ Learn and Understand AngularJS</p><p>☞ The Complete Angular Course: Beginner to Advanced</p><p>☞ Angular Crash Course for Busy Developers</p><p>☞ Angular Essentials (Angular 2+ with TypeScript)</p><p>☞ Angular (Full App) with Angular Material, Angularfire & NgRx</p><p>☞ Angular & NodeJS - The MEAN Stack Guide</p>

Angular 8 Tutorial By Example: (REST API, HttpClient GET, Components, Services & ngFor)

Angular 8 Tutorial By Example: (REST API, HttpClient GET, Components, Services & ngFor)

We’ll also learn how to use the basic concepts of Angular like components and services and how to use the ngFor directive to display collections of data.

We’ll be consuming a JSON API available from NewsAPI.org

Throughout this tutorial, we are going to build a simple example from scratch using Angular CLI 8 and we’ll see how to use HttpClient to send GET requests to third-party REST API servers and how to consume and display the returned JSON data.

In more details, we'll learn:

  • How to create an Angular 8 project using Angular CLI,
  • How to quickly set up routing in our project,
  • How to create Angular components and services,
  • How to subscribe to Observables,
  • How to use the ngFor directive in templates to iterate over data.

You can also follow this tutorial as a video:

<iframe class="ql-video" frameborder="0" allowfullscreen="true" src="https://www.youtube.com/embed/lZAP871qYDw?showinfo=0"></iframe>

Prerequisites

Before getting started, you need a few requirements. You need to have the following tools installed on your development machine:

  • Node.js and npm. You can install both of them from the official website.
  • Angular CLI 8 (You can install it from npm using: npm install -g @angular/cli)

Creating an Angular 8 Project

Now let’s create our Angular 8 project. Open a new terminal and run the following command:

<pre class="ql-syntax" spellcheck="false">$ ng new angular-httpclient-demo </pre>

The CLI will prompt you if Would you like to add Angular routing? (y/N), type y. And Which stylesheet format would you like to use? Choose CSS and type Enter.

Next, you can serve your application locally using the following commands:

<pre class="ql-syntax" spellcheck="false">$ cd ./angular-httpclient-demo $ ng serve </pre>

Your application will be running from http://localhost:4200.

Getting News Data

Before you can fetch the news data from NewsAPI.org which offers a free plan for open source and development projects, you first need to go the register page for getting an API key.

Adding an Angular Service

Next, let’s create a service that will take care of getting data from the news API. Open a new terminal and run the following command:

<pre class="ql-syntax" spellcheck="false">$ ng generate service api </pre>

Setting up HttpClient

Next, open the src/app/app.module.ts file then import HttpClientModule and add it to the importsarray:

<pre class="ql-syntax" spellcheck="false">// [...] import { HttpClientModule } from '@angular/common/http';

@NgModule({
declarations: [AppComponent],
entryComponents: [],
imports: [
// [...]
HttpClientModule,
],
// [...]
})
export class AppModule {}
</pre>

That's all, we are now ready to use the HttpClient in our project.

Injecting HttpClient in The Angular Service

Next, open the src/app/api.service.ts file and inject HttpClient via the service constructor:

<pre class="ql-syntax" spellcheck="false">import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable({
providedIn: 'root'
})
export class ApiService {

constructor(private httpClient: HttpClient) { }
}
</pre>

Sending GET Request for Fetching Data

Next, define an API_KEY variable which will hold your API key from the News API:

<pre class="ql-syntax" spellcheck="false">export class ApiService {
API_KEY = 'YOUR_API_KEY';
</pre>

Finally, add a method that sends a GET request to an endpoint for TechCrunch news:

<pre class="ql-syntax" spellcheck="false"> public getNews(){
return this.httpClient.get(https://newsapi.org/v2/top-headlines?sources=techcrunch&amp;apiKey=${this.API_KEY});
}
</pre>

That’s all we need to add for the service.

How the HttpClient.get() Method Works

The HttpClient get() method is designed to send HTTP GET requests. The syntax is as follows:

<pre class="ql-syntax" spellcheck="false">get(url: string, options: {
headers?: HttpHeaders;
observe: 'response';
params?: HttpParams;
reportProgress?: boolean;
responseType?: 'json';
withCredentials?: boolean;
}): Observable<HttpResponse<Object>>;
</pre>

It takes a REST API endpoint and an optional options object and returns an Observable instance.

Creating an Angular 8 Component

Now, let's create an Angular 8 component for displaying the news data. Head back to your terminal and run the following command:

<pre class="ql-syntax" spellcheck="false">$ ng generate component news
</pre>

Injecting ApiService in Your Component

Next, open the src/app/news/news.component.ts file and start by importing ApiService in your component:

<pre class="ql-syntax" spellcheck="false">import { ApiService } from '../api.service';
</pre>

Next, you need to inject ApiService via the component's constructor:

<pre class="ql-syntax" spellcheck="false">import { Component, OnInit } from '@angular/core';
import { ApiService } from '../api.service';
@Component({
selector: 'app-news',
templateUrl: './news.component.html',
styleUrls: ['./news.component.css']
})
export class NewsComponent implements OnInit {

constructor(private apiService: ApiService) { }
}
</pre>

Sending the GET Request & Subscribing to The Observable

Next, define an articles variable and call the getNews() method of the API service in the ngOnInit()method of the component:

<pre class="ql-syntax" spellcheck="false">export class NewsComponent implements OnInit {
articles;

constructor(private apiService: ApiService) { }
ngOnInit() {
this.apiService.getNews().subscribe((data)=>{
console.log(data);
this.articles = data['articles'];
});
}
}
</pre>

This will make sure our data is fetched once the component is loaded.

We call the getNews() method and subscribe to the returned Observable which will send a GET request to the news endpoint.

Displaying Data in The Template with NgFor

Let’s now display the news articles in our component template. Open the src/app/news.component.htmlfile and update it as follows:

<pre class="ql-syntax" spellcheck="false"><div *ngFor="let article of articles">
<h2>{{article.title}}</h2>

&lt;p&gt;
  {{article.description}}
&lt;/p&gt;
&lt;a href="{{article.url}}"&gt;Read full article&lt;/a&gt;

</div>
</pre>

Adding the Angular Component to The Router

Angular CLI 8 has automatically added routing for us, so we don’t need to set up anything besides adding the component(s) to our Router configuration. Open the src/app/app-routing.module.ts file and start by importing the news component as follows:

<pre class="ql-syntax" spellcheck="false">import { NewsComponent } from './news/news.component';
</pre>

Next, add the component to the routes array:

<pre class="ql-syntax" spellcheck="false">import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { NewsComponent } from './news/news.component';
const routes: Routes = [
{path:'news', component: NewsComponent}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
</pre>

You can now access your component from the /news path.

Conclusion

In this tutorial, we used Angular 8 to build a simple news application that retrieves data from a JSON REST API using the get() method of HttpClient. We’ve seen how to subscribe to the RxJS Observable returned by the get() method and how to use the *ngFor directive to iterate over fetched data in the template. Finally, we’ve seen how we can create an Angular 8 project using Angular CLI v8, how to generate components and services and how to configure routing for the component.

Originally published at  techiediaries.com

=========================================================

Follow me on Facebook | Twitter

Learn More


☞ Angular 8 (formerly Angular 2) - The Complete Guide

☞ Learn and Understand AngularJS

☞ The Complete Angular Course: Beginner to Advanced

☞ Angular Crash Course for Busy Developers

☞ Angular Essentials (Angular 2+ with TypeScript)

☞ Angular (Full App) with Angular Material, Angularfire & NgRx

☞ Angular & NodeJS - The MEAN Stack Guide