如何在 Angular 中使用可观察对象管理组件状态
How to manage component status with observables in Angular
上下文
在一个Angular项目中,我做了一个组件来封装提交请求页面的逻辑(代码如下)。
此页面有两个定义特定表单的子组件(firstForm 和 secondForm)和一个提交按钮。
每个子组件都有一个针对禁用状态
的输入
@Input() set disabled(value: boolean | null) {
if (value === null) {
value = false;
}
this._disabled = value;
this._onDisabledChange();
}
以及当表单 有效 或无效时发出的输出:
@Output() onValidChange: EventEmitter<boolean> = new EventEmitter();
我想要启用以下表格,之前的表格必须有效。
这些将是启用的依赖项:
first-form -(depends-on)-> no one
第二形式-(取决于)->第一形式
提交按钮-(依赖)->一式&二式
问题
当完成第一种形式和第二种形式(启用提交)时,我删除了一种第一种形式的必需输入(因此第一种形式无效),会发生这种情况:
- 禁用的第二形式更新(正确)
- 提交不会更新为禁用样式,直到我在输入外单击(不正确)
我希望立即更新提交禁用的样式。
注:
我正在使用 changeDetection: ChangeDetectionStrategy.OnPush
。作为测试,我使用了 Default
策略,控制台显示错误 ExpressionChangedAfterItHasBeenCheckedError
.
代码
由于这是一个复杂的问题,我简化了代码,只留下相关部分。
组件有另一个 EventEmitter 来发出表单值,但这里我想关注有效状态。
Html模板
<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
解决方案
我找到的唯一解决方案是在 Observable isSubmitStepEnabled$
.
上使用 delay(0)
isSubmitStepEnabled$: Observable<boolean> = combineLatest([
this.isFirstStepCompleted$,
this.isSecondStepCompleted$
]).pipe(delay(0), map(this._areAllTrue()));
我认为这不是一个好的解决方案,也许我有结构问题。
谢谢大家!
这是我找到的解决方案(不使用 delay
函数):
只需将 asObservable
(当我想从 behaviorSubject 获取流时)替换为 .pipe(shareReplay({ refCount: true, bufferSize: ONCE }))
。
两者的区别是:
- 与
asObservable
每次调用(由“侦听器”)时都会创建一个新订阅。
- 使用
shareReplay
,就好像您有一个订阅,它的数据会广播给您所有的“听众”
使用 asObservable
,我的代码没有 运行 更改检测,直到我在输入外部单击。
上下文
在一个Angular项目中,我做了一个组件来封装提交请求页面的逻辑(代码如下)。
此页面有两个定义特定表单的子组件(firstForm 和 secondForm)和一个提交按钮。
每个子组件都有一个针对禁用状态
的输入@Input() set disabled(value: boolean | null) {
if (value === null) {
value = false;
}
this._disabled = value;
this._onDisabledChange();
}
以及当表单 有效 或无效时发出的输出:
@Output() onValidChange: EventEmitter<boolean> = new EventEmitter();
我想要启用以下表格,之前的表格必须有效。 这些将是启用的依赖项:
first-form -(depends-on)-> no one
第二形式-(取决于)->第一形式
提交按钮-(依赖)->一式&二式
问题
当完成第一种形式和第二种形式(启用提交)时,我删除了一种第一种形式的必需输入(因此第一种形式无效),会发生这种情况:
- 禁用的第二形式更新(正确)
- 提交不会更新为禁用样式,直到我在输入外单击(不正确)
我希望立即更新提交禁用的样式。
注:
我正在使用 changeDetection: ChangeDetectionStrategy.OnPush
。作为测试,我使用了 Default
策略,控制台显示错误 ExpressionChangedAfterItHasBeenCheckedError
.
代码
由于这是一个复杂的问题,我简化了代码,只留下相关部分。
组件有另一个 EventEmitter 来发出表单值,但这里我想关注有效状态。
Html模板
<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
解决方案
我找到的唯一解决方案是在 Observable isSubmitStepEnabled$
.
delay(0)
isSubmitStepEnabled$: Observable<boolean> = combineLatest([
this.isFirstStepCompleted$,
this.isSecondStepCompleted$
]).pipe(delay(0), map(this._areAllTrue()));
我认为这不是一个好的解决方案,也许我有结构问题。
谢谢大家!
这是我找到的解决方案(不使用 delay
函数):
只需将 asObservable
(当我想从 behaviorSubject 获取流时)替换为 .pipe(shareReplay({ refCount: true, bufferSize: ONCE }))
。
两者的区别是:
- 与
asObservable
每次调用(由“侦听器”)时都会创建一个新订阅。 - 使用
shareReplay
,就好像您有一个订阅,它的数据会广播给您所有的“听众”
使用 asObservable
,我的代码没有 运行 更改检测,直到我在输入外部单击。