角度异步管道未触发NgOnChange的更改检测

时间:2018-12-07 08:26:44

标签: angular rxjs angular2-changedetection ngzone

我遇到了这样的代码。问题是,选择已在缓存中的项目后,进度条没有消失(当使缓存内的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>>;,并且正在过滤掉初始null发射

也在更改

<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正在作为异步操作运行,这就是为什么在组件上已经执行了更改检测后才执行tap的原因。像这样:

  1. this.isProgressBar = true;
  2. 变更检测运行
  3. 点击(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 个答案:

答案 0 :(得分:2)

对我来说,您的代码有两个主要问题:

  • 您的缓存可能不会发出新值(我不知道,因为您没有提供它的实现),这意味着async管道不会被触发,

  • 因为您检测到onPush,所以您的视图不会被任何事物刷新:只有您触摸的方法/属性才会被更新。 item$与进度条无关,使用detectChanges不会看到它正在更新(这会触发组件更改检测)。