close icon
Angular

Angular 9: What’s New?

Learn about the top seven features and upgrades we get with Angular 9

February 12, 2020

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

Tweet

Tweet This

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!

Want to get up to speed with OAuth2 and OpenID Connect?

Download the free ebook
Oauth2 OpenID Connect Professional Guide

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

Tweet

Tweet This

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

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!


  • Twitter icon
  • LinkedIn icon
  • Faceboook icon