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

  1. More semantic class names and selectors that are relevant in the context of component
  2. No conflicts with other class names and selectors in the application
  3. Styles are isolated and not effected be changes to styles elsewhere in the application.
  4. 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

concurrently

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:

  1. click
  2. mouseover
  3. ...

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

  1. Attribute directives
    1. ngClass, ngStyle
  2. Structural directives
    1. ngFor
    2. ngIf
    3. ngSwitch
  3. 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

  1. Create `tasks` module
  2. Create interface for Task object
  3. Create `task-list` component in that module. This component accepts `tasks` and renders them using `task` component and `*ngFor` directive. 
  4. 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

  1. DatePipe,
  2. UpperCasePipe,
  3. LowerCasePipe,
  4. CurrencyPipe
  5. 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));
  }
}
  1. View queries are set before the `ngAfterViewInit` callback is called, therefore, is available only from this point.
  2. 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