I have a search engine and I need to register observable and gets dropdown autocomplete suggestion but even if observable is triggered which I can see inside tap I can't seem to make the list load inside my template dropdown
here is my code:
<mat-form-field>
<mat-chip-grid #chipGrid aria-label="Value selection">
@for (fruit of fruits(); track $index) {
<mat-chip-row (removed)="remove(fruit)">
//logic for fow
</mat-chip-row>
}
</mat-chip-grid>
<input placeholder="Start typing to see suggestions" #fruitInput formControlName="value"
[matChipInputFor]="chipGrid" [matAutocomplete]="auto"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"/>
</mat-form-field>
<div *ngIf="getChipsValueDropdowns(select.value, i) | async as accounts">
<mat-autocomplete #auto="matAutocomplete">
<mat-option *ngFor="let account of accounts"
[value]="account">{{account.name}}</mat-option>
</mat-autocomplete>
</div>
<mat-autocomplete #auto="matAutocomplete">
<mat-option> Loading ...</mat-option>
</mat-autocomplete>
And in my ts file I have function:
getChipsValueDropdowns(key: string, index: number): Observable<Account[]> {
if (key === 'account') {
return this.registerAccountListener(key, index)
}
return of([])
}
registerAccountListener(key: string, index: number) {
const control: FormControl = this.formArray.controls[index].get('key') as FormControl
if (control.value === key) {
return this.accountService.searchAccounts('SHE').pipe(
debounceTime(300),
distinctUntilChanged(),
map((response: { body: Account[] | null }) => {
return response.body ?? []
}),
tap((accounts) => {
console.log('Emitting accounts:', accounts)
}),
)
}
return of([])
}
The problem is that you are calling the getChipsValueDropdowns()
function in the template, and every time the function runs it returns a new Observable. When the Observable returns a value, the async pipe marks the component for check and triggers a new change detection cycle, which calls the function again and re-starts the process. This is very bad practice, as it will trigger a lot of requests and potentially even an endless loop. You have worked around this issue by using the distinctUntilChanged()
operator on the actual request, but it only treats the symptoms of this issue.
Instead, you should combine the valueChanges
observables from the form and the Observable which performs the request with a switchMap()
:
export class YourComponent {
drowdownValues$ = yourFormGroup.controls.select.valueChanges.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap((value) => {
const control: FormControl = this.formArray.controls[index].get(
"key",
) as FormControl;
if (control.value === key) {
return this.accountService.searchAccounts("SHE").pipe(
map((response: { body: Account[] | null }) => {
return response.body ?? [];
}),
tap((accounts) => {
console.log("Emitting accounts:", accounts);
}),
);
}
return of([]);
}),
);
}
And use the Observable in your template like the following:
<div *ngIf="drowdownValues$ | async as accounts">