Getting started
What's New in Angular 5
Angular 5 (November 2017) introduces HttpClient as the default HTTP library, build optimizer for smaller bundles, RxJS pipeable operators, and improved forms validation control.
Key Features
- HttpClient with automatic JSON parsing
- Build optimizer (~20% smaller bundles)
- RxJS pipeable operators (RxJS 5.5)
- Forms
updateOnoption - Improved i18n pipes (CLDR-based)
- Service Worker / PWA support
- AOT fast enough for dev
Installation
npm install -g @angular/cli@1.5
ng new my-app
cd my-app
ng serve
Version Requirements
| Requirement | Version |
|---|---|
| TypeScript | 2.4+ (2.5 recommended) |
| RxJS | 5.5+ |
| Node.js | 6.9+ |
HttpClient (New Default)
Basic Usage
import { HttpClient } from '@angular/common/http';
constructor(private http: HttpClient) {}
// Auto JSON parsing
this.http.get('/api/users')
.subscribe(data => console.log(data));
HTTP module deprecated, use HttpClient.
Typed Responses
interface User {
id: number;
name: string;
}
this.http.get<User>("/api/user/1").subscribe((user) => {
console.log(user.name); // Type-safe
});
TypeScript generics for responses.
HTTP Methods
// GET
this.http.get<T>("/api/data");
// POST
this.http.post<T>("/api/data", body);
// PUT
this.http.put<T>("/api/data/1", body);
// DELETE
this.http.delete("/api/data/1");
// PATCH
this.http.patch<T>("/api/data/1", partial);
All return Observable<T>.
Request Options
import { HttpHeaders, HttpParams } from "@angular/common/http";
const headers = new HttpHeaders({
"Content-Type": "application/json",
Authorization: "Bearer token",
});
const params = new HttpParams().set("page", "1").set("size", "10");
this.http.get("/api", { headers, params });
Immutable headers and params.
Error Handling
import { catchError } from "rxjs/operators";
import { throwError } from "rxjs";
this.http
.get("/api")
.pipe(
catchError((error) => {
console.error(error);
return throwError(error);
}),
)
.subscribe();
Use pipeable catchError operator.
Interceptors
import {
HttpInterceptor,
HttpRequest,
HttpHandler,
} from "@angular/common/http";
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler) {
const authReq = req.clone({
headers: req.headers.set("Authorization", "Bearer token"),
});
return next.handle(authReq);
}
}
Modify requests/responses globally.
Module Setup
import { HttpClientModule } from "@angular/common/http";
@NgModule({
imports: [HttpClientModule],
})
export class AppModule {}
Import in root module.
Migration from Http
// OLD (Angular 4)
import { Http } from "@angular/http";
import "rxjs/add/operator/map";
this.http
.get("/api")
.map((res) => res.json())
.subscribe();
// NEW (Angular 5)
import { HttpClient } from "@angular/common/http";
this.http.get("/api").subscribe(); // Auto-parses JSON
HttpClient auto-parses JSON responses.
RxJS Pipeable Operators
Basic Usage
import { map, filter } from "rxjs/operators";
observable
.pipe(
map((x) => x * 2),
filter((x) => x > 10),
)
.subscribe();
Chain operators with pipe().
Migration
// OLD (patch operators)
import "rxjs/add/operator/map";
import "rxjs/add/operator/filter";
observable
.map((x) => x * 2)
.filter((x) => x > 10)
.subscribe();
// NEW (pipeable)
import { map, filter } from "rxjs/operators";
observable
.pipe(
map((x) => x * 2),
filter((x) => x > 10),
)
.subscribe();
Tree-shakeable imports.
Common Operators
import {
map,
filter,
tap,
switchMap,
mergeMap,
concatMap,
catchError,
retry,
debounceTime,
distinctUntilChanged,
take,
takeUntil,
finalize,
} from "rxjs/operators";
Import from rxjs/operators.
Renamed Operators
| Old Name | New Name |
|---|---|
do |
tap |
catch |
catchError |
switch |
switchAll |
finally |
finalize |
Example with HTTP
this.http
.get<User[]>("/api/users")
.pipe(
map((users) => users.filter((u) => u.active)),
tap((users) => console.log(users)),
catchError((error) => throwError(error)),
)
.subscribe();
Functional composition pattern.
Forms: updateOn Option
Reactive Forms
import { FormControl } from "@angular/forms";
// Update on blur
const ctrl = new FormControl("", {
updateOn: "blur",
});
// Update on submit
const ctrl = new FormControl("", {
updateOn: "submit",
});
Control validation timing.
Form Group Level
import { FormGroup, FormControl } from "@angular/forms";
const form = new FormGroup(
{
email: new FormControl(""),
password: new FormControl(""),
},
{
updateOn: "submit",
},
);
Apply to entire form.
Template-Driven Forms
<input name="email" ngModel [ngModelOptions]="{updateOn: 'blur'}" />
<form #f="ngForm" [ngFormOptions]="{updateOn: 'submit'}">
<input name="email" ngModel />
<input name="password" ngModel />
</form>
Use ngModelOptions for controls.
Available Options
| Option | Description |
|---|---|
'change' |
Default, update on input |
'blur' |
Update on focus loss |
'submit' |
Update on form submit |
Build Optimizer
Production Builds
# Build optimizer enabled by default
ng build --prod
# 20% smaller bundles
# Removes decorators from runtime
# Marks pure functions for tree-shaking
Automatic in production mode.
Disable Optimizer
# Not recommended
ng build --prod --build-optimizer=false
Rarely needed.
What It Does
- Removes Angular decorators at runtime
- Marks pure function calls (
/*@__PURE__*/) - Enables aggressive tree-shaking
- ~20% bundle size reduction
Internationalized Pipes
Currency Pipe
{{ price | currency }}
// Angular 4: USD4.99
// Angular 5: $4.99
{{ price | currency:'CAD' }}
// CA$4.99
{{ price | currency:'CAD':'symbol-narrow' }}
// $4.99
Default changed to 'symbol'.
Date Pipe (CLDR-based)
{{ today | date:'short' }}
// 8/15/17, 9:03 AM
{{ today | date:'medium' }}
// Aug 15, 2017, 9:03:01 AM
{{ today | date:'longDate' }}
// August 15, 2017
CLDR standard formats.
Percent Pipe
{{ 3.14159 | percent }}
// 314% (rounds by default)
{{ 3.14159 | percent:'1.4-4' }}
// 314.1592%
{{ 0.5 | percent }}
// 50%
Auto-rounding enabled.
Number Pipe
{{ 1234.5678 | number }}
// 1,234.568
{{ 1234.5678 | number:'1.0-0' }}
// 1,235
{{ 1234.5678 | number:'3.1-2' }}
// 1,234.57
Format: minIntegerDigits.minFractionDigits-maxFractionDigits.
Service Worker / PWA
Module Setup
import { ServiceWorkerModule } from "@angular/service-worker";
import { environment } from "../environments/environment";
@NgModule({
imports: [
ServiceWorkerModule.register("/ngsw-worker.js", {
enabled: environment.production,
}),
],
})
export class AppModule {}
Enable in production only.
Check for Updates
import { SwUpdate } from '@angular/service-worker';
constructor(private swUpdate: SwUpdate) {
if (swUpdate.isEnabled) {
swUpdate.available.subscribe(event => {
if (confirm('New version available. Load it?')) {
window.location.reload();
}
});
}
}
Notify users of updates.
Generate Service Worker
ng set apps.0.serviceWorker=true
ng build --prod
Creates ngsw-config.json and worker.
Router Lifecycle Events
New Events
import { Router, GuardsCheckStart, ResolveEnd } from '@angular/router';
constructor(private router: Router) {
router.events.subscribe(event => {
if (event instanceof GuardsCheckStart) {
// Show loading spinner
}
if (event instanceof ResolveEnd) {
// Hide loading spinner
}
});
}
Finer-grained navigation tracking.
Event Types
GuardsCheckStart/GuardsCheckEndResolveStart/ResolveEndActivationStart/ActivationEndChildActivationStart/ChildActivationEnd
Other Features
preserveWhitespaces
@Component({
template: ` <p>Text with spaces</p> `,
preserveWhitespaces: false,
})
export class MyComponent {}
Remove extra whitespace (smaller bundles).
Global Configuration
// tsconfig.json
{
"angularCompilerOptions": {
"preserveWhitespaces": false
}
}
Apply to all components.
Multiple exportAs
@Directive({
selector: "[appHighlight]",
exportAs: "highlight, appHighlight",
})
export class HighlightDirective {}
Multiple template reference names.
Usage
<div appHighlight #h1="highlight" #h2="appHighlight">
{{ h1 === h2 }}
<!-- true -->
</div>
Both names reference same directive.
StaticInjector
import { Injector } from "@angular/core";
// OLD (Angular 4)
import { ReflectiveInjector } from "@angular/core";
const injector = ReflectiveInjector.resolveAndCreate([MyService]);
// NEW (Angular 5)
const injector = Injector.create({
providers: [
{
provide: MyService,
deps: [ConfigService, LoggerService],
},
],
});
Explicit dependency array required.
Universal State Transfer
import { TransferState, makeStateKey } from '@angular/platform-browser';
const DATA_KEY = makeStateKey<any>('apiData');
constructor(private state: TransferState) {
const data = this.state.get(DATA_KEY, null);
if (data) {
this.data = data; // Use server data
} else {
this.http.get('/api').subscribe(result => {
this.data = result;
this.state.set(DATA_KEY, result);
});
}
}
Avoid duplicate HTTP requests.
Zone-less Angular
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
platformBrowserDynamic()
.bootstrapModule(AppModule, {
ngZone: 'noop'
});
// Manual change detection
constructor(private appRef: ApplicationRef) {}
updateData() {
this.data = newData;
this.appRef.tick(); // Trigger change detection
}
Manual change detection required.
AOT in Development
# Fast enough for dev now
ng serve --aot
# 95% faster incremental builds
# AOT as TypeScript transforms
Recommended for development.
Gotchas
Http Deprecated
// ❌ Don't use (deprecated)
import { Http } from "@angular/http";
// ✅ Use HttpClient
import { HttpClient } from "@angular/common/http";
Migrate to HttpClient.
Pipeable Operators
// ❌ Old style (still works)
import "rxjs/add/operator/map";
observable.map((x) => x);
// ✅ New style (preferred)
import { map } from "rxjs/operators";
observable.pipe(map((x) => x));
Better tree-shaking.
StaticInjector deps
// ❌ Missing deps array
Injector.create({
providers: [{ provide: MyService }],
});
// ✅ Explicit deps
Injector.create({
providers: [
{
provide: MyService,
deps: [ConfigService],
},
],
});
Deps array now required.
Currency Pipe Default
// Angular 4
{{ 9.99 | currency }} // USD9.99
// Angular 5
{{ 9.99 | currency }} // $9.99
// Explicit code
{{ 9.99 | currency:'USD':'code' }} // USD9.99
Default changed from 'code' to 'symbol'.
Percent Pipe Rounding
// Angular 4
{{ 3.14159 | percent }} // 314.159%
// Angular 5
{{ 3.14159 | percent }} // 314%
// Explicit precision
{{ 3.14159 | percent:'1.4-4' }} // 314.1592%
Auto-rounding enabled.
Template Tag
<!-- ❌ Deprecated (disabled by default) -->
<template>Content</template>
<!-- ✅ Use ng-template -->
<ng-template>Content</ng-template>
Use <ng-template> instead.
TypeScript Version
Requires TypeScript 2.4+, recommends 2.5+.