Macess  Hulk

Macess Hulk

1572062229

How to Build your First PWA using Angular

Progressive Web Application (PWA) has been quite the buzz word for the last few years, but what exactly is it? PWAs utilize a number of modern browser technologies to improve overall user experience. The core component of a PWA is a service worker, which is a piece of JavaScript code that runs in the background of a website intercepting and fetching all browser requests.**

If the service worker finds that it has an up-to-date version of that resource in the cache, it will provide the cached resource instead. In addition, an application manifest allows the application to be installed in the browser. This makes it possible to start up the PWA on a mobile device, even if the device is offline.

In this post, we’ll be using Angular 7, Angular CLI, and Angular Material to build a PWA that lets users search for book titles using the OpenLibrary service. Let’s dive in.

You may also like: Angular 7 Tutorial.

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.

npm install -g @angular/cli@7.1.3

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.

ng new AngularBooksPWA

This will create a new directory called AngularBooksPWA and 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.

npm install @angular/material@7.1.0 @angular/cdk@7.1.0 @angular/animations@7.1.0 @angular/flex-layout@7.0.0-beta.19

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.

<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">

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.

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

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.

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

    <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-button routerLink="/"><mat-icon>magnify</mat-icon></button></div>
      </div>
    </form>
</mat-menu>
<router-outlet></router-outlet>

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.

@import "~@angular/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;
}

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.

.expanded-toolbar {
  justify-content: space-between;
  align-items: center;
}

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.

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

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.

ng generate service books/books

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

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 => {
        params = params.set(key, data[key]);
      });
    }

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

    return new Promise<any>((resolve, reject) => {
      result.subscribe(resolve as any, reject as any);
    });
  }

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

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 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 book’s 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.

ng generate component home
ng generate component search
ng generate component details

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.

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

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.

<h1>Angular Books PWA</h1>
<h2>A simple progressive web application</h2>

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

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

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.

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

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.

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

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

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

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 okay 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 serve it using the http-server-spa command. Run the following commands.

npm install -g http-server-spa@1.3.0
ng build --prod --source-map
http-server-spa dist/AngularBooksPWA/ index.html 8080

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.

Search results

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.

Okta landing page

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.

This is image title

Adding a new application

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

This is image title

Selecting Single Page Application

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.

npm install @okta/okta-angular@1.0.7 --save-exact

Open app.module.ts and import the OktaAuthModule.

import { OktaAuthModule } from '@okta/okta-angular';

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

OktaAuthModule.initAuth({
  issuer: 'https://{yourOktaDomain}/oauth2/default',
  redirectUri: 'http://localhost:8080/implicit/callback',
  clientId: '{yourClientId}'
})

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.

import { OktaAuthService } from '@okta/okta-angular';

Create an isAuthenticated field as a property of the AppComponent.

isAuthenticated: boolean;

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

constructor(private formBuilder: FormBuilder,
            private router: Router,
            public oktaAuth: OktaAuthService) {
  this.oktaAuth.$authenticationState.subscribe(
    (isAuthenticated: boolean)  => this.isAuthenticated = isAuthenticated
  );
}

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

this.oktaAuth.isAuthenticated().then((auth) => {this.isAuthenticated = auth});

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.

login() {
  this.oktaAuth.loginRedirect();
}

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

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

<button mat-button *ngIf="!isAuthenticated" (click)="login()"> Login </button>
<button mat-button *ngIf="isAuthenticated" (click)="logout()"> Logout </button>

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.

import { OktaCallbackComponent, OktaAuthGuard } from '@okta/okta-angular';

Add the implicit/callback route to the routes array.

{ path: 'implicit/callback', component: OktaCallbackComponent }

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.

canActivate: [OktaAuthGuard]

Your routes array should look as follows after these changes.

const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'search', component: SearchComponent, canActivate: [OktaAuthGuard] },
  { path: 'details', component: DetailsComponent, canActivate: [OktaAuthGuard] },
  { path: 'implicit/callback', component: OktaCallbackComponent }
];

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:

ng build --prod --source-map
http-server-spa dist/AngularBooksPWA/ index.html 8080

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%.

This is image title

Lighthouse

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.

ng add @angular/pwa --project AngularBooksPWA

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.

ng generate service cache/request-cache
ng generate service cache/caching-interceptor

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

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 < (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 => {
      if (expiredEntry.lastRead < expired) {
        this.cache.delete(expiredEntry.url);
      }
    });
  }
}

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.

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

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.

import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { RequestCache } from './cache/request-cache.service';
import { CachingInterceptor } from './cache/caching-interceptor.service';

Update the providers section to include the services.

providers: [{ provide: HTTP_INTERCEPTORS, useClass: CachingInterceptor, multi: true }],

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 browser’s 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:

ng build --prod --source-map
http-server-spa dist/AngularBooksPWA/ index.html 8080

Monitor Your Network’s Status

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

offline: boolean;

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

Then edit the ngOnInit method and add the following lines.

window.addEventListener('online',  this.onNetworkStatusChange.bind(this));
window.addEventListener('offline', this.onNetworkStatusChange.bind(this));

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

<div *ngIf="offline">offline</div>

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

This is image title

Book Details

Pretty cool, don’t you think?!

In this tutorial, I’ve shown you how to create a progressive web app using Angular 7. Due to the effort put in by 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 are 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.

Thank for reading !

#angular #angular7 #angular-cli #pwa #authentication

What is GEEK

Buddha Community

How to Build your First PWA using Angular
Macess  Hulk

Macess Hulk

1572062229

How to Build your First PWA using Angular

Progressive Web Application (PWA) has been quite the buzz word for the last few years, but what exactly is it? PWAs utilize a number of modern browser technologies to improve overall user experience. The core component of a PWA is a service worker, which is a piece of JavaScript code that runs in the background of a website intercepting and fetching all browser requests.**

If the service worker finds that it has an up-to-date version of that resource in the cache, it will provide the cached resource instead. In addition, an application manifest allows the application to be installed in the browser. This makes it possible to start up the PWA on a mobile device, even if the device is offline.

In this post, we’ll be using Angular 7, Angular CLI, and Angular Material to build a PWA that lets users search for book titles using the OpenLibrary service. Let’s dive in.

You may also like: Angular 7 Tutorial.

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.

npm install -g @angular/cli@7.1.3

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.

ng new AngularBooksPWA

This will create a new directory called AngularBooksPWA and 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.

npm install @angular/material@7.1.0 @angular/cdk@7.1.0 @angular/animations@7.1.0 @angular/flex-layout@7.0.0-beta.19

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.

<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">

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.

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

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.

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

    <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-button routerLink="/"><mat-icon>magnify</mat-icon></button></div>
      </div>
    </form>
</mat-menu>
<router-outlet></router-outlet>

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.

@import "~@angular/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;
}

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.

.expanded-toolbar {
  justify-content: space-between;
  align-items: center;
}

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.

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

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.

ng generate service books/books

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

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 => {
        params = params.set(key, data[key]);
      });
    }

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

    return new Promise<any>((resolve, reject) => {
      result.subscribe(resolve as any, reject as any);
    });
  }

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

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 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 book’s 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.

ng generate component home
ng generate component search
ng generate component details

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.

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

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.

<h1>Angular Books PWA</h1>
<h2>A simple progressive web application</h2>

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

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

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.

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

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.

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

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

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

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 okay 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 serve it using the http-server-spa command. Run the following commands.

npm install -g http-server-spa@1.3.0
ng build --prod --source-map
http-server-spa dist/AngularBooksPWA/ index.html 8080

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.

Search results

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.

Okta landing page

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.

This is image title

Adding a new application

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

This is image title

Selecting Single Page Application

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.

npm install @okta/okta-angular@1.0.7 --save-exact

Open app.module.ts and import the OktaAuthModule.

import { OktaAuthModule } from '@okta/okta-angular';

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

OktaAuthModule.initAuth({
  issuer: 'https://{yourOktaDomain}/oauth2/default',
  redirectUri: 'http://localhost:8080/implicit/callback',
  clientId: '{yourClientId}'
})

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.

import { OktaAuthService } from '@okta/okta-angular';

Create an isAuthenticated field as a property of the AppComponent.

isAuthenticated: boolean;

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

constructor(private formBuilder: FormBuilder,
            private router: Router,
            public oktaAuth: OktaAuthService) {
  this.oktaAuth.$authenticationState.subscribe(
    (isAuthenticated: boolean)  => this.isAuthenticated = isAuthenticated
  );
}

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

this.oktaAuth.isAuthenticated().then((auth) => {this.isAuthenticated = auth});

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.

login() {
  this.oktaAuth.loginRedirect();
}

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

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

<button mat-button *ngIf="!isAuthenticated" (click)="login()"> Login </button>
<button mat-button *ngIf="isAuthenticated" (click)="logout()"> Logout </button>

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.

import { OktaCallbackComponent, OktaAuthGuard } from '@okta/okta-angular';

Add the implicit/callback route to the routes array.

{ path: 'implicit/callback', component: OktaCallbackComponent }

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.

canActivate: [OktaAuthGuard]

Your routes array should look as follows after these changes.

const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'search', component: SearchComponent, canActivate: [OktaAuthGuard] },
  { path: 'details', component: DetailsComponent, canActivate: [OktaAuthGuard] },
  { path: 'implicit/callback', component: OktaCallbackComponent }
];

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:

ng build --prod --source-map
http-server-spa dist/AngularBooksPWA/ index.html 8080

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%.

This is image title

Lighthouse

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.

ng add @angular/pwa --project AngularBooksPWA

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.

ng generate service cache/request-cache
ng generate service cache/caching-interceptor

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

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 < (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 => {
      if (expiredEntry.lastRead < expired) {
        this.cache.delete(expiredEntry.url);
      }
    });
  }
}

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.

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

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.

import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { RequestCache } from './cache/request-cache.service';
import { CachingInterceptor } from './cache/caching-interceptor.service';

Update the providers section to include the services.

providers: [{ provide: HTTP_INTERCEPTORS, useClass: CachingInterceptor, multi: true }],

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 browser’s 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:

ng build --prod --source-map
http-server-spa dist/AngularBooksPWA/ index.html 8080

Monitor Your Network’s Status

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

offline: boolean;

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

Then edit the ngOnInit method and add the following lines.

window.addEventListener('online',  this.onNetworkStatusChange.bind(this));
window.addEventListener('offline', this.onNetworkStatusChange.bind(this));

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

<div *ngIf="offline">offline</div>

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

This is image title

Book Details

Pretty cool, don’t you think?!

In this tutorial, I’ve shown you how to create a progressive web app using Angular 7. Due to the effort put in by 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 are 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.

Thank for reading !

#angular #angular7 #angular-cli #pwa #authentication

Christa  Stehr

Christa Stehr

1598940617

Install Angular - Angular Environment Setup Process

Angular is a TypeScript based framework that works in synchronization with HTML, CSS, and JavaScript. To work with angular, domain knowledge of these 3 is required.

  1. Installing Node.js and npm
  2. Installing Angular CLI
  3. Creating workspace
  4. Deploying your First App

In this article, you will get to know about the Angular Environment setup process. After reading this article, you will be able to install, setup, create, and launch your own application in Angular. So let’s start!!!

Angular environment setup

Install Angular in Easy Steps

For Installing Angular on your Machine, there are 2 prerequisites:

  • Node.js
  • npm Package Manager
Node.js

First you need to have Node.js installed as Angular require current, active LTS or maintenance LTS version of Node.js

Download and Install Node.js version suitable for your machine’s operating system.

Npm Package Manager

Angular, Angular CLI and Angular applications are dependent on npm packages. By installing Node.js, you have automatically installed the npm Package manager which will be the base for installing angular in your system. To check the presence of npm client and Angular version check of npm client, run this command:

  1. npm -v

Installing Angular CLI

  • Open Terminal/Command Prompt
  • To install Angular CLI, run the below command:
  1. npm install -g @angular/cli

installing angular CLI

· After executing the command, Angular CLI will get installed within some time. You can check it using the following command

  1. ng --version

Workspace Creation

Now as your Angular CLI is installed, you need to create a workspace to work upon your application. Methods for it are:

  • Using CLI
  • Using Visual Studio Code
1. Using CLI

To create a workspace:

  • Navigate to the desired directory where you want to create your workspace using cd command in the Terminal/Command prompt
  • Then in the directory write this command on your terminal and provide the name of the app which you want to create. In my case I have mentioned DataFlair:
  1. Ng new YourAppName

create angular workspace

  • After running this command, it will prompt you to select from various options about the CSS and other functionalities.

angular CSS options

  • To leave everything to default, simply press the Enter or the Return key.

angular setup

#angular tutorials #angular cli install #angular environment setup #angular version check #download angular #install angular #install angular cli

PWA  Tutorial

PWA Tutorial

1577729524

How to Build Progressive Web Apps (PWA) using Angular 9

In this tutorial, we will use the most recent version of Angular 9 to build a Progressive Web Application (PWA) that works on mobile or any platform that uses a standard-compliant browser.

What is PWA?

A progressive web app offers the high level of user experience because it has the same features as native apps have. Nowadays, PWA has become the big deal, and more companies are switching towards the Progressive web applications (PWA).

PWA does not require to be deployed via app stores; instead, we take a slightly different approach and deploy it through the web servers through URLs. To make the same PWA as the native apps, we have to fulfill the following requirements.

Responsive

Runs on almost every device desktop, mobile, or tablet.

Auto-Updated

Service worker keeps it always updated.

Secure

Content is considered to be safe due to serving through HTTPS.

Reliably Connected

Service workers support to make it work offline and on sparse networks.

Progressive

Web app that employs modern web capabilities to produce an app-like experience for every user.

Searchable

Very much searchable via search engines due to web app manifest.

Shareable

It does not require to be downloaded can easily be shared via a simple link or URL.

User Experience

Similar user experience by engaging the same interaction methodology as a native app has.

Installable

Fully installable on users’ mobile home screen, and the good thing is we do not have to visit the app store.

Regularly Communicate

Keeps you up to date via push notifications and offers you the latest updates, promotion offers, etc.

Table of Contents:

  • Configuring & Installing Angular Application
  • Adding Angular Material Library
  • Build & Consume REST API using HttpClient
  • Adding PWA in Angular 8/9
  • Service Workers in Angular
  • Configure Production Build with http-server
  • Audit PWA App with Lighthouse
  • Conclusion

Configuring & Installing Angular Application

In this step, we will configure the Angular project for giving you the demo of Angular PWA.

First, you make sure that you have the latest version of Node.js and NPM configured on your system.

node -v

# v10.16.0

Follow this URL to download and install Node & NPM.

Now, we are going to begin with installing the latest version of Angular CLI on your system.

npm install -g @angular/cli@latest

Run the following command to install an Angular app:

ng new angular-pwa

Get inside the project directory:

cd angular-pwa

Adding Angular Material Design UI Library

Adding a Material design library in Angular is very easy, It can be done by using just a single command. Run the following command from your terminal.

ng add @angular/material

Add the material theme in src/styles.css file.

@import "~@angular/material/prebuilt-themes/indigo-pink.css";

Ordinarily, we import the angular material components in the AppModule. But there is a slight twist. We are going to create a separate module file for material components and import the components here and then import the material component file inside the main AppModule file.

This is the process I highly recommend for managing the material components in an organized way. We will show the users data into the angular material table, Create app/material.module.ts file add the following code inside of it.

import { NgModule } from '@angular/core';
import { MatTableModule } from '@angular/material/table';
import { MatPaginatorModule, MatToolbarModule } from '@angular/material';

@NgModule({
    imports: [
        MatTableModule,
        MatPaginatorModule,
        MatToolbarModule
    ],
    exports: [
        MatTableModule,
        MatPaginatorModule,
        MatToolbarModule
    ]
})

export class MaterialModule { }

Next, import MaterialModule module in the main app.module.ts file as given below.

import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';

/* angular material */
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MaterialModule } from './material.module';
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    MaterialModule
  ],
  providers: [],
  bootstrap: [AppComponent],
  schemas: [CUSTOM_ELEMENTS_SCHEMA]
})

export class AppModule { }

Build & Consume REST API using HttpClient

In this step, create angular service to fetch the data from the remote server using an open-source REST API.

To make the HTTP requests we need to import and register HttpClientModule service in app.module.ts file.

import { HttpClientModule } from '@angular/common/http';

@NgModule({
  imports: [
    HttpClientModule
   ]
})

Let’s generate a service. In here, we will write the logic to fetch the users’ data with the help of JSONPlaceholder API, run the following command.

ng g service rest-api

Next, open the app/rest-api.service.ts file and add the following code in it:

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { HttpClient } from '@angular/common/http';

export interface User {
  id: string;
  name: string;
  email: string;
  website: string;
}

@Injectable({
  providedIn: 'root'
})

export class RestApiService {
  api: string = "https://jsonplaceholder.typicode.com/users";

  constructor(private http: HttpClient) { }

  getUsers(): Observable<User[]> {
    return this.http.get<User[]>(this.api)
  }
}

We are fetching the User data using the HttpClient service as an Observable via getUsers() method.

Next, open the app/app.component.ts file and add the given below code:

import { Component, ViewChild } from '@angular/core';
import { RestApiService } from './rest-api.service';
import { MatPaginator, MatTableDataSource } from '@angular/material';

export interface TableElement {
  id: string;
  name: string;
  email: string;
  website: string;
}

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

export class AppComponent {
  Data: TableElement[];
  col: string[] = ['id', 'name', 'email', 'website'];
  dataSource = new MatTableDataSource<TableElement>(this.Data);
  @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;

  constructor(private restApiService: RestApiService) {
    this.restApiService.getUsers().subscribe((res) => {
      this.dataSource = new MatTableDataSource<TableElement>(res);
      setTimeout(() => {
        this.dataSource.paginator = this.paginator;
      }, 0);
    })
  }

}

We imported the RestApiService in AppComponent to fetch and display the user data. We are using Angular Material table UI component to display the data. We can manipulate the item’s size using the angular material pagination module.

Build the PWA app UI using the angular material table, go to the app.component.html file to create the layout. Our layout will have the material navbar and a data table with pagination.

<mat-toolbar color="accent" class="header">
  <span>Angular PWA Example</span>
</mat-toolbar>

<table mat-table [dataSource]="dataSource" matSort>
  <ng-container matColumnDef="id">
    <th mat-header-cell *matHeaderCellDef mat-sort-header> ID. </th>
    <td mat-cell *matCellDef="let element"> {{element.id}} </td>
  </ng-container>

  <ng-container matColumnDef="name">
    <th mat-header-cell *matHeaderCellDef mat-sort-header> Name </th>
    <td mat-cell *matCellDef="let element"> {{element.name}} </td>
  </ng-container>

  <ng-container matColumnDef="email">
    <th mat-header-cell *matHeaderCellDef mat-sort-header> Email </th>
    <td mat-cell *matCellDef="let element"> {{element.email}} </td>
  </ng-container>

  <ng-container matColumnDef="website">
    <th mat-header-cell *matHeaderCellDef mat-sort-header> Website </th>
    <td mat-cell *matCellDef="let element"> {{element.website}} </td>
  </ng-container>

  <tr mat-header-row *matHeaderRowDef="col"></tr>
  <tr mat-row *matRowDef="let row; columns: col;"></tr>
</table>

<mat-paginator [pageSizeOptions]="[7, 14, 28]" showFirstLastButtons></mat-paginator>

Angular 9 PWA App Example

Adding PWA in Angular 8/9

It is undoubtedly very easy to convert an existing angular application into a Progressive Web App (PWA). The “ng add angular pwa” command can make your dreams come true.

ng add @angular/pwa

The above command automatically add PWA files and features inside an Angular app:

  • The manifest.webmanifest file
  • The ngsw-config.json service worker
  • Varying sizes of icons inside the assets/icons directory

The “index.html” file has been updated and added the following meta tag and theme colour attribute.

  <link rel="manifest" href="manifest.webmanifest">
  <meta name="theme-color" content="#1976d2">

Please make sure and add few PWA compliant icon assets.

Service Workers in Angular

A Service Worker is a script that works in the background and gets along with almost every modern browsers.

Service Workers work with HTTPS and works in the same manner as Web Workers does but a bit adversely. Progressive Web Application considers service workers as the primary technology. It allows deep platform integration, such as offline support, background sync, rich caching, and push notifications.

The “ng add angular pwa” command generated the ngsw-config.json file, It is solely responsible for service workers. Service workers are also automatically added to app.module.ts file.

// app.module.ts

import { ServiceWorkerModule } from '@angular/service-worker';
import { environment } from '../environments/environment';

@NgModule({
  declarations: [...],
  imports: [
    ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production })
  ],
  providers: [...],
  bootstrap: [...],
  schemas: [...]
})

export class AppModule { }

Have a look at the ngsw-config.json file.

// ngsw-config.json

{
  "$schema": "./node_modules/@angular/service-worker/config/schema.json",
  "index": "/index.html",
  "assetGroups": [
    {
      "name": "app",
      "installMode": "prefetch",
      "resources": {
        "files": [
          "/favicon.ico",
          "/index.html",
          "/manifest.webmanifest",
          "/*.css",
          "/*.js"
        ]
      }
    }, {
      "name": "assets",
      "installMode": "lazy",
      "updateMode": "prefetch",
      "resources": {
        "files": [
          "/assets/**",
          "/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"
        ]
      }
    }
  ]
}

The “ng add angular pwa” command also registered the service-worker inside the package.json file:

// package.json

{
  "dependencies": {
    ...
    "@angular/service-worker": "~8.2.14"
    ...
  }
}

The feature will only work with the app, which is being served with HTTPS.

Configure Production Build with http-server

Install the http-server package globally via NPM using the following command.

sudo npm install -g http-server

The http-server is a simple, zero-configuration command-line http server. It is powerful enough for production usage, but it’s simple and hackable enough to be used for testing, local development, and learning.
http-server

Run ng build prod command to build the app for production environment.

ng build --prod

Now, we have the production build ready at the dist/angular-pwa folder. Next, we will serve the angular PWA using the http-server package.

Get inside the prod build folder.

cd dist/angular-pwa

Start the prod build
Next, run the following command in your terminal.

http-server -o

The above command will open the angular app on the following URL http://127.0.0.1:8080 and also give you the following URLs, you can check your app by entering one of the URL in the browser’s address bar.

Available on:
http://127.0.0.1:8080
http://192.168.0.102:8080

Audit PWA App with Lighthouse

Now, we will verify the PWA application using Lighthouse extension on the Google Chrome browser. Add the following URL on the browser’s address bar: http://127.0.0.1:8080

Install the lighthouse extension from chrome web store.

Next, open the browser console by using the Ctrl + Shift + I.

Audit PWA App with Lighthouse

Conclusion

Finally, we have completed the Angular 9 PWA tutorial with an example. In this tutorial, we have got a chance to do the cover the following topics:

  • How to convert the existing angular application to PWA?
  • How to Add PWA features in an angular application?
  • How to work with Service Workers?
  • How to configure the add-to-home-screen feature using web manifest file?
  • How to audit the PWA app with Google’s Lighthouse extension?

Download the full code of this tutorial from this GitHub repository, I hope you will like this tutorial. Happy Coding!

#Angular #PWA #WebDev #angular

Why Use WordPress? What Can You Do With WordPress?

Can you use WordPress for anything other than blogging? To your surprise, yes. WordPress is more than just a blogging tool, and it has helped thousands of websites and web applications to thrive. The use of WordPress powers around 40% of online projects, and today in our blog, we would visit some amazing uses of WordPress other than blogging.
What Is The Use Of WordPress?

WordPress is the most popular website platform in the world. It is the first choice of businesses that want to set a feature-rich and dynamic Content Management System. So, if you ask what WordPress is used for, the answer is – everything. It is a super-flexible, feature-rich and secure platform that offers everything to build unique websites and applications. Let’s start knowing them:

1. Multiple Websites Under A Single Installation
WordPress Multisite allows you to develop multiple sites from a single WordPress installation. You can download WordPress and start building websites you want to launch under a single server. Literally speaking, you can handle hundreds of sites from one single dashboard, which now needs applause.
It is a highly efficient platform that allows you to easily run several websites under the same login credentials. One of the best things about WordPress is the themes it has to offer. You can simply download them and plugin for various sites and save space on sites without losing their speed.

2. WordPress Social Network
WordPress can be used for high-end projects such as Social Media Network. If you don’t have the money and patience to hire a coder and invest months in building a feature-rich social media site, go for WordPress. It is one of the most amazing uses of WordPress. Its stunning CMS is unbeatable. And you can build sites as good as Facebook or Reddit etc. It can just make the process a lot easier.
To set up a social media network, you would have to download a WordPress Plugin called BuddyPress. It would allow you to connect a community page with ease and would provide all the necessary features of a community or social media. It has direct messaging, activity stream, user groups, extended profiles, and so much more. You just have to download and configure it.
If BuddyPress doesn’t meet all your needs, don’t give up on your dreams. You can try out WP Symposium or PeepSo. There are also several themes you can use to build a social network.

3. Create A Forum For Your Brand’s Community
Communities are very important for your business. They help you stay in constant connection with your users and consumers. And allow you to turn them into a loyal customer base. Meanwhile, there are many good technologies that can be used for building a community page – the good old WordPress is still the best.
It is the best community development technology. If you want to build your online community, you need to consider all the amazing features you get with WordPress. Plugins such as BB Press is an open-source, template-driven PHP/ MySQL forum software. It is very simple and doesn’t hamper the experience of the website.
Other tools such as wpFoRo and Asgaros Forum are equally good for creating a community blog. They are lightweight tools that are easy to manage and integrate with your WordPress site easily. However, there is only one tiny problem; you need to have some technical knowledge to build a WordPress Community blog page.

4. Shortcodes
Since we gave you a problem in the previous section, we would also give you a perfect solution for it. You might not know to code, but you have shortcodes. Shortcodes help you execute functions without having to code. It is an easy way to build an amazing website, add new features, customize plugins easily. They are short lines of code, and rather than memorizing multiple lines; you can have zero technical knowledge and start building a feature-rich website or application.
There are also plugins like Shortcoder, Shortcodes Ultimate, and the Basics available on WordPress that can be used, and you would not even have to remember the shortcodes.

5. Build Online Stores
If you still think about why to use WordPress, use it to build an online store. You can start selling your goods online and start selling. It is an affordable technology that helps you build a feature-rich eCommerce store with WordPress.
WooCommerce is an extension of WordPress and is one of the most used eCommerce solutions. WooCommerce holds a 28% share of the global market and is one of the best ways to set up an online store. It allows you to build user-friendly and professional online stores and has thousands of free and paid extensions. Moreover as an open-source platform, and you don’t have to pay for the license.
Apart from WooCommerce, there are Easy Digital Downloads, iThemes Exchange, Shopify eCommerce plugin, and so much more available.

6. Security Features
WordPress takes security very seriously. It offers tons of external solutions that help you in safeguarding your WordPress site. While there is no way to ensure 100% security, it provides regular updates with security patches and provides several plugins to help with backups, two-factor authorization, and more.
By choosing hosting providers like WP Engine, you can improve the security of the website. It helps in threat detection, manage patching and updates, and internal security audits for the customers, and so much more.

Read More

#use of wordpress #use wordpress for business website #use wordpress for website #what is use of wordpress #why use wordpress #why use wordpress to build a website

Ayyaz Zafar

1624138795

Angular Material Autocomplete - Multiple Use Cases covered

Learn How to use Angular Material Autocomplete Suggestions Search Input. I covered multiple use cases.

Please watch this video. I hope this video would be helpful for you to understand it and use it in your projects

Please subscribe: https://www.youtube.com/channel/UCL5nKCmpReJZZMe9_bYR89w

#angular #angular-material #angular-js #autocomplete #angular-material-autocomplete #angular-tutorial