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:
- Subscribe for allData in the
ngOnInIt
itself and do check the value ofitems
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 thegetAllDataService
. - For child component, use double quotes to pass the value like this :
<child-component [selectedTab]="selectedTab"></child-component>
- 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>