Chapter 4: Building a PWA Weather Application Using Angular Service Worker

We can access a web application using different types of devices, such as a desktop, mobile, tablet, and various network types, such as broadband, Wi-Fi, and cellular. A web application should work seamlessly and provide the same user experience independently of the device and the network of the user.

Progressive Web Apps (PWA) is a collection of web techniques for building web applications with previous considerations in mind. One popular technique is the service worker, which improves the loading time of a web application. In this chapter, we will use the service worker implementation of the Angular framework to build a PWA that displays the weather of a city using the OpenWeather API.

We will cover the following topics in detail:

  • Setting up the OpenWeather API
  • Displaying weather data
  • Enabling offline mode with the service worker
  • Staying up to date with in-app notifications
  • Deploying our app with Firebase hosting

Essential background theory and context

Traditional web applications are usually hosted in a web server and are immediately available to any user at any given time. Native applications are installed on the device of the user, have access to its native resources, and can work seamlessly with any network. PWA applications stand between the two worlds of web and native applications and share characteristics from both. A PWA application is a web application that is based on the following pillars to convert into a native one:

  • Capable: It can access locally saved data and interact with peripheral hardware that is connected to the device of the user.
  • Reliable: It can have the same performance and experience in any network connection, even in areas with low connectivity and coverage.
  • Installable: It can be installed on the device of the user, can be launched directly from the home screen, and interact with other installed native applications.

Converting a web application into a PWA involves several steps and techniques. The most essential one is configuring a service worker. The service worker is a mechanism that is run on the web browser and acts as a proxy between the application and an external HTTP endpoint or other in-app resources such as JavaScript and CSS files. The main job of the service worker is to intercept requests to those resources and act on them by providing a cached or live response.

Important note

The service worker is persisted after the tab of the browser is closed.

The Angular framework provides an implementation for the service worker that we can use to convert our Angular applications into PWA.

It also contains a built-in HTTP client that we can use to communicate with a server over HTTP. The Angular HTTP client exposes an observable-based API with all standard HTTP methods such as POST and GET. Observables are based on the observer pattern, which is the core of reactive functional programming. In the observer pattern, multiple objects called observers can subscribe to an observable and get notified about any changes to its state. An observable dispatches changes to observers by emitting event streams asynchronously. The Angular framework uses a library called RxJS that contains various artifacts for working with observables. One of these artifacts is a set of functions called operators that can apply various actions on observables such as transformations and filtering. Next, let's get an overview of our project.

Project overview

In this project, we will build a PWA application to display the weather conditions of a city. Initially, we will learn how to configure the OpenWeather API, which we will use to get weather data. We will then learn how to use the API to display weather information in an Angular component. We will see how to convert our Angular application into PWA using a service worker. We will also implement a notification mechanism for our application updates. Finally, we will deploy our PWA application into the Firebase hosting provider.

Build time: 90 minutes

Getting started

The following software tools are required to complete this project:

Setting up the OpenWeather API

The OpenWeather API has been created by the OpenWeather team and contains current and historical weather information from over 200,000 cities worldwide. It also supports forecast weather data for more detailed information. In this project, we will focus on the current weather data.

We need to get an API key first to start using the OpenWeather API:

  1. Navigate to the OpenWeather API website: https://openweathermap.org/api.

    You will see a list of all available APIs from the OpenWeather team.

  2. Find the section entitled Current Weather Data and click on the Subscribe button.

    You will be redirected to the page with the available pricing schemes of the service. Each scheme supports a different combination of API calls per minute and month. For this project, we are going to use the Free tier.

  3. Click on the Get API key button.

    You will be redirected to the sign-up page of the service.

  4. Fill in all the required details and click on the Create Account button.

    A confirmation message will be sent to the email address that you used to create your account.

  5. Find the confirmation email and click on the Verify your email button to complete your registration.

    You will shortly receive another email from OpenWeather with details about your current subscription, including your API key and the HTTP endpoint that we will use for communicating with the API.

    Important note

    The API key may take some time to be activated, usually a couple of hours, before you can use it.

As soon as the API key has been activated, we can start using it within an Angular application. We will learn how to do this in the following section.

Displaying weather data

In this section, we will create an Angular application to display weather information for a city. The user will enter the name of the city in an input field, and the application will use the OpenWeather API to get weather data for the specified city. We will cover the following topics in more detail:

  • Setting up the Angular application
  • Communicating with the OpenWeather API
  • Displaying weather information for a city

Let's start by creating the Angular application first in the following section.

Setting up the Angular application

We will use the ng new command of the Angular CLI to create a new Angular application from scratch:

ng new weather-app --style=scss --routing=false

The preceding command will create a new Angular CLI application with the following properties:

  • weather-app: The name of the Angular application
  • --style=scss: Indicates that our Angular application will use the SCSS stylesheet format
  • --routing=false: Disables Angular routing in the application

The user should be able to enter the name of the city in an input field, and the weather information of the city should be visualized in a card layout. The Angular Material library provides a set of Angular UI components, including an input and a card, to use for our needs.

Angular Material components adhere to the Material Design principles and are maintained by the Components team of Angular. We can install the Angular Material using the following command of the Angular CLI:

ng add @angular/material --theme=indigo-pink --typography=true --animations=true

The preceding code uses the ng add command of the Angular CLI, passing additional configuration options:

  • @angular/material: The npm package name of the Angular Material library. It will also install the @angular/cdk package, a set of behaviors and interactions used to build Angular Material. Both packages will be added to the dependencies section of the package.json file of the application.
  • --theme=indigo-pink: The name of the Angular Material theme that we want to use. Adding a theme involves modifying several files of the Angular CLI workspace. It adds entries of the CSS theme file to the angular.json configuration file:

    ./node_modules/@angular/material/prebuilt-themes/indigo-pink.css

    It also includes the Material Design icons in the index.html file:

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

    Angular Material comes with a set of predefined themes that we can use. Alternatively, we can build a custom one that fits our specific needs.

  • --typography=true: Enables Angular Material typography globally in our application. Typography defines how text content is displayed and uses the Roboto Google font by default, which is included in the index.html file:

    <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">

    It adds the following class to the body of the HTML file:

    <body class="mat-typography">

      <app-root></app-root>

    </body>

    It also adds some CSS styles to the global styles.scss file of our application:

    html, body { height: 100%; }

    body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }

  • --animations=true: Enables browser animations in our application by importing BrowserAnimationsModule into the main application module, app.module.ts:

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

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

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

    import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

    @NgModule({

      declarations: [

        AppComponent

      ],

      imports: [

        BrowserModule,

        BrowserAnimationsModule

      ],

      providers: [],

      bootstrap: [AppComponent]

    })

    export class AppModule { }

We have nearly completed the setup and configuration of our Angular application. The last step is to add the API key that we created in the Setting up the OpenWeather API section.

The Angular CLI workspace contains the srcenvironments folder that we can use for defining application settings such as API keys and endpoints. It contains one TypeScript file for each environment that we want to support in our application. The Angular CLI creates two files by default:

  • environment.ts: The TypeScript file for the development environment. It is used when we start an Angular application using ng serve.
  • environment.prod.ts: The TypeScript file for the production environment. It is used when we build the application using ng build.

Each environment file exports an environment object. Add the following properties to the object in both development and production files:

apiUrl: 'https://api.openweathermap.org/data/2.5/',

apiKey: '<Your API key>'

In the preceding snippet, the apiUrl property is the URL of the endpoint that we will use to make calls to the OpenWeather API, and apiKey is our API key. Replace the <Your API key> value with the API key that you have.

We now have all the moving parts in place to build our Angular application. In the following section, we will create a mechanism for interacting with the OpenWeather API.

Communicating with the OpenWeather API

The application should interact with the OpenWeather API over HTTP to get weather data. Let's see how we can set up this type of communication in our application:

  1. First, we need to create an interface for describing the type of data we will get from the API. Use the following generate command of the Angular CLI to create one:

    ng generate interface weather

    The preceding command will create the weather.ts file in the srcapp folder of our Angular CLI project.

  2. Open the weather.ts file and modify it as follows:

    export interface Weather {

      weather: WeatherInfo[],

      main: {

        temp: number;

        pressure: number;

        humidity: number;

      };

      wind: {

        speed: number;

      };

      sys: {

        country: string

      };

      name: string;

    }

    interface WeatherInfo {

      main: string;

      icon: string;

    }

    Each property corresponds to a weather field in the OpenWeather API response. You can find a description for each one at https://openweathermap.org/current#parameter.

    Then, we need to set up the built-in HTTP client provided by the Angular framework.

  3. Open the app.module.ts file and add HttpClientModule to the imports array of the @NgModule decorator:

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

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

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

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

    import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

    @NgModule({

      declarations: [

        AppComponent

      ],

      imports: [

        BrowserModule,

        BrowserAnimationsModule,

        HttpClientModule

      ],

      providers: [],

      bootstrap: [AppComponent]

    })

    export class AppModule { }

  4. Use the generate command of the Angular CLI to create a new Angular service:

    ng generate service weather

    The preceding command will create the weather.service.ts file in the srcapp folder of our Angular CLI project.

  5. Open the weather.service.ts file and inject the HttpClient service into its constructor:

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

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

    @Injectable({

      providedIn: 'root'

    })

    export class WeatherService {

      constructor(private http: HttpClient) { }

    }

  6. Add a method in the service that accepts the name of the city as a single parameter and queries the OpenWeather API for that city:

    getWeather(city: string): Observable<Weather> {

      const options = new HttpParams()

        .set('units', 'metric')

        .set('q', city)

        .set('appId', environment.apiKey);

      return this.http.get<Weather>(environment.apiUrl +

        'weather', { params: options });

    }

    The getWeather method uses the get method of the HttpClient service that accepts two parameters. The first one is the URL endpoint of the OpenWeather API, which is available from the apiUrl property of the environment object.

    Important note

    The environment object is imported from the default environment.ts file. The Angular CLI is responsible for replacing it with the environment.prod.ts file when we build our application.

    The second parameter is an options object used to pass additional configuration to the request, such as URL query parameters with the params property. We use the constructor of the HttpParams object and call its set method for each query parameter that we want to add to the URL. In our case, we pass the q parameter for the city name, the appId for the API key that we get from the environment object, and the type of units we want to use. You can learn more about supported units at https://openweathermap.org/current#data.

    Tip

    We used the set method to create query parameters because the HttpParams object is immutable. Calling the constructor for each parameter that you want to pass will throw an error.

    We also set the type of response data as Weather in the get method. Notice that the getWeather method does not return Weather data, but instead an Observable of this type.

  7. Add the following import statements at the top of the file:

    import { HttpClient, HttpParams } from '@angular/common/http';

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

    import { Observable } from 'rxjs';

    import { environment } from '../environments/environment';

    import { Weather } from './weather';

The Angular service that we created contains all the necessary artifacts for interacting with the OpenWeather API. In the following section, we will create an Angular component for initiating requests and displaying data from it.

Displaying weather information for a city

The user should be able to use the UI of our application and enter the name of a city that wants to view weather details. The application will use that information to query the OpenWeather API, and the result of the request will be displayed on the UI using a card layout. Let's start building an Angular component for creating all these types of interactions:

  1. Use the generate command of the Angular CLI to create an Angular component:

    ng generate component weather

  2. Open the template of the main component, app.component.html, and replace its content with the selector of the new component, app-weather:

    <app-weather></app-weather>

  3. Open the app.module.ts file and add the following modules from the Angular Material library to the imports array of the @NgModule decorator:

    @NgModule({

      declarations: [

        AppComponent,

        WeatherComponent

      ],

      imports: [

        BrowserModule,

        BrowserAnimationsModule,

        HttpClientModule,

        MatIconModule,

        MatInputModule,

        MatCardModule

      ],

      providers: [],

      bootstrap: [AppComponent]

    })

    Also, add the necessary import statements at the top of the file:

    import { MatCardModule } from '@angular/material/card';

    import { MatIconModule } from '@angular/material/icon';

    import { MatInputModule } from '@angular/material/input';

  4. Open the weather.component.ts file, create a weather property of the Weather type, and inject WeatherService into the constructor of the WeatherComponent class:

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

    import { Weather } from '../weather';

    import { WeatherService } from '../weather.service';

    @Component({

      selector: 'app-weather',

      templateUrl: './weather.component.html',

      styleUrls: ['./weather.component.scss']

    })

    export class WeatherComponent implements OnInit {

      weather: Weather | undefined;

      constructor(private weatherService: WeatherService)

        { }

      ngOnInit(): void {

      }

    }

  5. Create a component method that subscribes to the getWeather method of WeatherService and assigns the result to the weather component property:

    search(city: string) {

      this.weatherService.getWeather(city).subscribe(weather     => this.weather = weather);

    }

We have already finished working with the TypeScript class file of our component. Let's wire it up with its template. Open the weather.component.html file and replace its content with the following HTML code:

weather.component.html

<mat-form-field>

  <input matInput placeholder="Enter city" #cityCtrl

    (keydown.enter)="search(cityCtrl.value)">

  <mat-icon matSuffix

    (click)="search(cityCtrl.value)">search</mat-icon>

</mat-form-field>

<mat-card *ngIf="weather">

  <mat-card-header>

    <mat-card-title>{{weather.name}},

      {{weather.sys.country}}</mat-card-title>

    <mat-card-subtitle>{{weather.weather[0].main}}

       </mat-card-subtitle>

  </mat-card-header>

  <img mat-card-image

    src="https://openweathermap.org/img/wn/

      {{weather.weather[0].icon}}@2x.png"

        [alt]="weather.weather[0].main">

  <mat-card-content>

    <h1>{{weather.main.temp | number:'1.0-0'}} &#8451;</h1>

    <p>Pressure: {{weather.main.pressure}} hPa</p>

    <p>Humidity: {{weather.main.humidity}} %</p>

    <p>Wind: {{weather.wind.speed}} m/s</p>

  </mat-card-content>

</mat-card>

The preceding template consists of several components from the Angular Material library, including a mat-form-field component that contains the following child elements:

  • An input HTML element for entering the name of the city. When the user has finished editing and presses the Enter key, it calls the search component method passing the value property of the cityCtrl variable as a parameter. The cityCtrl variable is a template reference variable and indicates the actual object of the native HTML input element.
  • A mat-icon component displays a magnifier icon and is located at the end of the input element, as indicated by the matSuffix directive. It also calls the search component method when clicked.

    Important note

    The cityCtrl template reference variable is indicated by a # and is accessible everywhere inside the template of the component.

A mat-card component presents information in a card layout and is displayed only when the weather component property has a value. It consists of the following child elements:

  • mat-card-header: The header of the card. It consists of a mat-card-title component that displays the name of the city and the country code and a mat-card-subtitle component that displays the current weather conditions.
  • mat-card-image: The image of the card that displays the icon of the weather conditions, along with a description as an alternate text.
  • mat-card-content: The main content of the card. It displays the temperature, pressure, humidity, and wind speed of the current weather. The temperature is displayed without any decimal points, as indicated by the number pipe.

Let's now spice things up a bit by adding some styles to our component:

weather.component.scss

:host {

  display: flex;

  align-items: center;

  justify-content: center;

  flex-direction: column;

  padding-top: 25px;

}

mat-form-field {

  width: 20%;

}

mat-icon {

  cursor: pointer;

}

mat-card {

  margin-top: 30px;

  width: 250px;

}

h1 {

  text-align: center;

  font-size: 2.5em;

}

The :host selector is an Angular unique CSS selector that targets the HTML element hosting our component, which in our case, is the app-weather HTML element.

If we run our application using ng serve, navigate to http://localhost:4200 and search for weather information in Athens, we should get the following output on the screen:

Figure 4.1 – Application output

Figure 4.1 – Application output

Congratulations! At this point, you have a fully working Angular application that displays weather information for a specific city. The application consists of a single Angular component that communicates with the OpenWeather API using an Angular service through HTTP. We learned how to style our component using Angular Material and give our users a pleasant experience with our app. But what happens when we are offline? Does the application work as expected? Does the user's experience remain the same? Let's find out in the following section.

Enabling offline mode with the service worker

Users from anywhere can now access our Angular application to get weather information for any city they are interested in. When we say anywhere, we mean any network type such as broadband, cellular (3G/4G/5G), and Wi-Fi. Consider the case where a user is in a place with low coverage or frequent network outages. How is our application going to behave? Let's find out by conducting an experiment:

  1. Run the Angular application using the serve command of the Angular CLI:

    ng serve

  2. Open your favorite browser and navigate to http://localhost:4200, which is the default address and port number for an Angular CLI project. You should see the input field for entering the name of the city:
    Figure 4.2 – Entering the name of a city

    Figure 4.2 – Entering the name of a city

  3. Open the developer tools of your browser and navigate to the Network tab. Set the value of the Throttling dropdown to Offline:
    Figure 4.3 – Offline network mode

    Figure 4.3 – Offline network mode

  4. Try to refresh your browser. You will see an indication that you are disconnected from the internet, as shown in the following screenshot:
Figure 4.4 – No internet connection (Google Chrome)

Figure 4.4 – No internet connection (Google Chrome)

The previous case is pretty standard in areas with low-quality internet connections. So, what can we do for our users in such areas? Luckily, the Angular framework contains an implementation of a service worker that can significantly enhance the UX of our application when running in offline mode. It can cache certain parts of the application and deliver them accordingly instead of making real requests.

Tip

The Angular service worker can also be used in environments with large network latency connections. Consider using a service worker in this type of network to also improve the experience of your users.

Run the following command of the Angular CLI to enable the service worker in our Angular application:

ng add @angular/pwa

The preceding command will transform the Angular CLI workspace accordingly for PWA support:

  • It adds the @angular/service-worker npm package to the dependencies section of the package.json file of the application.
  • It creates the manifest.webmanifest file in the src folder of the application. The manifest file contains information about the application needed to install and run the application as a native one. It also adds it to the assets array of the build options in the angular.json file.
  • It creates the ngsw-config.json file at the root of the project, which is the service worker configuration file. We use it to define configuration-specific artifacts, such as which resources are cached and how they are cached. You can find more details about the configuration of the service worker at the following link: https://angular.io/guide/service-worker-config#service-worker-configuration.

    The configuration file is also set in the ngswConfigPath property of the build configuration in the angular.json file.

  • It sets the serviceWorker property to true in the build configuration of the angular.json file.
  • It registers the service worker in the app.module.ts file:

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

    import { environment } from '../environments/environment';

    @NgModule({

      declarations: [

        AppComponent,

        WeatherComponent

      ],

      imports: [

        BrowserModule,

        BrowserAnimationsModule,

        HttpClientModule,

        MatIconModule,

        MatInputModule,

        MatCardModule,

        ServiceWorkerModule.register('ngsw-worker.js', {

      enabled: environment.production,

      // Register the ServiceWorker as soon as the app is

      // stable

      // or after 30 seconds (whichever comes first).

      registrationStrategy: 'registerWhenStable:30000'

    })

      ],

      providers: [],

      bootstrap: [AppComponent]

    })

    The ngsw-worker.js file is the JavaScript file that contains the actual implementation of the service worker. It is created automatically for us when we build our application in production mode. Angular uses the register method of the ServiceWorkerModule class to register it within our application.

  • It creates several icons to be used when the application is installed as a native one in the device of the user.
  • It includes the manifest file and a meta tag for theme-color in the head of the index.html file:

    <link rel="manifest" href="manifest.webmanifest">

    <meta name="theme-color" content="#1976d2">

Now that we have completed the installation of the service worker, it is time to test it! Before moving on, we should install an external web server because the built-in function of the Angular CLI does not work with service workers. A good alternative is http-server:

  1. Run the install command of the npm client to install http-server:

    npm install -D http-server

    The preceding command will install http-server as a development dependency of our Angular CLI project.

  2. Build the Angular application using the build command of the Angular CLI:

    ng build

  3. Open the package.json file of the Angular CLI workspace and add the following entry to the scripts property:

    "scripts": {

      "ng": "ng",

      "start": "ng serve",

      "build": "ng build",

      "watch": "ng build --watch –configuration

        development",

      "test": "ng test",

      "server": "http-server -p 8080 -c-1

        dist/weather-app"

    }

  4. Start the HTTP web server using the following command:

    npm run server

    The preceding command will start http-server at port 8080 and will have caching disabled.

  5. Open your browser and navigate to http://localhost:8080.

    Tip

    Prefer opening the page in private or incognito mode to avoid unexpected behavior from the service worker.

  6. Repeat the process that we followed at the beginning of the section for switching to offline mode.
  7. If you refresh the page now, you will notice that the application is working as expected.

    The service worker did all the work for us, and the process was so transparent that we could not tell whether we are online or offline. You can verify that by inspecting the Network tab:

Figure 4.5 – Service worker (offline mode)

Figure 4.5 – Service worker (offline mode)

The (ServiceWorker) value in the Size column indicates that the service worker served a cached version of our application.

We have now successfully installed the service worker and went one step closer to converting our application into a PWA. In the following section, we will learn how to notify users of the application about potential updates.

Staying up to date with in-app notifications

When we want to apply a change in a web application, we make the change and build a new version of our application. The application is then deployed to a web server, and every user has access to the new version immediately. But this is not the case with PWA applications.

When we deploy a new version of our PWA application, the service worker must act accordingly and apply a specific update strategy. It should either notify the user of the new version or install it immediately. Whichever update strategy we follow depends on our needs. In this project, we want to show a prompt to the user and decide whether they want to update. Let's see how to implement this feature in our application:

  1. Open the app.module.ts file and add MatSnackBarModule to the imports array of the @NgModule decorator:

    import { MatSnackBarModule } from '@angular/material/snack-bar';

    @NgModule({

      declarations: [

        AppComponent,

        WeatherComponent

      ],

      imports: [

        BrowserModule,

        BrowserAnimationsModule,

        HttpClientModule,

        MatIconModule,

        MatInputModule,

        MatCardModule,

        MatSnackBarModule,

        ServiceWorkerModule.register('ngsw-worker.js', {

          enabled: environment.production,

          // Register the ServiceWorker as soon as the app

          // is stable

          // or after 30 seconds (whichever comes first).

          registrationStrategy: 'registerWhenStable:30000'

        })

      ],

      providers: [],

      bootstrap: [AppComponent]

    })

    MatSnackBarModule is an Angular Material module that allows us to interact with snack bars. A snack bar is a pop-up window that usually appears on the bottom of the page and is used for notification purposes.

  2. Open the app.component.ts file and add the OnInit interface to the implemented interfaces of the AppComponent class:

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

    @Component({

      selector: 'app-root',

      templateUrl: './app.component.html',

      styleUrls: ['./app.component.scss']

    })

    export class AppComponent implements OnInit {

      title = 'weather-app';

    }

  3. Inject the MatSnackBar and SwUpdate services in the constructor of the AppComponent class:

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

    import { MatSnackBar } from '@angular/material/snack-bar';

    import { SwUpdate } from '@angular/service-worker';

    @Component({

      selector: 'app-root',

      templateUrl: './app.component.html',

      styleUrls: ['./app.component.scss']

    })

    export class AppComponent implements OnInit {

      title = 'weather-app';

      constructor(private updates: SwUpdate, private

        snackbar: MatSnackBar) {}

    }

    The MatSnackBar service is an Angular service exposed from MatSnackBarModule. The SwUpdate service is part of the service worker and contains observables that we can use to notify regarding the update process on our application.

  4. Create the following ngOnInit method:

    ngOnInit() {

      this.updates.available.pipe(

        switchMap(() => this.snackbar.open('A new version

          is available!', 'Update now').afterDismissed()),

        filter(result => result.dismissedByAction),

        map(() => this.updates.activateUpdate().then(() =>

          location.reload()))

      ).subscribe();

    }

    The ngOnInit method is an implementation method of the OnInit interface and is called upon initialization of the component. The SwUpdate service contains an available observable property that we can use to get notified when a new version of our application is available. Typically, we tend to subscribe to observables, but in this case, we don't. Instead, we subscribe to the pipe method, an RxJS operator for composing multiple operators.

  5. Add the following import statements at the top of the app.component.ts file:

    import { filter, map, switchMap } from 'rxjs/operators';

A lot is going on inside the pipe method that we defined previously, so let's break it down into pieces to understand it further. The pipe operator combines three RxJS operators:

  • switchMap: This is called when a new version of our application is available. It uses the open method of the snackbar property to show a snack bar with an action button and subscribes to its afterDismissed observable. The afterDismissed observable emits when the snack bar is closed either by clicking on the action button or programmatically using its API methods.
  • filter: This is called when the snack bar is dismissed using the action button.
  • map: This calls the activateUpdate method of the updates property to apply the new version of the application. Once the application has been updated, it reloads the window of the browser for the changes to take effect.

Let's see the whole process of updating to a new version in action:

  1. Run the build command of the Angular CLI to build the Angular application:

    ng build

  2. Start the HTTP server to serve the application:

    npm run server

  3. Open a private or incognito window of your browser and navigate to http://localhost:8080.
  4. Without closing the browser window, let's introduce a change in our application and add a header. Run the generate command of the Angular CLI to create a component:

    ng generate component header

  5. Open the app.module.ts file and import a couple of Angular Material modules:

    import { MatButtonModule } from '@angular/material/button';

    import { MatToolbarModule } from '@angular/material/toolbar';

    @NgModule({

      declarations: [

        AppComponent,

        WeatherComponent,

        HeaderComponent

      ],

      imports: [

        BrowserModule,

        BrowserAnimationsModule,

        HttpClientModule,

        MatIconModule,

        MatInputModule,

        MatCardModule,

        MatSnackBarModule,

        MatButtonModule,

        MatToolbarModule,

        ServiceWorkerModule.register('ngsw-worker.js', {

      enabled: environment.production,

      // Register the ServiceWorker as soon as the app is

      // stable

      // or after 30 seconds (whichever comes first).

      registrationStrategy: 'registerWhenStable:30000'

    })

      ],

      providers: [],

      bootstrap: [AppComponent]

    })

  6. Open the header.component.html file and create a mat-toolbar component with two HTML button elements, each one containing a mat-icon component:

    <mat-toolbar color="primary">

      <span>Weather App</span>

      <span class="spacer"></span>

      <button mat-icon-button>

        <mat-icon>refresh</mat-icon>

      </button>

      <button mat-icon-button>

        <mat-icon>share</mat-icon>

      </button>

    </mat-toolbar>

  7. Add the following CSS style to the header.component.scss file to position buttons at the far right end of the header:

    .spacer {

      flex: 1 1 auto;

    }

  8. Open the app.component.html file and add the app-header component on the top:

    <app-header></app-header>

    <app-weather></app-weather>

  9. Repeat steps 1 and 2 to build and start the application. Open a new tab in the browser window that you have already opened and you will see the following notification at the bottom of the page after a few seconds:
    Figure 4.6 – New version notification

    Figure 4.6 – New version notification

  10. Click on the Update now button, wait for the browser window to reload, and you should see your change:
Figure 4.7 – Application output

Figure 4.7 – Application output

Our Angular application has begun to transform into a PWA one. Additional to the caching mechanism that the Angular service worker provides, we have added a mechanism for installing new versions of our application. In the following section, we will learn how to deploy our application and install it natively on our device.

Deploying our app with Firebase hosting

Firebase is a hosting solution provided by Google that we can use to deploy our Angular applications. The Firebase team has put a lot of effort into creating an Angular CLI schematic for deploying an Angular application using one single command. Before diving deeper, let's learn how to set up Firebase hosting:

  1. Use a Google account to log in to Firebase at https://console.firebase.google.com.
  2. Click on the Add project button to create a new Firebase project.
  3. Enter the name of the project, weather-app, and click the Continue button.

    Important note

    Firebase generates a unique identifier for your project, such as weather-app-b11a2, underneath the name of the project. The identifier will be used in the hosting URL of your project later on.

  4. Disable the use of Google Analytics for your project and click the Create project button.
  5. Once the project has been created, the following will appear on the screen:
    Figure 4.8 – Firebase project creation

    Figure 4.8 – Firebase project creation

  6. Click on the Continue button, and you will be redirected to the dashboard of your new Firebase project.

We have now completed the configuration of the Firebase hosting. It is now time to integrate it with our Angular application. Run the following command of the Angular CLI to install the @angular/fire npm package in your Angular CLI project:

ng add @angular/fire

The preceding command will also authenticate you with Firebase and prompt you to select a Firebase project for deployment. Use the arrow keys to select the weather-app project that we created earlier and press Enter. The process will modify the Angular CLI workspace accordingly to accommodate its deployment to Firebase:

  • It will add several npm packages to the dependencies and devDependencies sections of the package.json file of the project.
  • It will create a .firebaserc file at the root folder that contains details of the selected Firebase project.
  • It will create a firebase.json file at the root folder, which is the Firebase configuration file:

    {

      "hosting": [

        {

          "target": "weather-app",

          "public": "dist/weather-app",

          "ignore": [

            "**/.*"

          ],

          "headers": [

            {

              "source": "*.[0-9a-f][0-9a-f][0-9a-f]

               [0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]

               [0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]

               [0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]

               [0-9a-f][0-9a-f].+(css|js)",

              "headers": [

                {

                  "key": "Cache-Control",

                  "value": "public,

                            max-age=31536000,immutable"

                }

              ]

            }

          ],

          "rewrites": [

            {

              "source": "**",

              "destination": "/index.html"

            }

          ]

        }

      ]

    }

    The configuration file specifies settings such as the folder that will be deployed to Firebase as stated from the public property and any rewrite rules with the rewrites property.

    Important note

    The folder that will be deployed by default is the dist output folder created by the Angular CLI when we run the ng build command.

  • It will add a deploy entry to the architect section of the angular.json configuration file:

    "deploy": {

      "builder": "@angular/fire:deploy",

      "options": {}

    }

To deploy the application, we only need to run a single Angular CLI command, and the Angular CLI will take care of the rest:

ng deploy

The preceding command will build the application and start deploying it to the selected Firebase project. Once deployment is complete, the Angular CLI will report back the following information:

  • Project Console: The dashboard of the Firebase project.
  • Hosting URL: The URL of the deployed version of the application. It consists of the unique identifier of the Firebase project and the .web.app suffix that is added automatically from Firebase.

    Important note

    The service worker requires an application to be served with HTTPS to work properly as a PWA, except in the localhost that is used for development. Firebase hosts web applications with HTTPS by default.

Now that we have deployed our application, let's see how we can install it as a PWA on our device:

  1. Navigate to the hosting URL and click on the plus button next to the address bar of the browser:
    Figure 4.9 – Installing the application (Google Chrome)

    Figure 4.9 – Installing the application (Google Chrome)

    Important note

    The Install button may be found in different locations in other browsers.

    The browser will prompt us to install the weather-app application.

  2. Click the Install button, and the application will open as a native window on our device:
Figure 4.10 – PWA application

Figure 4.10 – PWA application

It will also install a shortcut for launching the application directly from our device. Congratulations! We now have a full PWA application that displays weather information for a city.

Summary

In this chapter, we built a PWA application that displays weather information for a given city.

Initially, we set up the OpenWeather API to get weather data and created an Angular application from scratch to integrate it. We learned how to use the built-in HTTP client of the Angular framework to communicate with the OpenWeather API. We also installed the Angular Material library and used some of the ready-made UI components for our application.

After creating the Angular application, we introduced the Angular service worker and enabled it to work offline. We learned how to interact with the service worker and provide notifications for updates in our application. Finally, we deployed a production version of our application into the Firebase hosting and installed it locally on our device.

In the next chapter, we will learn how to create an Angular desktop application with Electron, the big rival of PWA applications.

Practice questions

Let's take a look at a few practice questions:

  1. What is the purpose of environment files in Angular?
  2. How do we pass query parameters in an HTTP request?
  3. How do we get results back from an HTTP get method?
  4. Why do we use a service worker?
  5. Which Angular Material component is used for notifications?
  6. How are we notified regarding updates from the service worker?
  7. What is the primary use of the pipe RxJS operator?
  8. How can we change the theme color of a PWA application?
  9. Which npm package do we use for deploying to Firebase?
  10. How can we change the folder that is deployed in Firebase?

Further reading

Add this before the bullet points:

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
44.212.50.220