1

I'm really banging my head against the wall with this one... :-\

I have a dumb component that with an @Input() that receives an array:

export class ChildComponent implements OnChanges {

  @Input() items: Item[];

  ngOnChanges(changes: SimpleChanges) {
    console.log('ngOnChanges fired:', changes['items'].currentValue.length)
  }
}

The parent component passes the array to the child component via a template binding and the async pipe:

<h1>Parent Component</h1>

<child [items]="items$ | async"></child>

My understanding is that changes should automatically be detected when using the async pipe with @Input() allowing them to be processed within the ngOnChanges lifecycle hook.

Is this correct?

However, ngOnChanges is not fired in the child component when items$ emits a new value.

I have a DataStore class that maintains this observable array of items. As new items are fetched, it generates a new array (immutable) and pushes it through the items$ observable. However, the changes aren't detected in the child component. If I click around the page a bit, eventually change detection kicks in and it will update.

Here's the kicker: I created a sample of the problem on StackBlitz, only to find out it works fine there!!!!

So, I know there is something different my real code vs the simplified example, but I have not been able to track it down.

That is why my question is:

How to debug RxJS / Angular?

  • I've added logging to the items$ inside the parent component and I can see the new values arriving just fine, but the child component is not receiving them. I can see that ngOnChanges is not being fired within the child component.

  • I thought maybe the items$ observable reference was getting reassigned somewhere, leaving the template with a subscription to an observable that isn't actually getting new emissions, so I made the @Input() a getter/setter can I could monitor each time it was set, and it only occurs once.

  • I tried changing the child component to use the OnPush change detection strategy, but it had no effect.

I'm stumped...

Any suggestions on how I can track down my issue?

Thanks! :-)

BizzyBob
  • 12,309
  • 4
  • 27
  • 51
  • Things like that can happen when the array reference doesnt change which means the input setter or ngOnChanges isnt being triggered – ukn Jul 09 '20 at 03:43
  • Take a look at this: https://stackoverflow.com/questions/43223582/why-angular-2-ngonchanges-not-responding-to-input-array-push – ukn Jul 09 '20 at 03:47
  • Have you tried to use the debugger? I'd place some breakpoints in the async pipe's implementation. – Andrei Gătej Jul 09 '20 at 09:32

1 Answers1

2

Make sure the observable is emitting by putting {{ items$ | async | json }} in the parent component somewhere near the child. You will probably find that items$ is not emitting values, if it was then the child would get updated.

Are you modifying the current array and reemitting the same instance? When you modify the array make sure to construct a new array with the spread operator, map or filer rather than push and popping values then having the observable emit the same instance of an array with modified data. Make sure you emit a new instance of an array, don't mutate the old one.

Not related to your question but I tend to use setter properties over OnChanges

export class ChildComponent {
  items: Item[];
  @Input('items')
  set itemsSet(items: Item[]) {
    this.items = items;
    console.log('items set:', items.length);
  }
}
Adrian Brand
  • 20,384
  • 4
  • 39
  • 60
  • `Make sure the observable is emitting`: In my parent component, I tacked on a `.pipe(tap(console.log))` where I defined `items$` and the new values are getting logged. – BizzyBob Jul 09 '20 at 03:47
  • I just updated my answer, you might be emitting the same instance of the array. If the values are changing in the parent but not triggering a change in the child you must be emitting the same array with modified values. Make sure you emit a new instance of an array, don't mutate the old one. – Adrian Brand Jul 09 '20 at 03:49
  • 1
    An easy way to test this is put a distinctUntilChanged() in the pipe before you tap, distinctUntilChanged will not emit the same instance even if values have changed. – Adrian Brand Jul 09 '20 at 03:52
  • I am in fact using the spread operator: `items: [...this.state.items, ...fakeItems]` – BizzyBob Jul 09 '20 at 03:52
  • Here is what my class looks like: [StackBlitz](https://stackblitz.com/edit/angular-child-input-with-async-pipe?file=src%2Fapp%2FDataStore.ts) – BizzyBob Jul 09 '20 at 03:52
  • Thanks for your help on this. I'll try adding `distinctUntilChanged()` and see what happens. It's weird to me that it doesn't work in my "real code" but the stack blitz does work :-\ – BizzyBob Jul 09 '20 at 03:54
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/217500/discussion-between-bizzybob-and-adrian-brand). – BizzyBob Jul 09 '20 at 04:04
  • I tried your `{{ items$ | async | json }}` trick in the parent component and I could see that it was NOT updating in the parent component. I noticed the observable is getting assigned in the `ngOnChanges` block, so I think my reference to the source `items$` observable is getting changed and my subscription is out of date. – BizzyBob Jul 10 '20 at 22:38
  • Now at least I know where the problem is (either the parent component, or the DataStore class). It's also good to know that using the async pipe in the child's @Input should work. – BizzyBob Jul 10 '20 at 22:38
  • Have a read of my article https://medium.com/@adrianbrand/angular-state-management-with-rxcache-468a865fc3fb I don't maintain that library anymore, I am working on a new one but the usage pattern is the same. Here is a demo of the new library https://stackblitz.com/edit/angular-kxfgrj I will be publishing in a few days. It should give you some tips on creating a store and usage patterns. – Adrian Brand Jul 10 '20 at 22:46