+ transpiler
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
variable: type [=value]
let name: string = 'some string';
function greetText(name: string): string {
return "Hello " + name;
}
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];
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) {}
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
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);
// 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;
// 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;
}
// named import
import {reportName as report, global, some} from './m'
// default import
import arbitraryName from 'm';
function setName(name: string):void {
this.name = name;
}
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?
Does not support parent-child relationship selectors.
@Component({
selector: 'my-app',
template: `<h1>Hello {{name}}</h1>`,
styles: 'h1 {color: green}'
})
export class AppComponent {
name = 'Angular';
}
encapsulated into the component's own view
encapsulation: [ Native | Emulated | None ]
Encapsulation mode (per component)
Created using @NgModule decorator
import { NgModule } from '@angular/core';
@NgModule({
declarations: [ NameComponent, ... ],
...
})
export class NameModule { }
export class AppModule { }
@NgModule({
imports: [ BrowserModule ]
@NgModule({
imports: [ NativeScriptModule ]
which provides platform specific renderers, and installs core directives like ngIf, ngFor, etc.
@NgModule({
bootstrap: [ AppComponent ]
})
used to extend the global application, usually don't export anything
for example, `CommonModule`
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)
@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.
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
@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
@Component({
interpolation: ['start', 'end'],
Template
<span>?name%</span>
Component
@Component({
interpolation: ['?', '%'],
...
})
export class AppComponent {
name: string = 'g';
<span>{{name}}</span>
Default can be changed
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>
HTMLDivElement supports events:
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
@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
}
@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);
<input type="text" />
@Directive({selector: 'input'})
class InputAttrDirective {
constructor(@Attribute('type') type: string) {
// type would be 'text' in this example
}
}
[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"
<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>
<some-element [ngStyle]="{'font-style': styleExp}">...</some-element>
<some-element [ngStyle]="{'max-width.px': widthExp}">...</some-element>
<some-element [ngStyle]="objExp">...</some-element>
*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.
Used with iterables
*ngFor="let item in items; let i = index;"
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>
<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}}
Slice
array_or_string_expression | slice:start[:end]
JSON
expression | json
@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);
});
}
@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
}
@HostListener('document:keyup', ['$event'])
window, document, body
@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;
Shadow DOM is designed as a tool for building component-based apps. Therefore, it brings solutions for common problems in web development:
<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
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>
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. |
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));
}
}
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`:
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>
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 :
@Component({
entryComponents: [ColorComponent],
...
@NgModule({
entryComponents: [ColorComponent],
...
A wrapper around underlying native element (DOM)
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
<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>
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
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);
}
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
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
...
}
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
@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
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.
@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
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
function main() {
var injector = new Injector(...)
var car = injector.get(Car);
car.drive();
}
Angular1 DI has some problem though:
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
}
}
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 {
}
export class AppComponent {
name = 'Angular';
constructor(@Inject(TestService) s) {
}
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}
]);
constructor ( @Inject(Engine) private engine ...
constructor ( private engine:Engine ...
"emitDecoratorMetadata": true
tsconfig.json
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
Re-implement the example above with different tokens:
1. Use injection token for `doors`
2. Use string token for `tires`
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 } ...
useFactory
{
provide: Engine,
useFactory: (IS_V8) => {
if (IS_V8.useV8) {
return new V8Engine();
} else {
return new V6Engine();
}
},
deps: [IS_V8]
}
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
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
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 {}
@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
constructor (@Optional() parentModule: CoreModule) {
if (parentModule) {
throw new Error(
'CoreModule is already loaded. Import it in the AppModule only');
}
}
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);
constructor (@Optional() @SkipSelf() parentModule: CoreModule) {
if (parentModule) {
throw new Error(
'CoreModule is already loaded. Import it in the AppModule only');
}
}
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({
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.
Promise VS Observable
http://reactivex.io/rxjs
https://github.com/ReactiveX/rxjs
vs
https://github.com/Reactive-Extensions/RxJS
let promise = x.then(valueFn, errorFn);
let observable = x.subscribe(valueFn, errorFn, completeFn);
...
observable.unsubscribe();
Promise
Observable
Lazy vs eager
Creating
Errors
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)
BehaviorSubject
Adding to the project
import {HttpModule} from '@angular/http';
@NgModule({
imports: [BrowserModule, HttpModule],
Injecting
@Component({
selector: 'my-app',
...
})
export class AppComponent {
constructor(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
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
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
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
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 {
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 { ... }
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
https://github.com/typicode/json-server
https://netbasal.com/a-taste-from-the-new-angular-http-client-38fcdc6b359b
@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
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).
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.
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);
@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
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
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)
})
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>
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]
})
});
}
The validation status of the control. There are four possible validation statuses:
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 |
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
http://blog.rangle.io/angular-2-ngmodel-and-custom-form-components/
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>
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');
}
}
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 {
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
?????
https://www.youtube.com/watch?v=gtOPAj9_FSM
Our testing toolchain will consist of the following tools:
/app/components/mycomponent.ts
/app/components/mycomponent.spec.ts