TypeScript
What is TS?
+ transpiler
Types
1. it helps when writing code because it can prevent bugs at compile time and
2. it helps when reading code because it clarifies your intentions
General form
variable: type [=value]
let name: string = 'some string';
function greetText(name: string): string {
return "Hello " + name;
}
Data types
Non-ES6 types
Enums
enum Role {Employee = 3, Manager, Admin};
var role: Role = Role.Employee;
Any
Void
function setName(name: string):void {
this.name = name;
}
let something: any = 'as string';
something = 1;
something = [1, 2, 3];
Classes
Syntactic sugar
// ES6
class Animal {
constructor(name) {
this.name = name;
this.type = 'carnivorous';
this.speed = 5;
}
// TypeScript
class Animal {
type = 'carnivorous';
constructor(public name, public speed = 5) {}
Classes
Access modifiers
class Animal {
constructor(public name, private speed, protected type) {}
}
class Rabbit extends Animal {
constructor(name, speed, type, readonly power) {
super(name, speed, type);
}
parentType() {
return super.type;
}
}
let someAnimal = new Animal('Zuma', 1000, 'carnivorous');
someAnimal.name; // Error - Private member is not accessible
someAnimal.speed; // Error - Private member is not accessible
someAnimal.type; // Error - Private member is not accessible
let abba = new Rabbit('Abba', 20, 'herbivorous', 50);
abba.parentType(); // 'herbivorous'
abba.power = 4; // Attempt to assign to a const variable
Interface
Why use interfaces?
// Without interface
function printLabel(labelledObj: { label: string }) {
console.log(labelledObj.label);
}
let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);
// Using interface
interface LabelledValue {
label: string;
}
function printLabel(labelledObj: LabelledValue) {
console.log(labelledObj.label);
}
let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);
Interface
// Defining an interface
interface F {
// object property
name: string,
// optional object property
callable?: boolean;
// readonly object property
readonly params: number;
// callable type
(): O;
// can be used as a function constructor
new (): O;
// object method
a(): number;
// indexable type
[index: string]: string;
}
// Defining object/function type when creating an object
let f = function() {} as F;
// Using type assertion
function f() {};
let fn = f as F;
let fn = <F>f;
Modules
// named export
function reportName(name) { console.log(name); }
let global = 3;
export { reportName, global };
export function some() { console.log(name); }
// default export
export default function cube(x) {
return x * x * x;
}
Import
Export
// named import
import {reportName as report, global, some} from './m'
// default import
import arbitraryName from 'm';
Decorators
function setName(name: string):void {
this.name = name;
}
Angular overview
Advantages over angular1
Local injectors
Web components support (isolated styles, shadow DOM)
Less confusing architecture approach
Platform independent
Lazy routing and child states out of the box
Performance improvements (AOT)
How to build such application?
Application is a tree of components
Component
Modules
Services
General architecture
Basic metadata properties
Components
-
selector
- CSS selector that identifies this component within a template. Supported selectors include element, [attribute], .class, and :not().
-
Does not support parent-child relationship selectors.
- template
- styles
@Component({
selector: 'my-app',
template: `<h1>Hello {{name}}</h1>`,
styles: 'h1 {color: green}'
})
export class AppComponent {
name = 'Angular';
}
Styles
encapsulated into the component's own view
Benefits
- More semantic class names and selectors that are relevant in the context of component
- No conflicts with other class names and selectors in the application
- Styles are isolated and not effected be changes to styles elsewhere in the application.
- Styles are isolated and can be removed with any effect on other styles in the application
encapsulation: [ Native | Emulated | None ]
Encapsulation mode (per component)
- provide context for template parsing, both in the Just In Time or Ahead Of Time Compilation scenarios
- provide encapsulation for components and directives
- useful simply as documentation for grouping related functionality
- adds providers to root injector
Modules
Created using @NgModule decorator
import { NgModule } from '@angular/core';
@NgModule({
declarations: [ NameComponent, ... ],
...
})
export class NameModule { }
- defines root component for bootstrap
export class AppModule { }
Application module (bootstrap/root)
- conventionally named AppModule
- imports platform specific core module:
@NgModule({
imports: [ BrowserModule ]
@NgModule({
imports: [ NativeScriptModule ]
which provides platform specific renderers, and installs core directives like ngIf, ngFor, etc.
@NgModule({
bootstrap: [ AppComponent ]
})
Feature module
used to extend the global application, usually don't export anything
Lazy-loaded modules
Shared module
- declares and exports a set of commonly used components, directives and pipes
- registers common services in the injector
for example, `CommonModule`
bootstrapping the application
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';
platformBrowserDynamic().bootstrapModule(AppModule);
Browser dynamic
import { platformNativeScriptDynamic } from "nativescript-angular/platform";
import { AppModule } from "./app.module";
platformNativeScriptDynamic().bootstrapModule(AppModule);
Mobile
import { platformBrowser } from '@angular/platform-browser';
import { AppModuleNgFactory } from './main.ngfactory';
// Launch with the app module factory.
platformBrowser().bootstrapModuleFactory(AppModuleNgFactory);
Browser static (AOT)
Dependencies: angular lib
@angular/core
runtime logic, includes all metadata decorators
@angular/common
include built-in services, pipes, and directives
@angular/compiler
template compiler that converts templates to code that makes the application run and render
@angular/platform-browser [-dynamic]
Everything DOM and browser related. Includes the bootstrapStatic/bootstrap method
Dependencies: Polyfils (angular peer dependencies)
core-js
Patches the global context (window) with essential features of ES2015 (ES6). You may substitute an alternative polyfill that provides the same core APIs. When these APIs are implemented by the major browsers, this dependency will become unnecessary.
rxjs
A polyfill for the Observables specification currently before the TC39 committee that determines standards for the JavaScript language. You can pick a preferred version of rxjs (within a compatible version range) without waiting for Angular updates.
zone.js
A polyfill for the Zone specification currently before the TC39 committee that determines standards for the JavaScript language. You can pick a preferred version of zone.js to use (within a compatible version range) without waiting for Angular updates.
Development dependencies
A utility to run multiple npm commands concurrently on OS/X, Windows, and Linux operating systems.
json-server
A light-weight static json and file server
typescript
the TypeScript language server, including the tsc TypeScript compiler.
Module loader
systemjs
templateUrl, styleUrl and moduleId
@Component({
moduleId: module.id,
selector: 'my-app',
template: `<h1>Hello {{name}}</h1>`,
templateUrl: 'app.component.html',
styleUrls: ['app.component.css']
})
export class AppComponent {
name = 'Angular';
}
(function(require, exports, module) {
"use strict";
...
AppComponent = __decorate([
core_1.Component({
moduleId: module.id,
selector: 'my-app',
template: "<h1>Hello {{name}}</h1>",
templateUrl: 'app.component.html',
styleUrls: ['app.component.css']
})
], AppComponent);
exports.AppComponent = AppComponent;
});
Transforms into
Bindings & Built-in directives
Interpolation
@Component({
interpolation: ['start', 'end'],
Template
<span>?name%</span>
Component
@Component({
interpolation: ['?', '%'],
...
})
export class AppComponent {
name: string = 'g';
<span>{{name}}</span>
Default can be changed
Property binding / Inputs
So we can bind to these properties:
class Node {
textContent: string;
}
class Element extends Node {
innerHTML: string;
}
class HTMLElement extends Element {}
class HTMLDivElement extends HTMLElement {}
HTMLDivElement class
<div [textContent]="text"></div>
<div [innerHTML]="html"></div>
Event binding / Outputs
HTMLDivElement supports events:
- click
- mouseover
- ...
So we can subscribe to these events:
<div (click)="clicked()"></div>
<div (mouseover)="hovered()"></div>
Check all properties and events on MDN or in dom_element_schema_registry.ts
Receiving Input
@Component({
inputs: ['tasks: items']
})
export class TaskListComponent {
tasks:string;
Using `@Component` decorator metadata
Using `@Input` property decorator
@Component({ ... })
export class TaskListComponent {
@Input('items') tasks: string;
Setters are also supported
@Component({ ... })
export class TaskListComponent {
@Input('items') set tasks(value) {
debugger
}
Emitting Output
@Component({
outputs: ['externalEventName: eventName']
})
export class TaskListComponent {
eventName: EventEmitter<type> = new EventEmitter();
...
this.eventName.emit(<type>data);
Using `@Component` decorator metadata
Using `@Input` property decorator
@Component({ ... })
export class TaskListComponent {
@Output('externalEventName') eventName: EventEmitter<type> = new EventEmitter();
...
this.eventName.emit(<type>data);
Getting attribute value with @Attribute
<input type="text" />
@Directive({selector: 'input'})
class InputAttrDirective {
constructor(@Attribute('type') type: string) {
// type would be 'text' in this example
}
}
Special property bindings
[class.name]="expression === true ? add : remove"
[attr.danger]="value ? true : null"
[style.name.unit]="expression"
[style.color]="green"
[style.fontSize.px]="5"
[class.custom-class]="true"
[attr.name]="expression === null ? hide: show"
Built-in Directives
- Attribute directives
- ngClass, ngStyle
- Structural directives
- ngFor
- ngIf
- ngSwitch
- Component directives
ngClass
<some-element [ngClass]="'first second'">...</some-element>
<some-element [ngClass]="['first', 'second']">...</some-element>
<some-element [ngClass]="{'first': true, 'second': true, 'third': false}">...</some-element>
<some-element [ngClass]="stringExp|arrayExp|objExp">...</some-element>
<some-element [ngClass]="{'class1 class2 class3' : true}">...</some-element>
ngStyle
<some-element [ngStyle]="{'font-style': styleExp}">...</some-element>
<some-element [ngStyle]="{'max-width.px': widthExp}">...</some-element>
<some-element [ngStyle]="objExp">...</some-element>
Structural directives
*ngIf, *ngFor, *ngSwitch
<div *ngIf="isValid;then content else other_content">here is ignored</div>
The * is a bit of syntactic sugar that makes it easier to read and write directives that modify HTML layout with the help of templates.
ngFor
Used with iterables
*ngFor="let item in items; let i = index;"
Tasks
- Create `tasks` module
- Create interface for Task object
- Create `task-list` component in that module. This component accepts `tasks` and renders them using `task` component and `*ngFor` directive.
- Create `task` component in that module. Make task component private to `task` module. Task component takes task object through input binding and renders its name. Add specific place to click on that will emit event to task-list component which will remove the task from the list
Pipes
A pipe takes in data as input and transforms it to a desired output. It is implemented using pipe ( | ) operator
<p>The hero's birthday is {{ birthday | date }}</p>
Parameterizing a Pipe
Chaining pipes
<p>The hero's birthday is {{ birthday | date:"MM/dd/yy" }} </p>
<p>The hero's birthday is {{ birthday | date:format }} </p>
{{ birthday | date | uppercase}}
Built-in pipes
- DatePipe,
- UpperCasePipe,
- LowerCasePipe,
- CurrencyPipe
- PercentPipe
Slice
array_or_string_expression | slice:start[:end]
JSON
expression | json
Async Pipe
@Component({
selector: 'async-observable-pipe',
template: '<div><code>observable|async</code>: Time: {{ time | async }}</div>'
})
export class AsyncObservablePipeComponent {
time = new Observable<string>((observer: Subscriber<string>) => {
setInterval(() => observer.next(new Date().toString()), 1000);
});
}
@HostListener
@Component({
moduleId: module.id,
selector: 'a-comp',
template: '<span>I am A component</span>'
})
export class AComponent {
@HostListener('click', ['$event.currentTarget'])
clicked(target) {
console.log(target.nodeName); // A-COMP
}
Listening for global events on:
@HostListener('document:keyup', ['$event'])
window, document, body
@HostBinding
@Component({
moduleId: module.id,
selector: 'a-comp',
template: '<span>I am A component</span>'
})
export class AComponent {
@HostBinding('style.color') color = 'black';
@HostListener('mouseenter') onEnter() {
this.color = 'green';
}
@HostListener('mouseleave') onLeave() {
this.color = 'black';
}
@HostBinding('style.color') color = 'black';
@HostBinding('attr.title') value = 'some title';
@HostBinding('class.some') add = true;
Accessing elements,
Lifecycle, shadow DOM vs light DOM
Shadow DOM
(component view)
Shadow DOM is designed as a tool for building component-based apps. Therefore, it brings solutions for common problems in web development:
- Isolated DOM: A component's DOM is self-contained (e.g. document.querySelector() won't return nodes in the component's shadow DOM).
- Scoped CSS: CSS defined inside shadow DOM is scoped to it. Style rules don't leak out and page styles don't bleed in.
- Composition: Design a declarative, markup-based API for your component.
- Simplifies CSS - Scoped DOM means you can use simple CSS selectors, more generic id/class names, and not worry about naming conflicts.
- Productivity - Think of apps in chunks of DOM rather than one large (global) page.
Shadow root (component view)
<task [id]="tasks[0].id" [name]="tasks[0].name" (remove)="removeTask($event)"></task>
h1 { color: green }
<div>
<span>{{id}}</span>
<span>{{name}}</span>
<a (click)="removeMe()">x</a>
</div>
Generated HTML
document.querySelector('.cantfindme'); // null
Distributed nodes (component content)
a way to import HTML content from outside the component and insert that content into the component's template in a designated spot.
Content projection
<task [id]="tasks[0].id" [name]="tasks[0].name" (remove)="removeTask($event)">
<!-- this light DOM, what goes inside the component -->
<header>
<img width="204" height="65" title="" alt="" src="data:image/png;ba..."/>
</header>
<!-- this light DOM -->
</task>
Defines content when using a component
Defines where content goes when designing a component
<div>
<!-- here the node with `header` tag will be projected -->
<ng-content select="header"></ng-content>
<!-- projection ends -->
<span class="cantfindme">{{id}}</span>
<span>{{name}}</span>
<a (click)="removeMe()">x</a>
</div>
Component lifecycle
Constructor | Mainly for injecting dependencies |
ngOnInit | Initialize the directive/component after Angular sets the directive/component's input properties. |
ngOnChanges | Angular calls its ngOnChanges method whenever it detects changes to input properties of the component (or directive) |
ngAfterContentInit | Respond after Angular projects external content into the component's view. |
ngAfterViewInit | Respond after Angular initializes the component's views and child views. |
ngOnDestroy | Cleanup just before Angular destroys the directive/component. Unsubscribe observables and detach event handlers to avoid memory leaks. |
@ViewChild & @ViewChildren
Returns the specified elements or directives from the view DOM as QueryList
@Component({
selector: 'my-app',
template: `
<alert></alert>
<alert type="danger"></alert>
<alert type="info"></alert>
`,
})
export class App {
@ViewChildren(AlertComponent) alerts: QueryList<AlertComponent>
ngAfterViewInit() {
this.alerts.forEach(alertInstance => console.log(alertInstance));
}
}
- View queries are set before the `ngAfterViewInit` callback is called, therefore, is available only from this point.
- ViewChildren don’t include elements that exist within the light DOM (<ng-content> tag).
Read parameter
By default, the @ViewChildren decorator returns the component instance
- Element references of components
@ViewChildren(AlertComponent, { read: ElementRef }) alerts: QueryList<AlertComponent>
- View Container references of child components
@ViewChildren(AlertComponent, { read: ViewContainerRef }) alerts: QueryList<AlertComponent>
- Element references of standard html tags
@ViewChildren('ref-name1, ref-name2'[, { read: ViewContainerRef }]) alerts: QueryList<any>
<div #ref-name1>...</div>
@ContentChild & @ContentChildren
Returns the specified elements or directives from the content DOM as QueryList
@Component({
selector: 'tab',
template: `
<p>{{title}}</p>
`,
})
@Component({
selector: 'tabs',
template: `
<ng-content></ng-content>
`,
})
export class TabsComponent {
@ContentChildren(TabComponent) tabs: QueryList<TabComponent>
ngAfterContentInit() {
this.tabs.forEach(tabInstance => console.log(tabInstance))
}
}
@Component({
selector: 'my-app',
template: `
<tabs>
<tab title="One"></tab>
<tab title="Two"></tab>
</tabs>
`,
})
Subscribing to changes
@ViewChildren(SomeType) viewChildren;
@ContentChildren(SomeType) contentChildren;
ngOnInit() {
this.viewChildren.changes.subscribe(changes => console.log(changes));
this.contentChildren.changes.subscribe(changes => console.log(changes));
}
Should be done inside `ngOnInit`:
Manipulating views
<ng-container> element
Logical container that can be used to group nodes but is not rendered in the DOM tree as a node.
It is rendered as an HTML comment.
<section *ngIf="show">
<div>
<h2>Div one</h2>
</div>
<div>
<h2>Div two</h2>
</div>
</section>
<template [ngIf]="show">
<div>
<h2>Div one</h2>
</div>
<div>
<h2>Div two</h2>
</div>
</template>
<ng-container *ngIf="show">
<div>
<h2>Div one</h2>
</div>
<div>
<h2>Div two</h2>
</div>
</ng-container>
Entry components
Specifies components that are not used directly in templates at the moment of compilation
Compiler recursive nature
entry components -> components in templates
-> components in templates...
-> tree shaking
Components are added automatically which are :
- listed in @NgModule.bootstrap
- referenced in router configuration
@Component({
entryComponents: [ColorComponent],
...
@NgModule({
entryComponents: [ColorComponent],
...
A wrapper around underlying native element (DOM)
ElementRef
class ElementRef {
constructor(nativeElement: any)
nativeElement: any
}
export class AppComponent implements AfterViewInit {
@ViewChild('span') childSpan: ElementRef;
constructor(private element: ElementRef) {}
ngAfterViewInit(): void {
console.log(this.childSpan.nativeElement.innerHTML);
console.log(this.element.nativeElement.innerHTML);
}
}
Usage
A mechanism for holding client-side content that is not rendered on a page and is available as a document fragment for subsequent use in the document
<template>
<script>
document.addEventListener("DOMContentLoaded", function(event) {
var t = document.querySelector('#t');
var container = document.querySelector('#container');
insertAfter(container, t.content);
});
</script>
<body>
<template id="t">
<div class="in-template">
<span>I am groot</span>
</div>
</template>
<div id="container"></div>
</body>
templateRef
Represents an Embedded Template that can be used to instantiate Embedded Views.
<template #t>some template text here</template>
export class AppComponent implements AfterViewInit {
@ViewChild('t') t: TemplateRef<any>;
ngAfterViewInit(): void {
this.t;
}
export class MyDirective {
constructor(t: TemplateRef<any>): void {
this.t;
}
<template myDirective>some template text here</template>
Usage in a component
Usage in a directive
viewRef
Represents an Angular View.
A View is a fundamental building block of the application UI. It is the smallest grouping of Elements which are created and destroyed together.
<template #t>
<div class="in-template">
<span>I am groot</span>
</div>
</template>
export class AppComponent implements AfterViewInit {
@ViewChild('t') t: TemplateRef<any>;
ngAfterViewInit(): void {
let view1: ViewRef = this.t.createEmbeddedView(null);
}
ViewContainerRef
Represents a container where one or more Views can be attached. Views represent some sort of layout to be rendered and the context under which to render it.
class ViewContainerRef {
element : ElementRef
clear() : void
length : number
insert(viewRef: ViewRef, index?: number) : ViewRef
get(index: number) : ViewRef
indexOf(viewRef: ViewRef) : number
detach(index?: number) : ViewRef
move(viewRef: ViewRef, currentIndex: number) : ViewRef
}
export class AppComponent implements AfterViewInit {
@ViewChild('t') t: TemplateRef<any>;
@ViewChild('vc', {read: ViewContainerRef}) vc: ViewContainerRef;
ngAfterViewInit(): void {
let view: ViewRef = this.t.createEmbeddedView(null);
this.vc.insert(view);
console.log(this.vc.length); // 1
}
Usage
ViewContainerRef
Two types of views can be attached to a view container:
class ViewContainerRef {
element: ElementRef
length: number
createComponent(componentFactory, index, injector, projectableNodes): ComponentRef<C>
createEmbeddedView(templateRef, context, index): EmbeddedViewRef<C>
clear(): void
...
}
- Host Views which are linked to a Component
- Embedded Views which are linked to a template
Creating host views ( components )
export class AppComponent {
@ViewChild('container', {read: ViewContainerRef}) container: ViewContainerRef;
componentRef: ComponentRef<ColorComponent>;
constructor(private vc: ViewContainerRef, private resolver: ComponentFactoryResolver) {}
createComponent() {
this.container.clear();
const factory: ComponentFactory<any> = this.resolver.resolveComponentFactory(ColorComponent);
this.componentRef: ComponentRef<ColorComponent> = this.container.createComponent(factory);
// binding to inputs and outputs
this.componentRef.instance.color = 'green';
this.componentRef.instance.output.subscribe(event => console.log(event));
}
ngOnDestroy() {
this.componentRef.destroy();
}
ComponentFactoryResolver
Creating embedded view
@Directive({
selector: '[appDelay]'
})
export class DelayDirective {
constructor(private templateRef: TemplateRef<any>,
private viewContainerRef: ViewContainerRef) {
}
createEmbeddedView() {
this.viewContainerRef.createEmbeddedView(this.templateRef);
}
viewContainerRef.createEmbeddedView(templateRef, context, index) {
const viewRef: EmbeddedViewRef<any> = templateRef.createEmbeddedView(context);
this.insert(viewRef, index);
return viewRef;
}
Just an alias for manual view manipulation
Renderer
Built-in service that provides an abstraction for UI rendering manipulations.
class Renderer {
setElementProperty(renderElement: any, propertyName: string, propertyValue: any) : void
setElementAttribute(renderElement: any, attributeName: string, attributeValue: string) : void
setElementClass(renderElement: any, className: string, isAdd: boolean) : void
setElementStyle(renderElement: any, styleName: string, styleValue: string) : void
...
}
renderElement
can be accessed from `elementRef.nativeElement`
Built-in service that provides an abstraction for UI rendering manipulations.
Creating attribute directives
@Directive({
selector: '[colorMe]'
})
export class ColorMeDirective implements OnInit {
constructor(private element: ElementRef, private renderer: Renderer) { }
ngOnInit(): void {
this.renderer.setElementStyle(this.element, 'color', 'green');
}
}
ngTemplateOutlet && NgComponentOutlet
https://netbasal.com/a-taste-from-angular-version-4-50be1c4f3550#.e50k8xx25
https://medium.com/@MichalCafe/angulars-content-projection-trap-and-why-you-should-consider-using-template-outlet-instead-cc3c4cad87c9#.isj9z2f12
DI
class Car {
constructor() {
this.engine = new Engine(internalPart);
this.tires = Tires.getInstance();
this.doors = app.get('doors');
}
}
What's the problem?
class Car {
constructor(engine, tires, doors) {
this.engine = engine;
this.tires = tires;
this.doors = doors;
}
}
let car = new Car(
new Engine(internalPart),
Tires.getInstance(),
app.get('doors')
);
A better alternative
Dependency Injection as a pattern
function main() {
var injector = new Injector(...)
var car = injector.get(Car);
car.drive();
}
Dependency Injection as a framework
Dependency Injection as a framework
Angular1 DI has some problem though:
- Internal cache - Dependencies are served as singletons. Whenever we ask for a service, it is created only once per application lifecycle. Creating factory machinery is quite hairy.
- Namespace collision - There can only be one token of a “type” in an application. If we have a car service, and there’s a third-party extension that also introduces a service with the same name, we have a problem.
- Built into the framework - Angular 1’s DI is baked right into the framework. There’s no way for us to use it decoupled as a standalone system.
Ingredients
Reflective Injector
import {ReflectiveInjector} from '@angular/core';
class MyService {
report() {
console.log('hello');
}
}
@Component({selector: 'my-app', ...})
export class AppComponent {
constructor() {
let token = MyService;
let injector = ReflectiveInjector.resolveAndCreate([
{provide: token, useClass: MyService}
]);
let my = injector.get(token);
my.report(); // hello
}
}
Angular DI
import {ReflectiveInjector} from '@angular/core';
class MyService {
report() {
console.log('hello');
}
}
@Component({selector: 'my-app', ... })
export class AppComponent {
constructor(@Inject(MyService) service) {
service.report(); // hello
}
}
Register provider
@NgModule({
providers: [{provide: MyService , useClass: MyService }],
...
})
export class AppModule {
}
Injecting a service - @Inject(token)
export class AppComponent {
name = 'Angular';
constructor(@Inject(TestService) s) {
}
Practice
Implement a service that exposes an injector that allows getting instances of car parts
constructor(carPartsProvider) {
const injector = carPartsProvider.injector;
const engine = injector.get(Engine);
const tires = injector.get(Tires);
const doors = injector.get(Doors);
console.log(engine.name); // engine
console.log(tires.name); // tires
console.log(doors.name); // doors
}
----------------
let injector = ReflectiveInjector.resolveAndCreate([
{provide: token, useClass: MyService}
]);
@Injectable/@Inject
constructor ( @Inject(Engine) private engine ...
constructor ( private engine:Engine ...
"emitDecoratorMetadata": true
tsconfig.json
Types of tokens
Class
[ {provide: Doors, useClass: Doors} ... ]
//shortcut
[ Doors ]
String
let configToken = 'config';
[ { provide: configToken, useClass: Config } ... ]
InjectionToken
const CONFIG_TOKEN = new InjectionToken('config');
[ { provide: CONFIG_TOKEN, useClass: Config } ... ]
POJO, Symbol
Practice
Re-implement the example above with different tokens:
1. Use injection token for `doors`
2. Use string token for `tires`
Types of providers
useClass
useValue
let featureEnabledToken = 'featureEnabled';
const FEATURE_ENABLED = true;
[ { provide: featureEnabledToken, useValue: FEATURE_ENABLED } ...
[ {provide: Doors, useClass: Doors} ... ]
//shortcut
[ Doors ]
useClass as alias
[ { provide: DoorsAlias, useClass: Doors } ...
Types of providers
useFactory
{
provide: Engine,
useFactory: (IS_V8) => {
if (IS_V8.useV8) {
return new V8Engine();
} else {
return new V6Engine();
}
},
deps: [IS_V8]
}
Types of providers
useExisting
const injector = ReflectiveInjector.resolveAndCreate([
{provide: A, useClass: A},
{provide: B, useClass: A},
{provide: C, useExisting: A}
]);
let a = injector.get(A);
let b = injector.get(B);
console.log(a === b); // false
let c = injector.get(C);
console.log(a === c); // true
It’s the only strategy that doesn’t actually create an instance, but instead, it points to another token which in turn will create the instance
Practice
Re-implement the example above:
1. engine depends on the config `ENGINE_CONFIG` that should be defined using InjectionToken and use value strategy on the root module
2. tires should be provided using factory strategy and it depends on the `TIRES_CONFIG`. `TIRES_CONFIG` is the same as `ENGINE_CONFIG` so define it using `USE_EXISTING` provider strategy
Types of providers
multi
const SOME_TOKEN: OpaqueToken = new OpaqueToken('SomeToken');
var injector = ReflectiveInjector.resolveAndCreate([
{ provide: SOME_TOKEN, useValue: 'dependency one', multi: true },
{ provide: SOME_TOKEN, useValue: 'dependency two', multi: true }
]);
var dependencies = injector.get(SOME_TOKEN);
// dependencies == ['dependency one', 'dependency two']
NG_VALIDATORS
@Directive({
selector: '[customValidator][ngModel]',
providers: [
provide: NG_VALIDATORS,
useValue: (formControl) => {
// validation happens here
},
multi: true
]
})
class CustomValidator {}
Angular root injector
@NgModule({
providers: [NameService],
...
})
export class AppModule {}
@Component({
selector: 'app',
template: '<h1>Hello !</h1>'
})
class App {
constructor(nameService: NameService) {
this.name = NameService.getName();
}
}
@NgModule registers providers with root injector
Can be injected into a component
Hierarchical injectors
Hierarchical injectors
@Optional
constructor (@Optional() parentModule: CoreModule) {
if (parentModule) {
throw new Error(
'CoreModule is already loaded. Import it in the AppModule only');
}
}
@Self
class Dependency {
}
@Injectable()
class NeedsDependency {
constructor(@Self() public dependency: Dependency) {
this.dependency = dependency;
}
}
const parent = ReflectiveInjector.resolveAndCreate([{provide: Dependency, useValue: 'A'}]);
const child = parent.resolveAndCreateChild([NeedsDependency]);
child.get(NeedsDependency);
@SkipSelf
constructor (@Optional() @SkipSelf() parentModule: CoreModule) {
if (parentModule) {
throw new Error(
'CoreModule is already loaded. Import it in the AppModule only');
}
}
@Host
forwardRef
class Door {
lock: Lock;
// Door attempts to inject Lock, despite it not being defined yet.
// forwardRef makes this possible.
constructor(@Inject(forwardRef(() => Lock)) lock: Lock) { this.lock = lock; }
}
// Only at this point Lock is defined.
class Lock {}
const injector = ReflectiveInjector.resolveAndCreate([Door, Lock]);
const door = injector.get(Door);
expect(door instanceof Door).toBeTruthy();
expect(door.lock instanceof Lock).toBeTruthy();
Component/directive injector
@Component({
selector: 'app',
template: '<h1>Hello !</h1>'
})
export class AppComponent {
constructor(
private elementRef: ElementRef,
private renderer: Renderer,
private view: ViewContainerRef
) {
@Component creates its own injector shared with directives
@Component can add it's own providers
@Component({
selector: 'app',
providers: [
{provide: NameService, useValue: 'Thomas' }
]
})
class App {
constructor(nameService: NameService) {
console.log(nameService); // Thomas
providers configures a child injector that is created for a component.
Lazy module injector
Providers vs ViewProviders
RxJS
RxJS
Promise VS Observable
RxJS
http://reactivex.io/rxjs
https://github.com/ReactiveX/rxjs
vs
https://github.com/Reactive-Extensions/RxJS
RxJS
let promise = x.then(valueFn, errorFn);
let observable = x.subscribe(valueFn, errorFn, completeFn);
...
observable.unsubscribe();
Promise
Observable
Lazy vs eager
RxJS
Creating
RxJS
Errors
RxJS
Key parts
Producer
A producer is the source of values for your observable. It could be a web socket, it could be DOM events, it could be an iterator
Observable
Consumer/Observer
Observables are functions that tie an observer to a producer.
let producer = function (observer) {
let a = [1, 2, 3];
a.forEach(function (value) {
observer.next(value);
})
};
let observer = function (event) {
console.log(event);
};
Observable.create(producer).subscribe(observer);
Practice
create fromEvent producer
create xhr producer
https://chrisnoring.gitbooks.io/rxjs-5-ultimate/content/observable-wrapping.html
Practice
The mechanics are still the same though: 1) where data is emitted, add a call to next() 2) if there is NO more data to emit call complete 3) if there is a need for it, define a function that can be called upon unsubscribe() 4) Handle errors through calling .error() in the appropriate place. (only done in the first example)
Operators
http://reactivex.io/rxjs
http://reactivex.io/rxjs/manual
Operators
of
do
let stream$ = Rx.Observable.of(1,2,3,4,5)
let stream$ =
Rx.Observable
.of(1,2,3)
.do((value) => {
console.log('emits every value')
});
Creates an Observable from an Array, an array-like object, a Promise, an iterable object, or an Observable-like object.
from
Marble Diagrams
Operators
filter
let stream$ =
Rx.Observable
.of(1,2,3,4,5)
.filter((value) => {
return value % 2 === 0;
})
// 2,4
http://rxmarbles.com/#filter
scan
It's like reduce, but emits the current accumulation whenever the source emits a value.
let stream$ = Rx.Observable
.of(1, 2, 3, 4, 5)
.scan((acc, value) => {
return acc + value;
});
// 1, 3, 6, 10, 15
Operators
map
//Map every every click to the clientX position of that click
var clicks = Rx.Observable.fromEvent(document, 'click');
var positions = clicks.map(ev => ev.clientX);
positions.subscribe(x => console.log(x));
http://rxmarbles.com/#filter
Operators
Merge
const p1 = Rx.Observable.interval(500).take(3).map((v) => {o: 1, v: v});
const p2 = Rx.Observable.interval(500).take(3).map((v) => {o: 2, v: v});
const p3 = Rx.Observable.interval(500).take(3).map((v) => {o: 3, v: v});
Rx.Observable.merge(p1, p2, p3).subscribe((v) => console.log(v));
{o: 1, v: 0}
{o: 2, v: 0}
{o: 3, v: 0}
{o: 1, v: 1}
{o: 2, v: 1}
{o: 3, v: 1}
{o: 1, v: 2}
{o: 2, v: 2}
{o: 3, v: 2}
Operators
mergeMap
{o: 1, v: 0}
{o: 2, v: 0}
{o: 3, v: 0}
{o: 1, v: 1}
{o: 2, v: 1}
{o: 3, v: 1}
{o: 1, v: 2}
{o: 2, v: 2}
{o: 3, v: 2}
Rx.Observable.of(1, 2, 3).mergeMap((i) => {
return Rx.Observable.interval(500).take(3).map((v) => {
return {o: i, v: v}
})
}).subscribe((v) => {
console.log(v);
});
Operators
Concat
const p1 = Rx.Observable.interval(500).take(3).map((v) => {o: 1, v: v});
const p2 = Rx.Observable.interval(500).take(3).map((v) => {o: 2, v: v});
const p3 = Rx.Observable.interval(500).take(3).map((v) => {o: 3, v: v});
Rx.Observable.concat(p1, p2, p3).subscribe((v) => console.log(v));
{o: 1, v: 0}
{o: 1, v: 1}
{o: 1, v: 2}
{o: 2, v: 0}
{o: 2, v: 1}
{o: 2, v: 2}
{o: 3, v: 0}
{o: 3, v: 1}
{o: 3, v: 2}
Operators
concatMap
Rx.Observable.of(1, 2, 3).concatMap((i) => {
return Rx.Observable.interval(500).take(3).map((v) => {
return {o: i, v: v}
})
}).subscribe((v) => {
console.log(v);
});
{o: 1, v: 0}
{o: 1, v: 1}
{o: 1, v: 2}
{o: 2, v: 0}
{o: 2, v: 1}
{o: 2, v: 2}
{o: 3, v: 0}
{o: 3, v: 1}
{o: 3, v: 2}
Operators
Switch
const p1 = Rx.Observable.interval(400).take(3).map((v) => {o: 1, v: v});
const p2 = Rx.Observable.interval(400).take(3).map((v) => {o: 2, v: v});
const p3 = Rx.Observable.interval(400).take(3).map((v) => {o: 3, v: v});
const os = [p1, p2, p3];
Rx.Observable.interval(1000).take(3).map((i) => {
return os[i];
}).switch().subscribe((v) => {
console.log(v);
});
{o: 1, v: 0}
{o: 1, v: 1}
{o: 2, v: 0}
{o: 2, v: 1}
{o: 3, v: 0}
{o: 3, v: 1}
{o: 3, v: 2}
Operators
SwitchMap
const p1 = Rx.Observable.interval(400).take(3).map((v) => {o: 1, v: v});
const p2 = Rx.Observable.interval(400).take(3).map((v) => {o: 2, v: v});
const p3 = Rx.Observable.interval(400).take(3).map((v) => {o: 3, v: v});
const os = [p1, p2, p3];
Rx.Observable.interval(1000).take(3).switchMap((i) => {
return os[i];
}).subscribe((v) => {
console.log(v);
});
{o: 1, v: 0}
{o: 1, v: 1}
{o: 2, v: 0}
{o: 2, v: 1}
{o: 3, v: 0}
{o: 3, v: 1}
{o: 3, v: 2}
Hot vs Cold
// cold observable example
let stream$ = Rx.Observable.of(1,2,3);
//subscriber 1: 1,2,3
stream.subscribe(
data => console.log(data),
err => console.error(err),
() => console.log('completed')
)
//subscriber 2: 1,2,3
stream.subscribe(
data => console.log(data),
err => console.error(err),
() => console.log('completed')
)
let liveStreaming$ = Rx.Observable.interval(1000).take(5);
let publisher$ = Rx.Observable
.interval(1000)
.take(5)
.publish();
liveStreaming$.subscribe(
data => console.log('subscriber from first minute')
err => console.log(err),
() => console.log('completed')
)
setTimeout(() => {
liveStreaming$.subscribe(
data => console.log('subscriber from 2nd minute')
err => console.log(err),
() => console.log('completed')
)
},2000)
Subjects
BehaviorSubject
ReplaySubject
AsyncSubject
HTTP
Adding to the project
import {HttpModule} from '@angular/http';
@NgModule({
imports: [BrowserModule, HttpModule],
Injecting
@Component({
selector: 'my-app',
...
})
export class AppComponent {
constructor(http: Http) {
HTTP
Request
class Request extends Body {
constructor(requestOptions: RequestArgs)
method: RequestMethod
headers: Headers
url: string
withCredentials: boolean
responseType: ResponseContentType
detectContentType(): ContentType
detectContentTypeFromBody(): ContentType
getBody(): any
}
let req = new Request({
url: 'blabla/tasks',
method: RequestMethod.Get
});
http.request(req).subscribe(function (response) {
console.log(response);
})
Usage
HTTP
RequestOptions
class RequestOptions {
method: RequestMethod|string|null
headers: Headers|null
body: any
url: string|null
params: URLSearchParams
get set search(params: URLSearchParams)
withCredentials: boolean|null
responseType: ResponseContentType|null
merge(options?: RequestOptionsArgs): RequestOptions
}
const options = new RequestOptions({
method: RequestMethod.Post,
url: 'https://google.com'
});
const req = new Request(options);
Usage
HTTP
Response
class Response extends Body {
constructor(responseOptions: ResponseOptions)
type : ResponseType
ok : boolean
url : string
status : number
statusText : string
bytesLoaded : number
totalBytes : number
headers : Headers
toString() : string
// come from Body
json(): any
text():
arrayBuffer()
}
http.get('api/tasks').subscribe(function (response) {
console.log(response.json());
})
Usage
HTTP
Headers
class Headers {
static fromResponseHeaderString(headersString: string): Headers
append(name: string, value: string): void
delete(name: string): void
forEach(fn: (values: string[], name: string|undefined, headers: Map<string, string[]>) => void): void
get(name: string): string|null
has(name: string): boolean
keys(): string[]
set(name: string, value: string|string[]): void
values(): string[][]
toJSON(): {[name: string]: any}
getAll(name: string): string[]|null
entries()
}
let headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('Authorization': api_token);
let options = new RequestOptions({ headers: headers });
return this.http.get(url, options);
Usage
HTTP
Extending BaseRequestOptions
export class DefaultRequestOptions extends BaseRequestOptions{
headers:Headers = new Headers({
'Content-Type': 'application/x-www-form-urlencoded'
});
}
{provide: RequestOptions, useClass: DefaultRequestOptions };
@NgModule({
providers: [
{provide: Http, useFactory: httpFactory, deps: [XHRBackend, RequestOptions]},
{provide: RequestOptions, useClass: BaseRequestOptions},
...
export class HttpModule {
Angular in-memory-web-api
Create class
import { InMemoryDbService } from 'angular-in-memory-web-api';
export class InMemHeroService implements InMemoryDbService {
createDb() {
let tasks = [
{ id: '1', name: 'Windstorm' },
{ id: '2', name: 'Bombasto' }
];
return {tasks};
}
}
Register
// other imports
import { HttpModule } from '@angular/http';
import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
import { InMemHeroService } from '../app/hero-data';
@NgModule({
imports: [
HttpModule,
InMemoryWebApiModule.forRoot(InMemHeroService),
...
],
...
})
export class AppModule { ... }
Angular in-memory-web-api
Overrides XHRBackend
@NgModule({
// Must useFactory for AoT
// https://github.com/angular/angular/issues/11178
providers: [ { provide: XHRBackend,
useFactory: inMemoryBackendServiceFactory,
deps: [Injector, InMemoryDbService, InMemoryBackendConfig]} ]
})
export class InMemoryWebApiModule {
@NgModule({
providers: [
// TODO(pascal): use factory type annotations once supported in DI
// issue: https://github.com/angular/angular/issues/3183
{provide: Http, useFactory: httpFactory, deps: [XHRBackend, RequestOptions]},
XHRBackend,
],
})
export class HttpModule {
}
HTTP uses XHRBackend
json-server
https://github.com/typicode/json-server
new http
https://netbasal.com/a-taste-from-the-new-angular-http-client-38fcdc6b359b
Forms
@angular/forms module
import { FormsModule } from '@angular/forms'; @NgModule({ imports: [BrowserModule, FormsModule],
Reactive VS template driven
this.control = new FormControl();
<input [formControl]="control">
<input name="name" ngModel>
Template driven
Standalone form control
this.control = new FormControl();
this.control.valueChanges.subscribe((value) => {
console.log(value);
});
this.control.setValue('updated');
// ------- HTML--------------
<input [formControl]="control">
You can also pass a model
<input [formControl]="control" [ngModel]="vm.name">
Syncs a standalone instance to a form control element.
In other words, this directive ensures that any values written to the
instance programmatically will be written to the DOM element (model -> view). Conversely, any values written to the DOM element through user input will be reflected in the instance (view -> model).
AbstractControl
Base class for FormControl, FormGroup, and FormArray.
It provides some of the shared behavior that all controls and groups of controls have, like running validators, calculating status, and resetting state. It also defines the properties that are shared between all sub-classes, like value, valid, and dirty.
It shouldn't be instantiated directly.
AbstractControl API
class AbstractControl {
constructor(validator: ValidatorFn|null, asyncValidator: AsyncValidatorFn|null)
value: any
parent: FormGroup|FormArray
status: string
valid: boolean
invalid: boolean
pending: boolean
disabled: boolean
enabled: boolean
errors: ValidationErrors|null
pristine: boolean
dirty: boolean
touched: boolean
untouched: boolean
valueChanges: Observable<any>
statusChanges: Observable<any>
setValidators(newValidator: ValidatorFn|ValidatorFn[]|null): void
setAsyncValidators(newValidator: AsyncValidatorFn|AsyncValidatorFn[]): void
clearValidators(): void
clearAsyncValidators(): void
markAsTouched(opts?: {onlySelf?: boolean}): void
markAsUntouched(opts?: {onlySelf?: boolean}): void
markAsDirty(opts?: {onlySelf?: boolean}): void
markAsPristine(opts?: {onlySelf?: boolean}): void
markAsPending(opts?: {onlySelf?: boolean}): void
disable(opts?: {onlySelf?: boolean, emitEvent?: boolean}): void
enable(opts?: {onlySelf?: boolean, emitEvent?: boolean}): void
setParent(parent: FormGroup|FormArray): void
setValue(value: any, options?: Object): void
patchValue(value: any, options?: Object): void
reset(value?: any, options?: Object): void
updateValueAndValidity(opts?: {onlySelf?: boolean, emitEvent?: boolean}): void
setErrors(errors: ValidationErrors|null, opts?: {emitEvent?: boolean}): void
get(path: Array<string|number>|string): AbstractControl|null
getError(errorCode: string, path?: string[]): any
hasError(errorCode: string, path?: string[]): boolean
root: AbstractControl
}
Form control API
const ctrl = formGroup.get(controlName);
const ctrl = new FormControl('', Validators.required);
console.log(ctrl.value); // ''
console.log(ctrl.status); // 'INVALID'
ctrl.setValue(value);
ctrl.patchValue(value);
// it is marked as pristine
// it is marked as untouched
// value is set to `Nancy` or null if not specified
ctrl.reset('Nancy');
ctrl.disable();
ctrl.valueChanges.subscribe(data => {
console.log('Form changes', data)
})
ctrl.statusChanges.subscribe(data => {
console.log('Form changes', data)
})
ctrl.registerOnChange(fn);
ctrl.registerOnDisabledChange(fn);
NgModel directive
@ViewChild(NgModel) ngModel;
const control = this.ngModel.control;
control.valueChanges.subscribe((v) => {
console.log(v);
});
control.setValue('updated');
<input [(ngModel)]="vm.name">
@Directive({
selector: '[ngModel]:not([formControlName]):not([formControl])',
providers: [formControlBinding],
exportAs: 'ngModel'
})
export class NgModel {
_control = new FormControl();
viewModel: any;
@Input() name: string;
@Input('ngModel') model: any;
Implementation
Form Group
Implementation
It stores all controls using names
this.control = new FormControl(5);
this.group = new FormGroup({age: this.control});
<form [formGroup]="group">
<input name="name" [formControlName]="'age'">
</form>
HTML
FormGroup class
export interface User {
name: string;
account: {
email: string;
confirm: string;
}
}
FormGroup -> 'user'
FormControl -> 'name'
FormGroup -> 'account'
FormControl -> 'email'
FormControl -> 'confirm'
Form group API
const form = new FormGroup({
first: new FormControl(),
last: new FormControl()
});
console.log(form.value); // {first: null, last: null}
form.setValue({first: 'Nancy', last: 'Drew'});
console.log(form.value); // {first: 'Nancy', last: 'Drew'}
form.patchValue({first: 'July'}); // {first: 'July', last: 'Drew'}
form.setValue({first: 'July'}); // throws an error
// it is marked as pristine
// it is marked as untouched
// values are set to specified or null if not specified
form.reset({first: 'name', last: 'last name'});
form.valueChanges.subscribe(data => {
console.log('Form changes', data)
})
form.statusChanges.subscribe(data => {
console.log('Form changes', data)
})
Form Array
It stores all controls using indices
this.group = new FormArray();
this.group = new FormControl('John');
<section>
<p>Any special requests?</p>
<ul formArrayName="specialRequests">
<li *ngFor="let item of orderForm.controls.specialRequests.controls; let i = index">
<input type="text" formControlName="{{i}}">
<button type="button" title="Remove Request" (click)="onRemoveSpecialRequest(i)">Remove</button>
</li>
</ul>
<button type="button" (click)="onAddSpecialRequest()">
Add a Request
</button>
</section>
HTML
Forms (template-driven)
import {FormsModule} from '@angular/forms';
ngForm -> '#f'
ngModel -> 'name'
ngModelGroup -> 'account'
-> ngModel -> 'email'
-> ngModel -> 'confirm'
<input [(ngModel)]="user.name">
<input [ngModel]="user.name"` (ngModelChange)="user.name = $event">
<form novalidate (ngSubmit)="onSubmit(f)" #f="ngForm">
Submit
Reactive forms (model-driven)
import {ReactiveFormsModule} from '@angular/forms';
ngOnInit() {
this.user = new FormGroup({
name: new FormControl(''),
account: new FormGroup({
email: new FormControl(''),
confirm: new FormControl('')
})
});
}
formGroup -> 'user'
formControlName -> 'name'
formGroupName -> 'account'
formControlName -> 'email'
formControlName -> 'confirm'
<form novalidate (ngSubmit)="onSubmit(user)" [formGroup]="user">
Submit
Validators
name: new FormControl('initial value', [Validators.required, Validators.minLength(2)]),
<input
name="name"
ngModel
minlength="2"
required>
- required - Requires a form control to have a non-empty value
- minlength - Requires a form control to have a value of a minimum length
- maxlength - Requires a form control to have a value of a maximum length
- pattern - Requires a form control’s value to match a given regex
Built-in validators
Form builder
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
export class SignupFormComponent implements OnInit {
user: FormGroup;
constructor(private fb: FormBuilder) {}
...
}
// before
ngOnInit() {
this.user = new FormGroup({
name: new FormControl('', [Validators.required, Validators.minLength(2)]),
account: new FormGroup({
email: new FormControl('', Validators.required),
confirm: new FormControl('', Validators.required)
})
});
}
// after
ngOnInit() {
this.user = this.fb.group({
name: ['', [Validators.required, Validators.minLength(2)]],
account: this.fb.group({
email: ['', Validators.required],
confirm: ['', Validators.required]
})
});
}
Validation status
The validation status of the control. There are four possible validation statuses:
- VALID: control has passed all validation checks
- INVALID: control has failed at least one validation check
- PENDING: control is in the midst of conducting a validation check
- DISABLED: control is exempt from validation checks
These statuses are mutually exclusive, so a control cannot be both valid AND invalid or invalid AND disabled
control = new FormControl('hello', [Validators.required]);
console.log(control.status); // INVALID
this.control.statusChanges.subscribe((s) => {
console.log(s); // INVALID
});
this.control.setValue('');
control.enabled
control.disabled
control.valid
control.invalid
control.pending
API (adds ng-valid/ng-invalid)
State | Description | Class if true | Class if false |
---|---|---|---|
touched/untouched | Control has been visited | ng-touched | ng-untouched |
pristine/dirty | Control's value has changed | ng-dirty | ng-pristine |
Interaction state
Referencing input values
<input #textbox (keyup)="onKeyup(textbox.value)">
<input (keyup)="onKey($event)" />
<input #textbox (keyup.enter)="onKeyup(textbox.value)">
Binding to a particular key
Value Accessors
http://blog.rangle.io/angular-2-ngmodel-and-custom-form-components/
Custom form components
Directive
Router
A router state is an arrangement of application components that defines what is visible on the screen.
tasks
tasks/3
tasks/create
Router State
A router state is a subtree of the configuration tree
Navigation is the act of transitioning from one router state to another.
The router:
- allows us to express all the potential states of app
- provides a mechanism for navigating from one state to another.
Router job
Defining a route
{
path: 'tasks',
component: ExTasksListComponent,
children: [...]
}
Registering a route
@NgModule({
imports: [ RouterModule.forRoot(routesArray), ...]
export class AppModule {
Navigating
<nav>
<a routerLink="pathRelativeToCurrentRoute" routerLinkActive="className">Tasks</a>
Specifying insertion point
app.component.html
<span>I will be above the inserted component</span>
<router-outlet></router-outlet>
<!-- Component will be inserted here -->
The RouterOutlet is a directive from the router library that marks the spot in the template where the router should display the views for that outlet.
Practical task
1. Create routing for tasks & users module
ActivatedRoute vs ActivatedRouteSnapshot
interface ActivatedRoute {
snapshot: ActivatedRouteSnapshot
url: Observable<UrlSegment[]>
params: Observable<Params>
queryParams: Observable<Params>
data: Observable<Data>
routeConfig: Route
root: ActivatedRoute
parent: ActivatedRoute
firstChild: ActivatedRoute
children: ActivatedRoute[]
}
Accessing parameters and resolved data
@Component({...})
class ConversationCmp {
conversation: Observable<Conversation>;
id: Observable<string>;
constructor(r: ActivatedRoute) {
this.conversation = r.data.subscribe(d => d.conversation);
this.id = r.params.subscribe(p => p.id);
}
}
From ActivatedRoute As observables
@Component({...})
class ConversationCmp {
conversation: Conversation;
constructor(r: ActivatedRoute) {
const s: ActivatedRouteSnapshot = r.snapshot;
this.conversation = s.data['conversation'];
this.conversation = s.params['id'];
}
}
From snapshot as values
Running Guards & Resolving Data
[
{
path: ':folder',
children: [
{
path: '',
component: ConversationsCmp,
resolve: {
conversations: ConversationsResolver
}
}
]
}
]
@Injectable()
class ConversationsResolver implements Resolve<any> {
constructor(private repo: ConversationsRepo, private currentUser: User) {
}
resolve(route: ActivatedRouteSnapshot, state: RouteStateSnapshot): Promise<Conversation[]> {
return this.repo.fetchAll(route.params['folder'], this.currentUser);
}
}
class ConversationsCmp {
conversations: Observable<Conversation[]>;
constructor(route: ActivatedRoute) {
this.conversations = route.data.pluck('conversations');
}
}
Lazy loading
let routes = {
path: 'tasks',
component: TaskCmp,
children: [
path: 'create',
component: TaskCreateCmp
]
}
@NgModule({
imports: [RouterModule.forRoot(routes);
export class AppModule {
let taskRoutes= [{
path: '',
component: TaskCmp,
children: [
path: 'create',
component: TaskCreateCmp
]
}];
@NgModule({
imports: [RouterModule.forChild(taskRoutes);
export class TaskModule {
let appRoutes= [{
path: 'tasks',
loadChildren: 'app/tasks/tasks.module#TaskModule'
}];
@NgModule({
imports: [RouterModule.forRoot(routes);
export class AppModule {
Lazy loading
let taskRoutes= [{
path: '',
component: TaskCmp,
children: [
path: 'create',
component: TaskCreateCmp
]
}];
@NgModule({
imports: [RouterModule.forChild(taskRoutes);
export class TaskModule {
let appRoutes= [{
path: 'tasks',
loadChildren: 'app/tasks/tasks.module#TaskModule'
}];
@NgModule({
imports: [RouterModule.forRoot(routes);
export class AppModule {
Example
[
{path: '', pathMatch: 'full', redirectTo: '/inbox'},
{
path: ':folder',
children: [
{
path: '',
component: ConversationsCmp
},
{
path: ':id',
component: ConversationCmp,
children: [
{path: 'messages', component: MessagesCmp},
{path: 'messages/:id', component: MessageCmp}
]
}
]
},
{
path: 'compose',
component: ComposeCmp,
outlet: 'popup'
},
{
path: 'message/:id',
component: PopupMessageCmp,
outlet: 'popup'
}
]
Navigating to `/inbox/33/messages/44`
[
{path: '', redirectTo: '/inbox'},
{path: ':folder', pathMatch: 'full', component: ConversationCmp},
{path: ':folder/:id', pathMatch: 'full', component: ConversationCmp},
{path: ':folder/:id/messages', pathMatch: 'full', component: ConversationCmp},
{path: ':folder/:id/messages/:id', pathMatch: 'full', component: ConversationCmp}
{path: 'compose', component: ComposeCmp},
{path: 'message/:id', component: PopupMessageCmp}
]
Applying Redirects
[
{path: '', pathMatch: 'full', redirectTo: '/inbox'},
A redirect is a substitution of a URL segment
Redirects can either be local or absolute.
Local redirects replace a single segment with a different one. Absolute redirects replace
the whole URL. Redirects are local unless you prefix the url with a slash.
Navigating to `/inbox/33/messages/44`
No redirect is applied
Recognizing States
Navigating to `/inbox/33/messages/44`
[
{path: '', redirectTo: '/inbox'},
{path: ':folder', component: ConversationCmp},
{path: ':folder/:id', component: ConversationCmp},
{path: ':folder/:id/messages', component: ConversationCmp},
{path: ':folder/:id/messages/:id', component: ConversationCmp}
{path: 'compose', component: ComposeCmp},
{path: 'message/:id', component: PopupMessageCmp}
]
`:param` matches any string
Which configuration will be matched?
Backtracking if not full url is matched
Constructing future router state
Running Guards
For now, it is sufficient to say that a guard is a function that the router runs to make sure that a
navigation to a certain URL is permitted.
Activating Components
instantiate all the
needed components and place them into appropriate router outlets.
That’s what the router will do. First, it will instantiate ConversationCmp and place it into the
?????
Change detection
Architecture
https://www.youtube.com/watch?v=gtOPAj9_FSM
Testing
@angular/core/testing
Our testing toolchain will consist of the following tools:
- Jasmine
- Karma
- Phantom-js
Filename Conventions
/app/components/mycomponent.ts
/app/components/mycomponent.spec.ts
Karma config
Angular 2
By maximk
Angular 2
- 1,282