Angular2
Michał Przyszczypkowski
Background
- revolution instead of evolution
- currently in BETA (since December 2015)
- release date not yet announced
Typescript
- Superset of JS (ES6)
- Compiles to plain JS
- Supported in all major IDE's
function greet(name: string):string {
return "Hello, " + name;
}
let greeting: string = greet("Mike");
Strongly typed
class Student {
public fullname : string;
private age: number;
private dontKnowWhatWillBeThere:any;
constructor(public firstname:string, public lastname:string) {
//...
}
}
Classes & interfaces
class Student {
lastname: string;
fullname : string;
constructor(firstname:string, lastname:string) {
this.firstname = firstname;
this.lastname = lastname;
}
}
interface Person {
firstname: string;
lastname: string;
}
Classes & interfaces
function greeter(person : Person) {
return "Hello, " + person.firstname + " " + person.lastname;
}
let user: Person = new Student("Mike", "Someone");
let user: Person = {firstname: 'Mike', lastname: 'Snow'}
Annotations / Decorators
@ExampleAnnotation({
annotationKey: annotationValue
})
export class ExampleClass {
}
- Decorators are proposed as standard for ES
- Already implemented in TS
@AnotherExampleAnnotation({
annotationKey: annotationValue
})
doSomething() {
//...
}
Modules
import * as library from "./module";
import { Person, PeopleService } from "./module";
console.log(library.value);
let peopleSrv = new PeopleService();
let people: Person[] = peopleSrv.getPeople();
export interface Person {
name: string;
}
export class PeopleService {
getPeople(): People[] {
return [{name: 'Mike'}];
}
}
export const value:string = 'Something';
Angular2
- App is made of components
- Tree structure
- Concepts from AngularJS 1.x no longer relevant
- $scope, $directive, $controller, $service, $factory - no longer exist
Angular2
- There is no $scope.$apply()
- No need to use $timeout, $interval etc.
- All events that may lead to bindings changes are patched within library
- We don't need to handle changes detection anymore
Components
@Component({
selector: 'click-me',
templateUrl: 'template.html'
})
export class ClickMeComponent {
private label: string = 'Click there!';
onClickMe(){
alert('Hello.');
}
}
<body>
<click-me></click-me>
</body>
properties
methods
component config
Selectors
@Component({
selector: 'click-me'
...
})
<click-me></click-me>
@Component({
selector: '[click-me]'
...
})
<div click-me=""></div>
Inputs
@Component({
selector: 'click-me',
templateUrl: 'template.html',
inputs: ['message']
})
export class ClickMeComponent {
private message: string;
onClickMe(){
alert(this.message);
}
}
<click-me message="Peekaboo"></click-me>
Outputs
@Component({
selector: 'click-me',
templateUrl: 'template.html',
outputs: ['onClicked']
})
export class ClickMeComponent {
private onClicked: EventEmitter<string> = new EventEmitter<string>();
onClickMe(){
this.onClicked.emit("Hello");
}
}
<body>
<click-me (onClicked)="doSomething($event)"></click-me>
</body>
Styles
@Component({
selector: 'click-me',
templateUrl: 'template.html',
styles: [`.click-btn {
color: red;
}`]
})
export class ClickMeComponent {
...
}
@Component({
selector: 'click-me',
templateUrl: 'template.html',
styles: [`.click-btn {
color: red;
}`],
encapsulation: ViewEncapsulation.None // Native / Emulated
})
export class ClickMeComponent {
...
}
Directives
@Directive({
selector: '[click-me]',
styles: [`.click-btn {
color: red;
}`]
})
export class ClickMeDirective {
...
}
Template language
@Component({
selector: 'click-me',
templateUrl: 'template.html'
})
export class ClickMeComponent {
private label: string = 'Click there!';
onClickMe(){
alert('Hello.');
}
}
<button (click)="onClickMe()">{{ label }}</button>
Template language
<click-me message="Peekaboo"></click-me>
<click-me [message]="peekabooVariable"></click-me>
<click-me [message]="peekabooVariable" (onClicked)="doSth($event)"></click-me>
Structural directives
<span *ngFor="#item of items; #index = index"> item no {{ index }} </span>
<span *ngFor="#item of items"> {{ item.name }} </span>
<span *ngIf="isVisible"> conditional item </span>
explicit declaration
Build-in directives
<span [class.blue]="isBlue"> TEXT </span>
<span [style.backgroundColor]="colorVariable"> TEXT </span>
<span [hidden]="isHidden"> TEXT </span>
<span (click)="onClick()" (mouseenter)="onMouseEnter()"> TEXT </span>
<span [style.display]="isHidden ? 'none' : 'block'"> TEXT </span>
Transclusion
<div class="example-component-template">
<h1>Outer title</h1>
<ng-content></ng-content>
</div>
<example-component>
<h1>Inner title</h1>
<span>Inner text</span>
</example-component>
<div class="example-component-template">
<h1>Outer title</h1>
<h1>Inner title</h1>
<span>Inner text</span>
</div>
content will go there
Services
class ItemsRepository {
private items: Product[];
getItems(): Products[] {
return this.items;
}
}
class ItemsRepository {
private items: Product[];
getItems(): Products[] {
return this.items;
}
}
import {ItemsRepository} from '../itemsRepo'
@Component({
selector: 'click-me',
templateUrl: 'template.html',
providers: [ItemsRepository]
})
export class ItemList {
private items: Product[];
constructor(repo: ItemsRepository) {
this.items = repo.getItems();
}
}
Dependency Injection
first import
set as provider
inject in constructor
App
ItemsEdition
ItemsList
C
D
E
providers: [ItemsRepository]
Whole app share the same instance of ItemsRepository service
Providers visibility
App
ItemsEdition
ItemsList
C
D
E
providers: [ItemsRepository]
Each subtree has its own instance of service.
providers: [ItemsRepository]
class Api {
loadItems(): Products[] {
return this.items;
}
}
DI between services
import {Api} from "./api";
@Injectable()
class ItemsRepository {
constructor(private api:Api) { }
getItems(): Products[] {
this.api.loadItems();
}
}
Mocking providers
import {FakeItemsRepository} from '../fakeItemsRepo'
@Component({
selector: 'click-me',
templateUrl: 'template.html',
providers: [
provide(ItemsRepository, {useClass: FakeItemsRepository})
]
})
export class ItemList {
// ...
}
use custom provider
Routing
routes point to components
@RouteConfig([
{path: '/', component: Home, as: 'Home'},
{path: '/list', component: Items, as: 'List'}
]}
@Component({..})
class ...
<router-outlet></router-outlet>
Routing parameters
@RouteConfig([
{path: '/item/:id', component: Item, as: 'Item'}
]}
constructor(params:RouteParams) {
let routeParamValue:string = params.get('id');
}
Nested routes
<router-outlet>
<router-outlet>
<router-outlet>
Nested routes
@RouteConfig([
{path: '/home', component: Home, as: 'Home'},
{path: '/items/...', component: Items, as: 'List'}
]}
@Component({..})
class ...
<router-outlet></router-outlet>
@RouteConfig([
{path: '/add', component: AddItem, as: 'Add'},
{path: '/edit/:id', component: EditItem, as: 'Edit'}
]}
@Component({..})
class ...
<router-outlet></router-outlet>
Nested routes
/home
/items/add
/items/edit/1
Home
Items
Items
AddItem
EditItem
Navigation
<a [routerLink]="['Home']">Home</a>
let router:Router;
router.navigate(['Home']);
<a [routerLink]="['Items', 'Add']">Home</a>
<a [routerLink]="['Items', 'Edit', {id: 99}]">Home</a>
<a [routerLink]="['Item', {id:99}, 'Edit']">Home</a>
/item/:id/edit
Lifecycle hooks
@Component({...})
export class ComponentClass implements OnInit, OnDestroy {
ngOnInit():any {
...
}
ngOnDestroy():any {
...
}
}
How to hook?
Component lifecycle hooks
- OnInit
- OnDestroy
- DoCheck
- OnChanges
- AfterContentInit
- AfterContentChecked
- AfterViewInit
- AfterViewChecked
Router lifecycle hooks
- CanActivate
- OnActivate
- CanDeactivate
- OnDeactivate
- OnReuse
- https://angular.io/docs/ts/latest/guide/
- http://blog.thoughtram.io/
Resources
Thank you.
Questions?
Angular2
By Michał Przyszczypkowski
Angular2
Introduction to Angular2 concepts
- 213