Управление состоянием

Advanced Angular

Петров А. С., Павлов М. Ю.

Занятие 1

2

О курсе

Цели:
1. Поделиться опытом

2. Подготовиться к грейду на мидла

 

Темы:

1. Управление состоянием

2. Сложные формы

3. Процесс сборки

4. DI

5. Роутинг

6. ...

7. Profit!

Состояние

Состояние – любые изменяемые данные, которыми оперирует наше приложение

Примеры:

1. Информация о текущем пользователе

2. Элементы панели быстрого доступа

2. Список задач в журнале

В чем проблемы работы с состоянием?

Синхронизация данных из разных источников

Сервер

URL

Пользователь

SessionStorage

LocalStorage

Сложные переходы между состояниями

  1. При изменении ОН:
    1. если выбран ОН, находящийся на уровне выше заданного в настройках основной фильтрации - все настройки основной и дополнительной фильтрации должны сохраняться;
    2. если выбран ОН, находящийся на уровне заданном в настройках основной фильтрации или ниже, но выше уровня заданного в настройках дополнительной фильтрации, то все настройки дополнительной фильтрации должны передаваться в соответствующие настройки основной фильтрации, при этом дополнительная фильтрация должна быть сброшена;
    3. если выбран ОН, находящийся на уровне заданном в настройках дополнительной фильтрации или ниже, то основная и дополнительная фильтрации должны быть сброшены;
    4. если выбран ОН, находящийся на уровне заданном в настройках основной фильтрации или ниже, и дополнительная фильтрация не задана, то фильтрация должна быть сброшена;

Из КТ по аналитике, небольшой пункт

Какие подходы к управлению состоянием существуют?

Простой подход: хранение в полях

@Injectable()
class DataService {

  private records: Record[];

  getRecords(): Record[] {
    return this.records;
  }

  setRecords(records: Record[]): void {
    this.records = records;
  }

}

Основная проблема такого подхода?

Посмотрим ещё раз...

@Injectable()
class DataService {

  private records: Record[];

  getRecords(): Record[] {
    return this.records;
  }

  setRecords(records: Record[]): void {
    this.records = records;
  }

}

Как оповещать компоненты об изменении данных?

Колбэки, колбэки и ещё раз колбэки!

@Injectable()
class DataService {

  private records: Record[];
  private onRecordsChanged: ChangeCallback<Record[]>[] = [];

  getRecords(): Record[] {
    return this.records;
  }

  setRecords(records: Record[]): void {
    this.records = records;
    this.onRecordsChanged.forEach(it => it(records));
  }

  registerOnRecordsChanged(callback: ChangeCallback<Record[]>): UnregisterCallback {
    this.onRecordsChanged.push(callback);
    return () => {
      this.onRecordsChanged = this.onRecordsChanged.filter(it => it !== callback);
    };
  }

}

Welcome to callback hell

Как сделать лучше?

Спрятать данные в потоки!

В сервисе – потоки с данными нашего состояния

@Injectable()
class DataService {

  private readonly records$: Observable<Record[]>;

  constructor(http: HttpClient) {
    this.records$ = http.get('/rest/data').pipe(shareReplay(1));
  }

  getRecords(): Observable<Record[]> {
    return this.records$;
  }

}

Компонент получает поток из сервиса

@Component({
             selector: 'sis-data-view',
             templateUrl: './data-view.component.html',
           })
export class DataViewComponent {

  records$: Observable<Record[]>;

  constructor(service: DataService) {
    this.records$ = service.getRecords();
  }
}

И поток используется в шаблоне

<div *ngFor="let records of records$ | async">
  <div>{{record.name}}</div>
</div>

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

Граф зависимостей данных

Поиск

Фильтры

Запрос строк

Строки

А теперь посмотрим на практике

Что мы сделаем:

1. Получим строки
2. Добавим фильтр
3. Добавим поиск
4. Добавим паджинацию

В чем плюсы?

1. Декларативно

2. Мощно

3. Реактивно

4. Лениво

В чем минусы?

Вспомним граф зависимостей данных...

Поиск

Фильтры

Запрос строк

Строки

Сложные графы

Паджинация

Фильтры

Запрос строк

Строки

Поиск

На самом деле еще сложнее

Избыточные вычисления

Паджинация

Фильтры

Запрос строк

Строки

Поиск

/csp/sou/rest/view/query

/csp/sou/rest/view/query

Дебаг – боль

ERROR TypeError: Cannot read property 'a' of undefined at MapSubscriber.push../src/app/shared/journal/main/class-journal-factory.service.ts.ClassJournalFactory.getQueryRequest [as project] (main.9a74b2b.js:66519) at MapSubscriber.push../node_modules/rxjs/_esm5/internal/operators/map.js.MapSubscriber._next (vendor.9ebaba3.js:246095) at MapSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber.next (vendor.9ebaba3.js:241059) at DistinctUntilChangedSubscriber.push../node_modules/rxjs/_esm5/internal/operators/distinctUntilChanged.js.DistinctUntilChangedSubscriber._next (vendor.9ebaba3.js:245041) at DistinctUntilChangedSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber.next (vendor.9ebaba3.js:241059) at ScanSubscriber.push../node_modules/rxjs/_esm5/internal/operators/scan.js.ScanSubscriber._tryNext (vendor.9ebaba3.js:247708) at ScanSubscriber.push../node_modules/rxjs/_esm5/internal/operators/scan.js.ScanSubscriber._next (vendor.9ebaba3.js:247695) at ScanSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber.next (vendor.9ebaba3.js:241059) at FilterSubscriber.push../node_modules/rxjs/_esm5/internal/operators/filter.js.FilterSubscriber._next (vendor.9ebaba3.js:245540) at FilterSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber.next (vendor.9ebaba3.js:241059)
Show 190 more frames

To be continued...

Задание на дом

Цель: написать простой журнал

Что почитать

Advanced Angular. Управление состоянием

By Сибирские интеграционные системы