#javascript #angular #typescript #rxjs #observable
#javascript #angular #typescript #rxjs #наблюдаемый
Вопрос:
Я пытаюсь создать наблюдаемое из значений, выдаваемых FormControls.valueChanges
наблюдаемым. Наблюдаемый объект создается в ngOnInit
методе следующим образом:
ngOnInit(): void {
this.myObservable$ = combineLatest([
this.formControl1.valueChanges,
this.formControl2.valueChanges
]).pipe(
map(([v1, v2]) => parseInt(v1, 10) parseInt(v2, 10))
);
this.isLoading = true;
this.loadData().subscribe({
next: result => {
this.formControl1.setValue(result[0]);
this.formControl2.setValue(result[1]);
this.isLoading = false;
},
});
}
Наблюдаемый объект подписывается после того, как isLoading
установлено значение false:
<div *ngIf="!isLoading">{{ myObservable$ | async }}</div>
Таким образом, в этом случае myObservable$
значения начнут выдаваться только после того, как formControl1
и formControl2
изменены вручную, поскольку первые выбросы valueChanges
произойдут до того, как на этот наблюдаемый объект будет подписан.
Вы могли бы написать довольно подробное решение для этого:
this.myObservable$ = defer(() => combineLatest([
this.formControl1.valueChanges.pipe(startWith(this.formControl1.value)),
this.formControl2.valueChanges.pipe(startWith(this.formControl2.value))
])).pipe(
map(([v1, v2]) => parseInt(v1, 10) parseInt(v2, 10))
);
Может быть, есть более привлекательное решение для этого?
(https://angular-ivy-n1gwvk.stackblitz.io является ли StackBlitz минимальным примером)
Ответ №1:
Вы могли бы сделать следующее в шаблоне. Также подробный, но это другой способ сделать это. В противном случае startWith
является правильным решением этой проблемы:
<ng-container *ngIf="{ value: (myObservable$ | async) } as my">
<div *ngIf="!isLoading">{{ my.value }}</div>
</ng-container>
Другим способом было бы подписаться на него и использовать shareReplay
:
isLoading: true;
readonly destroy$ = new Subject<void>();
ngOnInit(): void {
this.myObservable$ = combineLatest([
this.formControl1.valueChanges,
this.formControl2.valueChanges
]).pipe(
map(([v1, v2]) => parseInt(v1, 10) parseInt(v2, 10)),
shareReplay(1),
takeUntil(this.destroy$)
);
this.myObservable$.subscribe();
this.loadData().subscribe({
next: result => {
this.formControl1.setValue(result[0]);
this.formControl2.setValue(result[1]);
this.isLoading = false;
},
});
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
Ответ №2:
Единственный способ, который я могу придумать, — это извлечь какой-нибудь вспомогательный sand и попытаться удалить некоторое повторение.
// Helpers
const getControlValueStream = (({ control, valueChanges }): FormControl) => valueChanges.pipe(startWith(value));
const sumNumbers = (numbers: number[]) => numbers.reduce((a, b) => a b, 0);
// Component
const controls = [this.formControl1, this.formControl2];
const valueStreams = controls.map(getControlValueStream);
combineLatest(...valueStreams).pipe(
map(values => values.map(value => parseInt(value, 10))),
map(sumNumbers),
);
Однако вам или вашей команде это может показаться менее читабельным, что справедливо — привлекательность должна быть сбалансирована с удобством сопровождения.