Reputation: 113
I do an async service call to get a list of elements.
public elementList: Array<Element>;
...
this.service.getElementList().subscribe( list => {
this.elementList = list;
this.createFormGroup(list);
}
);
...
When I receive the list of elements (I have a subscription the the service call) I build the Form Group based on the list of elements. In the template, I have an *ngIf to paint the only if the list is bigger than zero.
*ngIf="elementList?.length>0"
As the elementList is obtained asynchronously, this ngIf does not work. I have read that I can use ngIf with AsnycPipe. So in the template I will have:
*ngIf="elementList$ && elementList$.length>0"
where elementList$ is the Observable returned from the service call.
public elementList$: Observable<Array<Element>>;
elementList$ =this.service.getElementList()
I would like to know how can I create the FormGroup once I receive the elementList. First I need to create the FormGroup and then the template should call the *ngIf that will paint the elements.
Upvotes: 6
Views: 5386
Reputation: 113
Based on the answer of @SiddAjmera, I have built a third option that meets my needs.
formGroup: FormGroup;
elementList$: Observable<Array<Element>>;
Once I have the API response:
this.elementList$ = this.service.getElements()
.pipe(
map((list: Array<Element>) => {
this.formGroup = this.fb.group({
elementId: [list[0].id],
elementDescription: [list[0].description]
});
return list;
})
);
And then in the template I have this:
<form
*ngIf="(elementList$ | async) as list"
[formGroup]="formGroup" (ngSubmit)="onSubmit()">
<label for=''>Element Id: </label>
<select formControlName="elementId">
<option
*ngFor="let element of list"
[value]="element.id">
{{ element.id }}
</option>
</select>
<br>
<label for=''>Element description: </label>
<select formControlName="elementDescription">
<option
*ngFor="let element of list"
[value]="element.description">
{{ element.description }}
</option>
</select>
<br>
<button type="submit">Click</button
</form>
In the component, when I click the button I access the form group to get the values:
public onSubmit() {
console.log(this.formGroup.toString());
}
Upvotes: 5
Reputation: 39462
There can be many different approaches to go about doing this. I'll just share two approaches at the moment and if you feel any of these approaches don't satisfy your use case, we can consider a different approach(provided that you give more information on what exactly you're trying to achieve here).
You have two properties on your Component:
form$: Observable<FormGroup>;
list: Array<Element>;
Once you have the API response, instead of subscribe
ing to it, you map
it and generate a form while also assigning the value of the response to the list
property that you've declared. Something like this:
this.form$ = this.service.getElements()
.pipe(
map((list: Array<Element>) => {
this.list = list;
return this.fb.group({
elementId: [list[0].id],
elementDescription: [list[0].description]
});
})
);
And then you use it in the template, somewhat like this:
<form
*ngIf="form$ | async as form"
[formGroup]="form">
<label for="">Element Id: </label>
<select formControlName="elementId">
<option
*ngFor="let element of list"
[value]="element.id">
{{ element.id }}
</option>
</select>
<br>
<label for="">Element description: </label>
<select formControlName="elementDescription">
<option
*ngFor="let element of list"
[value]="element.description">
{{ element.description }}
</option>
</select>
</form>
You might want to club both the list and the FormGroup
together. So you can create a single property in your Component:
elementListWithForm$: Observable<{ list: Array<Element>, form: FormGroup }>;
You'd then assign a value like this:
this.elementListWithForm$ = this.service.getElements()
.pipe(
map((list: Array<Element>) => ({
form: this.fb.group({
elementId: [list[0].id],
elementDescription: [list[0].description]
}),
list,
}))
);
And then you can use it in the template like this:
<form
*ngIf="(elementListWithForm$ | async) as formWithList"
[formGroup]="formWithList.form">
<label for="">Element Id: </label>
<select formControlName="elementId">
<option
*ngFor="let element of formWithList.list"
[value]="element.id">
{{ element.id }}
</option>
</select>
<br>
<label for="">Element description: </label>
<select formControlName="elementDescription">
<option
*ngFor="let element of formWithList.list"
[value]="element.description">
{{ element.description }}
</option>
</select>
</form>
Here's a Working Code Sample on StackBlitz for your ref.
PS: This approach is heavily inspired by the one I used in an article that I wrote about increasing the performance of a deeply nested reactive form in Angular for AngularInDepth. You might want to check that out as well.
Hope this helps. :)
Upvotes: 11
Reputation: 26811
First off (but just a nitpick), I believe that the correct terminology for "painting" elements would be "rendering" elements. (To be honest, I'm fine with what terminology you use.)
Regarding the issue itself, you can check the length of the list by piping the Observable
list through the async
pipe and then check the length of that piped list:
*ngIf="elementList$ && (elementList$ | async)?.length > 0"
Upvotes: 0