TLDR Давайте создадим наш собственный класс управления государственным управлением только с RXJS/поведением (вдохновленный некоторыми известными государственными менеджментами).
Управлять состоянием с RXJS поведение
Там есть несколько великолепных государственных библиотек по управлению там для управления состоянием в угловом углу: например. Ngrx, Akita или NGXS. У всех нас есть одно общее: они основаны на наблюдаемых RXJS, и состояние хранится в особом виде наблюдаемого: поведение.
Почему rxjs наблюдается?
- Наблюдается, что граждане первого класса в угловых. Многие из основных функций угловых имеют реализацию RXJS (например httpClient, Forms, маршрутизатор и более). Управляющее состояние с наблюдателями приятно интегрируется с остальной частью угловой экосистемы.
- С наблюдателями легко информировать компоненты о государственных изменениях. Компоненты могут подписаться на наблюдаемые, которые удерживают состояние. Эти «государственные» наблюдаемые заметные выделяют новое значение при изменении государства.
Что особенное в поведении поведение?
- Поведение преобразования испускает последнее значение для новых/поздних подписчиков
- Имеет первоначальное значение
- Его текущее значение может быть доступна через
GetValueметод - Новое значение может быть выпущено с помощью
Следующийметод - Поведение – это многоадресная передач: внутренне он содержит список всех подписчиков. Все подписчики разделяют одно и то же наблюдаемое исполнение. Когда поведение введение испускает новое значение, то то же самое значение нажата ко всем подписчикам.
Наше собственное государственное управление с поведением
Поэтому, если все большие государственные Libs Management используют RXJS поведение поведение и угловой поставляется с RXJS из коробки … Можем ли мы создать наше собственное государственное управление с лишним угловым сервисом и поведение?
Давайте создадим простой, но мощный класс государственного управления, который может быть расширенными угловыми сервисами.
Ключевыми целями являются:
- Быть в состоянии определить интерфейс состояния и установить начальное состояние
- Прямой API для обновления состояния и выберите Состояние:
SetState,Выбрать - Выбранное состояние должно быть возвращено как наблюдаемое. Наблюдаемые излучают, когда выбранные изменения состояния.
- Быть в состоянии использовать
CENSEDETICEGRATEGY. OnpushВ наших компонентах для лучшей производительности (Читайте больше на onpush здесь: “Комплексное руководство по угловой стратегии обнаружения изменения onpush” ).
Решение:
import { BehaviorSubject, Observable } from 'rxjs';
import { distinctUntilChanged, map } from 'rxjs/operators';
export class StateService {
private state$: BehaviorSubject;
protected get state(): T {
return this.state$.getValue();
}
constructor(initialState: T) {
this.state$ = new BehaviorSubject(initialState);
}
protected select(mapFn: (state: T) => K): Observable {
return this.state$.asObservable().pipe(
map((state: T) => mapFn(state)),
distinctUntilChanged()
);
}
protected setState(newState: Partial) {
this.state$.next({
...this.state,
...newState,
});
}
}
Давайте посмотрим на код выше:
- Установка информирования ожидает общий тип
TПредставление интерфейса состояния. Этот тип передан при расширении государственного обслуживания. Получить штат ()Возвращает текущий государственный снимок- Конструктор принимает начальное состояние и инициализирует поведение.
Выберитепринимает функцию обратного вызова. Эта функция называется, когдасостояние $испускает новое состояние. В пределах RXJSкартаФункция обратного вызова вернет кусок состояния.РазноцветныеБудет пропустить выбросы, пока выбранная часть состояния не содержит новую ссылку на объект.Это .state $ .Asobsibable ()Убедитесь, чтоВыберитеМетод возвращает наблюдаемый (а неанонимсубъектировать).SetStateпринимает частичный тип. Это позволяет нам лениться и проходить только некоторые свойства более крупного интерфейса состояния. Внутрисостояние $ .NextМетод частичного состояния объединяется с полным состоянием объекта. Наконец поведение поведениеЭто .state $выделяет совершенно новый государственный объект.
Применение
Угловые сервисы, которые должны управлять каким-либо состоянием, могут просто продлить инверсию для выбора и обновления и обновления.
В мире есть только одна вещь, чтобы управлять: Тодос!:) Давайте создадим тодосстатесвис.
interface TodoState {
todos: Todo[];
selectedTodoId: number;
}
const initialState: TodoState = {
todos: [],
selectedTodoId: undefined
};
@Injectable({
providedIn: 'root'
})
export class TodosStateService extends StateService{
todos$: Observable = this.select(state => state.todos);
selectedTodo$: Observable = this.select((state) => {
return state.todos.find((item) => item.id === state.selectedTodoId);
});
constructor() {
super(initialState);
}
addTodo(todo: Todo) {
this.setState({todos: [...this.state.todos, todo]})
}
selectTodo(todo: Todo) {
this.setState({ selectedTodoId: todo.id });
}
}
Давайте пройдемся через код TodosStateService:
- ToSstateService расширяет
Условия использованияи передает интерфейс состоянияТодость - Конструктор должен позвонить
Super ()и пройти начальное состояние - Общественные наблюдаемые
TODOS $иВыбратьTodo $подвергать соответствующие данные состояния для заинтересованных потребителей, таких как компоненты или другие услуги - Общественные методы
AddtodoиSelectTodoвыдержать публичное API для обновления состояния.
Взаимодействие с компонентами и Backend API
Давайте посмотрим, как мы можем интегрировать наш тодосстатеждервис с угловыми компонентами и API Backend:
- Компоненты называют публичными методами ToSstateService для обновления состояния
- Компоненты, заинтересованные в штате, просто подписываются на соответствующие публичные наблюдаемые, которые выставляются в ToSstateService.
- Вызывы API тесно связаны с состоянием. Довольно часто API-ответ будет напрямую обновлять состояние. Поэтому вызовы API запускаются ToSstateService. После завершения вызова API государство может быть обновлено сразу, используя
setstate.
Демонстрация
Смотрите полный приложение Todos Todos, используя ToSstateService: Stackblitz – угловой государственный менеджер
Примечания
Неизменные данные
Чтобы извлечь выгоду из CENSEDETICEGRATEGY. Onpush В наших компонентах мы должны убедиться, что не мутируют государство. Наша обязанность всегда передавать новый объект к SetState метод. Если мы хотим обновить вложенное свойство, которое содержит объект/массив, то мы должны также назначить новый объект/массив.
Смотрите полный ТодосстатесСервис (на стекблиц) Для получения дополнительных примеров неизменных обновлений состояния.
FYI Есть libs, которые могут помочь вам сохранить данные состояния неизменными: Иммер Immutablejs.
Формы ведомых шаблонов с двусторонним связыванием данных
Что касается неизменных данных … Мы должны быть осторожны при точке состояния в форму на основании шаблона, где используются входы формы [(NGModel)] Отказ Когда пользователь меняет входное значение формы, то состояние состояния будет мутирован напрямую … Но мы хотели остаться неизменными и изменять состояние, только явно используя SetState Отказ Поэтому это лучшая альтернатива использовать реактивные формы. Если он должен быть формы на основе шаблонов, то еще есть хороший компромисс: односторонние данные, связывающие [NGModel] Отказ Другой вариант должен (глубоко) клонировать данные формы … В таком случае вы все еще можете использовать [(NGModel)] Отказ
Асинхронная труба для подписок
В большинстве случаев компоненты должны подписаться на «государство» наблюдаемых, используя async Труба в шаблоне. Асинхронная труба подписывается для нас и автоматически обрабатывает отписания, когда компонент разрушен.
Существует еще одна польза от Async Pipe: когда компоненты используют стратегию обнаружения изменений onPush, они будут обновлять свое мнение только в этих случаях автоматически:
- Если
@Inputполучает новую ссылку на объект/объект - Если событие DOM срабатывает от компонента или одного из его детей
Существуют ситуации, когда компонент не имеет ни события DOM, ни @input, который меняется. Если этот компонент подписался на изменение состояния внутри класса компонентов, то обнаружение угловых изменений не будет знать, что представление необходимо обновлять после того, как наблюдаемое состояние испускает.
Вы можете исправить это, используя CENFIGNETECTREF.MORKFORCHECK () Отказ Он говорит об изменетелетелетеле, чтобы проверить наличие изменений состояния (в текущем или следующем цикле обнаружения изменений) и обновить вид, если необходимо.
@Component({
changeDetection: ChangeDetectionStrategy.OnPush
})
export class TodoShellComponent {
todos: Todo[];
constructor(
private todosState: TodosStateService,
private cdr: ChangeDetectorRef
) {
this.todosState.todos$.subscribe(todos => {
this.todos = todos;
this.cdr.markForCheck(); // Fix View not updating
});
}
}
Но мы также можем использовать async Труба в шаблоне вместо этого. Это призывает CENTERVETECTREF.MORKFORCHECK для нас. Смотрите здесь, в угловом источнике: async_pipe.
Намного короче и красивее:
Асинхронная труба много делает. Подписаться, Отписаться, MarkForcheck. Давайте использовать это, где это возможно.
Смотрите асинхронный труб в действии в демонстрации: todo-shell.component.html.
Выбор обратных вызовов часто называется
Мы должны знать о том, что обратный вызов передан на Выберите Метод должен быть выполнен на каждом вызове setstate. . Поэтому выборочный обратный вызов не должен содержать тяжелых расчетов.
Multicast исчезли
Если есть много подписчиков для наблюдаемого, которая возвращается Выберите Метод тогда мы видим что-то интересное: многоядерку поведение поведение прошло … Функция обратного вызова передана на Выберите Метод называется несколько раз при изменении состояния. Наблюдаемый выполнен на подписчик. Это потому, что мы преобразовали поведение поведение на наблюдаемое использование Это .state $ .Asobsibable () Отказ Наблюдаемые не многоадресные.
К счастью, RXJS предоставляет (многоадрессионный) оператор, чтобы сделать наблюдаемые многоадресные: SharerePlay Отказ
Я бы предложил использовать оператор SharerePlay только там, где он необходим. Давайте предположим, что есть несколько подписчиков для TODOS $ Наблюдаемый. В таком случае мы могли бы сделать это многоадресным по таким:
todos$: Observable= this.select(state => state.todos).pipe( shareReplay({refCount: true, bufferSize: 1}) );
Важно использовать RefCount: True Чтобы избежать утечек памяти. Буферизация: 1 Убедитесь, что поздние подписчики все еще получают последнее исходное значение.
Узнайте больше о многоадресных операторах здесь: Магия операторов обмена RXJS и их различия
Фасадный рисунок
Есть еще одна хорошая вещь. Государственная служба управления способствует фасадный рисунок : Выбрать и SetState Охраняемые функции. Поэтому они могут быть вызваны только внутри ТодосстатесСервис Отказ Это помогает поддерживать компоненты наклоняться и чистыми, поскольку они не смогут использовать SetState / Выберите Методы напрямую (например, на инъецированном тодосстатеревице). Государственные детали реализации остаются внутри ТодосстатесСервис. Шаблон фасада позволяет легко рефакторизоровать ToSstateService в другое государственное решение для управления (например, Ngrx) – если вы когда-нибудь хотите:)
Спасибо
Особое спасибо за рассмотрение этого блога:
Статьи, которые вдохновили меня:
- Простое государственное управление в угловом угловании с ограниченными услугами и RXJS по Аслан Васаев
- Очень похожий подход: Создание простого STATSTATE () магазина с использованием поведения RXJS В угловых 6.1.10 по Бен Надель
Оригинал: “https://dev.to/angular/simple-yet-powerful-state-management-in-angular-with-rxjs-4f8g”