在store.select订阅中进行更改检测需要markForCheck。为什么?

时间:2018-07-22 11:55:11

标签: angular ngrx angular-changedetection

我的应用程序组件正在对商店选择进行订阅。我将ChangeDetectionStrategy设置为OnPush。 我一直在阅读这是如何工作的。需要更新对象引用以触发更改。 但是,当您使用异步管道时,Angular期望可以观察到新的变化并为您执行MarkForCheck。 因此,为什么在订阅被触发并将MarkForCheck设置为新的可观察通道数组后,我的代码为什么不呈现通道(除非我调用channels$)。

@Component({
  selector: 'podcast-search',
  changeDetection: ChangeDetectionStrategy.OnPush, // turn this off if you want everything handled by NGRX. No watches. NgModel wont work
  template: `
    <h1>Podcasts Search</h1>
    <div>
      <input name="searchValue" type="text" [(ngModel)]="searchValue" ><button type="submit" (click)="doSearch()">Search</button>
    </div>
    <hr>
    <div *ngIf="showChannels">

      <h2>Found the following channels</h2>
      <div *ngFor="let channel of channels$ | async" (click)="loadChannel( channel )">{{channel.trackName}}</div>
    </div>
  `,
})

export class PodcastSearchComponent implements OnInit {
  channels$: Observable<Channel[]>;
  searchValue: string;
  showChannels = false;
  test: Channel;

  constructor(
    @Inject( Store)  private store: Store<fromStore.PodcastsState>,
    @Inject( ChangeDetectorRef ) private ref: ChangeDetectorRef,
  ) {}

  ngOnInit() {

    this.store.select( fromStore.getAllChannels ).subscribe( channels =>{
      if ( channels.length ) {
        console.log('channels', !!channels.length, channels);
        this.channels$ =  of ( channels );
        this.showChannels = !!channels.length;
        this.ref.markForCheck();
      }
    } );
  }

我尝试了多种解决方案,包括使用subject和调用next,但是除非我调用MarkForCheck,否则这是行不通的。

有人可以告诉我如何避免打电话给markForCheck吗?

1 个答案:

答案 0 :(得分:2)

这可能很难解释,但我会尽力而为。当原始的Observable(商店)发出时,它并不绑定到模板。由于您使用的是OnPush更改检测,因此,由于缺少绑定,因此该可观察到的值发出时不会将组件标记为更改。

您正试图通过覆盖组件属性来触发更改标记。即使您要在组件属性本身上创建新引用,也不会将组件标记为更改,因为组件正在更改其自身的属性,而不是在组件上推入的新值。 / p>

您认为在发出新值时异步管道将组件标记为更改是正确的。您可以在以下Angular源代码中看到它:https://github.com/angular/angular/blob/6.0.9/packages/common/src/pipes/async_pipe.ts#L139

不过,您会注意到,只有在async管道中使用的值(称为async)与this._obj(即{ {1}}管道已经记录为正在发射的Observable。

由于您正在执行asyncchannels$ = <new Observable>实际上是不正确的,因为您正在更改对象引用。这就是为什么您的组件未标记为更改的原因。

您也可以在我放在一起的Stackblitz中看到这一点。第一个组件将覆盖传递给async === this._obj管道的Observable,而第二个组件则不会覆盖它,并通过响应发出的更改来更新数据-这是您想要做的:

https://stackblitz.com/edit/angular-pgk4pw(我使用async是因为它是模拟第三方未绑定Observable来源的简便方法。使用输出绑定(例如点击更新)更难以设置,因为在同一组件中完成操作,输出操作将触发更改标记。

一切都不会为您丢失-我建议您改为timerthis.channels$ = this.store.select(...)管道为您处理async。如果您使用的是.subscribe管道,则无论如何都不应该使用async

.subscribe

请注意,您也可以将this.channels$ = this.store.select(fromStore.getAllChannels).pipe( filter(channels => channels.length), // channels.length should always be truthy at this point tap(channels => console.log('channels', !!channels.length, channels), ); 与异步管道一起使用,这将消除对ngIf的需求。