简体   繁体   中英

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

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!

  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!

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.

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>

I managed to get it so that the passed value on the child side is no longer undefined with an ngIf, so:

<child-component *ngIf=selectedData [selectedData]="selectedData"></child-component>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM