TL;DR: Angular is an all-encompassing JavaScript framework that is frequently used by developers all over the world for building web, desktop, and mobile applications. In this article, I'll cover the new features in Angular 5 and several other changes and deprecations.


Angular is built and maintained by Google.. It is a platform that combines declarative templates, dependency injection, end to end tooling, and integrated best practices to solve development challenges. Web platforms such as Google Adwords, Google Fiber, Adsense, and Winc use Angular to build their user interfaces.

Angular 5 was announced to the world on November 1, 2017. The previous Angular version was 4.4.0. This release focused on making Angular smaller and faster to use. Let's go through the major changes in version 5.

1. Http Deprecated, HttpClient Here to Stay

Before version 4.3, the @angular/http module was used for making HTTP requests in Angular applications. The Angular team has now deprecated Http in version 5. The HttpClient API from @angular/common/http package that shipped in version 4.3 is now recommended for use in all apps. The HttpClient API features include:

  • Typed, synchronous response body access, including support for JSON body types.
  • JSON is an assumed default and no longer needs to be explicitly parsed.
  • Interceptors allow middleware logic to be inserted into the pipeline.
  • Immutable request/response objects.
  • Progress events for both request upload and response download.
  • Post-request verification & flush based testing framework.
import { HttpClientModule } from '@angular/common/http';

2. Support for Multiple Export Alias in Angular 5

In Angular 5, you can now give multiple names to your components and directives while exporting. Exporting a component with multiple names can help your users migrate without breaking changes.

"Exporting a component with multiple names can help your users migrate without breaking changes."

Example Usage:

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

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  exportAs:'dashboard, logBoard'
})
export class AppComponent {
  title = 'app';
}

3. Internationalized Number, Date, and Currency Pipes

Angular 5 ships with new number, date, and currency pipes that increase standardization across browsers and eliminate the need for i18n polyfills. The pipes rely on the CLDR to provide extensive locale support and configurations for any locales you want to support. To use the old pipes, you will have to import the DeprecatedI18NPipesModule after the CommonModule.

  import { NgModule } from '@angular/core';
  import { CommonModule, DeprecatedI18NPipesModule } from '@angular/common';

  @NgModule({
    imports: [
      CommonModule,
      // import deprecated module after
      DeprecatedI18NPipesModule
    ]
  })
  export class AppModule { }

4. Improved Decorator Support in Angular 5

Angular 5 now supports expression lowering in decorators for lambdas, and the value of useValue, useFactory, and data in object literals. Furthermore, a lambda can be used instead of a named function like so:

In Angular 5

  Component({
    provider: [{provide: 'token', useFactory: () => null}]
  })
  export class MyClass {}

Before Angular 5

  Component({
    provider: [{provide: 'token', useValue: calculated()}]
  })
  export class MyClass {}

5. Build Optimization

The Angular team focused on making Angular 5 faster, smaller and easier to use. In Angular 5, production builds created with the Angular CLI will now apply the build optimizer by default. The build optimizer removes Angular decorators from your app's runtime codes thereby reducing the size of your bundle and increasing the boot speed of your application. In addition, the build optimizer removes part of your application that is not needed during runtime via tree-shaking. This action leads to a decreased bundle size, and faster application speed.

"In Angular 5, production builds created with the Angular CLI will now apply the build optimizer by default."

6. Angular Universal Transfer API

The Angular Universal team has added Domino to the platform-server. This simply means more DOM manipulations can happen out of the box within server-side contexts.

Furthermore, two modules, ServerTransferStateModule and BrowserTransferModule have been added to Angular Universal. These modules allow you to generate information as part of your rendering with platform-server and then transfer it to the client side to avoid re-generation of the same information. In summary, it transfers state from the server which means developers do not need to make a second HTTP request once the application makes it to the client.

7. Faster Compiler in Angular 5

A lot of improvements have been made to the Angular compiler to make it faster. The Angular compiler now leverages TypeScript transforms. You can take advantage of it by running:

ng serve --aot

Angular.io was used as a case study and the compiler pipeline saved 95% of the build time when an incremental AOT build was performed on it.

Note: TypeScript transforms are a new feature introduced as part of TypeScript 2.3, that allows hooking into the standard TypeScript compilation pipeline.

8. Forms Validation in Angular 5

In Angular 5, forms now have the ability to decide when the validity and value of a field or form are updated via on blur or on submit, instead of every input event.

Example Usage

<input name="nickName" ngModel [ngModelOptions]="{updateOn: 'blur'}">

Another Example

<form [ngFormOptions]="{updateOn: 'submit'}">

In the case of Reactive forms, you can add the option like so:

ngOnInit() {
  this.newUserForm = this.fb.group({
    userName: ['Bob', { updateOn: 'blur', validators: [Validators.required] }]
  });
}

9. Animations in Angular 5

In Angular 5, we have two new transition aliases, :increment and :decrement.

...
animations: [
  trigger('bannerAnimation', [
   transition(":increment", group([
     query(':enter', [
       style({ left: '100%' }),
       animate('0.5s ease-out', style('*'))
     ]),
     query(':leave', [
       animate('0.5s ease-out', style({ left: '-100%' }))
     ])
   ])),
   transition(":decrement", group([
     query(':enter', [
       style({ left: '-100%' }),
       animate('0.5s ease-out', style('*'))
     ]),
     query(':leave', [
       animate('0.5s ease-out', style({ left: '100%' }))
     ])
   ])),
 ])
]

Animation queries now support negative limits, in which case elements are matched from the end rather than from the beginning like so:

...
animations: [
 trigger(
   'myAnimation',
   [
     transition(
         '* => go',
         [
           query(
               '.item',
               [
                 style({opacity: 0}),
                 animate('1s', style({opacity: 1})),
               ],
               {limit: -3}),
        ]),
   ]),
]
...

10. New Router Lifecycle Events in Angular 5

Some new lifecycle events have been added to the router. The events are GuardsCheckStart, ChildActivationStart, ActivationStart, GuardsCheckEnd, ResolveStart, ResolveEnd, ActivationEnd, and ChildActivationEnd. With these events, developers can track the cycle of the router from the start of running guards through to completion of activation.

Furthermore, you can now configure the router to reload a page when it receives a request to navigate to the same URL.

providers: [
  // ...
  RouterModule.forRoot(routes, {
    onSameUrlNavigation: 'reload'
  })
]

11. Better Support for Service Workers in Angular 5

In Angular 5, we have better support for service workers via the @angular/service-worker package. The service worker package is a conceptual derivative of the @angular/service-worker package that was maintained at github.com/angular/mobile-toolkit, but has been rewritten to support use-cases across a much wider variety of applications.

Note: Right now you will have to manually integrate the package because it's not fully integrated with the CLI v1.5 yet. It is available as beta in version 1.6 of the CLI.

Deprecations and Other Updates

  • NgFor has been removed as it was deprecated since v4. Use NgForOf instead. This does not impact the use of *ngFor in your templates.
  • The compiler option enableLegacyTemplate is now disabled by default as the <template> element was deprecated since v4. Use <ng-template> instead. The option enableLegacyTemplate and the <template> element will both be removed in Angular v6.
  • The method ngGetContentSelectors() has been removed as it was deprecated since v4. Use ComponentFactory.ngContentSelectors instead.
  • ReflectiveInjector is now deprecated. Use Injector.create as a replacement.
  • NgProbeToken has been removed from @angular/platform-browser as it was deprecated since v4. Import it from @angular/core instead.

Check out other Angular 5 updates here.

Upgrading to Angular 5

The Angular team built a nifty tool to make upgrading as easy as possible.

Angular 5 Upgrade Angular 5 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 centralized 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 centralized Lock instance
  • 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, ID 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 client app and API so Auth0 can interface with an Angular app and Node API.

Set Up a Client App

  1. Go to your Auth0 Dashboard and click the "create a new client" button.
  2. Name your new app and select "Single Page Web Applications".
  3. In the Settings for your new Auth0 client app, add http://localhost:4200/callback to the Allowed Callback URLs.
  4. Scroll down to the bottom of the Settings section and click "Show Advanced Settings". Choose the OAuth tab and verify that the JsonWebToken Signature Algorithm is set to RS256.
  5. If you'd like, you can set up some social connections. You can then enable them for your app in the Client 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.

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

Change the CLIENT_DOMAIN variable to your Auth0 client 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 [CLIENT_ID] and [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: '[CLIENT_ID]',
  CLIENT_DOMAIN: '[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.

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

@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
  auth0 = new auth0.WebAuth({
    clientID: AUTH_CONFIG.CLIENT_ID,
    domain: AUTH_CONFIG.CLIENT_DOMAIN,
    responseType: 'token id_token',
    redirectUri: AUTH_CONFIG.REDIRECT,
    audience: AUTH_CONFIG.AUDIENCE,
    scope: AUTH_CONFIG.SCOPE
  });
  userProfile: UserProfile;

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

  constructor() {
    // If authenticated, set local profile property and update login status subject
    if (this.authenticated) {
      this.userProfile = JSON.parse(localStorage.getItem('profile'));
      this.setLoggedIn(true);
    }
  }

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

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

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

  private _getProfile(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('access_token', authResult.accessToken);
    localStorage.setItem('id_token', authResult.idToken);
    localStorage.setItem('profile', JSON.stringify(profile));
    localStorage.setItem('expires_at', JSON.stringify(expTime));
    this.userProfile = profile;
    this.setLoggedIn(true);
  }

  logout() {
    // Remove tokens and profile and update login status subject
    localStorage.removeItem('access_token');
    localStorage.removeItem('id_token');
    localStorage.removeItem('profile');
    localStorage.removeItem('expires_at');
    this.userProfile = undefined;
    this.setLoggedIn(false);
  }

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

}

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. An Auth0 centralized 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 idToken, accessToken, and expiresIn in the hash from Auth0 when returning to our app. The handleAuth() method uses Auth0's parseHash() method callback to get the user's profile (_getProfile()) and set the session (_setSession()) by saving the tokens, profile, and token expiration to local storage 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.

The handleAuth() method can then be called in the app.component.ts constructor like so:

// src/app/app.component.ts
import { AuthService } from './auth/auth.service';
...
  constructor(private auth: AuthService) {
    // Check for authentication and handle if hash present
    auth.handleAuth();
  }
...

Finally, we have a logout() method that clears data from local storage and updates the loggedIn$ subject. We also have an authenticated accessor to return current authentication status based on access token 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 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 } from '@angular/core';
import { Subscription } from 'rxjs/Subscription';
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 {

  constructor(private auth: AuthService, private router: Router) { }

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

}

Making Authenticated API Requests

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

// src/app/api.service.ts
...
  getDragons$(): Observable<any[]> {
    return this.http
      .get(`${this.baseUrl}dragons`, {
        headers: new HttpHeaders().set(
          'Authorization', `Bearer ${localStorage.getItem('access_token')}`
        )
      })
      .catch(this._handleError);
  }
...

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 5 came loaded with new features and significant improvements. It is smaller and faster. I am proud of what the Angular team achieved with this release.

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