Search code examples
angularngrx

NGRX: Update input properties on child components without using ngOnChanges or async pipe


I am aware that by using Ngrx and the Containers/Presentational components architecture the container-smart component should be the one which communicates with the store and basically passes data to the child components through their exposed Input properties. Also as a side note in the container component we are utilizing the OnPush change detection strategy.

The best option to achieve that, i suppose is to pass an observable to the template and unwrap it using async pipe. Something like

//in the container's component.ts file 
this.file$ = this.store.pipe(takeUntil(this.destroy$), select(getFiles))

// in the container's html.ts
<child-component [sth] = 'files$ | async'

In most cases though this value is used again inside the container's ts file for various reasons and that's why i am forced to use something like

 //in the container's component.ts file 
 this.store
  .pipe(takeUntil(this.destroy$), select(getFiles))
  .subscribe((files: Files[]) => { 
       this.files = files;
  }

// in the container's html.ts
<child-component [sth] = 'files'

So that makes me to either

Inject the ChangeDetectorRef in the Container and call the detectChanges function as soon as i get a value from the observable stream.

Or

Implement the ngOnChanges life-cycle hook in the child component to check when the input value changes.

I am wondering, Is there a better way to achieve this update by not having to use manually detectChanges all over the place or have to write this boilerplate code in the child components with ngOnChanges ?

Thanks.


Solution

  • Why do you need the files value in your component? Not knowing your use-case I think it's a bad practice (but it might be necessary in your case).

    I prefer to let Angular handle subscriptions on its own with the async pipe, to assign the local value you can use the tap operator.

    this.file$ =  this.store
      .pipe(
        select(getFiles), // notice, we're not using takeUntil because Angular handles the subscription
        tap(files => { 
         this.files = files; // assign the value here
        })
    ) 
    

    Now you can use Observable as before:

    <child-component [sth] = 'files$ | async'