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."
Tweet This
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:
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."
Tweet This
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.
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.
Set up an Auth0 application
First, sign up for a free account here. Then, set up an Auth0 application with the following steps:
- Go to your Applications section of the Auth0 Dashboard and click the "Create Application" button.
- Name your new app and select "Single Page Web Applications" as the application type.
- 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. - 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
andYOUR_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! 😊