2
votes

Disclaimer: this is closely related to this question.

Currently I have two components, Parent and Child. The Parent passes an Observable to the Child, which subscribes to this Observable in the template to avoid lifecycle/memory leak issues. But I have to pipe the Observable in the Child component to set up a few things.

parent.ts:

public $obs = Observable<any>;
....
loadData() {
   this.obs$ = this.myService.getData();
}

parent.html:

<child [data$]="obs$"></child>

child.ts:

@Input() data$ : Observable<any>;

ngOnChanges(changes) {

  if(changes.data$) {
    console.log("changes detected");
    this.data$.pipe(tap(data => console.log("tap worked")));
  }
}

child.html

<div *ngIf="data$ | async as data;">
{{ data || json }}
</div>

The data is present in the template, the console log shows that the changes have been detected, but the "tap worked" is never shown, leading me to suspect that maybe my call to pipe comes too late. Is there any way to pipe before the template "calls" subscribe?

The idea is having many components similiar to child, all doing different things on the data object. I thought about subscribing to the service in the Parent and passing the json values directly to all childs, but that would require me to write up an *ngIf in all childs.

3
This sounds confusing, but you could write a pipe Angular pipe which takes a rxjs pipe() (the static one from the rxjs entry point) as its argument and similarly to the async pipe takes care of dealing with when the input observable changes. Then use it like data$ | pipe: yourPipes | async with yourPipes = pipe(tap(...)) - Ingo Bürk
As for your current attempt, operators always return a new observable, they don't mutate the original one. Also I don't actually recommend my approach above, but it should work. I'd probably prefer not passing observables as inputs since that generally requires assumptions in the child component, eg about it being replayed. - Ingo Bürk
Could it be that you are declaring a variable named $obs, and assigning the output of this.myService.getData() to this.obs$? Or it that just a typo in the post? - R. Richards
@R.Richards no thats not a typo, thats what I'm doing. Is that wrong? - Hafnernuss
@IngoBürk thanks for your suggestion. This sounds... a bit overly complicated. For now I am passing the plain data to the children. - Hafnernuss

3 Answers

1
votes

I suggest, that you don't subscribe to data$ directly. Instead create another Observable you can read.

@Input() data$: Observable<any>;
customData$: Observable<any>;

ngOnChanges(changes) {
  if(changes.data$) {
    console.log("changes detected");
    this.customData$ = this.data$.pipe(tap(data => console.log("tap worked")));
  }
}
<div *ngIf="customData$ | async as data;">
  {{ data || json }}
</div>
0
votes

Observables by nature are lazy. They are not executed until you subscribe to them. To see the "tap worked" you need to subscribe to it.

this.data$.pipe(tap(data => console.log("tap worked"))).subscribe();

The async by default subscribes and unsubscribes the observable for you, also do note that each subscriber in observable get a new execution context and this behaviour can be overriden.

To cache result in case it's subscribe late you can use either BehaviourSubject or ReplaySubject in your service.

0
votes

I think the problem is in your ngOnChanges. There you have the condition

  if(changes.data$) {
    console.log("changes detected");
    this.data$.pipe(tap(data => console.log("tap worked")));
  }

this will check if the reference of data$ will change but NOT if data$ emits new values what you actually want. Because of that your tap is not executed.

Instead you could

 public data$: Observable<any>;
  @Input() set data(data$: Observable<any>) {
    this.data$ = data$.pipe(tap(x => console.log("tap worked")));
  }