Sorry, I'm not even sure if this is the right title for my question. I tried to keep the code as light as possible.
Basically, I have a FormArray of FormGroups that each consist of an Angular Material Autocomplete. I want the options for the autocompletes to be an Observable so that I can update the entire list of options whenever one of the FormGroups in the array is changed - basically, whenever an option is selected I don't want it to be able to be selected by any of the other FormGroup's in the array.
But, and this is where my new-ness to angular will shine, I am updating the list of options on valueChanges
, but when I push a new FormGroup on the array, I need a timeout that triggers a second valueChanges
in order for the autocomplete options to show up at all. If I don't have it in there, then the list doesn't show up in the most recently added FormGroup.
Why is this?
The code is here on stackblitz
Just click Add Input and you'll see a list of users in the autocomplete, but if you comment out line 72 and click Add Input, you won't see the list in the most recently added Form Group.
EDIT
It's going to be way easier to view the question by looking at the code on stackblitz, but because I got downvoted and close suggested, here's the component code:
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, FormControl, FormArray } from '@angular/forms';
import { switchMap } from 'rxjs/operators';
import { of } from 'rxjs'
const users = [
{
id:1,
name:'user1',
},
{
id:2,
name:'user2',
},
{
id:3,
name:'user3',
}
]
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
usersForm: FormArray;
users = users;
filteredUsers: Observable<any>;
selectedUsers = [];
canAddInput = true;
constructor(private fb: FormBuilder ) {}
ngOnInit() {
this.usersForm = this.fb.array([]);
this
.usersForm
.valueChanges
.subscribe(data => {
this.selectedUsers = data.map(d => d.userNameSelector).filter(d => !!d);
this.canAddInput = this.getUnselectedUsers().length !== 0 && this.usersForm.controls.length < this.users.length
})
//filtered users is an observable
this.filteredUsers = this
.usersForm
.valueChanges
.pipe(
switchMap(() => of(this.getUnselectedUsers()))
)
}
getUnselectedUsers()
{
//console.log(this.users.filter(u => this._isNotSelectedUser(u.name)));
return this.users.filter(u => this._isNotSelectedUser(u.name));
}
_isNotSelectedUser = user => !this.selectedUsers.includes(user)
addInput() {
this.usersForm.push(this.fb.group({userNameSelector:null}));
//why do I need this - it triggers usersForm.valueChanges and filteredUsers is updated, but won't the code work without it why?
setTimeout(() => this.usersForm.updateValueAndValidity(),1);
}
removeInput = idx => this.usersForm.removeAt(idx)
}
And the HTML:
<form class="example-form" [formGroup]='usersForm'>
<div *ngIf="usersForm.controls">
<div
*ngFor="let ctrlName of usersForm.controls; let i = index;" [formGroupName]="i">
<mat-form-field class="example-full-width">
<input
matInput
placeholder="Choose a user"
[matAutocomplete]="auto"
formControlName="userNameSelector"
>
</mat-form-field>
<!-- <span>Your choice is: {{usersForm.get(input.name).value | json}}</span> -->
<mat-autocomplete #auto="matAutocomplete">
<mat-option
<!-- this async pipe is going to be important here -->
*ngFor="let user of filteredUsers | async"
[value]="user.name"
>
<span>{{ user.name }}</span>
</mat-option>
</mat-autocomplete>
<button
mat-raised-button
color="warn"
(click)="removeInput(i)"
>Remove Input</button>
</div>
</div>
<button
*ngIf="canAddInput"
mat-raised-button color="primary"
(click)="addInput()"
>Add Input</button>
</form>