TL;DR: In this article, I'll cover the new features in Angular 6 and several other changes and deprecations.


Angular is a very popular framework that is actively maintained by Google. Its popularity and wide adoption have been driven by how it keeps evolving and getting better by the day. Angular powers a lot of web platforms such as Google Adwords, Google Fiber, Adsense, and Winc.

The previous major Angular version was Angular 5. We covered the major features and bug fixes that shipped with Angular 5 before. Now, we are going to cover the latest major release, Angular 6, which focuses on making Angular smaller and faster to use.

Let's go through the major changes in Angular 6.

1. Improved Service Worker Support

Angular 6 now supports the configuration of navigation URLs in Service Workers. The service worker will redirect navigation requests that don't match any asset or data group to the specified index file.

"Angular 6 now supports configuration of navigation URLs in Service Workers."

By default, a navigation request can have any URL except for those containing __ and URLs containing a file extension (i.e a .) in the last path segment. Sometimes it is great to be able to configure different rules for the URLs of navigation requests (e.g. ignore specific URLs and pass them through to the server).

Now, you can specify an optional navigationUrls list in ngsw-config.json. which contains URLs or simple globs. Only requests whose URLs match any of the positive URLs/patterns and none of the negative ones (i.e. URLs/patterns starting with !) will be considered navigation requests and handled the right way by the service worker.

Before now, the service worker would enter a degrade mode where only existing clients would be served if either the client or server was offline while trying to fetch ngsw.json. In Angular 6, the service worker remains in the current mode until connectivity to the server is restored.

Furthermore, a helper script, safety-worker.js, has been added to the service worker package to enable easy deactivation of an existing service worker in production.

packages/service-worker/safety-worker.js

self.addEventListener('install', event => { self.skipWaiting(); });

self.addEventListener('activate', event => {
  event.waitUntil(self.clients.claim());
  self.registration.unregister().then(
      () => { console.log('NGSW Safety Worker - unregistered old service worker'); });
});

2. Goodbye Template Element

The <template> element was deprecated long ago, precisely in Angular 4. It has been removed completely in Angular 6.

The <ng-template> element should be used instead.

Before:

<template>some template content</template>

Now:

<ng-template>some template content</ng-template>

3. Better URL Serialization

Before now, there were issues around routes and URL serialization such as this below:

What's new in Angular 6 - URL Serialization What's new in Angular 6 - URL Serialization

In Angular 6, issues like the one above have been fixed and:

  • URI fragments will now serialize the same as query strings.
  • In the URL path, (portion prior to the query string and/or fragment), the plus sign (+) and ampersand (&) will appear decoded.
  • In the URL path, parentheses values (( and )) will now appear percent-encoded as %28 and %29 respectively.
  • In the URL path, semicolons will be encoded in their percent encoding %3B.

It's also important to know that parentheses and semicolons denoting auxiliary routes will, in any case, show up in their decoded shape except for parentheses and semicolons used as values in a segment or key/value pair for matrix params which will show up encoded.

4. Support Added for Custom Elements

This is crazy. When I discovered this, I jumped! Let me explain before you get too excited.

The support is currently experimental and unstable. It's targeted to land and become stable in the Angular 6.x release cycle.

With this support, developers can simply register Angular Components as Custom Elements. Once registered, these components can be used just like built-in HTML elements. They are HTML Elements, so why not?

So, Angular developers can do something like this:

//my-name.ts

import { Component, Input, NgModule } from '@angular/core';
import { createCustomElement } from '@angular/elements';

@Component({
  selector: 'my-name',
  template: `<h1>Hello my name is {{name}}</h1>`
})

export class MyName {
  @Input() name: string = 'Prosper!';
}

@NgModule({
  declarations: [ MyName ],
  entryComponents: [ MyName ]
})

export class MyNameModule {}
//app.component.ts
import { Component, NgModuleRef } from '@angular/core';
import { createCustomElement } from '@angular/elements';

import { MyName } from './my-name.ngfactory';

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

export class AppComponent {
  constructor(private injector: Injector, ngModuleRef: NgModuleRef) {
    const ngElementConfig = createCustomElement(MyName, injector);
    customElements.define('my-name', NgElementConfig);
  }
}

5. Forms Validation in Angular 6

Before now, ngModelChange was always emitted before the underlying form control was updated.

If you had a handler for the ngModelChange event that checked the value through the control, the old value would be logged instead of the updated value. This is not the case if you pass the value through the $event keyword directly.

Check out this example:

Passing the value through the $event keyword directly

<input [(ngModel)]="name" (ngModelChange)="onChange($event)">
onChange(value) {
   console.log(value); // logs updated value
}

Using a Handler

<input #modelDir="ngModel" [(ngModel)]="name" (ngModelChange)="onChange(modelDir)">
onChange(ngModel: ngModel) {
   console.log(ngModel.value); // logs old value
}

In Angular 6, ngModelChange is now emitted after the value is updated on its form control.

"In Angular 6, ngModelChange is now emitted after the value is updated on its form control."

onChange(ngModel: NgModel) {
   console.log(ngModel.value); // logs updated value
}

6. Multiple Validators for Form Builder Array

In previous versions of Angular, you could set only one validator on a FormArray field with the FormBuilder.array method.

In Angular 6, you can set multiple validators with the FormBuilder.array method:

...
ngOnInit() {
  this.speakerForm = this.formBuilder.group({
    text: ['', Validators.required],
    options: this.formBuilder.array([], [MyValidators.correctProposalCount, MyValidators.totalProposals])
  });
}

7. Token Marking for Runtime Animation Context

In Angular 6, it's now possible to determine which animation context is used for a component at runtime. A token is provided as a marker to determine whether the component is running a BrowserAnimationsModule or NoopAnimationsModule context at runtime.

8. Hello Schematics

Schematics is a new scaffolding library that's used by the Angular CLI to generate custom templates. The Angular team has always been keen on improving developer productivity, which explains the birth of schematics.

With Schematics, you can easily create Angular libraries like so:

First, install the necessary schematic tools:

npm i -g  ng-lib-schematics  @angular-devkit/core @angular-devkit/schematics-cli

Next, create a new angular-cli project:

ng new avengers --skip-install // avengers is the name of the new library I'm trying to create

Finally, you can just run schematics like so:

schematics ng-lib-schematics:lib-standalone --name avengers

A new lib directory will be generated for you inside the src folder. The lib directory ships with a sample demo and the build tools necessary for a typical Angular package.

Check out this deep and excellent guide to Schematics.

Deprecations and Other Updates

  • Angular 6 ships with Rxjs 6.0.0.
  • @Injectable now supports tree-shakeable tokens.
  • Service workers now properly handle invalid hashes in all scenarios.
  • The router sometimes hits a race condition while a route is being instantiated and a new navigation request arrives. This issue has been solved in Angular 6.
  • Avoid overriding ngInjectableDef in the decorator if present on the type.

Check out other Angular 6 updates here.

Upgrading to Angular 6

The Angular team built an awesome tool to make upgrading as easy as possible.

Angular 6 Upgrade Angular 6 upgrade tool

Aside: Authenticate an Angular App and Node API with Auth0

We can protect our applications and APIs so that only authenticated users can access them. Let's explore how to do this with an Angular application and a Node API using Auth0. You can clone this sample app and API from the angular-auth0-aside repo on GitHub.

Auth0 login screen

Features

The sample Angular application and API has the following features:

  • Angular application generated with Angular CLI and served at http://localhost:4200
  • Authentication with auth0.js using a login page
  • Node server protected API route http://localhost:3001/api/dragons returns JSON data for authenticated GET requests
  • Angular app fetches data from API once user is authenticated with Auth0
  • Profile page requires authentication for access using route guards
  • Authentication service uses a subject to propagate authentication status events to the entire app
  • User profile is fetched on authentication and stored in authentication service
  • Access token, profile, and token expiration are stored in local storage and removed upon logout

Sign Up for Auth0

You'll need an Auth0 account to manage authentication. You can sign up for a free account here. Next, set up an Auth0 application and API so Auth0 can interface with an Angular app and Node API.

Set Up an Auth0 Application

  1. Go to your Auth0 Dashboard and click the "create a new application" button.
  2. Name your new app and select "Single Page Web Applications".
  3. In the Settings for your new Auth0 app, add http://localhost:4200/callback to the Allowed Callback URLs. Click the "Save Changes" button.
  4. If you'd like, you can set up some social connections. You can then enable them for your app in the Application options under the Connections tab. The example shown in the screenshot above utilizes username/password database, Facebook, Google, and Twitter. For production, make sure you set up your own social keys and do not leave social connections set to use Auth0 dev keys.

Note: Under the OAuth tab of Advanced Settings (at the bottom of the Settings section) you should see that the JsonWebToken Signature Algorithm is set to RS256. This is the default for new applications. If it is set to HS256, please change it to RS256. You can read more about RS256 vs. HS256 JWT signing algorithms here.

Set Up an API

  1. Go to APIs in your Auth0 dashboard and click on the "Create API" button. Enter a name for the API. Set the Identifier to your API endpoint URL. In this example, this is http://localhost:3001/api/. The Signing Algorithm should be RS256.
  2. You can consult the Node.js example under the Quick Start tab in your new API's settings. We'll implement our Node API in this fashion, using Express, express-jwt, and jwks-rsa.

We're now ready to implement Auth0 authentication on both our Angular client and Node backend API.

Dependencies and Setup

The Angular app utilizes the Angular CLI. Make sure you have the CLI installed globally:

$ npm install -g @angular/cli

Once you've cloned the project, install the Node dependencies for both the Angular app and the Node server by running the following commands in the root of your project folder:

$ npm install
$ cd server
$ npm install

The Node API is located in the /server folder at the root of our sample application.

Find the config.js.example file and remove the .example extension from the filename. Then open the file:

// server/config.js (formerly config.js.example)
module.exports = {
  CLIENT_DOMAIN: '[AUTH0_CLIENT_DOMAIN]', // e.g. 'you.auth0.com'
  AUTH0_AUDIENCE: 'http://localhost:3001/api/'
};

Change the AUTH0_CLIENT_DOMAIN identifier to your Auth0 application domain and set the AUTH0_AUDIENCE to your audience (in this example, this is http://localhost:3001/api/). The /api/dragons route will be protected with express-jwt and jwks-rsa.

Note: To learn more about RS256 and JSON Web Key Set, read Navigating RS256 and JWKS.

Our API is now protected, so let's make sure that our Angular application can also interface with Auth0. To do this, we'll activate the src/app/auth/auth0-variables.ts.example file by deleting .example from the file extension. Then open the file and change the [AUTH0_CLIENT_ID] and [AUTH0_CLIENT_DOMAIN] strings to your Auth0 information:

// src/app/auth/auth0-variables.ts (formerly auth0-variables.ts.example)
...
export const AUTH_CONFIG: AuthConfig = {
  CLIENT_ID: '[AUTH0_CLIENT_ID]',
  CLIENT_DOMAIN: '[AUTH0_CLIENT_DOMAIN]',
  ...

Our app and API are now set up. They can be served by running ng serve from the root folder and node server.js from the /server folder.

With the Node API and Angular app running, let's take a look at how authentication is implemented.

Authentication Service

Authentication logic on the front end is handled with an AuthService authentication service: src/app/auth/auth.service.ts file.

// src/app/auth/auth.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import * as auth0 from 'auth0-js';
import { AUTH_CONFIG } from './auth0-variables';
import { UserProfile } from './profile.model';

(window as any).global = window;

@Injectable()
export class AuthService {
  // Create Auth0 web auth instance
  // @TODO: Update AUTH_CONFIG and remove .example extension in src/app/auth/auth0-variables.ts.example
  private _auth0 = new auth0.WebAuth({
    clientID: AUTH_CONFIG.CLIENT_ID,
    domain: AUTH_CONFIG.CLIENT_DOMAIN,
    responseType: 'token',
    redirectUri: AUTH_CONFIG.REDIRECT,
    audience: AUTH_CONFIG.AUDIENCE,
    scope: AUTH_CONFIG.SCOPE
  });
  userProfile: UserProfile;
  accessToken: string;

  // Create a stream of logged in status to communicate throughout app
  loggedIn: boolean;
  loggedIn$ = new BehaviorSubject<boolean>(this.loggedIn);

  constructor() {
    // You can restore an unexpired authentication session on init
    // by using the checkSession() endpoint from auth0.js:
    // https://auth0.com/docs/libraries/auth0js/v9#using-checksession-to-acquire-new-tokens
  }

  private _setLoggedIn(value: boolean) {
    // Update login status subject
    this.loggedIn$.next(value);
    this.loggedIn = value;
  }

  login() {
    // Auth0 authorize request
    this._auth0.authorize();
  }

  handleLoginCallback() {
    // When Auth0 hash parsed, get profile
    this._auth0.parseHash((err, authResult) => {
      if (authResult && authResult.accessToken) {
        window.location.hash = '';
        this.getUserInfo(authResult);
      } else if (err) {
        console.error(`Error: ${err.error}`);
      }
    });
  }

  getUserInfo(authResult) {
    // Use access token to retrieve user's profile and set session
    this._auth0.client.userInfo(authResult.accessToken, (err, profile) => {
      this._setSession(authResult, profile);
    });
  }

  private _setSession(authResult, profile) {
    const expTime = authResult.expiresIn * 1000 + Date.now();
    // Save session data and update login status subject
    localStorage.setItem('expires_at', JSON.stringify(expTime));
    this.accessToken = authResult.accessToken;
    this.userProfile = profile;
    this._setLoggedIn(true);
  }

  logout() {
    // Remove token and profile and update login status subject
    localStorage.removeItem('expires_at');
    this.accessToken = undefined;
    this.userProfile = undefined;
    this._setLoggedIn(false);
  }

  get authenticated(): boolean {
    // Check if current date is greater than expiration
    // and user is currently logged in
    const expiresAt = JSON.parse(localStorage.getItem('expires_at'));
    return (Date.now() < expiresAt) && this.loggedIn;
  }

}

This service uses the config variables from auth0-variables.ts to instantiate an auth0.js WebAuth instance.

An RxJS BehaviorSubject is used to provide a stream of authentication status events that you can subscribe to anywhere in the app.

The login() method authorizes the authentication request with Auth0 using your config variables. A login page will be shown to the user and they can then log in.

Note: If it's the user's first visit to our app and our callback is on localhost, they'll also be presented with a consent screen where they can grant access to our API. A first party client on a non-localhost domain would be highly trusted, so the consent dialog would not be presented in this case. You can modify this by editing your Auth0 Dashboard API Settings. Look for the "Allow Skipping User Consent" toggle.

We'll receive accessToken and expiresIn in the hash from Auth0 when returning to our app. The handleLoginCallback() method uses Auth0's parseHash() method callback to get the user's profile (getUserInfo()) and set the session (_setSession()) by saving the token, profile, and token expiration and updating the loggedIn$ subject so that any subscribed components in the app are informed that the user is now authenticated.

Note: The profile takes the shape of profile.model.ts from the OpenID standard claims.

Finally, we have a logout() method that clears data from and updates the loggedIn$ subject. We also have an authenticated accessor to return current authentication status based on presence of a token and the token's expiration.

Once AuthService is provided in app.module.ts, its methods and properties can be used anywhere in our app, such as the home component.

Callback Component

The callback component is where the app is redirected after authentication. This component simply shows a loading message until the login process is completed. It executes the handleLoginCallback() method to parse the hash and extract authentication information. It subscribes to the loggedIn$ Behavior Subject from our Authentication service in order to redirect back to the home page once the user is logged in, like so:

// src/app/callback/callback.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';
import { AuthService } from './../auth/auth.service';
import { Router } from '@angular/router';

@Component({
  selector: 'app-callback',
  templateUrl: './callback.component.html',
  styleUrls: ['./callback.component.css']
})
export class CallbackComponent implements OnInit, OnDestroy {
  loggedInSub: Subscription;

  constructor(private auth: AuthService, private router: Router) {
    // Parse authentication hash
    auth.handleLoginCallback();
  }

  ngOnInit() {
    this.loggedInSub = this.auth.loggedIn$.subscribe(
      loggedIn => loggedIn ? this.router.navigate(['/']) : null
    )
  }

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

}

Making Authenticated API Requests

In order to make authenticated HTTP requests, we need to add an Authorization header with the access token in our api.service.ts file.

// src/app/api.service.ts
import { Injectable } from '@angular/core';
import { throwError, Observable } from 'rxjs';
import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
import { catchError } from 'rxjs/operators';
import { AuthService } from './auth/auth.service';

@Injectable()
export class ApiService {
  private baseUrl = 'http://localhost:3001/api/';

  constructor(
    private http: HttpClient,
    private auth: AuthService
  ) { }

  getDragons$(): Observable<any[]> {
    return this.http
      .get<any[]>(`${this.baseUrl}dragons`, {
        headers: new HttpHeaders().set(
          'Authorization', `Bearer ${this.auth.accessToken}`
        )
      })
      .pipe(
        catchError(this._handleError)
      );
  }

  private _handleError(err: HttpErrorResponse | any) {
    const errorMsg = err.message || 'Unable to retrieve data';
    return throwError(errorMsg);
  }

}

Final Touches: Route Guard and Profile Page

A profile page component can show an authenticated user's profile information. However, we only want this component to be accessible if the user is logged in.

With an authenticated API request and login/logout implemented, the final touch is to protect our profile route from unauthorized access. The auth.guard.ts route guard can check authentication and activate routes conditionally. The guard is implemented on specific routes of our choosing in the app-routing.module.ts file like so:

// src/app/app-routing.module.ts
...
import { AuthGuard } from './auth/auth.guard';
...
      {
        path: 'profile',
        component: ProfileComponent,
        canActivate: [
          AuthGuard
        ]
      },
...

More Resources

That's it! We have an authenticated Node API and Angular application with login, logout, profile information, and protected routes. To learn more, check out the following resources:

Conclusion

Angular 6 came loaded with new features and significant improvements. Kudos to the Angular team on making Angular faster and better to use.

Have you upgraded to Angular 6 yet? What are your thoughts? Did you notice any significant improvement? Let me know in the comments section! 😊