Home > other >  How to pass data to child component's @Input from Observable
How to pass data to child component's @Input from Observable

Time:09-05

I have an angular component that I use as a tab in a for loop on the html page:

...
<ng-container *ngFor="let tabData of data$ | async;">
  <tab-component
   id="{{ tabData.id }}"
   name="{{ tabData.name }}"
  >
  </tab-component>
</ng-container>
<child-component [selectedData]="selectedData"></child-component>

And in the .ts file:

public data$: Observable<Data[]>
public selectedData: Data

ngOnInit() {
  this.data$ = this.service.getAllData();
}
ngAfterContentInit() {
  this.data$.subscribe(items => this.selectedData = items[0])
}

I would like the first tab to always be the selectedData by default when first loading the page (element 0 in the array). Then on click or the right/left arrow keys, dynamically update the value of selectedData passed to the child component. So far, I've tried everything and the value of selectedData in the child component has always been undefined

Please help me, how can I achieve this!

CodePudding user response:

  1. Subscribe for allData in the ngOnInIt itself and do check the value of items before assigning it - whether you are getting it or not and if you are not able to find the value there, then there must be the issue with the getAllDataService.
  2. For child component, use double quotes to pass the value like this : <child-component [selectedTab]="selectedTab"></child-component>
  3. Create a dummy variable in the parent ( or a hardcoded value ) and pass it to child. If your child component is working fine, then there's issue with only data and how you are assigning it.

Hope this helps!

CodePudding user response:

Where exactly are you using the selectedData in your template HTML file?

In the snippet you provided there is a selectedTab used, but no selectedData anywhere...

<ng-container *ngFor="let tabData of data$ | async;">
  <tab-component
   id="{{ tabData.id }}"
   name="{{ tabData.name }}"
  >
  </tab-component>
</ng-container>
<child-component [selectedTab]=selectedTab></child-component>

Also, you can follow @Eugene's advice and do:

ngOnInit() {
   this.data$ = this.service.getAllData().pipe(
      tap((items) => this.selectedData = items[0])
   );
}

without using ngAfterContentInit() and the need to subscribe a second time.

CodePudding user response:

You could use a subject to express the currently selected tab data, then use combineLatest to create an observable of both sources.

private data$: Observable<Data[]> = this.service.getAllData();
private selectedData$ = new BehaviorSubject<Data>(undefined);

vm$ = combineLatest([this.data$, this.selectedData$]).pipe(
    map(([tabData, selected]) => ({
        tabData,
        selectedTab: selected ?? tabData[0]
    })
);

setSelected(data: Data) {
  this.selectedData$.next(data);
}

Here we create a single observable that the view can use (a view model) using combineLatest. This observable will emit whenever either of its sources emit.

We set the selectedData$ BehaviorSubject to emit an initial value of undefined. Then, inside the map, we set the selectedTab property to use tabData[0] when selected is not yet set. So, initially, it will use tabData[0], but after setSelected() gets called, it will use that value.

<ng-container *ngIf="let vm$ | async as vm">

  <tab-component *ngFor="let tabData of vm.tabData"
    [id]    = "tabData.id"
    [name]  = "tabData.name"
    (click) = "setSelected(tabData)">
  </tab-component>

  <child-component [selectedTab]="vm.selectedTab"></child-component>

</ng-container>
  • Related