Службы потоко-зрелищных данных в Angular

Здесь мы затрагиваем темы зрелищ и сабжей, краткое введение можно найти соответственно здесь и здесь.

Службы потоко-зрелищных данных (Observable data services) могут использоваться как хранилища данных, переиспользуемых в нескольких модулях или компонентах.

Рассмотрим пример службы потоко-зрелищных данных в Angular:

import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Observable';
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import 'rxjs/add/operator/do';
export interface ExampleState {
data: ExampleData;
}
const initialState: ExampleState = {
data: null
};
@Injectable()
export class ExampleStore {
private _state: BehaviorSubject<ExampleState> = new BehaviorSubject(initialState);
public readonly state: Observable<ExampleState> = this._state.asObservable();
  fetch() {
return this.http.get('https://someapi.ru/api/get-info')
.do((response: { data: ExampleData }) => this._state.next({
...this._state.getValue(),
data: response.data
}));
}
  constructor(private http: HttpClient) { }
}

Последовательно рассмотрим вышеуказанный код сверху вниз:

export interface ExampleState {
data: ExampleData;
}
const initialState: ExampleState = {
data: null
};
@Injectable()
export class ExampleStore {

Данные, которые содержит хранилище и которые доступны всем, кто использует это хранилище, называются состоянием хранилища. Выше мы создаём интерфейс ExampleState , который описывает структуру состояния хранилища. Используя этот интерфейс мы также определяем переменную initialState — начальное состояние хранилища.

Декоратор @Injectable() используется в Angular для того, чтобы создаваемая служба могла принимать инъекции зависимостей. В конкретном примере мы проводим инъекцию службы HttpClient в нашу службу, чтобы иметь возможность отправлять HTTP-запросы.

private _state: BehaviorSubject<ExampleState> = new BehaviorSubject(initialState);
public readonly state: Observable<ExampleState> = this._state.asObservable();

Далее создаём поведенческий сабж _state и передаём в его конструктор переменную initialState . При подписке каждый зритель вынужден будет отреагировать на последнее состояние хранилища (или на initialState , если в сабже ещё не происходило событий).

Чтобы обновить состояние хранилища, мы создаём в сабже событие с помощью метода .next(newState) , где newState — новое состояние хранилища, соответствующее интерфейсу ExampleState .

Чтобы получить последнее состояние хранилища, мы можем использовать метод .getValue() поведенческого сабжа.

Следует обратить внимание на то, что поле _state имеет модификатор private , чтобы метод .next сабжа можно было использовать только внутри хранилища. Иначе было бы очень трудно следить за всеми изменениями состояния хранилища.

Чтобы сохранить возможность подписываться на изменения состояния хранилища для внешних зрителей, мы создаём зрелище state с помощью метода .asObservable() сабжа _state . Все события, происходящие в сабже _state будут дублироваться в зрелище state . Зрители так же будут получать последнее состояние хранилища при подписке на зрелище state .

fetch() {
return this.http.get('https://someapi.ru/api/get-info')
.do((response: { data: ExampleData }) => this._state.next({
...this._state.getValue(),
data: response.data
}));
}

Метод fetch() создаёт зрелище, которое при подписке выполнит GET-запрос на указанный адрес. К этому зрелищу мы применяем оператор .do() , принимающий функцию в качестве аргумента. Эта функция будет вызываться на каждое событие в зрелище.

Следует обратить внимание на то, что GET-запрос и, соответственно, изменение состояние хранилища не будут выполнены, пока на зрелище, возвращаемое методом fetch() не подпишется зритель. Подписка на зрелище также может быть использована для логики, которая должна быть выполнена после получения ответа сервера, подобно Promise.then() .

  constructor(private http: HttpClient) { }
}

Наконец, в конструкторе мы проводим инъекцию службы HttpClient в наше хранилище.