Getting started
What's New in Angular 4
Angular 4 major improvements.
Enhanced *ngIf
// if/else syntax
<div *ngIf="user$ | async; else loading; let user">
{{ user.name }}
</div>
<ng-template #loading>Loading...</ng-template>
// as keyword
<div *ngIf="user$ | async as user">
{{ user.name }}
</div>
ng-template
<!-- OLD (deprecated) -->
<template ngFor let-hero [ngForOf]="heroes"> {{ hero.name }} </template>
<!-- NEW -->
<ng-template ngFor let-hero [ngForOf]="heroes"> {{ hero.name }} </ng-template>
Replaces <template> tag.
Version Notice
Angular 3 was skipped (v2 → v4). TypeScript 2.1+ required.
Animations
Setup
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
@NgModule({
imports: [BrowserAnimationsModule],
})
export class AppModule {}
Animations moved to separate package.
Basic Animation
import { trigger, state, style, transition, animate } from '@angular/animations';
@Component({
selector: 'app-hero',
animations: [
trigger('heroState', [
state('inactive', style({
backgroundColor: '#eee',
transform: 'scale(1)'
})),
state('active', style({
backgroundColor: '#cfd8dc',
transform: 'scale(1.1)'
})),
transition('inactive => active', animate('100ms ease-in')),
transition('active => inactive', animate('100ms ease-out'))
])
]
})
Enter/Leave Transitions
trigger("flyInOut", [
transition(":enter", [
style({ transform: "translateX(-100%)" }),
animate("100ms"),
]),
transition(":leave", [
animate("100ms", style({ transform: "translateX(100%)" })),
]),
]);
<div [@flyInOut]="state"></div>
Keyframes
import { keyframes } from "@angular/animations";
animate(
300,
keyframes([
style({ opacity: 0, transform: "translateY(-100%)", offset: 0 }),
style({ opacity: 1, transform: "translateY(15px)", offset: 0.3 }),
style({ opacity: 1, transform: "translateY(0)", offset: 1.0 }),
]),
);
Animation Callbacks
<div
(@flyInOut.start)="animStart($event)"
(@flyInOut.done)="animDone($event)"
[@flyInOut]="state"
></div>
animStart(event: AnimationEvent) {
console.log('Animation started', event);
}
animDone(event: AnimationEvent) {
console.log('Animation done', event);
}
Group Animations
import { group } from "@angular/animations";
transition("* => *", [
group([
animate("1s", style({ opacity: 0 })),
animate("1s", style({ transform: "translateX(100%)" })),
]),
]);
HttpClient (4.3+)
Setup
import { HttpClientModule } from "@angular/common/http";
@NgModule({
imports: [HttpClientModule],
})
export class AppModule {}
Introduced in Angular 4.3.
Basic Requests
import { HttpClient } from '@angular/common/http';
constructor(private http: HttpClient) {}
// Typed GET
this.http.get<Hero[]>('/api/heroes')
.subscribe(heroes => {
this.heroes = heroes;
});
// POST
this.http.post<Hero>('/api/heroes', hero)
.subscribe(result => {
console.log(result);
});
// PUT
this.http.put<Hero>('/api/heroes/1', hero)
.subscribe();
// DELETE
this.http.delete('/api/heroes/1')
.subscribe();
Error Handling
import { catchError } from "rxjs/operators";
import { of } from "rxjs";
this.http
.get<Hero[]>("/api/heroes")
.pipe(
catchError((err) => {
console.error(err);
return of([]);
}),
)
.subscribe((heroes) => {});
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("limit", "10");
this.http.get("/api/heroes", { headers, params }).subscribe();
Interceptors
import {
HttpInterceptor,
HttpRequest,
HttpHandler,
HttpEvent,
} from "@angular/common/http";
import { Observable } from "rxjs";
export class AuthInterceptor implements HttpInterceptor {
intercept(
req: HttpRequest<any>,
next: HttpHandler,
): Observable<HttpEvent<any>> {
const authReq = req.clone({
headers: req.headers.set("Authorization", "Bearer token"),
});
return next.handle(authReq);
}
}
Register Interceptor
import { HTTP_INTERCEPTORS } from '@angular/common/http';
@NgModule({
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true
}
]
})
Response Type
// JSON (default)
this.http.get<any>("/api/data").subscribe();
// Text
this.http.get("/api/text", { responseType: "text" }).subscribe();
// Blob
this.http.get("/api/file", { responseType: "blob" }).subscribe();
// Full response
this.http.get("/api/data", { observe: "response" }).subscribe((resp) => {
console.log(resp.headers);
console.log(resp.body);
});
Renderer2
Replacing Renderer
import { Renderer2, ElementRef } from '@angular/core';
constructor(
private renderer: Renderer2,
private el: ElementRef
) {}
Renderer deprecated, use Renderer2.
Style Manipulation
// Set style
this.renderer.setStyle(this.el.nativeElement, "color", "red");
// Remove style
this.renderer.removeStyle(this.el.nativeElement, "color");
Class Manipulation
// Add class
this.renderer.addClass(this.el.nativeElement, "active");
// Remove class
this.renderer.removeClass(this.el.nativeElement, "inactive");
Attribute Manipulation
// Set attribute
this.renderer.setAttribute(this.el.nativeElement, "role", "button");
// Remove attribute
this.renderer.removeAttribute(this.el.nativeElement, "disabled");
Element Creation
// Create element
const div = this.renderer.createElement("div");
// Create text
const text = this.renderer.createText("Hello");
// Append child
this.renderer.appendChild(this.el.nativeElement, div);
// Remove child
this.renderer.removeChild(this.el.nativeElement, div);
Event Listeners
const unlisten = this.renderer.listen(
this.el.nativeElement,
"click",
(event) => {
console.log("Clicked", event);
},
);
// Cleanup
unlisten();
Properties
// Set property
this.renderer.setProperty(this.el.nativeElement, "value", "New Value");
Router
ParamMap
import { ActivatedRoute, ParamMap } from '@angular/router';
import { switchMap } from 'rxjs/operators';
constructor(
private route: ActivatedRoute,
private service: HeroService
) {}
Route Parameters
ngOnInit() {
this.route.paramMap.pipe(
switchMap((params: ParamMap) =>
this.service.getHero(params.get('id'))
)
).subscribe(hero => {
this.hero = hero;
});
}
Query Parameters
ngOnInit() {
this.route.queryParamMap.subscribe(params => {
const filter = params.get('filter');
const page = params.get('page');
});
}
Get All Parameters
// All route params
params.keys.forEach((key) => {
console.log(key, params.get(key));
});
// Check if param exists
if (params.has("id")) {
const id = params.get("id");
}
// Get all values for key
const ids = params.getAll("id");
Navigate with Params
import { Router } from '@angular/router';
constructor(private router: Router) {}
// Navigate with route params
this.router.navigate(['/hero', heroId]);
// Navigate with query params
this.router.navigate(['/heroes'], {
queryParams: { filter: 'active', page: 1 }
});
Lifecycle Hooks
Interfaces vs Abstract Classes
import { OnInit, OnDestroy, OnChanges } from "@angular/core";
export class MyComponent implements OnInit, OnDestroy {
ngOnInit() {
console.log("Component initialized");
}
ngOnDestroy() {
console.log("Component destroyed");
}
}
Now interfaces instead of abstract classes.
Hook Order
| Hook | Description |
|---|---|
ngOnChanges |
Input property changes |
ngOnInit |
First change detection |
ngDoCheck |
Every change detection |
ngAfterContentInit |
Content projection done |
ngAfterContentChecked |
Content checked |
ngAfterViewInit |
View initialization done |
ngAfterViewChecked |
View checked |
ngOnDestroy |
Before component destroyed |
Forms
Template-driven Forms
import { FormsModule } from '@angular/forms';
@NgModule({
imports: [FormsModule]
})
<form #heroForm="ngForm">
<input [(ngModel)]="hero.name" name="name" required #name="ngModel" />
<div *ngIf="name.invalid && name.touched">Name is required</div>
<button [disabled]="heroForm.invalid">Submit</button>
</form>
Reactive Forms
import { ReactiveFormsModule, FormBuilder, Validators } from '@angular/forms';
@NgModule({
imports: [ReactiveFormsModule]
})
export class HeroFormComponent {
heroForm = this.fb.group({
name: ["", Validators.required],
power: ["", Validators.required],
});
constructor(private fb: FormBuilder) {}
onSubmit() {
console.log(this.heroForm.value);
}
}
<form [formGroup]="heroForm" (ngSubmit)="onSubmit()">
<input formControlName="name" />
<input formControlName="power" />
<button [disabled]="heroForm.invalid">Submit</button>
</form>
Pipes
Built-in Pipes
// Date
{{ birthday | date:'medium' }}
{{ birthday | date:'yyyy-MM-dd' }}
// Currency
{{ price | currency:'USD':true }}
{{ price | currency:'EUR':'symbol':'1.2-2' }}
// Decimal
{{ pi | number:'3.1-5' }}
// Percent
{{ ratio | percent:'2.1-2' }}
// Uppercase/Lowercase
{{ name | uppercase }}
{{ name | lowercase }}
// JSON
{{ object | json }}
// Slice
{{ [1,2,3,4,5] | slice:1:3 }}
// Async
{{ promise | async }}
{{ observable$ | async }}
Custom Pipe
import { Pipe, PipeTransform } from "@angular/core";
@Pipe({ name: "exponential" })
export class ExponentialPipe implements PipeTransform {
transform(value: number, exponent = 1): number {
return Math.pow(value, exponent);
}
}
{{ 2 | exponential:10 }}
Directives
Structural Directives
<!-- *ngIf -->
<div *ngIf="condition">Content</div>
<div *ngIf="condition; else elseBlock">Content</div>
<ng-template #elseBlock>Else content</ng-template>
<!-- *ngFor -->
<div *ngFor="let item of items; let i = index">{{ i }}: {{ item }}</div>
<!-- *ngSwitch -->
<div [ngSwitch]="value">
<div *ngSwitchCase="1">One</div>
<div *ngSwitchCase="2">Two</div>
<div *ngSwitchDefault>Other</div>
</div>
Attribute Directives
<!-- ngClass -->
<div [ngClass]="{'active': isActive, 'disabled': isDisabled}">
<!-- ngStyle -->
<div [ngStyle]="{'color': color, 'font-size': size + 'px'}">
<!-- ngModel -->
<input [(ngModel)]="name" />
</div>
</div>
Custom Directive
import { Directive, ElementRef, HostListener } from "@angular/core";
@Directive({
selector: "[appHighlight]",
})
export class HighlightDirective {
constructor(private el: ElementRef) {}
@HostListener("mouseenter") onMouseEnter() {
this.el.nativeElement.style.backgroundColor = "yellow";
}
@HostListener("mouseleave") onMouseLeave() {
this.el.nativeElement.style.backgroundColor = null;
}
}
<p appHighlight>Hover me</p>
Gotchas
Migration Issues
HttpClient (4.3+) is in @angular/common/http, old Http is in @angular/http.
Animations moved to @angular/animations (separate install).
<template> deprecated → use <ng-template>.
Breaking Changes
Renderer deprecated → use Renderer2.
Lifecycle hooks changed from abstract classes to interfaces.
TypeScript 2.1+ required.
Also see
- Angular 4 Changelog - Official changelog
- Angular 2 Documentation - Legacy docs
- Updating to Version 4 - Migration guide
- Angular Animations - Animation guide
- HttpClient Guide - HTTP client documentation