I'm trying to update an RxJS Subject via a component's method . But it is not happening properly .
cricketData$ = new Subject<string>();
ngOnChanges() {
this.cricketData$
.asObservable()
.pipe(
tap(() =>
console.log(
'Triggering REST call for cricket tab with the year ',
this.year
)
),
take(1)
)
.subscribe();
this.cricketData$.next('sachin');
}
updateCricketData() {
this.cricketData$.next('dravid');
}
If I remove the take operator , subject is updated . But multiple service calls are triggered while selecting the values from dropdown . Is there any other operator / approach in RxJS using which I can achieve the below scenarios?
- service calls are triggered only once while selecting the values from dropdown
- subject is also updated via component's method
Please refer the below StackBlitz link . Thanks in advance .
Editor URL Stackblitz: EditorURL
App URL Stackblitz: AppURL
CodePudding user response:
There are two issues.
First:
ngOnChanges
triggers multiple times, and every time you change the value you are adding a new pipe working parally.
take(1)
makes your previous pipe spot and only the recent pipe tab
is triggered.
ngOnInit(): void { // runs once
this.cricketData$
.asObservable()
.pipe( // runs on every next
tap(() =>
console.log(
'Triggering REST call for cricket tab with the year ',
this.year
)
)
)
.subscribe();
}
ngOnChanges() {
this.cricketData$.next('sachin'); // runs on every event on the page
}
Second issue: Do you really want to trigger this function on ngChange when every there a update on the page rather than just the dropdown, if the answer is only when there is a change in the dropdown then you can do this.
you have to use a combination of Input set year(value: string)
export class CricketComponent implements OnChanges {
private _selectedYear: string;
@Input() set year(value: string) {
this._selectedYear = value;
this.cricketData$.next('sachin'); // will trigger with every drop down change
}
cricketData$ = new Subject<string>();
constructor() {}
ngOnInit(): void {
this.cricketData$
.asObservable()
.pipe(
tap((value) =>
console.log(
'Triggering REST call for cricket tab with the year ',
this._selectedYear,
value
)
)
)
.subscribe();
}
ngOnChanges() {}
updateCricketData() {
this.cricketData$.next('dravid');
}
}
CodePudding user response:
How about using a formcontrol for the year selector, we can listen to the changes of the formcontrol and the subject you have created with combineLatest
, which will trigger when either of them emit (as long as they have emitted at least once). Don't know your exact case of how/when your subject is updated initially, so I just added a startWith(null)
value. Apply it to your usecase...
So the formcontrol for the parent:
year = new FormControl(2019);
and template where we mark the form control as well as pass the formcontrol to the child:
<select [formControl]="year">
<option value="2019">2019</option>
<option value="2020">2020</option>
<option value="2021">2021</option>
</select>
<app-cricket [year]="year"></app-cricket>
Then the child listens to this in OnInit
together with the Subject:
ngOnInit() {
combineLatest([
this.year.valueChanges.pipe(startWith(this.year.value)),
this.cricketData$.asObservable().pipe(startWith(null)),
]).pipe(
switchMap(values => {
return REST API request...
})
).subscribe(result => console.log(result))
}
Using startWith
to make them trigger initially. Remember to unsubscribe!!
STACKBLITZ (without the switchMap
)