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 </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 with Auth0

By integrating Auth0 in your Angular application, you will be able to manage user identities, including password resets, creating, provisioning, blocking, and deleting users. It requires just a few steps.

Auth0 login screen

Set up an Auth0 application

First, sign up for a free account here. Then, set up an Auth0 application with the following steps:

  1. Go to your Applications section of the Auth0 Dashboard and click the "+ Create Application" button.
  2. Name your new app and select "Single Page Web Applications" as the application type.
  3. In the Settings for your new Auth0 app, add http://localhost:4200 to the Allowed Callback URLs, Allowed Web Origins, and Allowed Logout 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 uses username/password database, Facebook, Google, and Twitter.

Note: Set up your own social keys and do not leave social connections set to use Auth0 dev keys, or you will encounter issues with token renewal.

Add dependencies and configure

In the root folder of your Angular project, install the auth0-spa-js library by typing the following command in a terminal window:

npm install @auth0/auth0-spa-js

Then, edit the environment.ts file in the src/environments folder and add the CLIENT_DOMAIN and CLIENT_ID keys as follows:

// src/environments/environment.ts

export const environment = {
  production: false,
  auth: {
    CLIENT_DOMAIN: 'YOUR_DOMAIN',
    CLIENT_ID: 'YOUR_CLIENT_ID'
  }
};

export const config = {
};

Replace the YOUR_DOMAIN and YOUR_CLIENT_ID placeholders with the actual values for the domain and client id you found in your Auth0 Dashboard.

Add the authentication service

Authentication logic in your Angular application is handled with an AuthService authentication service. So, use Angular CLI to generate this new service by running the following command:

ng generate service auth

Now, open the src/app/auth.service.ts file and replace its content with the following:

//src/app/auth.service.ts

import { Injectable } from '@angular/core';
import createAuth0Client from '@auth0/auth0-spa-js';
import Auth0Client from '@auth0/auth0-spa-js/dist/typings/Auth0Client';
import { from, of, Observable, BehaviorSubject, combineLatest, throwError } from 'rxjs';
import { tap, catchError, concatMap, shareReplay } from 'rxjs/operators';
import { Router } from '@angular/router';
import { environment } from './../environments/environment';

@Injectable({
  providedIn: 'root'
})
export class AuthService {
  // Create an observable of Auth0 instance of client
  auth0Client$ = (from(
    createAuth0Client({
      domain: environment.auth.CLIENT_DOMAIN,
      client_id: environment.auth.CLIENT_ID,
      redirect_uri: `${window.location.origin}`
    })
  ) as Observable<Auth0Client>).pipe(
    shareReplay(1), // Every subscription receives the same shared value
    catchError(err => throwError(err))
  );
  // Define observables for SDK methods that return promises by default
  // For each Auth0 SDK method, first ensure the client instance is ready
  // concatMap: Using the client instance, call SDK method; SDK returns a promise
  // from: Convert that resulting promise into an observable
  isAuthenticated$ = this.auth0Client$.pipe(
    concatMap((client: Auth0Client) => from(client.isAuthenticated())),
    tap(res => this.loggedIn = res)
  );
  handleRedirectCallback$ = this.auth0Client$.pipe(
    concatMap((client: Auth0Client) => from(client.handleRedirectCallback()))
  );
  // Create subject and public observable of user profile data
  private userProfileSubject$ = new BehaviorSubject<any>(null);
  userProfile$ = this.userProfileSubject$.asObservable();
  // Create a local property for login status
  loggedIn: boolean = null;

  constructor(private router: Router) {
    // On initial load, check authentication state with authorization server
    // Set up local auth streams if user is already authenticated
    this.localAuthSetup();
    // Handle redirect from Auth0 login
    this.handleAuthCallback();
  }

  // When calling, options can be passed if desired
  // https://auth0.github.io/auth0-spa-js/classes/auth0client.html#getuser
  getUser$(options?): Observable<any> {
    return this.auth0Client$.pipe(
      concatMap((client: Auth0Client) => from(client.getUser(options))),
      tap(user => this.userProfileSubject$.next(user))
    );
  }

  private localAuthSetup() {
    // This should only be called on app initialization
    // Set up local authentication streams
    const checkAuth$ = this.isAuthenticated$.pipe(
      concatMap((loggedIn: boolean) => {
        if (loggedIn) {
          // If authenticated, get user and set in app
          // NOTE: you could pass options here if needed
          return this.getUser$();
        }
        // If not authenticated, return stream that emits 'false'
        return of(loggedIn);
      })
    );
    checkAuth$.subscribe();
  }

  login(redirectPath: string = '/') {
    // A desired redirect path can be passed to login method
    // (e.g., from a route guard)
    // Ensure Auth0 client instance exists
    this.auth0Client$.subscribe((client: Auth0Client) => {
      // Call method to log in
      client.loginWithRedirect({
        redirect_uri: `${window.location.origin}`,
        appState: { target: redirectPath }
      });
    });
  }

  private handleAuthCallback() {
    // Call when app reloads after user logs in with Auth0
    const params = window.location.search;
    if (params.includes('code=') && params.includes('state=')) {
      let targetRoute: string; // Path to redirect to after login processed
      const authComplete$ = this.handleRedirectCallback$.pipe(
        // Have client, now call method to handle auth callback redirect
        tap(cbRes => {
          // Get and set target redirect route from callback results
          targetRoute = cbRes.appState && cbRes.appState.target ? cbRes.appState.target : '/';
        }),
        concatMap(() => {
          // Redirect callback complete; get user and login status
          return combineLatest([
            this.getUser$(),
            this.isAuthenticated$
          ]);
        })
      );
      // Subscribe to authentication completion observable
      // Response will be an array of user and login status
      authComplete$.subscribe(([user, loggedIn]) => {
        // Redirect to target route after callback processing
        this.router.navigate([targetRoute]);
      });
    }
  }

  logout() {
    // Ensure Auth0 client instance exists
    this.auth0Client$.subscribe((client: Auth0Client) => {
      // Call method to log out
      client.logout({
        client_id: environment.auth.CLIENT_ID,
        returnTo: `${window.location.origin}`
      });
    });
  }
}

This service provides the properties and methods necessary to manage authentication across your Angular application.

Add the login and logout buttons

To add a new component that allows you to authenticate with Auth0, run the following command in a terminal window:

ng generate component login-button

Open the src/app/login-button/login-button.component.ts file and replace its content with the following:

//src/app/login-button/login-button.component.ts

import { Component, OnInit } from '@angular/core';
import { AuthService } from '../auth.service';

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

  constructor(public auth: AuthService) { }

  ngOnInit() { }
}

Next, define the component's UI by replacing the content of the src/app/login-button/login-button.component.html with the following markup:

<!-- src/app/login-button/login-button.component.html -->
<div>
  <button (click)="auth.login()" *ngIf="!auth.loggedIn">Log In</button>
  <button (click)="auth.logout()" *ngIf="auth.loggedIn">Log Out</button>
</div>

Finally, put the <app-login-button></app-login-button> tag within the src/app/app.component.html file, wherever you want the component to appear.

Your Angular application is ready to authenticate with Auth0!

Check out the Angular Quickstart to learn more about integrating Auth0 with Angular applications.

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