Как управлять состоянием компонента с помощью наблюдаемых в Angular - PullRequest
0 голосов
/ 05 марта 2020

Контекст

В проекте Angular я создал компонент для инкапсуляции логи c страницы запроса на отправку (код ниже).

На этой странице есть два подкомпонента которые определяют c формы ( firstForm и secondForm ) и кнопку отправки.

Каждый подкомпонент имеет вход для состояния disabled

@Input() set disabled(value: boolean | null) {
    if (value === null) {
      value = false;
    }
    this._disabled = value;
    this._onDisabledChange();
  }

и вывод для вывода, если форма действительна или нет:

@Output() onValidChange: EventEmitter<boolean> = new EventEmitter();

Я хочу, чтобы включить следующую форму, предыдущие имеют быть действительным. Это будут включенные зависимости:

  • первая форма - (зависит от) -> никто

  • вторая форма - (зависит от) -> первая форма

  • кнопка отправки - (зависит от) -> первая форма и вторая форма

Проблема

Когда первая форма и вторая форма заполнены (отправка включена), и я удаляю один из обязательных входных данных первой формы (поэтому первая форма недопустима) ), это происходит:

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

Я бы хотел, чтобы этот отключенный стиль отправки обновлялся мгновенно.

Примечание:

Я использую changeDetection: ChangeDetectionStrategy.OnPush. В качестве теста я использовал стратегию Default, и консоль показала ошибку ExpressionChangedAfterItHasBeenCheckedError.

Код

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

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

Html template

<div>
  <first-form (onValidChange)="firstFormValid = $event"></first-form>

  <second-form
    [disabled]="!(isSecondStepEnabled$ | async)"
    (onValidChange)="secondFormValid = $event"
  ></second-form>

  <div [ngClass]="{ disabled: !(isSubmitStepEnabled$ | async) }">
    <ul>
      <li>
        <button
          id="submit"
          [disabled]="!(isSubmitStepEnabled$ | async)"
          (click)="submit()"
        >
          Submit
        </button>
      </li>
    </ul>
  </div>
</div>

Angular Компонент

export class PageComponent {

  private _isFirstFormValid$: BehaviorSubject<boolean> = new BehaviorSubject<
    boolean
  >(false);

  private _isSecondFormValid$: BehaviorSubject<boolean> = new BehaviorSubject<
    boolean
  >(false);

  set firstFormValid(valid: boolean) {
    this._isFirstFormValid$.next(valid);
  }

  set secondFormValid(valid: boolean) {
    this._isSecondFormValid$.next(valid);
  }

  get isSecondFormValid$(): Observable<boolean> {
    return this._isSecondFormValid$.asObservable();
  }

  isFirstStepCompleted$: Observable<
    boolean
  > = this._isFirstFormValid$.asObservable();

  isSecondStepEnabled$: Observable<
    boolean
  > = this._isFirstFormValid$.asObservable();

  isSecondStepCompleted$: Observable<boolean> = combineLatest([
    this.isSecondStepEnabled$,
    this.isSecondFormValid$
  ]).pipe(map(this._areAllTrue()));

  isSubmitStepEnabled$: Observable<boolean> = combineLatest([
    this.isFirstStepCompleted$,
    this.isSecondStepCompleted$
  ]).pipe(map(this._areAllTrue()));

  private _areAllTrue(): (value: boolean[]) => boolean {
    return (values: boolean[]) => {
      return values.every(valid => valid === true);
    };
  }

  submit() {
      // call external service
  }
}

Версия

Angular CLI: 9.0.4
Node: 13.6.0
OS: darwin x64
Angular: 9.0.4
rxjs: 6.5.4
typescript: 3.7.5
webpack: 4.41.6

Решение

Единственное решение, которое я нашел, это использование delay(0) в Observable isSubmitStepEnabled$ .

  isSubmitStepEnabled$: Observable<boolean> = combineLatest([
    this.isFirstStepCompleted$,
    this.isSecondStepCompleted$
  ]).pipe(delay(0), map(this._areAllTrue()));

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

Спасибо всем!

...