Угловая асинхронная труба не запускает обнаружение изменений из NgOnChange
Я сталкивался с таким кодом. Проблема заключалась в том, что индикатор выполнения не исчезал после выбора элемента, который уже находился в кэше (когда был сделан вызов API внутри кэша, он работает нормально). Я смог придумать, что обнаружение изменений не запускается после выполнения операции в tap. Может кто-нибудь объяснить мне, почему?
@Component({
selector: 'app-item',
templateUrl: `
<app-progress-bar
[isDisplayed]="isProgressBar"
></app-progress-bar>
<app-item-content
[item]="item$ | async"
></app-item-content>`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ItemComponent {
@Input()
set currentItemId(currentItemId: string) {
if (currentItemId) {
this.isProgressBar = true;
this.item$ = this.cache.get(currentItemId).pipe(
tap(() => {
this.isProgressBar = false;
//adding this.changeDetector.detectChanges(); here makes it work
})
);
} else {
this.isProgressBar = false;
this.item$ = of(null);
}
}
item$: Observable<ItemDto>;
isProgressBar = false;
constructor(
private cache: Cache,
private changeDetector: ChangeDetectorRef
) {}
}
Кеш хранит предметы в private _data: Map<string, BehaviorSubject<ItemDto>>;
и отфильтровывает начальный нулевой выброс
также меняется
<app-progress-bar
[isDisplayed]="isProgressBar"
></app-progress-bar>
в
<app-progress-bar
*ngIf="isProgressBar"
></app-progress-bar>
заставляет его работать без ручного запуска обнаружения изменений, почему?
кэш:
export class Cache {
private data: Map<string, BehaviorSubject<ItemDto>>;
get(id: string): Observable<ItemDto> {
if (!this.data.has(id)) {
this.data.set(id, new BehaviorSubject<ItemDto>(null));
}
return this.data.get(id).asObservable().pipe(
tap(d => {
if (!d) {
this.load(id);
}
}),
filter(v => v !== null)
);
}
private load(id: string) {
this.api.get(id).take(1).subscribe(d => this.data.get(id).next(d));
}
Редактировать:
Итак, я понял: tap выполняется как асинхронная операция, поэтому она выполняется после того, как обнаружение изменений уже запущено на компоненте. Что-то вроде этого:
- this.isProgressBar = true;
- запуск обнаружения изменений
- нажмите (this.isProgressBar = false;)
Но я возился с этим и сделал что-то вроде этого:
templateUrl: `
<app-progress-bar
[isDisplayed]="isProgressBar$ | async"
></app-progress-bar>
<app-item-content
[item]="item$ | async"
></app-item-content>`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ItemComponent {
@Input()
set currentItemId(currentItemId: string) {
if (currentItemId) {
this.itemService.setProgressBar(true);
this.item$ = this.cache.get(currentItemId).pipe(
tap(() => {
this.itemService.setProgressBar(false);
})
);
} else {
this.itemService.setProgressBar(false);
this.item$ = of(null);
}
}
item$: Observable<ItemDto>;
isProgressBar$ = this.itemService.isProgressBar$;
И теперь я понятия не имею, почему после выполнения операции в tap() обнаружение изменений не запускается на компоненте, имеет ли оно какое-то отношение к зонам?
ItemService:
private progressBar = new Subject<boolean>();
setProgressBar(value: boolean) {
this.progressBar.next(value);
}
get isProgressBar$() {
return this.progressBar.asObservable();
}
1 ответ
Для меня есть два основных вопроса с вашим кодом:
Ваш кеш, вероятно, не генерирует новые значения (чего я не знаю, потому что вы не предоставили его реализацию), то есть
async
труба не срабатывает,потому что вы обнаруживаете
onPush
ваш взгляд ничем не обновляется: обновляются только те методы / свойства, к которым вы прикоснулись.item$
не связаны с вашим индикатором выполнения, вы не увидите, что он обновляется, если вы используетеdetectChanges
(который запускает обнаружение изменения компонента).