What's new in Angular 5!

slides.com/gerardsans |  @gerardsans

Google Developer Expert

Master of Ceremonies

Blogger

International Speaker

Spoken at 56 events in 19 countries

Angular Trainer

Community Leader

900

1K

Angular In Flip Flops

Timeline

Angular v2

Future ready

Angular v4

Stable and performant

4.2 Animations

New angular.io website

4.3 HttpClient

Angular v5

Smaller, Faster and Easier to use

PWA support

SSR. Material components

Bazel. Build time tools

Semantic Versioning

Semantic Versioning

X . Y . Z

   MAJOR       MINOR        PATCH  

Semantic Versioning

  • v2 Sept 2016
  • v4 March 2017
  • v5 Sept/Oct 2017
  • v6 March 2018
  • v7 Sept/Oct 2018

Long Term Support (LTS)

  • Only critical fixes and security patches
  • Angular v4 begins October 2017

Goals

  • Fixed release calendar
  • Incremental upgrades
  • Stability
  • Reliability

Angular CLI

Angular Rapid Development

Main Features

  • Command Line Interface to create Angular Applications
  • Full configuration: build, test, scaffolding
  • Development and Production
  • Follows latest Style Guide

Angular CLI 1.3

  • Support for ES8, TypeScript 2.4 and Angular v5
  • Smaller bundles
    • Scope hoisting (webpack 3)
    • --build-optimizer @angular/devkit
  • Named Lazy load modules
    • --named-chunks
  • Server Side Rendering

10th Aug

Angular CLI 1.4

  • Schematics
    • schematics/@angular
  • Custom Serve Path
  • AOT Missing translation Strategy (I18n)
    • --missing-translation error

7th Sept

Angular CLI 1.5

  • Build Optimiser active by default
    • Mark pure sections
    • Remove decorators (reflect-metadata)
  • Incremental compilation
    • ng serve --aot
    • disable: --no-build-optimizer

1st Nov

Compiler

Latest changes

  • Faster builds
    • Incremental compilation
    • Replace ReflectiveInjector (static)
  • Performance
  • Support run-time expressions​​
    • {provider: N, useValue: calculate()}

Latest changes

  • Faster Zone implementation
    • Disable zones (high performance)
  • Multiple exportAs
    • Angular Material migration

Universal

Latest changes

  • New State Transfer API
  • Server Side DOM (Domino)
    • 3rd party Component Libraries

RxJS 5.5

lettable Operators

// before
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/interval';
import 'rxjs/add/operator/filter';

Observable.interval(1000)
  .filter(x => x%2!==0)   // --1--3--5--

// after
import { Observable } from 'rxjs/Observable';
import { interval } from 'rxjs/observable/interval';
import { filter } from 'rxjs/operators'; 

interval(1000)
.pipe(
  filter(x => x%2!==0)
)

Forms

Latest changes

  • Added new options argument
    • FormControl, FormGroup, FormArray
  • Fine-tune when to update and validate
    • ngFormOptions, ngModelOptions
  • New validators
    • min and max

New options argument

// validator function
const ctrl = new FormControl('', Validators.required);	

// options argument
const form = new FormGroup({
   password: new FormControl('')
   passwordRepeat: new FormControl('') 
}, { 
  validators: passwordMatchValidator, 
  asyncValidators: userExistsValidator
});

// arrays
const g = new FormGroup({
   one: new FormControl()
}, [passwordMatchValidator]);

Template driven: updateOn

<form [ngFormOptions]="{updateOn: 'blur'}">
  <!-- will update on blur-->
  <input name="one" ngModel>  
</form>

<form [ngFormOptions]="{updateOn: 'blur'}">
  <!-- overrides and will update on change-->
  <input name="one" 
    ngModel [ngModelOptions]="{updateOn: 'change', standalone: true}">  
</form>

Model driven: updateOn

const c = new FormGroup({
   one: new FormControl()
}, {updateOn: 'change'});

const c = new FormArray([
  new FormControl()
], {updateOn: 'blur'});

const c = new FormControl(, {
   updateOn: 'submit'
});

min and max Validators

<form>
  <!-- Requires formControlName, formControl, ngModel -->
  <input type="number" ngModel [min]="2" [max]="100">
</form>

// { 'min':   2, 'actual':    1 }
// { 'max': 100, 'actual': 2000 }

Router

Latest changes

  • Added new events
    • ActivationStart/End
    • GuardsCheckStart/End
    • ResolveStart/End
  • Tracking activation of specific routes
    • RouterEvent

New Events

import { Router, ActivationStart, ActivationEnd } from '@angular/router';
import 'rxjs/add/operator/filter';

@Component({
  selector: 'my-app',
  template: `<router-outlet></router-outlet>`
})
export class AppComponent { 
  constructor(private router: Router) {
    router.events
      .filter(e => 
        e instanceof ActivationStart || e instanceof ActivationEnd)
      .subscribe(e => console.log(e.toString()));
  }
}
    
 

Tracking specific urls

import { Router, RouterEvent } from '@angular/router';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/distinctUntilChanged';

@Component({
  selector: 'my-app',
  template: `<router-outlet></router-outlet>`
})
export class AppComponent { 
  constructor(private router: Router) {
    router.events
      .filter(e => e instanceof RouterEvent)
      .filter(e => e.url == '/about')
      .distinctUntilChanged((e1, e2) => e1.id == e2.id && e1.url == e2.url )
        .subscribe(e => {
          console.log(e.id, e.url);
        });
  }
}
    
 

i18n

Latest changes

  • Unicode Common Locale Data Repository (CLDR) instead of Internationalisation API
  • Uses timezone/locale from user
  • Default locale: en-US
  • Support for
    • DatePipe, CurrencyPipe, DecimalPipe, PercentPipe

Using specific locales

// no en-US locale
import { registerLocaleData } from '@angular/common';
import localeEs from '@angular/common/locales/es';
registerLocaleData(localeEs);

// period data extras Eg: midnight, noon, morning, afternoon, evening, night
import { registerLocaleData } from '@angular/common';
import localeEs from '@angular/common/locales/es';
import localeEsExtra from '@angular/common/locales/extra/es';
registerLocaleData(localeEs, localeEsExtra);

DatePipe

  • New formats
    • long, longDate, longTime
    • full, fullDate, fullTime 
  • Added timezone and locale support

DatePipe Examples

// common usage using pre-defined format string
{{ d | date: 'shortDate' }}               // en-US: 1/18/17

// specific locale
{{ d | date: 'shortDate': null: 'es' }}   // es-ES: 18/1/17

// custom format string
{{ d | date: 'M/d/yy': null: 'es' }}      // es-ES: 18/1/17

Breaking changes

DecimalPipe

//  number[:digitInfo[:locale]]

// n = 2.718281828459045
// digitInfo defaults to 1.0-3

{{ n | number }}                   // outputs: '2.718'
{{ n | number: '4.5-5' }}          // outputs: '0,002.71828'
{{ n | number: '4.5-5': 'es' }}    // outputs: '0.002,71828'

CurrencyPipe

//  currency[:currencyCode[:display[:digitInfo[:locale]]]]

// c = 1.3495
// display defaults to symbol
// digitInfo defaults to 1.2-2

{{ c | currency }}                                  // outputs: '$1.35'
{{ c | currency: 'CAD' }}                           // outputs: 'CA$1.35'
{{ c | currency: 'CAD': 'code' }}                   // outputs: 'CAD1.35'
{{ c | currency: 'CAD': 'symbol-narrow': '1.2-2': 'es' }}  // outputs: '1,35 $'

PercentPipe

//  percent[:digitInfo[:locale]]

// p = 0.718281828459045
// digitInfo defaults to 1.0-0

{{ p | percent }}                   // outputs: '72%'
{{ p | percent: '4.5-5' }}          // outputs: '0,071.82818%'
{{ p | percent: '4.5-5': 'es' }}    // outputs: '0.071,82818 %'

HttpClient

Main Features

  • Immutable Request/Response
  • Typed Response body access
    • JSON is the default
  • Interceptors/Progress Events
  • Improvements Unit Testing

Latest changes

  • @angular/http is deprecated
  • HttpHeaders and HttpParams accept object literals

HttpHeaders and HttpParams

// without classes
http.get(url, { headers: {'X-Option': 'true'} });
http.get(url, { params: {'test': 'true'} });

// using classes
const headers = new HttpHeaders().set('X-Option', 'true');
http.get(url, { headers }); 

const params = new HttpParams().set('test', 'true');
http.get(url, { params });

Dependencies

  • Additional bundle
    • @angular/common/http
  • Import HttpClientModule
    • HttpClient

Creating a Http Service

// app.module.ts
import { HttpClientModule } from '@angular/common/http';
@NgModule({ 
  imports: [HttpClientModule], ... 
})
export class AppModule {}

// usersService.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import 'rxjs/add/operator/map';

@Injectable()
export class UsersService {
  constructor(private http: HttpClient) { }

  public get() { 
    return this.http.get(USERS_ENDPOINT)
      .map(response => response.users);
  }
}

JSON Response

{ 
  "users": [
    {
     "id": 34,
     "username": "spiderman",
     "roles": ["admin", "user"], 
     "superuser": true
    }, 
    {
     "id": 67,
     "username": "batman",
     "roles": ["user"]
    }
  ]
}

Adding Types

// usersService.ts
interface User {
  id: number;
  username: string;
  roles: Array<string>;
  superuser?: boolean;  
}

interface UsersResponse {
  users: Array<User>;
}

export class UsersService {
  public get() { 
    return this.http.get<UsersResponse>(USERS_ENDPOINT)
      .map(response => response.users);
  }
}

Consuming a Http Service

import { Component } from '@angular/core';
import { UsersService } from '../services/usersService';

@Component({
  selector: 'users',
  template: `<h1>Users</h1>
    <tr *ngFor="let user of userslist | async">
      <td>{{user.username}}</td>
    </tr>`
})
export class Users {
  private userslist;
  
  constructor(users: UsersService) {
    this.userslist = users.get();
  }
}

POST

// usersService.ts
@Injectable()
export class UsersService {
  constructor(private http: HttpClient) { }

  public post() {
    const payload = {
      userId: 1,
      title: 'HttpClient',
      body: 'In this article...',
    };
    return this.http.post('https://jsonplaceholder.typicode.com/posts', payload);
  }
}

Upload Progress

// users.service.ts
export class UsersService {
  public post(payload) { 
    return new HttpRequest('POST', URL, payload, {
      reportProgress: true,
    })
  }
}

// users.component.ts
usersService.post(payload).subscribe(event => {
  if (event.type === HttpEventType.UploadProgress) {
    const percentage = Math.round(event.loaded/event.total*100);
    console.log(`${percentage}% uploaded.`);
  } else if (event instanceof HttpResponse) {
    console.log('Uploaded finished!');
  };
});

Interceptors

Main Features

  • Transform Request/Response
    • Events
  • Applications:
    • Security
    • Logging
    • Error Handling
    • Caching

Setup

import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { BasicInterceptor } from './basic.interceptor.ts';

@NgModule({
  providers: [{ 
    multi: true, provide: HTTP_INTERCEPTORS, useClass: BasicInterceptor 
  }],
})
export class AppModule {}

Basic Interceptor

// ./basic.interceptor.ts
import {Injectable} from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } 
  from '@angular/common/http';

@Injectable()
export class BasicInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(req);
  }
}

Auth0 Interceptor

// ./auth0.interceptor.ts
@Injectable()
export class Auth0Interceptor implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>{
    const clonedRequest = req.clone({ 
      setHeaders: { authorization: localStorage.getItem('auth0IdToken') } 
    });
    return next.handle(clonedRequest);
  }
}

Logging Interceptor

// ./logging.interceptor.ts
@Injectable()
export class LoggingInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>{
    return next.handle(req).do(event => {
      if (event instanceof HttpResponse) {
        const bodyLength = event.headers.get('Content-Length');
        console.log(`Request: ${req.urlWithParams}-Response Size: ${bodyLength}`);
      }
    });
  }
}

Animations

States & Transitions

void

*

Special Keywords

void => *           :enter

* => void          :leave

void <=> *

STATE

STATE

fadeIn

fadeOut

States & Transitions

TRANSITIONS

STATE

STATE

fadeIn => fadeOut

fadeOut => fadeIn

fadeIn <=> fadeOut

Animations Setup

// npm install --save @angular/animations
// app.module.ts

import {Component, NgModule} from '@angular/core';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';

@Component({ })
export class App { }

@NgModule({
  imports: [ BrowserModule, BrowserAnimationsModule ],
  declarations: [ App ],
  bootstrap: [ App ]
})
export class AppModule {}

platformBrowserDynamic().bootstrapModule(AppModule)

Animation Example

import {fade} from './animations';

@Component({
  selector: 'my-app',
  template: `<button [@fade]='fade' (click)="toggleFade()">Fade</button>`,
  animations: [ fade ]
})
export class App {
  fade = 'fadeIn';
  toggleFade(){
    this.fade = this.fade === 'fadeIn' ? 'fadeOut' : 'fadeIn';
  }
}

Animation Example

import { 
  trigger, transition, state, style, animate 
} from '@angular/animations';

export const fade = trigger('fade', [
  state('fadeIn', style({ opacity: 1 })),
  state('fadeOut', style({ opacity: 0.1 })),
  transition('fadeIn <=> fadeOut', animate('2000ms linear'))
]);

Execution Order

sequence

group

time

Composition

animateChild

time

Stagger

time

Dynamic Selectors

class="container"

class="item"

class="item"

query(selector)

Dynamic Selectors

query('.item')

class="container"

class="item"

class="item"

Dynamic Selectors

query('.item:enter')

class="container"

class="item"

class="item"

class="item"

added

Dynamic Animations

Builder

(${x}, ${y})

(100, 50)

Player

Template

1

2

(100, 50)

3

build()

play()

AnimationBuilder

import {AnimationBuilder} from "@angular/animations";
export class MyComponent {
  constructor(
    private elem: ElementRef, 
    private builder: AnimationBuilder
  ) {
    const player = this.playerMoveTo({ x: 100, y: 100 });
    player.play();
  } 
  playerMoveTo(to) {
    const moveAnimation = this.builder.build([
      animate('1s', style({ transform: `translate(${x}px, ${y}px)` }))
    ]);
    return moveAnimation.create(this.elem.nativeElement
      , {params: { x: to.x, y: to.y }});
  }
}

Tracking mouse position

constructor(
  private elem: ElementRef
) {
  // find out center
  this.mx = elem.nativeElement.offsetWidth/2;
  this.my = elem.nativeElement.offsetHeight/2;
} 

ngOnInit() {
  const mouseMove$ = Observable.fromEvent(document, 'mousemove')
    .map(e => ({ x: e.pageX, y: e.pageY }));

  mouseMove$.subscribe({
    next: e => {
      this.lastPosition = { x: e.x-this.mx, y: e.y-this.my };
      const player = this.playerMoveTo(this.lastPosition);
      player.play();
    }
  })
}

What's new in Angular 5!

By Gerard Sans

What's new in Angular 5!

In this interactive session, we'll cover all the cool new stuff and changes in version 5 of Angular!

  • 2,496