Открыть меню    

angular 2, angular 4

Первоисточник: angular.io docs ts latest guide/

Концепция Angular

  • export - 'раскрывает' некоторые компоненты/директивы/фильтры другим модулям приложения. Без этого компоненты/директивы/фильтры определенные в модуле доступны лишь в этом модуле.
  • imports делает экспортированные объявления (exported declarations) других модулей доступными в текущем модуле, например, AutoCompleteModule из primeng
  • declarations - указываем компоненты, директивы и pipes, которые принадлежат модулю. Тем самым делаем эти директивы, включая компоненты и фильтры, из текущего модуля доступными для других директив в текущем модуле. Селекторы директив, компонентов или фильтров сопостовляются только с HTML, если они объявлены или импортированы (declared or imported)
  • providers - делают сервисы и значения доступными для DI. Они добавляются в корневой scope и они вводятся (injected) в другие сервисы или директивы, которые имеют их как зависимости.

stackoverflow.com: В чем разница между declarations, providers и import в NgModule

@angular/core

Возможности import рассмотрим на примере:

import {
    Component,
    Pipe,
    Directive,
    NgModule,
    Input,
    Output,
    ViewEncapsulation,
    EventEmitter,
    PipeTransform,
    OnInit,
    HostListener
} from '@angular/core';

Модули

Angular 2 состоит из модулей. Приложение на angular 2 также является модулем.

Все модули подключаются в массив imports:

 @NgModule({
    imports: [

Само приложение, если оно у вас большое, лучше всего разделять на модули, например, модулями могут быть папки products, user.

     Модуль для роутинга (пример)

//app/app-routing.module.ts
import { NgModule } from '@angular/core';

import { RouterModule } from '@angular/router';
import {WelcomeComponent} from "./home/welcome.component";
import {PageNotFoundComponent} from "./page-not-found.component";

@NgModule({
    imports: [
        //II
        RouterModule.forRoot([{
            path: 'welcome',
            component: WelcomeComponent
        }, {
            path: '',
            redirectTo: 'welcome',
            pathMatch: 'full'
        }, {
            path: '**',
            component: PageNotFoundComponent
        }
        ]),
    ],
    exports: [
        // делаем так, чтобы app.module.ts мог его использовать
        RouterModule
    ]
})
export class AppRoutingModule { }
// подключаем в основном модуле (app/app.module.ts)
import { AppRoutingModule } from './app-routing.module';

@NgModule({
  imports: [
      AppRoutingModule,
  ],

Компоненты

Компоненты отвечают за внешний вид и за взаимодействие с пользователем.

Компоненты представляют основные строительные блоки приложения Angular 2. Каждое приложение Angular имеет как минимум один компонент.

Компоненты представляют из себя часть пользовательского интерфейса.

Идея компонентов состоит в том, чтобы разделить пользовательские интерфейсы на составные части.

При создании компонентов имена тегов в свойстве selector должны подчиняться следующим правилам:

  • слова разделяться дефисом
  • желательно не использовать заглавные буквы
  • обязателен закрывающий тег
  • <custom-comp></custom-comp>

Директивы

Директивы - можно представить как пользовательские атрибуты в HTML, которые позволяют добавлять элементам новый функционал.
Директивы те же компоненты, но без представлений.
Если же у директивы есть шаблон, то она становится компонентом.

     Структурная директива

Структурная директива (на это указывает *), которая меняет DOM - (например, ngFor - добавляет элементы)

  • *ngIf - удаляет или создает часть дерева DOM на основании выражения.
  • *ngFor
<todo-item *ngFor="let todo of todos" 

     Атрибутные директивы

Атрибутные директивы (на это указывает []) предназначены для изменения внешнего вида или поведения элементов (они не создают и не удаляют элементы, например, [ngClass])

<div class="todo-item"  [ngClass]=" { 'completed' : todo.completed } ">
  • [ngStyle]
  • [ngClass]
  • [ngSwitch]
  • [ngSwitchWhen]
  • [ngSwitchDefault]

     ngSwitch

ngSwitch - директива используется для того, чтобы отобразить один элемент (включая дочерние) из набора возможных элементов. При использовании директив ngSwitchCase и ngSwitchDefault обязательно нужно использовать (*). Можно использовать любой тип данных при установке значения в ngSwitchCase

<div [ngSwitch]="choice">
    <div *ngSwitchCase="'1'">
        Выбор 1
    </div>
    <div *ngSwitchCase="'2'">
        Выбор 2
    </div>
    <div *ngSwitchCase="'3'">
        Выбор 3
    </div>
    <div *ngSwitchDefault>
        Выбор по умолчанию
    </div>
</div>

Сделайте свой Выбор <input type="text" [(ngModel)]="choice" />

     Свойство selector

Свойство selector позволяет определить к какому DOM-элементу или компоненту применить директиву. Свойство selector:

// Custom Directives
@Directive({
    // нам нужно придать поведение, например, компоненту с атрибутом [task]
    selector: '[task]'
})
class TaskMyDirective {
    private defaultMyText: string;
    //...

     Пользовательские директивы

Импортируем класс Directive

import {
    Directive,

} from '@angular/core';

@HostListener - с помощью данного декоратора можно подключить обработчики событий, возникающие во вмещающем компоненте.

@HostListener('mouseover')
onMouseOver() {
    //toDO
}

Фильтры (pipes)

Фильтры - так называемые 'помощники', которые форматируют вывод данных в шаблонах в зависимости от наших потребностей.

<h1> {{ minutes }}:{{ seconds | number: '2.0' }} </h1>
  • uppercase
  • lowercase
  • number
  • percent
  • currency
  • slice
  • date
  • json
  • replace
  • i18nPlural - число по объекту в строку
  • i18nSelect - анализирует строку
  • async - наблюдает за асинхронными данными

     Пользовательские фильтры

Импортируем декоратор pipe и интерфейс PipeTransform

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

Реализуем класс:

@Pipe({
    name: 'nameFilter',
    pure: false // если false, то фильтр будет следить за изменением состояния
})

Обязательный метод класса transform имеет два параметра:

  • 1 - входные данные
  • 2 - параметры для настройки фильтра

И возвращает преобразованные данные

@Pipe({
    name: 'componentFormattedTime'
})
class FormattedTimePipe implements PipeTransform {
    transform(totalMinutes: number): string {
        let minutes: number = totalMinutes % 60;
        let hours: number = Math.floor(totalMinutes / 60);
        return `${hours}h:${minutes}m`;
    }
}

В шаблоне:

<p>{{ 90 | componentFormattedTime }}</p>
@NgModule({
    imports: [BrowserModule],
    declarations: [
        //...
        FormattedTimePipe,
        //...
    ],
    bootstrap: [MyComponent],
})

Декоратор

Декоратор - особенность, появившаяся в ES7, и затем добавленная в TypeScript, позволяющая добавлять метаданные в классы.

Все декораторы узнаются по префиксу @

Простейший пример декоратора:

function Greeter(target: Function): void {
  target.prototype.greet = function(): void {
    console.log('Привет!');
  }
}

@Greeter
class Greeting {
  constructor() {}
}

var myGreeting = new Greeting();
myGreeting.greet();   //  'Привет!'

     Декоратор @ViewChild

В шаблоне одного компонента нам нужно получить достпу к другому компоненту и, чтобы мы могли манипулировать подключенным компонентом непосредственно в компоненте 'родителя' можно использовать декоратор @ViewChild - получаем доступ к дочернему компоненту.

дочерний компонент

// child
@Component({
    selector: 'app-block',
    templateUrl: './block.component.html',
    styleUrls: ['./block.component.scss']
})
export class BlockComponent implements OnInit {
    private visible: boolean = false;

    constructor() {}
    ngOnInit() {}

    show() {
        this.visible =  true;
    }
// child шаблон
<div *ngIf="visible">
    visible block
</div>

host компонент

// parent component
export class TestComponent implements OnInit {

    // для получения доступа к дочернему компоненту
    @ViewChild(BlockComponent)
    block: BlockComponent;

    constructor() {}
    ngOnInit() {}

    showBlock() {
        this.block.show();
    }
// parent шаблон
<app-block></app-block>
<button (click) = "showBlock()" >button</button>

     Декоратор @ViewChildren

Декоратор @ViewChildren позволяет получить доступ сразу ко всем компонентам. В примере ниже получаем все дочерние компонеты (реализованы см. выше) в родительском компоненте.

// parent component
export class TestComponent implements OnInit {

    // для получения доступа к дочернему компоненту
    @ViewChild(BlockComponent)
    block: BlockComponent;

    // для получения доступа ко всем компонентам указанного типа
    @ViewChildren(BlockComponent)
    blocks: QueryList<BlockComponent>;

    showAll() {
        this.blocks.forEach(x => x.show());
    }
// parent шаблон
<app-block></app-block>
<app-block></app-block>
<app-block></app-block>
<button (click) = "showAll()" >showAll</button>

Чтобы получить из множества дочерних компонентов какой-то определенный можно задать template reference variable ( #heroInput ).

<app-block></app-block>
<app-block #my></app-block>
    @ViewChild('my')
    blockMySpecific: BlockComponent;

     Декоратор @ViewContent

@ViewChildren и @ViewChild работают лишь с теми компонентами, которые нахлдятся непосредственно в шаблоне. Если же мы получили компонент через ng-content то, чтобы получить к нему доступ нужно воспользоваться декоратором @ViewContent.

Template Reference Variable

Выше мы уже затронули template reference variable. Давайте расмотрим это понятие более подробно.

Template Reference Variable - ссылка на DOM элемент или директиву в шаблоне. К переменной можно обратиться в любой части шаблона, в котором она объявлена.

Есть два способа объявления переменной:

  • префикс #, например #varNameThere
  • префикс ref-, например, ref-varNameThere
a <input #aInput type="text"/> <br />
b <input ref-bInput type="text"/> <br />
<!--aInput - ссылка на элемент input, созданный в начале шаблона -->
<button (click)="calculateSum(aInput.value, bInput.value)">Рассчитать</button>

Result = {{result}}

ng-content

Рассмотрим использование ng-content на примере.

Шаблон компонента потомока:

<div *ngIf="visible">
    <h2>Message:</h2>
    <p>
        <ng-content></ng-content>
    </p>
    <button (click)="hide()">Hide</button>
</div>

Вмещающий компонент родитель:

<message-box>
    This is first message
</message-box>
<message-box>
    Lorem ipsum dolor sit amet....
</message-box>

Типы привязки

Типы привязки могут быть сгруппированы в три категории в зависимости от потока данных: от source-to-view, от view-to-source, и в двунаправленной последовательности: view-to-source-to-view:

Направление данных Синтаксис Тип
Однонаправленный от источника данных к view
{{expression}}
                [target]="expression"
                bind-target="expression"
Interpolation
Property
Attribute
Class
Style
Однонаправленный от view к источнику с данными
(target)="statement"
                on-target="statement"
Event
Двунаправленный
[(target)]="expression"
                bindon-target="expression"
Two-way

Атрибуты

     [] Входное свойство

[] - сообщают феймворку, что атрибут является входным свойством.

Если требуется связать свойство с переменной компонента, то: [my_prop]="{{valueProp}}".

<countdown
    [seconds]="25">
</countdown>
class CountdownComponent {
    @Input() seconds: number;
    //...

Декортатор Input

Декортатор Input указывают, какие свойства вы можете установить на компоненте.

В самом todo-item импортируем декортатор Input (им помечаются входные данные) -

<todo-item *ngFor="let todo of todos" [todo]="todo" 
 { Component, Input } from '@angular/core';

Используем в компоненте:

export class TodoItemComponent {
    //Декоратором @Input помечаются входные данные
    @Input() todo: Todo;

}

При удалении нужно передать данные от потомка родителю ((delete)="delete($event)"), один из способов - это события (можно подписаться на события пользовательских компонентов).

<div class="todo-list" *ngIf="todos.length > 0">
    <todo-item *ngFor="let todo of todos" [todo]="todo" (delete)="delete"></todo-item>
    <!--
        (delete) привязываемся к событию;
        ="delete" - метод у родительскокого компонента,
        который будет вызван по emit delete на todo-item
    -->
</div>

     () Выходное свойство

() - сообщают феймворку, что атрибут является выходным свойством, запускающее подключенный к нему обработчик событий.

Значение можно определить явно - для этого значение следует заключить в одинарных кавычках внутри двойных кавычках.

<--todo-list.component.html-->

<todo-item
            [(size)]="sizeInToDo"
            *ngFor="let todo of todos"
            [todo]="todo"
            (delete)="delete($event)"
            (toggle)="toggle($event)"></todo-item>

     Двунаправленное связывание ( [(...)] )

Часто требуется, чтобы данные свойства и отображались и обновлялись при изменении его пользователем.

Элемент принимает комбинацию из настройки специфического свойства и прослушивает события элемента.

Для этих целей у ангуляра есть специальное двунаправленное связывание: [(x)].

У свойства [(x)] будет соответствующее событие xChange.

Двунаправленное связывание (Two-way binding ( [(...)] ))

//todo-list.components.ts (родитель)

sizeInToDo: number;

ngOnInit() {
    this.sizeInToDo = 0;
}
<--todo-list.component.html-->

<todo-item [(size)]="sizeInToDo"></todo-item>

<div>
    size in todo-list: {{sizeInToDo}}
</div>
//todo-item.components.ts (потомок)
@Input() size: number | string;
@Output() sizeChange = new EventEmitter();

dec() {
    this.resize(-1);
}
inc() {
    this.resize(+1);
}

resize(delta: number) {
    this.size = +this.size + delta;
    this.sizeChange.emit(this.size);
}
<button (click) = "dec()">dec(-)</button>
<button (click) = "inc()">inc(+)</button>

Декоратор Output (EventEmitter)

В дочернем импортируем декоратор Output (им помечаются выходные данные)

Или Output идентифицируют события, которые компонент может инициировать для отправки информации по иерархии родительскому элементу.

Для передачи события в дочернем компоненте нужно определить выходное свойство и связать его с функцие обработчиком в родительском компоненте. Для этого используются декоратор Output и класс EventEmitter


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

Класс EventEmitter предназначен для объектов способных генерировать события. Также эти объекты предоставляют интерфейс для подписки на эти события.

     Пример использования декоратора Output

От ребенка (todo-item.component) родителю (todo-list.component.html)

<!-- todo-item.component.html -->
<div class="todo-item"  [ngClass]=" { 'completed' : todo.completed } ">
    <button class="checkbox icon" (click)="onToggle()">
export class TodoItemComponent {
    @Output() toggle = new EventEmitter();

    onToggle() {
        this.toggle.emit(this.todo);
    }
<!-- todo-list.component.html -->
<div class="todo-list" *ngIf="todos.length --> 0"-->
    <todo-item *ngFor="let todo of todos" [todo]="todo" (toggle)="toggle($event)"--></todo-item-->
export class TodoListComponent implements OnInit {

    toggle(todo: Todo) {
        this.todoService.toggleTodo(todo);
    }

Привязка данных: Шаблонные теги

Шаблонные теги (интерполяция) - один из видов привязки данных

Пример:

<!-- Шаблонные теги (интерполяция) - один из видов привязки данных -->
<i>
    {{ todo.completed ? 'check_box' : 'check_box_outline_blank' }}
</i>

Привязка данных: через свойство DOM-элемента

Пример:

<i class="material-icons" [innerText] = "todo.completed ? 'check_box' : 'check_box_outline_blank'" >
</i>

Привязка события (event binding)

<button class="checkbox icon" (click)="toggle()">
    Hello
</button>

TypeScript

     Интерфейс

Можно так:

let drawPoint = (point: { x: number, y: number }) => {
    //..
}

drawPoint({
    x: 1,
    y: 2
})

Но, как вы видите, это решение актуально для лишь конкретной функции (drawPoint). Решение в использование интерфейсов.

interface Point {
    x: number,
    y: number
}

let drawPoint = (point: Point) => {
    //..
}

drawPoint({
    x: 1,
    y: 2
})

Интерфейс - описание формы объекта

interface ITodo {
    title: string;
    completed: boolean;
}

todo: ITodo

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

Например, интерфейс OnInit:

export class ListArticles implements OnInit {
    //..

Что нужно знать об интерфейсах: интерфейсы используются только во время компиляции TypeScript, и затем удаляются. В конечном JavaScript их не будет.

//Привести пустой объект к типу интерфейса Task
this.Task = <Task>{ };

     Классы

Классы отличаются от интерфейсов тем, что на основе классов мы можем создать объект. То есть в отличие от интерфейсов это не просто описание формы чего-либо.

Интерфейсы могут быть реализованы не только объектами, но и классами. Для этого используется ключевое слово implements.

Имена классов определяются в стиле UpperCamelCase с добавлением суффикса, указывающего на его тип (компонент, директива, фильтр и т.д.)

Отличная статья по поводу классов и интерфейсов в typescript: Classes vs Interfaces от James Henry.

     Необязательные параметры в функциях

Добавим символ ? в конце параметра, сделаем такой параметр необязательным.

function greetMe(name: string, greeting?: string): string {
    return greeting ? greeting : 'Hello' + ', ' + name;
}

Или можно использовать непосредственно в шаблоне:

 <a > {{currentUser?.nickname}} </a>

     Типы в уловых скобках

getAll(): Promise<Task[]> {
    return tasksPromise;
}

Возвращаемый тип данных - Promise, но при этом с помощью угловых скобок <Task[]> мы указали, что именно в себе хранит этот Promise. Когда мы делаем любую асинхронную операцию, которая возвращает Promise, мы понимаем, что в Promise хранится значение, которое появится через какое-то время. С помощью угловых скообок мы сообщаем это значение. В данном случае Promise будет содержать массив задач. <Task[]>

Ниже мы приводим пустой объект к типу интерфейса Task

//Привести пустой объект к типу интерфейса Task
this.Task = <Task>{ };
// определение массива чисел
let list1: number[] = [1, 2, 3];
// определение массива чисел с использования generic типа Array<elemType>
let list2: Array<number> = [1, 2, 3];


// phrase.ts
export class Phrase {
    value: string;
    language: string;
}

import { Phrase } from "./phrase";
// определяем массив Phrase
let arrPhrase: Phrase[] = [
    { value: "Hello World", language: "English" }
]

     assertions (утверждение типа)

assertions бывают двух видов - <> или as

let message; // by default type any
message = 'abc';

// Метод endsWith() определяет, заканчивается ли строка символами другой строки, возвращая, соотвественно, true или false.
// https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith

// принудительно указывает тип для переменной message
let endWithC = (<string>message).endsWith('c');
let alternativeEndWithC = (message as string).endsWith('c');

     Типы

let a: number;
let b: boolean;
let c: string;
let d: any;
let e: number[] = [1,2,3,4,5];
let f: any[] = [1, true, 'a', false];

// enum

const ColorRed = 0;
const ColorGreen = 1;
const ColorBlue = 2;

enum Color { Red, Green, Blue };
let backgroundColor = Color.Red; // 0
// or
enum Color { Red = 0, Green = 1, Blue = 2, Purple = 3 }
console.log(backgroundColor)   // 0
console.log(Color.Purple)      // 3

Формы

Для работы с формами используется модуль:

import { FormsModule } from '@angular/forms'; // импортируем модуль для работы с формами

Благодаря модулю FormsModule мы можем использовать ngModel для привязки к свойству, ngSubmit:

<!-- #todoForm="ngForm" : этим angular поместит в переменную todoForm объект формы  -->
<form class="todo-form" (ngSubmit)="create(); todoForm.reset()" #todoForm="ngForm">
    <input   name="title"
                [(ngModel)]="newTodoTitle"
                type="text"
                placeholder="Что нужно сделать?"
                required >
    <button type="submit" [disabled]="todoForm.form.invalid">
        Добавить
    </button>
</form>
export class AppComponent {
    newTodoTitle: string = '';
    // с использования модуля от angular FormsModule
    // (привязка по ngModel), а также использование (ngSubmit)
    create() {
        let todo: Todo = new Todo(this.newTodoTitle);
        this.todos.push(todo);
        this.newTodoTitle = '';
    }
}

     Директива NgForm

Директива NgForm - отслеживает состояние всех элементов формы.

     Директива NgModel

Директива NgModel создает экземпляр FormControl из модели и связывает его с элементом формы.

В формах оставлена двусторонняя привяка при помощи Директивы NgModel.

[(ngModel)] - указывает на строковое свойство компонента, обеспечит обновление модели в обоих направлениях (иначе говоря, двусторонняя привязка данных).

    {{title}}
    <input name="title"
           [(ngModel)]="title"
           type="text"
           required />

     Валидация и NgModel

NgModel

<input [(ngModel)]="name" #ctrl="ngModel" required>

<p>Value: {{ name }}</p>
<p>Valid: {{ ctrl.valid }}</p>

<p *ngIf = "ctrl.valid" > Valid ok  </p>

     Валидация при помощи valueChanges и statusChanges

Более реактивную валидацию можно реализовать при помощи valueChanges и statusChanges.

Они оба возвращают Observable:

//loginForm: ControlGroup
const email = this.loginForm.controls['email'];
email.valueChanges.subscribe(value => {
    this.emailValid = (email.dirty && value.indexOf('@') < 0);
});

valueChanges

     Объект FormControl

FormControl

FormControl один из трех фундоментальных строительных блоков форма Angular, помимо FormGroup и FormArray.

Чтобы сообщить Angular о том, что input связан с name FormControl в классе (reactive-forms#create-the-template), вам потребуется в шаблоне воспользоваться директивой formControl, в angular 2 - ngControl.

export class HeroDetailComponent1 {
    name = new FormControl();
}
<input class="form-control" [formControl]="name">
  • Первый аргумент - значение
  • Второй аргумент - функция-валидатор
const ctrl = new FormControl('', Validators.required);
console.log(ctrl.value);     // ''
console.log(ctrl.status);   // 'INVALID'

     FormGroup

FormGroup

     FormBuilder

Класс FormBuilder обеспечиват большую гибкость при создании сложных форм внутри контроллера компонента.

FormBuilder

<form [formGroup]="heroForm" novalidate>

Как видите ниже FormGroupDirectiver связывает существующую FormGroup с DOM элементом (в Angular 2 использовалась ngForModel).

import { Component }  from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';

import { states } from './data-model';

export class HeroDetailComponent4 {
  heroForm: FormGroup;
  states = states;

  constructor(private fb: FormBuilder) {
    this.createForm();
  }

  createForm() {
    this.heroForm = this.fb.group({
      name: ['', Validators.required ],
      street: '',
      city: '',
      state: '',
      zip: '',
      power: '',
      sidekick: ''
    });
  }
}

Пользовательская валидация

     Императивный или декларативный подход

Формы в angular 2 можно создать, используя несколько подходов, например, императивный или декларативный.

Есть принципиальная разница этих двух подходов.

Декларативная программа определяет ЧТО должно быть осуществлено, в то время как императивная предписывает, КАК это будет осуществляться. Императвная программа использует язык команд для непосредственного управления ходом программы, а декларативная программа использует язык ОПИСАНИЙ для того, чтобы исполнитель САМ по этим описаниям создал алгоритм для достижения поставленной цели.

Например язык HTML является чисто декларативным языком, так как чисто описывает то, что потом осуществит браузер. При этом нам совершенно не интересно КАК он это осуществит. В императивных языках мы сами командуем тем КАК всё это осуществить.

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

Декомпозиция

Процесс разделения проекта на части или компоненты называется декомпозицией.

Взаимодействие компонентов

<todo-item *ngFor="let todo of todos" [todo]="todo"></todo-item>

Свойство todo будет доступно в компоненте todo-item

Класс EventEmitter

Изпользуется директивами и компонентами для испускания пользовательских событий.

Например, вы можете в сервисе аутенфикации определить общедоступное свойство EventEmmiter, которое будет использоваться для отправки уведомлений другим компонентам и классам о входе и выходе пользователя из системы. Далее эти классы и компоненты могут подписаться на эти уведомления и соответствующим образом могли бы на них реагировать.

//authentication.service.ts
    userIslogin: EventEmitter<boolean>;
    constructor() {
        this.userIslogin = new EventEmitter();
    }

    login({ username, password }): Promise<boolean> {
        return new Promise(resolve => {
            let validCredentials: boolean = true;

            // toDo check User credentials

            // испускаем событие
            this.userIsloggedIn.emit(validCredentials);
            resolve(validCredentials);
        });
    }

Далее (не забудьте внедрить сервис аутенфикации в нужный компонент) мы уже в компоненте подписываемся (subscribe) на уведомления о входе и выходе пользователя из системы.

userIslogin: boolean;

    constructor(private authenticationService: AuthenticationService) {

        authenticationService.userIslogin.subscribe(userIslogin => {
            this.userIslogin = userIslogin;
        });
    }

Жизненный цикл компонента

Для решения задач связанных с жизненным циклом компонента angular предоставляет несколько методов: life cycle hooks (крюки жизненного цикла).

Например, ngOnInit (потребуется для сервиса) , интерфейс который называется OnInit и находится в @angular/core

     ngOnChanges

// { [propertyName: string]: SimpleChange } - объект с строковыми ключами и значением типа SimpleChange
ngOnChanges(changes: { [propertyName: string]: SimpleChange }) {
    let msgArray: string[] = []
    console.log('changes: ', changes);
    for (let propName in changes) {
        msgArray.push(`property = ${propName}, current value = ${changes[propName].currentValue}, previous value = ${changes[propName].previousValue}`)
    }
    console.log(msgArray.join("; "));
}

// поменяли свойство name компонента:
/*
changes:  Object { name: Object }  sample3.component.ts:18:8
property = name, current value = value 1510097941159, previous value = undefined
*/

Сервисы

Сервисы отвечают за получение, предоставление и работу с данными.

Для того чтобы в нашем сервисе иметь возможность использовать другие сервисы (например, Http) необходимо добавить аннотацию с помощью декоратора injectable.

Проблема: логика по работе с задачами разбросана по компонентам:
(за добавление отвечает компонент app,
удаление - todo-list,
выполнение - todo-item).

Одним из вариантов разделения ответственности между различными частями приложения являются сервисы.

Традиционно в сервисы помещается логика, то есть непосредственно работа с данными приложения.

В нашем случае сервис (todo.service.js) будет отвечать за предоставление задач, их добавление и удаление.

Сервисы в angular 2 это простые классы.

Важно: клиенты, которые используют сервис, не знают и не должны знать как сервис получает данные.

     Сервисы: иерархия

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

@Component({
    selector: "component",
    templateUrl: "component.html",
    providers: [CounterService] // данный компонент использует свой экземпляр сервиса
})
export class Component {
    private counter: number = 0;
    constructor(private counterService: CounterService) { }

    //..

Иначе компонет будет использовать экземпляр сервсиа CounterService, который создан для текущего модуля:

// component.ts
// Данный компонент использует экземпляр сервсиа CounterService, который создан для текущего модуля.
@Component({
    selector: "component",
    templateUrl: "component.html"
})
export class Component {
    private counter: number = 0;
    constructor(private counterService: CounterService) { }
    //..


// main.module.ts
@NgModule({
    imports: [CommonModule],
    declarations: [Component],
    providers: [CounterService]
})
export class ServiceHierarchyModule { }

     Регистрация провайдера с использованием Provider Object Literal

@Component({ selector: "sample1", templateUrl: "sample1.component.html", providers: [{ provide: Logger, useClass: Logger }] // [{ provide: Logger, useClass: Logger }] - регистрация провайдера с использованием Provider Object Literal // первый параметр - token // второй параметр - provider definition object // альтернативный класс провайдера //providers: [{ provide: Logger, useClass: Logger2 }] // убрать комментарий }) export class Sample1Component { private message: string; constructor(private logger: Logger) { } logMessage() { this.logger.log(this.message); } }

     Внедрение зависимостей (dependencies injection)

Сервис необходимо внедрить как зависимость, чтобы разные компоненты могли работать с одним и тем же сервисом одновременно.

В angular 2 имеется встроенный механизм внедрения зависимостей.

Внедрить сервис в компонент:

1. Компоненту добавить constructor и указать параметр с типом сервиса

2. Зарегистрировать сервис в приложении (файл app.module.ts):

//app.module.ts
import { TodoService } from './shared/todo.service.ts';

@NgModule({
    // ...
    // в свойстве providers мы регистрируем сервисы
    providers: [TodoService],

    // ...

})

     Пример сервиса

//todo.service.ts
import { todos } from './data';
import { Todo } from './todo';

export class TodoService {
    todos: Todo[] = todos;

    // возвращает задачи
    getTodos(): Todo[] {
        return this.todos;
    }

    createTodo(title: string) {
        let todo = new Todo(title);
        this.todos.push(todo);
    }

    deleteTodo(todo: Todo) {
        let index = this.todos.indexOf(todo);

        if (index > -1 ) {
            this.todos.splice(index, 1);
        }
    }

    toggleTodo(todo: Todo) {
        todo.completed = !todo.completed;
    }

}
<--todo-list.component.html-->

<div class="todo-list" *ngIf="todos.length > 0">
        <todo-item
            *ngFor="let todo of todos"
            [todo]="todo"
            (delete)="delete($event)"
            (toggle)="toggle($event)"></todo-item>
</div>
//todo-list.components.ts
// с использованием сервиса
import { Component, Input, OnInit } from '@angular/core';
import { Todo } from '../shared/todo';
//импортируем сервис
import { TodoService } from '../shared/todo.service';

@Component({
    moduleId: module.id,
    selector: 'todo-list',
    templateUrl: 'todo-list.component.html',
    styleUrls: ['todo-list.component.css']
})

export class TodoListComponent implements OnInit {
    todos: Todo[];
    todoService: TodoService;

    // конструктор предназначен для инициализации свойств
    // (если конструктор сработал это еще не значит, что компонент создан и готов к работе)
    constructor(todoService: TodoService) {
        this.todoService = todoService;
        this.todos = [];
    }

    // вызовется в момент инициализации компонента
    ngOnInit() {
        this.todos = this.todoService.getTodos();
    }

    delete(todo: Todo) {
        // от компонента todo-item мы получим задачу, которую дальше отправим в сервис
        this.todoService.deleteTodo(todo);
    }

    // выполнение задачи и далее в сервис
    toggle(todo: Todo) {
        this.todoService.toggleTodo(todo);
    }
}

     Внедрение зависимостей II

Внедрение зависимостей полагается на список поставщиков (providers) и внедрение через конструктор. Каждый раз когда поставщик находит зависимость, он создает и возвращает одиночный экземпляр этого типа. Основа - фабрика.

Зависимость ищется снизу вверх (от дочернего к родит-му компоненту).

Зависимость можно зарегестрировать на уровне компонета или всего модуля:

@NgModule({
    imports: [BrowserModule],
    providers: [TaskService],

Далее добавляем сервис через конструктор:

export class TodoFormComponent {

//добавим конструктор для того чтобы получить service
constructor(private taskService: TaskService) {

}

     Внедрение зависимостей внутрь отдельного компонента, на примере Http

Посредством свойстваproviders:

@Component({
    providers: [Http],
    selector: '[data-hello]'
})
class MyClass {
data: string;
    consructor(http: Http){
        http.het('googgle.com')
        .map((res: Response) => res.text())
        .subscribe((data: string) => this.data = data)
    }
}

     Декортатор @Injectable

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

@Injectable()
export default class MyService {
    constructor(myService: MyService) {

    }
    //...

Декортатор @Injectable делает служебные классы видимыми для механизма внедрения зависимостей.

HTTP

Общение с сервером происходит в сервисе посредством HTTP. HTTP - это отдельный модуль, который не является частью angular. Все HTTP-запросы, выполняемые классом HTTP, возвращают поток экземпляров класса Response.

     Как использовать

1. Необходимо зарегестрировать http-модуль в основном модуле приложения.

//app.module.ts
import { HttpModule } from '@angular/http';

//...

@NgModule({
    imports: [BrowserModule, FormsModule, HttpModule], // что модуль импортирует
//...

2. Далее в сервисе мы можем импортировать объект Http

//todo.service.ts
    import { Http } from '@angular/http';

3. Экземпляр сервиса мы получим через конструктор.


// инициализируем экземпляр Http
constructor(private http: Http) {}
// таким образом экземпляр сервиса Http будет помещен в свойство класса под названием http

Для того чтобы в нашем сервисе (todo.service.ts) иметь возможность использовать другие сервисы, необходимо добавить аннтотация с помощью декоратора injectable

// todo.service.ts
import { Injectable } from '@angular/core';
import { Http, Headers, RequestOptions } from '@angular/http';

@Injectable()
export class TodoService {
    //..

Благодаря модулю angular-in-memory-web-api можно создать фейковый сервер.

npm i angular-in-memory-web-api --save

Для загрузки модулей мы используем system.js, поэтому в нем (systemjs.config.js) необходимо указать откуда брать модуль angular-in-memory-web-api.

Далее нужно создать класс, который будет являться БД для фейкового сервера.

/shared/data.service.ts

Через сервис http мы будем на сервер отправлять запросы (мы будем работать с restfull api):

  • post - для создания задач
  • delete - для удаления
  • put - для сохранения измененных задач

Для отправки данных на сервер нам нужно будет указать заголовок content-type, для того чтобы сервер знал, что мы ему отправляем JSON.

Для удобства создания заголовком мы импортируем два класса Headers и RequestOptions из Http

//todo.service.ts
import { Http, Headers, RequestOptions } from '@angular/http';

     Переходим к запросам

import {RequestOptions, Request, RequestMethod} from '@angular/http';
var options = new RequestOptions({
  method: RequestMethod.Post,
  url: 'https://google.com'
});
var req = new Request(options);
var myHttpRequest = Observable<Response> = http.request(req);

// сокращенная запись:
var myHttpRequest = Observable<Response> = http.post('https://google.com');

//методы класса Http возвращают наблюдаемый поток экземпляров класса Response

myHttpRequest.map(response: Response => response.json())
    .subscribe(data => console.log(data))

     Response

Http возвращают наблюдаемый поток экземпляров класса Response.

response.json():

Парсим JSON: Данные в JSON формате. Приложение должно распарсить эти данные в JavaScript объект, вызвав response.json().

https://angular.io/docs/ts/latest/guide/server-communication.html#!#parse-to-json

     Ошибки

Для обработок ошибок в angular есть специальный оператор - catch.

myHttpRequest.map(response: Response => response.json())
    .subscribe(data => console.log(data))
    .catch(error: Response => console.error(error));

Локальные ссылки в шаблонах

Идентификаторы с предшествующим символом решетки (#) - это такие локальные имена, которые используются для ссылок на компоненты в представлениях, чтобы в дальнейшем получить к ним доступ.

  <count
    [seconds]="125"
    #counter>
  </count>

{{ counter.seconds }}

Кроме того через локальную ссылку можно ссылаться на определенный элемент, например, абзац:

<p #tooltip >Hello?</p>

Стили компонента

Есть несколько способов определить стили внутри компонента:

     1. Свойство styles

Свойство styles декоратора @Component

@Component({
  styles: [`
    h1 {
        color: #900
    }
    p {
        color: #000
    }
  `],

     2. Свойство styleUrls

@Component({
        styleUrls: ['path-to-style.css']

     3. Внутрення таблица стилей

@Component({
  template: '
    <style>    h1 {
        color: #900
    }</style>
    <h1>Hello, world!</h1>',

Стили компонента (:host, :host-context, /deep/)

С помощью псевдо класса :host можно изменить стили элемента, в котором находится компонент (другими словами мы меняем стили селектора, который определили в компоненте).

:host {
    border: 1px solid #000;
}

:host-context - проверка родительских элементов относительно компонента на соответствие селектору указанному в параметрах (то есть, если у какого-либо элемента родителя компонента будет класс theme-yellow, то правило ниже сработает).

:host-context(.theme-yellow) h2 {
    color: yellow;
}

Как использовать /deep/: все параграфы текущего компонента и дочерних Shadow DOM получат следующие стили

:host /deep/ p {
    text-decoration: underline;
}

Соглашение об именовании в angular 2

Имя модуля требуется указывать в верблюжьей нотации, а также суффикс типа (например, Pipe): NumberOnlyPipe

Маршрутизация

Отметьте: Использование массива для описания маршрута приложения подходит лишь в тестовых целях или для небольших проектов.
Для более сложного приложения маршрутизацию лучше всего выносить в отдельные модули.

     Порядок настройки роутинга

//app.module.ts
//I
import { RouterModule } from '@angular/router'

@NgModule({
  imports: [
      //II
    RouterModule.forRoot([{
            path: 'welcome',
            component: WelcomeComponent
        }, {
            path: '',
            redirectTo: 'welcome',
            pathMatch: 'full'
        }, {
            path: '**',
            component: PageNotFoundComponent
        }
    ]),

/// III (index.html)

<router-outlet></router-outlet>

     Вторичные роуты

Допустим у нас есть app/products/product.module.ts, которые содержит общий модуль для работы с продуктами.

//app.module.ts
@NgModule({
  imports: [
    ProductModule,


// product.module.ts
@NgModule({
  imports: [
    SharedModule,
      RouterModule.forChild([
          {
            // переход спокойно сработает с ./app/app.component.html
            /*
            Это происходит потому, что angular merge данный роутинг с основным роутингом,
            причем второстепенные модули он merge вверх, поэттому ** из основного модуля не сработают
            */
            path: 'products',
            component: ProductListComponent
          }
      ])
  ],

Routing
DeborahK/Angular-Routing

<base href='/'> - сообщает браузеру путь, по которому он должен идти, чтобы загрузить внешние стили, катринки и т.д.

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

import { ROUTER_PROVIDERS, RouteConfig, ROUTER_DIRECTIVES } from '@angular/router-deprecated';

@Component({
  selector: 'my-app',
  directives: [ROUTER_DIRECTIVES],
  providers: [ROUTER_PROVIDERS],

Документация с актуальными мудулями: https://angular.io/docs/ts/latest/guide/router.html

Опции маршрута в устаревшей версии прописываются в декораторе:

@RouteConfig([
    {   path: '',
        name: 'Home',
        redirectTo: ['TasksComponent']
    },
    //....

Актуально:

 //app.module.ts
import { RouterModule, Routes } from '@angular/router';

// определение маршрутов
const routes: Routes = [
    // pathMatch:'full' указывает, что запрошенный адрес должен полностью соответствовать маршруту
    { path: '', redirectTo: '/dashboard', pathMatch: 'full' },
    { path: 'dashboard',  component: DashboardComponent },
    { path: 'detail/:id', component: HeroDetailComponent },
    { path: 'heroes',     component: HeroesComponent }
];

@NgModule({
    //Чтобы применить маршруты:
    imports: [ RouterModule.forRoot(routes) ],
    exports: [ RouterModule ]
})
export class AppRoutingModule {}

     Директивы RouterOutlet, RouterLink и routerLinkActive

RouterOutlet - директива (<router-outlet>) станет заполнителем, где роутер отобразит view (при этом все предыдущие компоненты будут удалены).

Для определения адресов ссылок применяется директива routerLink. Для стилизации активных ссылок применяется специальная директива routerLinkActive. Активная ссылка с директивой [routerLink] получает класс router-link-active.

Значение {exact:true} указывает на то, что для установки активной ссылки будет применяться полное соответствие:

<a routerLink=""
    routerLinkActive="active"
    [routerLinkActiveOptions]="{exact:true}">
        Главная
</a>
<a routerLink="/about"
    routerLinkActive="active">
        О сайте
</a>

     Принудительная навигация в компоненте

Как видите, методу navigate передается строка, содержащая полный путь.

gotoDetail(): void {
    this.router.navigate(['/detail', this.selectedHero.id]);

    // { path: 'detail/:id', component: HeroDetailComponent },

}

2 пример:

app/user/login.component.ts

import { Router } from '@angular/router';

export class LoginComponent {

    constructor(private router: Router ) { }

    login(loginForm: NgForm) {
            // здесь welcome подменит только основной сегмент (/welcome) из www.test.com/welcome(second:value)
            this.router.navigate(['/welcome']);

            // здесь welcome подменит все (/welcome(second:value)) из www.test.com/welcome(second:value)
            this.router.navigateByUrl('/welcome');

     Динамические параметры

<button (click)="gotoDetail(i)">View Details</button>
gotoDetail(index: number): void {
//Переходим к компоненту Timer с параметром id
    this.router.navigate(['Timer', { id: index }]);
}

Или

<a *ngFor="let product of products"
  [routerLink]="['/product-details', product.id]">
  {{ product.name }}
</a>

     Настраиваем и получаем параметры

Сейчас актуально: Получение параметров routeparams посредством сервиса: ActivatedRoute.
Deprecated: RouteParams.

ActivatedRoute - содержит информацию о маршруте связанную с компонентом, который загружен в outlet

// определение маршрутов
const appRoutes: Routes =[
    { path: 'item/:id', component: MyComponent},
    {
        path: 'products/:id/edit',
        component: ProductEditComponent
    }

Mы сможем обратиться к компоненту с запросом типа /item/7, и число 7 будет представлять параметр id.

<a [routerLink]="['item', '7']">item 7</a>

<a class="btn btn-primary" [routerLink] = "['/products', product.id, '/edit']">
    Edit
</a>
import { Component, OnDestroy} from '@angular/core';
import { ActivatedRoute} from '@angular/router';
import {Subscription} from 'rxjs/Subscription';

export class MyComponent implements OnDestroy {
    constructor(private activateRoute: ActivatedRoute){
        this.subscription = activateRoute.params.subscribe(params=>this.id=params['id']);
    }
    ngOnDestroy(){
        this.subscription.unsubscribe();
    }
}

Метод subscribe() позволяет установить подписку на изменение параметра маршрута.

Поэтому при переходе на компонент с разных ссылок (с разными параметрами) всегда будет актуальное значение id.

Про маршрутизацию можно посмотреть также на русскоязычном сайте metanit Параметры строки запроса


     Получаем параметры текущего route

С использованием snapshot:

Однако учтите, что snapshot не следит за изменениями при route.

import { Component, OnInit} from '@angular/core';
import { ActivatedRoute } from '@angular/router';

export class ProductDetailComponent implements OnInit {
    constructor(private productService: ProductService,
                private route: ActivatedRoute) {
        // плохая практика вызывать в конструкторе "тяжелые" операции (в том числе и связанные с route)
        // поэтому лучше его использовать в ngOnInit()
    }

    ngOnInit(): void {
        // первый вариант вытащить параметры от роутинга
        // snapshot - параметры route на момент инициализации
        let id = +this.route.snapshot.params['id'];
        this.getProduct(id);
    }

    getProduct(id: number) {
        this.productService.getProduct(id).subscribe(
            product => this.product = product,
            error => this.errorMessage = <any>error);
    }
}

Подписываемся на изменения параметров route при помощи route.params.subscribe:

В 4-м ангуляре route.params заменена на route.paramMap

    ngOnInit(): void {
    // route.params возвращает Observable, а значит на него можно подписаться
    // и мы можем следить за изменениям наших параметров
    // и перерисовывать страницу
    this.route.params.subscribe(
        params => {
            let id = +params['id'];
            this.getProduct(id);
        }
    );
}

     Дополнительные параметры

<a
   [routerLink] = "['/products', product.id, 'edit', { name: 'John', city: 'Moscow' }]">
    Edit
</a>

В браузере: http://localhost:3000/products/1/edit;name=John;city=Moscow

Получить в компоненте можно точно также как и для основных параметров (см. пример выше).

     Строка запроса

http://localhost:3000/item?name=Vasya&year=200 часть name=Vasya&year=200 является строкой запроса.

constructor(private route: ActivatedRoute){

Используем route.queryParams

Пример

Передаем:

<a [routerLink] = "['/products', product.id]"
    [queryParams] = "{filterBy: listFilter, showImage: showImage}"
>
{{product.productName}}
</a>

В браузере: http://localhost:3000/products/1?filterBy=leaf&showImage=false

Сохранили параметры, пришедшие на странцу выше и отдали обратно:

<a class="btn btn-default" [routerLink] = "['/products']"
    [preserveQueryParams] = "true"
    >
    Back
</a>

В 4-м ангуляре preserveQueryParams заменена на queryParamsHandling = "preserve"

Читаем данные в компоненте:

ngOnInit(): void {

    this.listFilter = this.route.snapshot.queryParams["filterBy"] || '';
    this.showImage = this.route.snapshot.queryParams["showImage"] === 'true';

     Route Resolvers

Изначально при переходе с одной страницы на другую может возникнуть белая страница.

Это происходит из-за того, что сервис получает, например, информацию о продукте в ngOnInit асинхронно, то есть для этого требуется время, а компонент уже загружен:

    ngOnInit(): void {
    this.route.params.subscribe(
        params => {
            let id = +params['id'];
            this.getProduct(id);
        }
    );
}

Чтобы этого избежать мы можем загружать данные до того как перейдем на компонент при помощи Route Resolvers. Route Resolvers implements как отдельный сервис.

//app/products/product-resolver.service.ts
import { Injectable } from "@angular/core";
import { IProduct } from "./product";
import {Resolve, ActivatedRouteSnapshot, RouterStateSnapshot} from "@angular/router";
import {Observable} from "rxjs/Rx";
import {ProductService} from "./product.service";

@Injectable()
// возвращаться будет IProduct
export class ProductResolver implements Resolve<IProduct> {
    constructor(private productService: ProductService){ }
    // route - текущее состояние активного route
    // state - текущее состояние всего route
    resolve(route: ActivatedRouteSnapshot,
            state: RouterStateSnapshot): Observable<IProduct> {
        let id = +route.params['id'];

        let product = this.productService.getProduct(id);
        return product;
    }
}

Добавляем вышеописанный сервис в роутинг (то есть здесь мы подгружаем продукт до перехода на route):

//app/products/product.module.ts
@NgModule({
  imports: [
    RouterModule.forChild([{
        {
            path: 'products/:id',
            component: ProductDetailComponent,
            // key - имя того что ресолвить (product), ProductResolver - resolver
            resolve: {product: ProductResolver}
        },
    ],
  providers: [
    ProductResolver,
    ProductService
  ]

Модифицируем компонент для работы с resolver:

//products/product-detail.component.ts

export class ProductDetailComponent implements OnInit {
    pageTitle: string = 'Product Detail';
    product: IProduct;
    errorMessage: string;

    constructor(private route: ActivatedRoute) {}

    ngOnInit(): void {
        // продукт загруженные resolver
        this.product = this.route.snapshot.data['product'];
    }

}

Подписываемся на изменения (нужно если с одного компонента переходим на него же, но с другими данными):

/product-edit.component.ts
    ngOnInit(): void {
        this.route.data.subscribe(
            data => {
                let product = data['product'];
                this.onProductRetrieved(product);
            }
        );

    }

    onProductRetrieved(product: IProduct): void {
        this.product = product;
    }

Также удобно в resolver обрабатывать ошибки и, например, редиректить пользователя на нужную страницу.

     ... (многоточие), маршрут во вложенном компоненте

... (многоточие) в конце маршрута, указывает маршрутизатору Router, что он должен смотреть определение маршрута во вложенном компоненте.

Получаем параметры в компоненте:

// программный переход в
this.router.navigate(['TimerComponent', 'TaskTimer', { id: index }]);
import { RouteParams, CanReuse, OnReuse } from '@angular/router-deprecated';

constructor(
    private routeParams: RouteParams) {
}

ngOnInit(): void {
    let taskIndex = parseInt(this.routeParams.get('id'));

     интерфейс CanActivate

Интерфейс CanActivate расширяет класс для решения задачи по активации маршрута.

Метод canActivate - возвращает логическое значение, указывающее на необходимость активации компонента. Удобно для проверки паролем и т.д.

// auth-guard-admin.service.ts
/*
    Guard - механизм для выполнения проверок перед активацией и деактивацией маршрута

    CanActivate - Определяет возможность активации маршрута
    CanActivateChild - Определяет возможность активации дочерних маршрутов текущего маршрута
    CanDeactivate - Определяет можно ли уйти с текущего маршрута
    CanLoad - Определяет может ли модуль загрузиться с использованием lazy loading

    Установка объектов Guard происходит при настройке маршрутизации.
*/

@Injectable()
export class AuthGuardAdmin implements CanActivate {
    // Observable<boolean>|Promise<boolean>|boolean - возможные результаты работы метода
    // Если return true, то маршрут будет активирован, иначе - нет
  constructor(public auth: AuthService, private router: Router) {}

  canActivate() {
    return this.auth.isAdmin;
  }

}
// routing.module.ts
const routes: Routes = [
    //...
    { path: 'delete-article', component: DeleteArticleComponent, canActivate: [AuthGuardAdmin] },
    //...

И не забудьте добавить AuthGuardLogin в providers:

// app.module.ts
//...
providers: [
    AuthGuardLogin
//...

     Интерфейс onActivate

     Использованы материалы в том числе:
codedojo.ru

Использование относительного пути


<button (click)="goToPhrasesList()">Go Back</button>
goToPhrasesList() {
    let pId = this.phrase ? this.phrase.id : null;
    // использование относительного пути при перенаправлении пользователя.
    // ../ подняться на уровень выше
    this.router.navigate(["../", { id: pId, p1: "test", p2: 123 }], { relativeTo: this.activatedRoute });
}

// http://localhost:3000/phrases;id=3;p1=test;p2=123/3
// на
// http://localhost:3000/phrases;id=3;p1=test;2=123

Шаблон Observable

Если уже совсем простым языком:

Экземпляр наблюдаемого объекта (observable) имеет подписку (subscribe) с определенным функционалом; наблюдатель (observer) при помощи, например, метода next говорит об изменении состояния наблюдаемого объекта, благодаря чему вызывается функционал из подписки (subscribe) наблюдаемого объекта. Метод next наблюдателя (observer) может передавать параметры, которые затем принимает подписка (subscribe) наблюдаемого объекта (observable).

Observable (наблюдаемый объект) - источник асинхронного события; Observable сообщает об изменении своего состояния объекту - Observer (наблюдатель или подписчик).

В независимости от того переданы данные или нет объект Observable может быть оставлен в любое время(в отличие от тех же Promise).

RxJS - библиотека, необходима для создания Observable-объектов.

В отличие от Promise (где сработает только 1 раз) нижеприведенный код будет работать как задумано (https://jsfiddle.net/3cz80mjn/):


//  в этом примере мы создали наблюдаемый объект и подписались на его события
// observable это и есть stream
var observable = Rx.Observable.create(observer => {

    observer.next('Асинхронная операция 0');

    setInterval(() => {
        observer.next('Асинхронная операция');
    }, 1000);

});

observable.subscribe(response => {
    console.log('response: ', response);
})

Observable производят непрерывный поток событий (тоже самое что и коллекция объектов), на которые можно подписать абонентов. Подписчики получают уведомления об этих событиях и реагирует на них. Прежде чем поток достигнет подписанных на него наблюдателей его можно предварительно обработать: при помощи методов, например, map(), filter().

Реактивное программирование - подписка на асинхронные события и преобразование потоков наблюдаемых событий.

Observable с использованием map, filter b subscribe https://jsfiddle.net/dnzl/g2juyk7d/5/

     Метод map

Метод map в реактивном программировании аналогичен функциональному методу map:

В методу map результат выполнения callback-функции добавляется в новый массив, который возвращается после последней итерации. Другими словами, результатом метода map всегда является новый массив с результатами выполнения функции callback на исходном массиве.

var nums = [11, 21, 31, 41];
var results = nums.map(function(num, index, arr) {
    return num * 2;
});

// Исходный массив nums не изменяется
console.log(nums); // [11, 21, 31, 41]
// результат выполнения map, записанный в переменную
console.log(results); // [ 22, 42, 62, 82 ]
var numbers = [1, 4, 9];
var roots = numbers.map(Math.sqrt);

// теперь roots равен [1, 2, 3], а numbers всё ещё равен [1, 4, 9]
console.log('roots: ', roots); // [ 1, 2, 3 ]
https://jsfiddle.net/y08234mv/1/
var objs = [
    {which: 1},
    {which: 0},
    {data: 0}
];
var objsWithWhich = objs.map(val => val.which);

console.log('objsWithWhich: ', objsWithWhich); // [ 1, 0, undefined ]

В отличие от forEach при использовании map вам становится доступен chaining.

http://jsraccoon.ru/fn-array-methods

     Метод filter

Метод filter служит для фильтрации массива по условию, заданным в callback функции. В результате создаётся новый массив, куда добавляются все элементы прошедшие провеку колбэком.

function isBigEnough(value) {
  return value >= 10;
}
var filtered = [1, 5, 8, 13, 454].filter(isBigEnough);

console.log('filtered: ', filtered); // [ 13, 454 ]

     Строгая типизация

Если вы взгляните на исходники Angular, то увидите, что Http модуль содежит метод request (https://github.com/angular/angular/blob/2.4.1/modules/%40angular/http/src/http.ts#L111).

Все другие методы обернуты этим request'ом. Также вы можете увидеть, что request возвращает Observable c generic'ом класса Response. Response класс является частью Http модуля.

import { HttpModule, Http, Response } from '@angular/http';
...
theDataSource: Observable<Response>;

Если вам не требуется строгая типизация, то вы можете передать any как параметр generic.

theDataSource: Observable<any>;

     pipe async

Вывести Observable на странице можно при помощи фильтров.

<pre>{{latestNoteBooks$ | async | json}}</pre>

RxJs и Observable

Observable

const observer = {
    next: function nextCallback(data) {
        console.log(data)
    },
    complete: function completeCallback() {
        console.log('Done')
    },
    error: function errorCallback(err) {
        console.log('error: ', err)
    }
};

// эта ф-я должна взять предыдущий Observable (его значение), создать новый Observable, на
// который потом кто-то сможет подписаться
function mapFn(transformationFn) {
    const inputObservable = this;
    const outputObservable = createObservable(function subscribe(obs) {

        inputObservable.subscribe({
            next: (x) => obs.next(transformationFn(x)), // transformationFn - преобразовываем данные
            complete: () => obs.complete(),
            error: (err) => obs.error(err)
        });

    });

  return outputObservable;
};

// ф-я создает observable
function createObservable(subscribeFn) { // subscribeFn будет emit наши данные
    return {
        map: mapFn, // одинаковы для каждого объекта observable
        subscribe: subscribeFn // изначально: function startReceivingData(obs) {......} // функция подписки
  }
};

// intervalObservable - это объект, созданный при помощи createObservable
const intervalObservable = createObservable(function startReceivingData(obs) {
  let counts = 0;
  const intervalId = setInterval(() => {
      counts++;
      obs.next(counts);

      if (counts > 5) {
            clearInterval(intervalId);
            obs.complete();
        }
    }, 300);
});

intervalObservable
    .map(x => x*10) // возвращает новый Observable
    .subscribe(observer); // observer подписывается на данные или observer - это объект, который получает данные

/*
    10
    20
    30
    40
    50
    60
    Done
*/

jsfiddle.net

     RxJs операторы - 120+

// позволяют создавать observable
from()          // из, например, [
fromEvent()     // observable из event
fromPromise()   // observable из Promise

// методы
map()           // изменяем конкретные данные
filter()        // фильтрация
take()          // позволяет взять несколько значений

//
scan()      // позволяет накапливать данные
delay()     // откладывать emit

     Какие задачи легко решать с RxJs

  • Объединение нескольких событий вместе
  • Фильтрация событий (delay, debounce, throttle)
  • Управление асинхронными действиями
  • Когда нужна отмена запросов

     Какие задачи не нужно решать с RxJs

  • Используйте там где это имеет смысл
  • RxJs не нужен, чтобы обрабатывать клик по кнопке
  • RxJs не нужен, чтобы submit форму на бекенд
  • RxJs не фреймворк, не нужно писать вс приложение, как один Observable

Реактивное программирование - мы не навешиваем на кучу событий, мы оперируем потоком данных (комбинируем события) и формируем поток данныч, которые меняются (как мы им задали).

Баррель

Баррель - это библиотечный модуль Angular, объединяющий модули с одинаковым назначением.

Как подключать одно в другое

Компоненты и директивы и фильтры:
Все директивы, компоненты и фильтры объявляются в свойстве declarations основного модуля.

Сервисы
Сервисы подключаются в providers и внедряются через конструктор:

Соглашение об именовании

  • Имена фалов и папок: в нижнем регистре и с дефисом между словами.
  • К фафлам директов, компонентам, фильтрам и сервисам следует добавлять суффикс определяющий их тип: slider.component.ts
  • Селекторы директив, имена фильтров, модули задаются в верблюжьейНотации
  • Селекторы компонентов в нижнем-регистре-с-дефисом-между-словами

Полезные ресурсы

Комментарии к статье

аватарка пользователя
2017-07-05
test

test

аватарка пользователя
2017-10-24
Марк

Жаль что автор не указан ! Это САМАЯ КРУТАЯ СТАТЬЯ КОТОРУЮ Я НАШЕЛ !!! СПАСИБО ОГРОМНОЕ АВТОРУ !!!

аватарка пользователя
2017-10-24
dnzl

не за что

Добавить комментарий