【问题标题】:Angular async pipe not triggering change detection from NgOnChange角度异步管道未触发来自 NgOnChange 的更改检测
【发布时间】:2019-05-08 23:57:00
【问题描述】:

我遇到了这样的代码。问题是在选择已经在缓存中的项目后进度条没有消失(当缓存内的 api 调用正常工作时)。我能够想出的是在点击执行操作后没有运行更改检测。有人可以向我解释为什么吗?

@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&lt;string, BehaviorSubject&lt;ItemDto&gt;&gt;; 并且正在过滤掉初始的空发射

也在变化

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

  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() 中进行操作后更改检测没有在组件上运行,它与区域有关吗?

物品服务:

private progressBar = new Subject<boolean>();

    setProgressBar(value: boolean) {
        this.progressBar.next(value);
    }

    get isProgressBar$() {
        return this.progressBar.asObservable();
    }

【问题讨论】:

  • 你的“缓存”服务总是返回一个 observable? - 如果不使用 of(value) 来返回一个 observable- 你必须包括你的调用失败的情况才能使 this.isProgressBar=false -use catchError-
  • 是的,缓存返回 observable。我知道错误没有得到处理,但不介意,我只想知道为什么在这个例子中没有运行变更检测。
  • 我想你需要编写一个小的工作示例来清除这个。至于 zone,你可以注入 NgZone 并检查“onMicrotaskEmpty”生命周期钩子,看看不同异步任务的执行流程是怎样的。

标签: angular rxjs angular2-changedetection ngzone


【解决方案1】:

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

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

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

【讨论】:

  • 包含有问题的缓存。我不完全理解你在第二个 * 中的意思,但是 app-item-content 正在刷新,而 app-progress-bar 没有
  • 我的意思是在onPush 模式下,async 管道会触发更改检测,但变量值更改不会。由于您使用原始值(布尔值),因此不会检测到您的更改,从而导致问题。 This tutorial 会比我解释得更好!
  • 是的,我知道,但是,this tap(() =&gt; { this.isProgressBar = false; }) 在输入的设置器中,所以更改检测在@Input currentItemId 更改后触发
  • 就是这样,您的输入不会改变!这实际上是我的第一点。而且,即使输入发生变化,我认为只有 async 会发生变化,除非您手动触发变化检测。如果您想尝试一下,请尝试将您的 isProgressBar 转换为 observable 并使用 async
  • 输入变化,输入变化触发变化检测。场景是这样的——提供缓存中已经存在的currentItemId。所以有输入变化。您可以阅读我在编辑中找到的内容。我也尝试了你建议的 observable,事实上从一开始就是这样,因为其他组件也可以点亮进度条。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-02
  • 2020-08-26
  • 1970-01-01
相关资源
最近更新 更多