Angular 2

formally Angular 4

Angular 2

features

Features, or advantages

Cross platform network

Angular 2 > Angular 1

Angular 2 is faster and easier than Angular 1

Speed up

It will speed up the initial load through server side rendering

Mobile

Angular 2 is mainly focused on mobile apps

IE9+ , Android 4.1+

It supports both latest version of browsers, and also old browsers including IE9+ and Android 4.1+

Components

Everything will be the component based approach

DI

It uses Dependency Injection to maintain applications without writing too long code

Server side rendering

It uses server side rendering for fast views on mobile

Key differences with Angular 1

Angular 2 is not upgrade of Angular 1. Angular 2 is completely rewritten

Typescript

Angular 2 is mobile oriented

Mobile

Angular 1 core concept was $scope. You will not find $scope in Angular 2

$scope

Angular 1 controllers are gone. We can say that controllers are replaced with "Components" in Angular 2

Controller

now it is ngFor

ng-repeat

In Angular 2 local variables are defined using hash(#) prefix

#

Two-way data binding: ng-model is replaced with [(ngModel)]

[(ngModel)]

Hierarchical DI system is major performance booster

Hierarchical DI

Angular 2 implements unidirectional tree based change detection which again increases performance

Change detection

Angular 2 file size is 20kb less than Angular 1

File size

Filtering functionality is called pipes

Pipes

Angular 2 uses camelCase syntax for built-in directives. ng-class is now ngClass

camelCase

In Angular 1 we can define a service via 5 different ways

Services

- Factory

- Service

- Provider

- Constant

- Values

In Angular 2, class is the only way to define a service

@Component

import { Component } from '@angular/core';

@Component({
    selector: 'my-app',
    template: `<label>Name:</label>
               <input [(ngModel)]="name" placeholder="name">
               <h1>Welcome {{name}}!</h1>`
})
export class AppComponent {
    name= '';
}

@Component

@Component({ 
  changeDetection?: ChangeDetectionStrategy
  viewProviders?: Provider[]
  moduleId?: string
  templateUrl?: string
  template?: string
  styleUrls?: string[]
  styles?: string[]
  animations?: any[]
  encapsulation?: ViewEncapsulation
  interpolation?: [string, string]
  entryComponents?: Array<Type<any>|any[]>
})

@Component: Metadata Properties

  • animations - list of animations of this component
  • changeDetection - change detection strategy used by this component
  • encapsulation - style encapsulation strategy used by this component
  • entryComponents - list of components that are dynamically inserted into the view of this component
  • exportAs - name under which the component instance is exported in a template
  • host - map of class property to host element bindings for events, properties and attributes
  • inputs - list of class property names to data-bind as component inputs
  • interpolation - custom interpolation markers used in this component's template
  • moduleId - ES/CommonJS module id of the file in which this component is defined

@Component: Metadata Properties

  • outputs - list of class property names that expose output events that others can subscribe to
  • providers - list of providers available to this component and its children
  • queries - configure queries that can be injected into the component
  • selector - css selector that identifies this component in a template
  • styleUrls - list of urls to stylesheets to be applied to this component's view
  • styles - inline-defined styles to be applied to this component's view
  • template - inline-defined template for the view
  • templateUrl - url to an external file containing a template for the view
  • viewProviders - list of providers available to this component and its view children

Templates

templateUrl?: string

Specifies a template URL for an Angular component.

template?: string

Specifies an inline template for an Angular component.

@Component({
    selector: 'my-app',
    template: `<label>Name:</label>
               <input [(ngModel)]="name" placeholder="name">
               <h1>Welcome {{name}}!</h1>`,
    styleUrls: ["app/app.component.css"]
})
//app.component.ts
@Component({
    selector: 'my-app',
    templateUrl: "app/app.component.html",
    styleUrls: ["app/app.component.css"]
})
//app.component.html
<label>Name:</label>
<input [(ngModel)]="name" placeholder="name">
<h1>Welcome {{name}}!</h1>

Only one of templateUrl or template can be defined per Component.

Styles

 The styles property takes an array of strings that contain CSS code

@Component({
    selector: 'my-app',
    template: `<label>Name:</label>
               <input [(ngModel)]="name" placeholder="name">
               <h1>Welcome {{name}}!</h1>`,
    styles: ['h1 { color: red; }']
})

You can load styles from external CSS files by adding a styleUrls attribute into a component's @Component decorator

//app.component.ts
@Component({
    selector: 'my-app',
    template: `<label>Name:</label>
               <input [(ngModel)]="name" placeholder="name">
               <h1>Welcome {{name}}!</h1>`,
    styleUrls: ["app/app.component.css"]
})
//app.component.css
h1 {
    color: red;
}

:host

The :host selector is the only way to target the host element. You can't reach the host element from inside the component with other selectors because it's not part of the component's own template. The host element is in a parent component's template.

//app.component.css
:host {
    display: block;
    border: 1px solid black;
}

h1 {
    color: red;
}

/deep/

Use the /deep/ shadow-piercing descendant combinator to force a style down through the child component tree into all the child component views. The /deep/ combinator works to any depth of nested components, and it applies to both the view children and content children of the component.

//app.component.css
/deep/ .some-cls {
    background: yellow;
}

Data binding

Data direction Syntax Type
One-way
from data source
to view target
Interpolation
Property
Attribute
Class
Style
One-way
from view target
to data source
Event
Two-way

 
Two-way
{{expression}}
[target]="expression"
bind-target="expression"
(target)="statement"
on-target="statement"
[(target)]="expression"
bindon-target="expression"

Binding targets

The target of a data binding is something in the DOM. Depending on the binding type, the target can be an (element | component | directive) property, an (element | component | directive) event, or (rarely) an attribute name.

<img [src]="heroImageUrl">
<hero-detail [hero]="currentHero"></hero-detail>
<div [ngClass]="{'special': isSpecial}"></div>
  • Element property

  • Component property

  • Directive property

  • Element event

  • Component event

  • Directive event

<button (click)="onSave()">Save</button>
<hero-detail (deleteRequest)="deleteHero()"></hero-detail>
<div (myClick)="clicked=$event" clickable>click me</div>

Event and property

<input [(ngModel)]="name">

class/stye property

<button 
    [attr.aria-label]="help">
        help
</button>

Attribute

<div 
    [class.special]="isSpecial">
        Special
</div>
<button 
[style.color]="isSpecial ? 'red' : 'green'">

Examples

Component Interaction

Pass data from parent to child with input binding

//parent tempalte
<child
        [inputData]="setData"
        [specialInputName]="setSpecialData"
></child>
//child component
import { Component, Input } from '@angular/core';

@Component({
    selector: 'child',
    template: `
        <p>Input data: {{inputData}}</p>
        <p>Input data with special name: {{special}}</p>
    `,
})
export class ChildComponent{
    @Input()
    public inputData:string;

    @Input("specialInputName")
    public special: string;
}
//parent component
import { Component } from '@angular/core';

@Component({
    selector: 'my-app',
    templateUrl: "app/app.component.html",
    styleUrls: ["app/app.component.css"]
})
export class AppComponent {
    public setData: string = "bla-bla";
    public setSpecialData: string = "special bla-bla";
}

Component Interaction

Intercept input property changes with a setter

import { Component, Input } from '@angular/core';

@Component({
    selector: 'child',
    template: `
        <p>Input data: {{inputData}}</p>
    `,
})
export class ChildComponent{
    private inputDataPR: string;
    
    @Input()
    public set inputData(value: string) {
        //some code
        this.inputDataPR = value
    };

    public get inputData(): string {
        return this.inputDataPR;
    }
}

Component Interaction

Parent listens for child event

//child component
import { 
    Component, 
    Output, 
    EventEmitter 
} from '@angular/core';

@Component({
    selector: 'child',
    template: `
        <button (click)="sendEvent()">
            Emmit event
        </button>
    `,
})
export class ChildComponent{
    @Output()
    public outputEvent: EventEmitter = 
        new EventEmitter<string>();

    public sendEvent(): void {
        this.outputEvent.emit("emited event");
    }
}
//parent template
<child
        (outputEvent)="showEvent($event)"
></child>
//parent component
import { Component } from '@angular/core';

@Component({
    selector: 'my-app',
    templateUrl: "app/app.component.html",
    styleUrls: ["app/app.component.css"]
})
export class AppComponent {
    public showEvent(str: string):void {
       console.log(str); 
    }
}

Component Interaction

Parent interacts with child via local variable

//child component
import {
    Component,
} from '@angular/core';

@Component({
    selector: 'child',
    template: `
        <p>{{sharedStr}}</p>
    `,
})
export class ChildComponent {
    public sharedStr: string = "shared string";
}
//parent component
import { Component } from '@angular/core';

@Component({
    selector: 'my-app',
    templateUrl: "app/app.component.html",
    styleUrls: ["app/app.component.css"]
})
export class AppComponent {
}
//parent template
<child #sharedVar ></child>
<button
       (click)="sharedVar.sharedStr = 'new shared string'"
> Change shared variable
</button>

Lifecycle Hooks

  • ngOnChanges: вызывается до метода ngOnInit() при начальной установке свойств, которые связаны механизмом привязки, а также при любой их переустановке или изменении их значений. Данный метод в качестве параметра принимает объект класса SimpleChanges, который содержит предыдущие и текущие значения свойства.

  • ngOnInit: вызывается один раз после установки свойств компонента, которые участвуют в привязке. Выполняет инициализацию компонента

  • ngDoCheck: вызывается при каждой проверке изменений свойств компонента сразу после методов ngOnChanges и ngOnInit

  • ngAfterContentInit: вызывается один раз после метода ngDoCheck() после вставки содержимого в представление компонента кода html

  • ngAfterContentChecked: вызывается фреймворком Angular при проверке изменений содержимого, которое добавляется в представление компонента. Вызывается после метода ngAfterContentInit() и и после каждого последующего вызова метода ngDoCheck().

  • ngAfterViewInit: вызывается фреймворком Angular после инициализации представления компонента, а также представлений дочерних компонентов. Вызывается только один раз сразу после первого вызова метода ngAfterContentChecked()

  • ngAfterViewChecked: вызывается фреймворком Angular после проверки на изменения в представлении компонента, а также проверки представлений дочерних компонентов. Вызывается после первого вызова метода ngAfterViewInit() и после каждого последующего вызова ngAfterContentChecked()

  • ngOnDestroy: вызывается перед тем, как фреймворк Angular удалит компонент.

ViewChild & ContentChild

import {
    Component,
    ViewChild,
    ChangeDetectionStrategy,
    AfterViewInit
} from '@angular/core';
import { ChildComponent } from "app/child.component";

@Component({
    selector: 'my-app',
    templateUrl: "app/app.component.html",
    styleUrls: ["app/app.component.css"],
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent implements AfterViewInit{
    @ViewChild(ChildComponent)
    private childCmp: ChildComponent;

    ngAfterViewInit() {
        console.log(this.childCmp);
    }
}

ViewChild & ContentChild

//component
import {
    Component,
    ViewChild,
    ChangeDetectionStrategy,
    AfterViewInit
} from '@angular/core';

@Component({
    selector: 'my-app',
    templateUrl: "app/app.component.html",
    styleUrls: ["app/app.component.css"],
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent implements AfterViewInit{
    @ViewChild("childCmp")
    private childCmp: HTMLElement;

    ngAfterViewInit() {
        console.log(this.childCmp);
    }
}
//template
<child #childCmp></child>

ViewChild & ContentChild

//child component
import {
    Component,
    ContentChild,
    AfterContentInit
} from '@angular/core';

@Component({
    selector: 'child',
    template: `
        <ng-content></ng-content>
        <p>{{sharedStr}}</p>
    `,
})
export class ChildComponent implements AfterContentInit{
    public sharedStr: string = "shared string";

    @ContentChild("content")
    private currentContent: HTMLElement;
    
    ngAfterContentInit(): void {
        console.log(this.currentContent);
    }
}
//parent template
<child>
    <p #content>This's ng-content</p>
</child>

@NgModule

import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule }   from '@angular/forms';
import { AppComponent }   from './app.component';
 
@NgModule({
    imports:      [ BrowserModule, FormsModule ],
    declarations: [ AppComponent ],
    bootstrap:    [ AppComponent ]
})
export class AppModule { }

Every Angular app has a root module class. By convention, the root module class is called AppModule and it exists in a file named app.module.ts

@NgModule

  • NgModule: функциональность декоратора NgModule, без которой мы не сможем создать модуль

  • BrowserModule: модуль, необходимый для работы с браузером

  • FormsModule: модуль, необходимый для работы с формами html и, в частности, с элементами input. (Так как класс компонента работает с подобными элементами, то мы обязаны также импортировать этот модуль)

  • AppComponent: функциональность корневого компонента приложения

import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule }   from '@angular/forms';
import { AppComponent }   from './app.component';
 
@NgModule({
    imports:      [ BrowserModule, FormsModule ],
    declarations: [ AppComponent ],
    bootstrap:    [ AppComponent ]
})
export class AppModule { }

@NgModule

  • declarations: классы представлений (view classes), которые принадлежат модулю. Angular имеет три типа классов представлений: components, directives,  pipes

  • exports: набор классов представлений, которые должны использоваться в шаблонах компонентов из других модулей

  • imports: другие модули, классы которых необходимы для шаблонов компонентов из текущего модуля

  • providers: классы, создающие сервисы, используемые модулем

  • bootstrap: корневой компонент, который вызывается по умолчанию при загрузке приложения

import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule }   from '@angular/forms';
import { AppComponent }   from './app.component';
 
@NgModule({
    imports:      [ BrowserModule, FormsModule ],
    declarations: [ AppComponent ],
    bootstrap:    [ AppComponent ]
})
export class AppModule { }

@Directive

В Angular есть три типа директив:

  • Компоненты: компонент по сути также является директивой, а декоратор @Component расширяет возможности декоратора@Directive с помощью добавления функционала по работе с шаблонами.

  • Атрибутивные: они изменяют поведение уже существующего элемента, к которому они применяются. Например, ngModel, ngStyle, ngClass

  • Структурные: они изменяют структуру DOM с помощью добавления, изменения или удаления элементов hmtl. Например, это директивы ngFor и ngIf

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>

How To Use

Attribute Directives

import { Directive, ElementRef, Input } from '@angular/core';

@Directive({ selector: '[myHighlight]' })
export class HighlightDirective {
    constructor(el: ElementRef) {
       el.nativeElement.style.backgroundColor = 'yellow';
    }
}

How To Use

Example: (let's switch on my local example)

HostListener

Декоратор @HostListener позволяет связать события DOM и методы директивы. В частности, в декоратор передается название события, по которому будет вызываться метод.

import {
    Component,
    HostListener,
    Renderer2,
    ElementRef
} from '@angular/core';

@Component({
    selector: 'child',
    template: `
        <input type="text" [(ngModel)]="textInput">
        <br/>
        <p *ngIf="textInput === '1'; else elseBlock">{{sharedStr}}</p>
        <ng-template #elseBlock>
            Alternate text while primary text is hidden
        </ng-template>
    `,
})
export class ChildComponent{
    private renderer: Renderer2;
    private element: ElementRef;
    public sharedStr: string = "shared string";

    constructor(renderer: Renderer2, element: ElementRef){
        this.renderer = renderer;
        this.element = element;
    }
    @HostListener("mouseenter") onMouseEnter() {
        this.renderer.setStyle(this.element.nativeElement, "color","red");
    }

    @HostListener("mouseleave") onMouseLeave() {
        this.renderer.removeStyle(this.element.nativeElement, "color");
    }
}

HostBinding

Декоратор  @HostBinding позволяет связать обычное свойство класса со свойством элемента, к которому применяется директива

import {
    Component,
    HostBinding,
    HostListener,
    Renderer2,
    ElementRef
} from '@angular/core';

@Component({
    selector: 'child',
    template: `
        <input type="text" [(ngModel)]="textInput">
        <br/>
        <p *ngIf="textInput === '1'; else elseBlock">{{sharedStr}}</p>
        <ng-template #elseBlock>
            Alternate text while primary text is hidden
        </ng-template>
    `,
})
export class ChildComponent{
    private renderer: Renderer2;
    private element: ElementRef;
    private color = "yellow";
    public sharedStr: string = "shared string";

    constructor(renderer: Renderer2, element: ElementRef){
        this.renderer = renderer;
        this.element = element;
    }

    @HostBinding("style.color")
    private get getColor(){
        return this.color;
    }

    @HostBinding("style.cursor")
    private get getCursor(){
        return "pointer";
    }
 @HostListener("mouseenter")
    private onMouseEnter() {
        this.renderer.setStyle(this.element.nativeElement, "color","red");
    }

    @HostListener("mouseleave")
    private onMouseLeave() {
        this.renderer.removeStyle(this.element.nativeElement, "color");
    }
}

Host

Вместо применения декораторов HostListener и HostBinding для реагирования директивы на действия пользователя мы можем определить обработчики событий в декораторе Directive с помощью его свойства host.

import {Directive, ElementRef, Renderer2} from '@angular/core';

@Directive({
    selector: '[bold]',
    host: {
        '(mouseenter)': 'onMouseEnter()',
        '(mouseleave)': 'onMouseLeave()'
    },
})
export class BoldDirective{
    private elementRef: ElementRef;
    private renderer: Renderer2;

    constructor(elementRef: ElementRef, renderer: Renderer2){
        this.elementRef = elementRef;
        this.renderer = renderer;
    }

    public onMouseEnter(){
        this.renderer.setStyle(this.elementRef.nativeElement, "font-size", "40px");
    }

    public onMouseLeave() {
        this.renderer.removeStyle(this.elementRef.nativeElement, "font-size");
    }
}

Structural Directives

Structural directives are responsible for HTML layout. They shape or reshape the DOM's structure, typically by adding, removing, or manipulating elements.

import { 
    Directive, 
    Input, 
    TemplateRef, 
    ViewContainerRef 
} from '@angular/core';

@Directive({
    selector: '[if]'
})
export class IfDirective {
    private templateRef: TemplateRef<any>;
    private viewContainer: ViewContainerRef;

    constructor(templateRef: TemplateRef<any>,
                viewContainer: ViewContainerRef) {
        this.templateRef = templateRef;
        this.viewContainer = viewContainer;
    }

    @Input() 
    public set if(condition: boolean) {
        if (condition) {
            this.viewContainer.createEmbeddedView(this.templateRef);
        } else {
            this.viewContainer.clear();
        }
    }
}
import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule }   from '@angular/forms';
import { AppComponent }   from './app.component';
import { ChildComponent } from "./child.component";
import { BoldDirective } from "./bold.directive";
import { IfDirective } from "./if.directive";

@NgModule({
    imports:      [ BrowserModule, FormsModule ],
    declarations: [
        AppComponent,
        ChildComponent,
        BoldDirective,
        IfDirective
    ],
    bootstrap:    [ AppComponent ]
})
export class AppModule { }
<p *if="condition">With true</p>
<p *if="!condition">With false</p>
export class AppComponent {
    public condition: boolean = true;
}

Let's look at local example (if.directive.ts)

ngIf

import {
    Component
} from '@angular/core';

@Component({
    selector: 'child',
    template: `
        <input type="text" [(ngModel)]="textInput">
        <p *ngIf="textInput === '1'" bold>{{sharedStr}}</p>
    `,
})
export class ChildComponent{
    public sharedStr: string = "shared string";
}
//Only for Angular4!!!!!!
import {
    Component
} from '@angular/core';

@Component({
    selector: 'child',
    template: `
        <input type="text" [(ngModel)]="textInput">
        <p *ngIf="textInput === '1'; else elseBlock">
            {{sharedStr}}
        </p>
        <ng-template #elseBlock>
            Alternate text while primary text is hidden
        </ng-template>
    `,
})
export class ChildComponent{
    public sharedStr: string = "shared string";
}

Директива ngIf позволяет удалить или, наоборот, отобразить элемент при определенном условии.

ngFor

import { Component} from '@angular/core';
 
@Component({
    selector: 'my-app',
    template: `
        <ul>
            <li *ngFor="let item of items">{{item}}</li>
        </ul>`
})
export class AppComponent {
     
    items =[
        "Apple iPhone 7", 
        "Huawei Mate 9", 
        "Samsung Galaxy S7", 
        "Motorola Moto Z"
    ];
}

Директива ngFor позволяет перебрать в шаблоне элементы массива.

В качестве значения директива принимает значение перебора аля-foreach: let item of items. Каждый перебираемый элемент помещается в переменную item, которую можно вывести на страницу.

При переборе элементов доступен текущий индекс элемента через переменную index, которую мы можно использовать. 

<div>
    <p *ngFor="let item of items; 
        let i = index">{{i+1}}.{{item}}</p>
</div>

ngSwitch

С помощью директивы ngSwitch можно встроить в шаблон конструкцию switch..case и в зависимости от ее результата выполнения выводить тот или иной блок

import { Component} from '@angular/core';
 
@Component({
    selector: 'my-app',
    template: `<div [ngSwitch]="count">
                  <template [ngSwitchCase]="1">{{count * 10}}</template>
                  <template [ngSwitchCase]="2">{{count * 100}}</template>
                  <template ngSwitchDefault>{{count * 1000}}</template>
                </div>`
})
export class AppComponent {
    public count: number = 5;
}

Директива ngSwitch в качестве значения принимает некоторое выражение. В данном случае это свойство count. В элемент templateпомещается инструкция ngSwitchCase, которая сравнивает значение выражения из ngSwitch с другим выражением. Если оба выражения равны, то используется данный элемент template. Иначе выполнение переходит к следующим инструкциям ngSwitchCase. Если же ни одна из инструкций ngSwitchCase не была выполнена, то вызывается инструкция ngSwitchDefault.

@Service

Стандартные задачи сервисов:

  • Предоставление данных приложению. Сервис может сам хранить данные в памяти, либо для получения данных может обращаться к какому-нибудь источнику данных, например, к серверу.

  • Сервис может представлять канал взаимодействия между отдельными компонентами приложения

  • Сервис может инкапсулировать бизнес-логику, различные вычислительные задачи, задачи по логгированию, которые лучше выносить из компонентов. Тем самым код компонентов будет сосредоточен непосредственно на работе с представлением. Кроме того, тем самым мы также можем решить проблему повторения кода, если нам потребуется выполнить одну и ту же задачу в разных компонентах и классах

Planets

Jupiter

Earth

Saturn

radius: number
turnaround() { ... }
radius: number
turnaround() { ... }
vortexAccelerate() { ... }
radius: number
turnaround() { ... }
interiorRadius: number

DRY

  • Single source of truth

  • Reusability

  • Maintainability

  • Testability

Example

Earth

Jupiter

Saturn

SERVICE



radius: number
turnaround() { ... }
SERVICE

vortexAccelerate() { ... }
SERVICE

interiorRadius: number
SERVICE

Example

Earth

Jupiter

Saturn

SERVICE



radius: number
turnaround() { ... }
SERVICE

vortexAccelerate() { ... }
SERVICE

interiorRadius: number
SERVICE

How to inject Service into Component?

  • Create Service class
  • Add @Injector() decorator to Service
  • Add Service to providers
  • Inject Service into Component

@Pipes

"|"

Bult in pipes

Custom pipes

Pure / Impure

Async

Data

date: Date = new Date(2017, 7, 9);
// '09/07/2017'

user: Object = {
    name: "John"
}
// '{"name":"john"}'

// Sat Aug 09 2017 00:00:00 GMT+0300 (Belarus Standard Time)

[object Object]

Data

date = formattedDate(date);
// formattedDate is not built-in function, 

user = JSON.stringify(user);

'09/08/2017'

'{"name":"john"}'

Data

<p>{{ date | date: "dd/MM/yyyy"}}</p>

<p>{{ user | json }}</p>

'09/08/2017'

'{"name":"john"}'

Pipe

{{ value | pipe[:param1[:param2...]] }}

Pipe

{{ value | pipe[:param1[:param2...]] }}

single

chaining

{{ value | pipe1[:param1] | pipe2[:param2] }}

Pipe

export interface PipeTransform {
    transform(value: any, ...args: any[]): any;
}

How it works

Pipe

import { UpperCasePipe } from 'angular/common'

@Component...

export class myComponent {
    text: string = 'little pony';
    upperCaseText: string = new UpperCasePipe().transform(this.text);
}

What is transform

Pipe

import { Component } from '@angular/core';

@Component({
  selector: 'hero-birthday',
  template: `<p>The hero's birthday is {{ birthday | date }}</p>`
})

export class HeroBirthdayComponent {
  birthday = new Date(1988, 3, 15); // April 15, 1988
}

@Routing

The browser is a familiar model of application navigation:

  • Enter a URL in the address bar and the browser navigates to a corresponding page.
  • Click links on the page and the browser navigates to a new page.
  • Click the browser's back and forward buttons and the browser navigates backward and forward through the history of pages you've seen.

structure

https://home/category1/page2

https://home/about

benefits

  • Separate different areas of the app

  • Maintain the state in the app

  • Protect areas of the app based on certain rules

  • refresh
  • bookmark
  • share state
  • browser history

base

<head>
    <base href="/">
</head>
<head>
    <script>document.write('<base href="' + document.location + '" />');</script>
</head>

server-side

single page application

/about

server

about.html

#about

JUMP

<a name="about"><h1>About</h1></a>

/about

angular/router

import { RouterModule, Routes } from '@angular/router';
const appRoutes: Routes = [
  { path: 'planetarium', component: PlanetsListComponent },
  { path: 'planet/:id',  component: PlanetDetailComponent },
];

@NgModule({
  imports: [
    RouterModule.forRoot(
      appRoutes,
      { enableTracing: true } // <-- debugging purposes only
    )
    // other imports here
  ],
  ...
})
export class AppModule { }

RouterModule.forRoot()

routerLink

<a routerLink="/planetarium" routerLinkActive="active">Planetarium</a>
<a routerLink="/planets" routerLinkActive="active">Planets</a>
<router-outlet></router-outlet>

redirectTo, pathMatch

 { path: '',   redirectTo: '/planets', pathMatch: 'full' }
{ path: '**', component: PageNotFoundComponent }
 { path: '',   redirectTo: '/planets', pathMatch: 'prefix' }

Feature areas

@NgModule({
  imports: [
    RouterModule.forChild(planetsRoutes)
  ],
  exports: [
    RouterModule
  ]
})
export class PlanetRoutingModule { }

@testing

Что такое unit tests?

  • проверяет наименьшие тестируемые части ПО  - methods/modules/objects
  • делается разработчиками

Польза от unit tests

  • Ошибки находятся на начальном этапе
  • Unit testing помогает отлаживать и менять код
  • Т.к. баги находятся на раннем этапе перед выходом в продакшн, то это уменьшает стоимость таких багов
  • Упрощает процесс дебагинга. Т.к. тест упал в каком-то из кейсов, значит надо дебажить только последнюю часть кода, и мы можем оперативно найти источник пробемы
  • Документация и use cases которые не нужно прописывать отдельно

Польза от unit tests

  • Ошибки находятся на начальном этапе
  • Unit testing помогает отлаживать и менять код
  • Т.к. баги находятся на раннем этапе перед выходом в продакшн, то это уменьшает стоимость таких багов
  • Упрощает процесс дебагинга. Т.к. тест упал в каком-то из кейсов, значит надо дебажить только последнюю часть кода, и мы можем оперативно найти источник пробемы
  • Документация и use cases которые не нужно прописывать отдельно

Принцип FIRST

1) Fast

  • Чем меньше время загрузки тем лучше
  • Никто не будет делать тесты, которые выполняются целый час

2) Isolated

  • Только одна причина падения

3) Repeated

  • Результат всегда одинаков, если не было никаких изменений

5) Timely

  • Писать тесты нужно только тогда, когда они вам действительно нужны
  • Лучше всего делать это как можно раньше

4) Self-verifying

  • Тесты должны подтверждать правильность кода
  • Тесты должны быть "утвердительными"

Покрытие

Should

Should not

  • Единственный компонент
  • Поведение бизнесс логики
  • Сторонние библиотеки
  • DBs и другие storage
  • Внешние ресурсы (в том числе вызовы сервера http)
  • обычный тривиальный код (getter, setter)
  • недетрменированные результаты

Tools, Frameworks

Angular,

Angular 2

  • Karma - раннер тестов
  • Jasmine - синтаксис

Установка

1) Angular CLI

2) Manual setup (karma, karma-jasmine, karma-webpack)

Конфигурация

1) karma init (karma.config.js  - имя файла по умолчанию)

2) Изменение конфигурационного файла с использованием http://karma-runner.github.io/1.0/config/configuration-file.html

Запуск

1) npm test

2) karma start

Запуск

1) npm test

2) karma start

Что нужно, чтобы написать unit test

1) Setup

  • Создать контекст теста (test fixture) включая тестируемый юнит
  • Инициализировать что-то, что необходимо для тестовго юнита для задуманного поведения

2) Excercise

  • Взаимодействовать с тестируемым юнитом
  • Постоянный вызов одного метода

3) Verify

  • Проверить совпадает ли результат на выходе с тем, что мы ожидаем
  • Написать утверждения для проверки состояния юнита или возвращаемого результата
  • Определить поведение которое вы ожидали с использованием mock данных

4) Teardown

  • Подчищаем все то, что может перейти в следующий тест
  • Тесты должны быть изолированы

Doubles - ngMock

  • Dummy - пустой объект, который отправляется в качестве параметра или dependency
  • Fake - упрощенный объект (getter/setter), его не проверяем
  • Stub - проверка состояния, вносит логику в тест. Объект с функциями, которые возвращают значения. Замена dependency. Например getLibraries.
  • Mock - проверка поведения, что что-то было вызвано.
  • Spy - Stub + Mock, что было вызвано, с какими параметрами, что при это вернуло

Jasmine syntax

describe - описание компонента, может иметь древовидную структуру

describe('name', ()=>{})

it - сам наш тест, листья дерева

it('should be', ()=>{})
  • beforeEach - сетап компонента, выполняется перед каждым тестом, делает чтобы код от теста к тесту не дублировался.
  • beforeAll - независимые блоки получают одно значение. Вызывается один раз в начале всех тестов
beforeEach, beforeAll
afterEach, afterAll
  • afterEach - сетап компонента, выполняется после каждого теста, подчищает код.
  • afterAll -  Вызывается один раз в конце всех тестов

expect - что-то должно выполнять наши ожидания

expect(something).toBe(0);
expect(someObject).not.toEqual(otherObject);

expect.not - противополжное expect

spyOn(object, 'methodName').and.returnValue() ||
.and.callThrough() || ...

spuOn - шпионы вешаются на методы, можем пускать вызов и смотреть как он пройдет, какие значния вернет, сколько раз был вызван, с какими параметрами. 

Angular 2 syntax

TestBed.configureTestingModule().overrideComponent().compileComponents()
ComponentFixture = TestBed.createComponent()

TestBed - утилита, которая предоставляет возможность создать модуль, переписать компоненты, скомпилировать.

Service = TestBed.get()

То, с чем мы рабтаем

ComponentFixture.componentInstance
ComponentFixture.debugElement
ComponentFixture.nativeElement
async
inject

BDD - Jasmine

Test

Behavior

assert.equal(6, factorial(3));

assert.lengthOf(bevarages.tea, 3,
 'bevarages has 3 types of tea');
factorial(3).should.equal(6);

expect(factorial(3)).toBe(6);

beverages.should.have.property('tea')
.with.length(3);

expect(beverages.tea.length).toEqual(3);

Tricks

describe

it

xdescribe

fdescribe
xit

fit

Imports

@amgular/core/testing

import { ComponentFixture, TestBed } from '@angular/core/testing'

@amgular/platform-browser

import { By } from '@amgular/platform-browser'

@amgular/core

import { DebugElement } from '@angular/core'

Component with inner template

describe('BannerComponent (inline template)', () => {

  let comp:    BannerComponent;
  let fixture: ComponentFixture<BannerComponent>;
  let de:      DebugElement;
  let el:      HTMLElement;

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [ BannerComponent ], // declare the test component
    });

    fixture = TestBed.createComponent(BannerComponent);

    comp = fixture.componentInstance; // BannerComponent test instance

    // query for the title <h1> by CSS element selector
    de = fixture.debugElement.query(By.css('h1'));
    el = de.nativeElement;
  });
});

Component lifecycle

it('should display original title', () => {
  fixture.detectChanges();
  expect(el.textContent).toContain(comp.title);
});

it('should display a different test title', () => {
  comp.title = 'Test Title';
  fixture.detectChanges();
  expect(el.textContent).toContain('Test Title');
});

Component with external template

import { Component } from '@angular/core';

@Component({
  selector: 'app-banner',
  templateUrl: './banner.component.html',
  styleUrls:  ['./banner.component.css']
})
export class BannerComponent {
  title = 'Test Tour of Heroes';
}
// async beforeEach
beforeEach(async(() => {
  TestBed.configureTestingModule({
    declarations: [ BannerComponent ], // declare the test component
  })
  .compileComponents();  // compile template and css
}));
import { async } from '@angular/core/testing';

Component with dependencies

beforeEach(() => {
  // stub UserService for test purposes
  userServiceStub = {
    isLoggedIn: true,
    user: { name: 'Test User'}
  };

  TestBed.configureTestingModule({
     declarations: [ WelcomeComponent ],
     providers:    [ {provide: UserService, useValue: userServiceStub } ]
  });

  fixture = TestBed.createComponent(WelcomeComponent);
  comp    = fixture.componentInstance;

  // UserService from the root injector
  userService = TestBed.get(UserService);

  //  get the "welcome" element by CSS selector (e.g., by class name)
  de = fixture.debugElement.query(By.css('.welcome'));
  el = de.nativeElement;
});

Component with input

// async beforeEach
beforeEach( async(() => {
  TestBed.configureTestingModule({
    declarations: [ DashboardHeroComponent ],
  })
  .compileComponents(); // compile template and css
}));

// synchronous beforeEach
beforeEach(() => {
  fixture = TestBed.createComponent(DashboardHeroComponent);
  comp    = fixture.componentInstance;
  heroEl  = fixture.debugElement.query(By.css('.hero')); // find hero element

  // pretend that it was wired to something that supplied a hero
  expectedHero = new Hero(42, 'Test Name');
  comp.hero = expectedHero;
  fixture.detectChanges(); // trigger initial data binding
});

Component with output

it('should raise selected event when clicked', () => {
  let selectedHero: Hero;
  comp.selected.subscribe((hero: Hero) => selectedHero = hero);

  heroEl.triggerEventHandler('click', null);
  expect(selectedHero).toBe(expectedHero);
});

Directive

import { Component } from '@angular/core';
@Component({
  template: `
  <h2 highlight="skyblue">About</h2>
  <twain-quote></twain-quote>
  <p>All about this sample</p>`
})
export class AboutComponent { }
beforeEach(() => {
  fixture = TestBed.configureTestingModule({
    declarations: [ AboutComponent, HighlightDirective],
    schemas:      [ NO_ERRORS_SCHEMA ]
  })
  .createComponent(AboutComponent);
  fixture.detectChanges(); // initial binding
});

it('should have skyblue <h2>', () => {
  const de = fixture.debugElement.query(By.css('h2'));
  const bgColor = de.nativeElement.style.backgroundColor;
  expect(bgColor).toBe('skyblue');
});

Service

describe('DependentService without the TestBed', () => {
  let service: DependentService;
 
  it('#getValue should return real value by way of the real FancyService', () => {
    service = new DependentService(new FancyService());
    expect(service.getValue()).toBe('real value');
  });
 
  it('#getValue should return faked value by way of a fakeService', () => {
    service = new DependentService(new FakeFancyService());
    expect(service.getValue()).toBe('faked value');
  });
 
  it('#getValue should return faked value from a fake object', () => {
    const fake =  { getValue: () => 'fake value' };
    service = new DependentService(fake as FancyService);
    expect(service.getValue()).toBe('fake value');
  });
 
  it('#getValue should return stubbed value from a FancyService spy', () => {
    const fancy = new FancyService();
    const stubValue = 'stub value';
    const spy = spyOn(fancy, 'getValue').and.returnValue(stubValue);
    service = new DependentService(fancy);
 
    expect(service.getValue()).toBe(stubValue, 'service returned stub value');
    expect(spy.calls.count()).toBe(1, 'stubbed method was called once');
    expect(spy.calls.mostRecent().returnValue).toBe(stubValue);
  });
});

Pipe

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({name: 'titlecase', pure: false})
/** Transform to Title Case: uppercase the first letter of the words in a string.*/
export class TitleCasePipe implements PipeTransform {
  transform(input: string): string {
    return input.length === 0 ? '' :
      input.replace(/\w\S*/g, (txt => txt[0].toUpperCase() + txt.substr(1).toLowerCase() ));
  }
}
describe('TitleCasePipe', () => {
  // This pipe is a pure, stateless function so no need for BeforeEach
  let pipe = new TitleCasePipe();
 
  it('transforms "abc" to "Abc"', () => {
    expect(pipe.transform('abc')).toBe('Abc');
  });
 
  it('transforms "abc def" to "Abc Def"', () => {
    expect(pipe.transform('abc def')).toBe('Abc Def');
  });
 
  // ... more tests ...
});

Angular 2

By Sergey Shalyapin

Angular 2

Angular 2 full presentation

  • 1,165