Angular v9 is here! Along with it, we now get Ivy, the default compiler that can reduce bundle size, improve debugging, and much more. It’s been almost three years since Angular released an update this large (ironic, since this update makes the code smaller!), and we can see it’s been worth the wait.

GitHub view of Angular v9

In this article, we are going to go over how to update from v8 to v9 and the most Magnificent Seven things that Angular v9 has given us. There are many things we can look forward to in this update, let’s talk about some of our favorites!

"Angular v9 is here! Along with it, we now get Ivy!"

Angular 9 is Ready for its Closeup

This upgrade has been through extensive testing. With more developers and projects using it, the bugs have been reported, and over 300 have been fixed. Angular is used internally at Google for projects like Google Domains and Firebase. These projects will use new versions of Angular to test and validate before it goes beyond beta and RC. This upgrade is ready!

Version 9 is more about changes to the framework, not the code that we wrote. This makes it easy for developers. To upgrade to v9, we won’t have to dive into individual files or components and update any code ourselves. The new Ivy renderer works with all our current code!

"Angular v9 is more about changes to the framework, not the code that we wrote."

How to Update from v8 to v9

We can find step-by-step instructions for updating from v8 to v9 on the official update.angular.io. Let’s go over exactly what needs to happen to upgrade from Angular v8 to Angular v9.

Note: Before starting the upgrade process, be sure to commit all changes to git. This will also be helpful to see how the upgrade modified certain files.

First, ensure we are using Node v10.13 or later. It’s recommended to use the Node 12.15.0 LTS version since it will be supported!

Once we confirm our Node version, we will want to run the following command in our project’s directory:

ng update @angular/core@8 @angular/cli@8

This command will update our @angular/core and @angular/cli in our package.json for our project.

Once we run that, we will want to run:

ng update @angular/core @angular/cli

Terminal - Updating to Angular 9 confirmation

During the update process, we should see numerous messages telling us exactly what the CLI is updating. It will also tell us which files were modified. At a minimum, we should see the following files modified:

VS Code view of files that were modified. tsconfig.app.json, angular.json, package-lock.json, package.json

We may see other files that get modified depending on where the project sits. For example, if the project has the older lazy load syntax or if the project is missing the @injectable() in a service file, those would also get updated.

Once complete, head to our package.json, and we will see that we are up-to-date with the latest Angular v9!

View of package.json showing v9 updates

We have now updated from Angular v8 to Angular v9! Let’s explore more of the features.

The Magnificent Seven

In this article, we are going to list the most “Magnificent Seven” features in Angular v9, all to the theme of movie titles. You get bonus points for spotting all the movie and television show references and listing them in the comments!

1 - Honey I Shrunk Our App

It wasn’t accidental that the Angular team focused on helping our apps become smaller and making our apps faster than they were in previous versions of Angular. Our users expect the app experience to be fast. The Ivy compiler focuses on targeting our code with laser-beam accuracy to find and remove unneeded code.

The Ivy compiler looks for parts of Angular that are not being used. It uses tree-shaking (a way of detecting unused code, also known as dead code removal), and it generates less code for each Angular component.

As the chart below shows, apps of all sizes can benefit from the Ivy compiler.

Graph showing how Ivy improves app size (Image credit)

Once we update to Angular 9, we can run ng build --prod and notice how our file sizes shrink.

2 - Ivy Knows What You Did Last Summer, in that Template

That’s right. Last summer, we wrote some pretty tragic code that we mostly forgot about. But Angular 9 hasn’t forgotten. We made poor choices in our templates and those errors are now detected by Angular 9. Fortunately, Angular is friendly enough to share those template errors with us in a clear and helpful way.

When we reference a component in a template, Angular expects that the component exists in the app. Imagine that we have this template:

<div>
  <app-fake></app-fake>
  Hello {{hero.name}}
</div>

If no component exists that matches the app-fake selector, this is a problem. However, in Angular 9, when we run ng build, we get a very detailed error explaining that it knows exactly what we did wrong.

ng build error

The error message very clearly tells us that it cannot find the component, shows us which component it is referring to, and it shows us exactly where this reference appears in the code!

3 - Winter is Coming, and so is Our Angular Build

Ivy is so fast that we’ll have fewer opportunities to step away while waiting for our builds to complete. Prior to Angular 9, some of our app build times have felt like it would be a race to see what would come first: Winter or the completion of our Angular build.

Well, Ivy knows things about our apps and it always pays its debts. Ivy speeds up our build times significantly. Faster build times mean less stepping away from the computer while we build our apps.

4 - Back to the Future, with AOT!

Remember that time before Angular 9 when we found out we had an AOT error that surfaced only in Continuous Integration (CI) or, heaven forbid, in production? Hello McFly! That’s about when we want to go back in time and correct our mistakes by running ng build --prod.

Why did this happen? Because prior to Angular 9, the AOT compilation step only ran when we executed using the production flag ng build --prod. It seems that AOT compilation would have made the dev builds too slow to be a good experience, so the Angular team opted (thankfully) to only put AOT compilation in the production builds. While this was fine as long as we remembered to run it before pushing or merging (or whatever your CI process is), it certainly had its flaws.

What we wish is that in the future, we can see the AOT errors in all builds. Well, that future is here!

The good news is that Angular 9 brings AOT to the dev build, too! The Ivy compiler makes our builds much faster. So fast that it makes it reasonable to pull AOT into the dev builds (and of course, ng serve) without causing significant delays in build times.

The bottom line is that AOT is now dev and prod. Who cares? We do! AOT errors that never appeared in a dev build used to surprise us when we ran with a prod build. Now that dev and prod use AOT, we’ll find these errors early and we’ll see good error messages explaining how to fix them.

5 - The Phantom Template Variable Menace

There once was a time when we could create phantom variables in our templates - variables that had never been referenced in the template’s associated component. Creating these phantom variables can menace our applications. All of this changes in Angular 9 as we now get a compiler error when we create a template variable that has not been defined in a component.

Let’s take a look at what this means. Imagine we have a template and we set a villain variable when a user clicks on a <div>. Now, we may have intended for them to set the hero model, which exists in the templates’ component. But we might have made a copy and paste error. Or maybe we really wanted to create a villain model, in which case we should have defined the model in the component file.

<div (click)="villain = { id: 1, name: 'john' }">

When we build with Angular 9, this error will be displayed. Notice it tells us that the villain doesn’t exist in the component. It even shows us exactly where to find this code.

Error handling in Components

So what can we do about this? Well, if the intention was to set a hero, now we know we have to change it. If we really need to set the villain model in the template, just create the villain model in the component. Before Angular 9, this may have gone unnoticed, but now Ivy detects and alerts us about all template-only variables.

6 - Brave: An ng update Story

At times running ng update with Angular 8 (or previous) is amazingly easy and helpful. And other times, it felt like we were poking a sleeping bear. It’s almost like the magical wisps inside of the Angular CLI were playing games with us.

Angular 9 now has a vastly improved ng update experience. It is more accurate, more consistent, and more transparent. Here are three key reasons why the Angular CLI is now more reliable than ever:

  1. It now uses the latest version of the CLI to perform all updates.
  2. Now displays a detailed diary depicting exactly what it is doing during the update.
  3. Refactors our code where it detects a need to be compatible with the latest version.

Since a detailed diary of the update progress is displayed, we always know what code is being refactored. Plus, we can also check our git changes for any refactored code. Here are three examples of the scenarios that the Angular CLI will refactor for us:

  • Update lazy loading syntax to use dynamic imports.
  • Removes the static flag from dynamic queries. As of Angular 9, the "static" flag defaults to false and is no longer required for our view and content queries.
  • Update our services without @Injectable to use @Injectable.

This is really convenient, so we do not have to know every new change in Angular 9 (and beyond). The Angular CLI knows these changes, reports to use what changes it is looking for, and then refactors when it finds any places in our code that need those changes.

7 - The Legendary Journey of Entry Components

A long time ago, in a time of myth and legend, only one person knew when we needed to define entryComponents in our NgModules. Ok, that’s not entirely accurate as many of us have used entryComponents. But do we always remember what they do and when they are needed? Or was that knowledge lost in our mind? With no more hope of resurfacing than that of a lost soul in the Minotaur’s maze.

Lest we forget, this was needed when we used a component (perhaps in code), but it never appeared in a template. Prior to Angular 9, specifying the component in the entryComponents array was the recommended way to tell Angular, “Hey, we have a component here!”.

@NgModule({
  // ...
  entryComponents: [ModalComponent]
})
export class AppModule {}

Here is a scenario where we might have a component that is not referenced in a template. Imagine that we create a ModalComponent which shows a modal dialog to the user, letting them select “yes” or “no”. When using Angular Material, we could use the MatDialog.open() function and pass it the ModalComponent to display to the user. This could be the only place in the app that the component is referenced. Prior to Angular 9, this must be set in the entryComponents array in a NgModule. Otherwise, Angular would not know what ModalComponent is.

Wow, this is something we probably don’t do that often. However, when we do need this feature, we often spend some time searching Google for how to solve it.

The good news here is that in Angular 9 there have been changes to the compiler and runtime that make it so we no longer need to specify this in the entryComponents array. Angular will find the component on its own.

The bad news, if we can call it bad news, is that Angular will not remove the component from the entryComponents array for us. So we should search our code after updating and manually remove them. Then test … always test. Just in case.

Upgraded Tooling

One of the best things about Angular is the amazing and helpful ecosystem. When we upgrade to version 9 we really want to make sure we’re using the latest tools that go with Angular. Here are two tools we recommend installing or upgrading:

  1. VS Code editor
  2. Angular Essentials Extension for Visual VS Code

The Angular Essentials extension includes a set of useful extensions to enhance the development experience with Angular. The one that stands out most is the Angular Language Service, which adds a lot of features to VS Code, so it knows how to help us write our Angular code. Others include useful Angular snippets, ESLint, and debugging extensions.

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 subjects to provide authentication and profile data to the app

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: Applications section and click the "+ Create 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.
  4. Add http://localhost:4200 to both the Allowed Web Origins and Allowed Logout URLs. Click the "Save Changes" button.
  5. 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.

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 on GitHub, 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: '[YOUR_AUTH0_DOMAIN]', // e.g., 'you.auth0.com'
  AUTH0_AUDIENCE: 'http://localhost:3001/api/'
};

Change the CLIENT_DOMAIN value to your full Auth0 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/environments/environment.ts.example file by deleting .example from the file extension. Then open the file and change the [YOUR_CLIENT_ID] and [YOUR_AUTH0_DOMAIN] strings to your Auth0 information:

// src/environments/environment.ts (formerly environment.ts.example)
...
export const environment = {
  production: false,
  auth: {
    CLIENT_ID: '[YOUR_CLIENT_ID]',
    CLIENT_DOMAIN: '[YOUR_AUTH0_DOMAIN]', // e.g., 'you.auth0.com'
    ...
  }
};

Our app and API are now set up. They can be served by running ng serve from the root folder and node server from the /server folder. The npm start command will run both at the same time for you by using concurrently.

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. We'll step through this code below.

// src/app/auth/auth.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable, bindNodeCallback, of } from 'rxjs';
import * as auth0 from 'auth0-js';
import { environment } from './../../environments/environment';
import { Router } from '@angular/router';

@Injectable()
export class AuthService {
  // Create Auth0 web auth instance
  // @TODO: Update environment variables and remove .example
  // extension in src/environments/environment.ts.example
  private _Auth0 = new auth0.WebAuth({
    clientID: environment.auth.CLIENT_ID,
    domain: environment.auth.CLIENT_DOMAIN,
    responseType: 'id_token token',
    redirectUri: environment.auth.REDIRECT,
    audience: environment.auth.AUDIENCE,
    scope: 'openid profile email'
  });
  // Track whether or not to renew token
  private _authFlag = 'isLoggedIn';
  // Create stream for token
  token$: Observable<string>;
  // Create stream for user profile data
  userProfile$ = new BehaviorSubject<any>(null);
  // Authentication navigation
  onAuthSuccessUrl = '/';
  onAuthFailureUrl = '/';
  logoutUrl = environment.auth.LOGOUT_URL;
  // Create observable of Auth0 parseHash method to gather auth results
  parseHash$ = bindNodeCallback(this._Auth0.parseHash.bind(this._Auth0));
  // Create observable of Auth0 checkSession method to
  // verify authorization server session and renew tokens
  checkSession$ = bindNodeCallback(this._Auth0.checkSession.bind(this._Auth0));

  constructor(private router: Router) { }

  login() {
    this._Auth0.authorize();
  }

  handleLoginCallback() {
    if (window.location.hash && !this.authenticated) {
      this.parseHash$().subscribe(
        authResult => {
          this._setAuth(authResult);
          window.location.hash = '';
          this.router.navigate([this.onAuthSuccessUrl]);
        },
        err => this._handleError(err)
      )
    }
  }

  private _setAuth(authResult) {
    // Observable of token
    this.token$ = of(authResult.accessToken);
    // Emit value for user data subject
    this.userProfile$.next(authResult.idTokenPayload);
    // Set flag in local storage stating this app is logged in
    localStorage.setItem(this._authFlag, JSON.stringify(true));
  }

  get authenticated(): boolean {
    return JSON.parse(localStorage.getItem(this._authFlag));
  }

  renewAuth() {
    if (this.authenticated) {
      this.checkSession$({}).subscribe(
        authResult => this._setAuth(authResult),
        err => {
          localStorage.removeItem(this._authFlag);
          this.router.navigate([this.onAuthFailureUrl]);
        }
      );
    }
  }

  logout() {
    // Set authentication status flag in local storage to false
    localStorage.setItem(this._authFlag, JSON.stringify(false));
    // This does a refresh and redirects back to homepage
    // Make sure you have the logout URL in your Auth0
    // Dashboard Application settings in Allowed Logout URLs
    this._Auth0.logout({
      returnTo: this.logoutUrl,
      clientID: environment.auth.CLIENT_ID
    });
  }

  private _handleError(err) {
    if (err.error_description) {
      console.error(`Error: ${err.error_description}`);
    } else {
      console.error(`Error: ${JSON.stringify(err)}`);
    }
  }

}

This service uses the auth config variables from environment.ts to instantiate an auth0.js WebAuth instance. Next an _authFlag member is created, which is simply a flag that we can store in local storage. It tells us whether or not to attempt to renew tokens with the Auth0 authorization server (for example, after a full-page refresh or when returning to the app later). All it does is state, "This front-end application thinks this user is authenticated" and then allows us to apply logic based on that estimation and verify whether or not it's accurate.

We'll add and type a token$ observable, which will provide a stream of the access token string. This is for use with the token interceptor. We don't want our interceptor to utilize a stream that emits a default value without any useable values. We'll declare token$ in our _setAuth() method below, when the access token becomes available.

We will use an RxJS BehaviorSubject to provide a stream of the user profile that you can subscribe to anywhere in the app. We'll also store some paths for navigation so the app can easily determine where to send users when authentication succeeds, fails, or the user has logged out.

The next thing that we'll do is create observables of the auth0.js methods parseHash() (which allows us to extract authentication data from the hash upon login) and checkSession() (which allows us to acquire new tokens when a user has an existing session with the authorization server). Using observables with these methods allows us to easily publish authentication events and subscribe to them within our Angular application.

We'll create observables of the callbacks from these two auth0.js methods using using RxJS's bindNodeCallback. In order to preserve the scope of this, we'll bind() it like so:

bindNodeCallback(this._Auth0.parseHash.bind(this._Auth0))

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

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, expiresIn, and idTokenPayload in the URL hash from Auth0 when returning to our app after authenticating at the login page. The handleLoginCallback() method subscribes to the parseHash$() observable to stream authentication data (_setAuth()) by creating our token$ observable and emitting a value for the userProfile$ behavior subject. This way, any subscribed components in the app are informed that the token and user data has been updated. The _authFlag is also set to true and stored in local storage so if the user returns to the app later, we can check whether to ask the authorization server for a fresh token. Essentially, the flag serves to tell the authorization server, "This app thinks this user is authenticated. If they are, give me their data." We check the status of the flag in local storage with the accessor method authenticated.

Note: The user profile data takes the shape defined by OpenID standard claims.

The renewAuth() method, if the _authFlag is true, subscribes to the checkSession$() observable to ask the authorization server if the user is indeed authorized (we can pass arguments to this observable as we would to the auth0.js function). If they are, fresh authentication data is returned and we'll run the _setAuth() method to update the necessary authentication streams in our app. If the user is not authorized with Auth0, the _authFlag is removed and the user will be redirected to the URL we set as the authentication failure location.

Next, we have a logout() method that sets the _authFlag to false and logs out of the authentication session on Auth0's server. The Auth0 logout() method then redirects back to the location we set as our logoutUrl.

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 authentication service's handleLoginCallback() method to parse the hash and extract authentication information.

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

@Component({
  selector: 'app-callback',
  template: `<div>Loading...</div>`,
  styles: []
})
export class CallbackComponent implements OnInit {
  constructor(private auth: AuthService) { }

  ngOnInit() {
    this.auth.handleLoginCallback();
  }

}

Making Authenticated API Requests

In order to make authenticated HTTP requests, it's necessary to add an Authorization header with the access token to our outgoing requests. Note that the api.service.ts file does not do this.

Instead, this functionality is in an HTTP interceptor service called token.interceptor.ts.

// src/app/auth/token.interceptor.ts
import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
import { AuthService } from './auth.service';
import { Observable } from 'rxjs';
import { mergeMap } from 'rxjs/operators';

@Injectable()
export class InterceptorService implements HttpInterceptor {
  constructor(private auth: AuthService) { }

  intercept(
    req: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    // @NOTE: If you have some endpoints that are public
    // and do not need Authorization header, implement logic
    // here to accommodate that and conditionally let public
    // requests pass through based on your requirements
    return this.auth.token$
      .pipe(
        mergeMap(token => {
          if (token) {
            const tokenReq = req.clone({
              setHeaders: { Authorization: `Bearer ${token}` }
            });
            return next.handle(tokenReq);
          }
        })
      );
  }
}

As mentioned above, we can return the token$ observable to acquire a token, then clone the outgoing HTTP request and attach an Authorization header before sending the request on its way.

The interceptor should be provided like so in the app-routing.module.ts file:

// src/app/app-routing.module.ts
...
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { InterceptorService } from './auth/token.interceptor';
...

@NgModule({
  imports: [...],
  providers: [
    ...,
    {
      provide: HTTP_INTERCEPTORS,
      useClass: InterceptorService,
      multi: true
    }
  ],
  ...
})
export class AppRoutingModule {}

Note: We set multi to true because we could implement multiple interceptors, which would run in the order of declaration.

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 in the Home component, 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';
...

@NgModule({
  imports: [
    RouterModule.forRoot([
      ...,
      {
        path: 'profile',
        component: ProfileComponent,
        canActivate: [
          AuthGuard
        ]
      },
      ...
    ])
  ],
  providers: [
    AuthGuard,
    ...
  ],
  ...
})
export class AppRoutingModule {}

To Do: Elegant Error Handling

Now that the primary functionality is there, you'll want to think about gracefully handling and reacting to errors. Some functionality will need to be implemented for this. The errors are there to react to, but you'll want to consider how you prefer to respond to them when they occur.

More Resources

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

That’s All Folks!

It looks like we have a lot to look forward to with Angular v9. We have faster build times, a more accurate ng update, and Ivy is making smaller bundle sizes, just to name a few! The Angular team has been working countless hours on this version and we can now reap the benefits!

Which is your favorite of those Magnificent Seven? Which one will help your project the most? Let us know in the comments below!