Getting started
Component
import { Component } from "@angular/core";
@Component({
selector: "my-app",
template: `<h1>{{ title }}</h1>`,
styles: [
`
h1 {
color: blue;
}
`,
],
})
export class AppComponent {
title = "My App";
}
Defines reusable UI components
Module
import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
@NgModule({
imports: [BrowserModule],
declarations: [AppComponent],
providers: [HeroService],
bootstrap: [AppComponent],
})
export class AppModule {}
Groups related components/services
Bootstrap
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
platformBrowserDynamic().bootstrapModule(AppModule);
Launches the application
Template Syntax
Interpolation
<h1>{{title}}</h1>
<p>{{hero.name}}</p>
<div>{{1 + 1}}</div>
Binds component data
Property Binding
<button [disabled]="isDisabled">Click</button>
<img [src]="heroImageUrl" />
<app-hero [hero]="selectedHero"></app-hero>
One-way data binding
Event Binding
<button (click)="onSave()">Save</button>
<input (keyup)="onKey($event)" />
<app-hero (deleted)="onHeroDeleted($event)"></app-hero>
Responds to user events
Two-Way Binding
<input [(ngModel)]="hero.name" />
Combines property + event binding
Attribute Binding
<button [attr.aria-label]="help">Help</button>
<td [attr.colspan]="colSpan">Content</td>
Binds to HTML attributes
Class Binding
<div [class.active]="isActive">Content</div>
<div [ngClass]="{'active': isActive, 'disabled': isDisabled}"></div>
Toggles CSS classes
Style Binding
<div [style.color]="color">Colored text</div>
<div [style.font-size.px]="fontSize">Text</div>
<div [ngStyle]="{'color': color, 'font-size.px': fontSize}"></div>
Sets inline styles dynamically
Directives
*ngIf
<div *ngIf="hero">{{hero.name}}</div>
<div *ngIf="heroes.length > 0">Has heroes</div>
<div *ngIf="condition; else elseBlock">Content</div>
<ng-template #elseBlock>Else content</ng-template>
Conditional rendering
*ngFor
<div *ngFor="let hero of heroes">{{hero.name}}</div>
<div *ngFor="let hero of heroes; let i = index">{{i}}: {{hero.name}}</div>
<div *ngFor="let hero of heroes; trackBy: trackByHeroId"></div>
Loop over collections
ngSwitch
<div [ngSwitch]="hero?.emotion">
<div *ngSwitchCase="'happy'">Happy</div>
<div *ngSwitchCase="'sad'">Sad</div>
<div *ngSwitchDefault>Confused</div>
</div>
Switch statement for templates
Template References
<input #heroInput /> <button (click)="callHero(heroInput.value)">Call</button>
Reference DOM elements
Decorators
Class Decorators
| Decorator | Purpose |
|---|---|
@Component |
Component metadata |
@NgModule |
Module definition |
@Injectable |
DI service |
@Directive |
Directive definition |
@Pipe |
Pipe definition |
Property Decorators
| Decorator | Purpose |
|---|---|
@Input |
Input property |
@Output |
Event emitter |
@ViewChild |
Query view element |
@ContentChild |
Query projected content |
@HostListener |
Host event listener |
@HostBinding |
Host property binding |
@Input / @Output
import { Component, Input, Output, EventEmitter } from "@angular/core";
@Component({
selector: "app-hero",
template: `
<h2>{{ hero.name }}</h2>
<button (click)="delete()">Delete</button>
`,
})
export class HeroComponent {
@Input() hero: Hero;
@Output() deleted = new EventEmitter<Hero>();
delete() {
this.deleted.emit(this.hero);
}
}
Parent-child communication
Lifecycle Hooks
Hook Order
| Hook | When Called |
|---|---|
ngOnChanges |
Input property changes |
ngOnInit |
After first ngOnChanges |
ngDoCheck |
Custom change detection |
ngAfterContentInit |
After content projection |
ngAfterContentChecked |
After content check |
ngAfterViewInit |
After view init |
ngAfterViewChecked |
After view check |
ngOnDestroy |
Before destroy |
Implementation
import { Component, OnInit, OnDestroy } from "@angular/core";
export class MyComponent implements OnInit, OnDestroy {
ngOnInit() {
console.log("Component initialized");
}
ngOnDestroy() {
console.log("Component destroyed");
}
}
Implement interface for type safety
Services & Dependency Injection
Injectable Service
import { Injectable } from "@angular/core";
import { Http } from "@angular/http";
import { Observable } from "rxjs/Observable";
import "rxjs/add/operator/map";
@Injectable()
export class HeroService {
constructor(private http: Http) {}
getHeroes(): Observable<Hero[]> {
return this.http.get("api/heroes").map((res) => res.json());
}
}
@Injectable() required for all services
Provider Registration
// Module-wide
@NgModule({
providers: [HeroService],
})
export class AppModule {}
// Component-level
@Component({
providers: [HeroService],
})
export class HeroListComponent {}
Register in module or component
Injection
export class HeroListComponent {
heroes: Hero[];
constructor(private heroService: HeroService) {}
ngOnInit() {
this.heroService.getHeroes().subscribe((heroes) => (this.heroes = heroes));
}
}
Inject via constructor
HTTP
GET Request
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';
constructor(private http: Http) { }
getData() {
return this.http.get('/api/data')
.map(res => res.json());
}
Returns Observable
POST Request
import { Http, Headers, RequestOptions } from '@angular/http';
postData(data: any) {
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers });
return this.http.post('/api/data', JSON.stringify(data), options)
.map(res => res.json());
}
Set headers and options
Error Handling
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';
getData() {
return this.http.get('/api/data')
.map(res => res.json())
.catch(this.handleError);
}
private handleError(error: any) {
console.error('Error:', error);
return Observable.throw(error.json().error || 'Server error');
}
Use catch operator
Routing
Route Configuration
import { RouterModule, Routes } from "@angular/router";
const routes: Routes = [
{ path: "heroes", component: HeroListComponent },
{ path: "hero/:id", component: HeroDetailComponent },
{ path: "", redirectTo: "/heroes", pathMatch: "full" },
{ path: "**", component: PageNotFoundComponent },
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule {}
Define application routes
Router Outlet
<nav>
<a [routerLink]="['/heroes']" routerLinkActive="active">Heroes</a>
<a [routerLink]="['/crisis-center']">Crisis</a>
</nav>
<router-outlet></router-outlet>
Display routed components
Programmatic Navigation
import { Router } from '@angular/router';
constructor(private router: Router) { }
gotoHero(hero: Hero) {
this.router.navigate(['/hero', hero.id]);
}
Navigate in component code
Route Parameters
import { ActivatedRoute } from '@angular/router';
constructor(private route: ActivatedRoute) { }
ngOnInit() {
this.route.params.subscribe(params => {
let id = +params['id'];
this.getHero(id);
});
}
Access route parameters
Forms
Template-Driven Form
<form (ngSubmit)="onSubmit()" #heroForm="ngForm">
<input [(ngModel)]="model.name" name="name" required #name="ngModel" />
<div [hidden]="name.valid || name.pristine">Name is required</div>
<button type="submit" [disabled]="!heroForm.form.valid">Submit</button>
</form>
name attribute required with ngModel
Reactive Form
import { FormBuilder, FormGroup, Validators } from "@angular/forms";
export class HeroFormComponent {
heroForm: FormGroup;
constructor(private fb: FormBuilder) {
this.heroForm = this.fb.group({
name: ["", Validators.required],
power: ["", Validators.required],
});
}
onSubmit() {
console.log(this.heroForm.value);
}
}
More control and testability
Reactive Form Template
<form [formGroup]="heroForm" (ngSubmit)="onSubmit()">
<input formControlName="name" />
<div *ngIf="heroForm.get('name').invalid && heroForm.get('name').touched">
Name is required
</div>
<button [disabled]="heroForm.invalid">Submit</button>
</form>
Binds to FormGroup
Validation States
| State | Meaning |
|---|---|
valid |
All validations pass |
invalid |
At least one fails |
pristine |
User hasn't changed value |
dirty |
User changed value |
touched |
User blurred field |
untouched |
User hasn't blurred |
Pipes
Built-in Pipes
<p>{{ birthday | date }}</p>
<p>{{ birthday | date:'MM/dd/yyyy' }}</p>
<p>{{ price | currency:'USD':true }}</p>
<p>{{ ratio | percent:'2.1-2' }}</p>
<p>{{ message | uppercase }}</p>
<p>{{ message | lowercase }}</p>
<p>{{ object | json }}</p>
<p>{{ promise | async }}</p>
<p>{{ collection | slice:1:3 }}</p>
Transform displayed values
Custom Pipe
import { Pipe, PipeTransform } from "@angular/core";
@Pipe({ name: "exponentialStrength" })
export class ExponentialStrengthPipe implements PipeTransform {
transform(value: number, exponent: string): number {
let exp = parseFloat(exponent);
return Math.pow(value, isNaN(exp) ? 1 : exp);
}
}
Implement PipeTransform interface
Using Custom Pipes
<p>{{ 2 | exponentialStrength:10 }}</p>
@NgModule({
declarations: [ ExponentialStrengthPipe ]
})
Register in module declarations
AngularJS Migration
Syntax Comparison
| AngularJS | Angular 2 |
|---|---|
| Controllers | @Component |
$scope |
Class properties |
ng-app |
bootstrapModule() |
ng-repeat |
*ngFor |
ng-if |
*ngIf |
ng-show/ng-hide |
[hidden] |
ng-class |
[ngClass] |
ng-model |
[(ngModel)] |
| Filters | Pipes |
angular.module() |
@NgModule |
Module Definition
// AngularJS
angular.module("myApp", []).controller("MyCtrl", function ($scope) {
$scope.title = "Hello";
});
// Angular 2
@Component({
selector: "my-app",
template: "<h1>{{title}}</h1>",
})
export class AppComponent {
title = "Hello";
}
Components replace controllers
Gotchas
Zone.js
Zone.js runs change detection automatically after async operations (setTimeout, promises, HTTP). For manual control, use ChangeDetectorRef.
RxJS Operators
Must import operators explicitly in Angular 2:
import "rxjs/add/operator/map";
import "rxjs/add/operator/catch";
import "rxjs/add/observable/throw";
Observable Subscriptions
Always unsubscribe from Observables in ngOnDestroy to prevent memory leaks, or use the async pipe:
// Manual subscription
this.subscription = this.service.getData().subscribe(data => {});
ngOnDestroy() {
this.subscription.unsubscribe();
}
// Or use async pipe
// template: <div>{{ data$ | async }}</div>
Module Types
Angular modules (@NgModule) are different from ES6 modules (import/export). Don't confuse them.
Forms and ngModel
The name attribute is REQUIRED when using [(ngModel)] in template-driven forms:
<!-- Required -->
<input [(ngModel)]="hero.name" name="name" />
Injectable Decorator
@Injectable() is needed for ALL services, even those without dependencies, for future-proofing.
Structural Directives
Always use * prefix with structural directives:
<div *ngIf="condition"></div>
<div *ngFor="let item of items"></div>
Also see
- Angular 2 Official Docs - Complete documentation
- Angular 2 Architecture Guide - Core concepts
- Template Syntax Guide - Comprehensive template reference
- AngularJS to Angular Quick Reference - Migration guide