Контекст
В проекте 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()));
Я думаю, что это не очень хорошее решение, и, возможно, у меня проблема со структурой.
Спасибо всем!