Creating a Dynamic Select with Angular Forms
When dealing with certain forms, we need to pre-populate the form with data for the user to select or choose. Commonly this data is loaded from an asynchronous source like an API request. In this example, we will see how to set the option values in an HTML select input from an asynchronous data source. Let's get started.
Our example we will create a simple select input that will iterate over a list of orders for a user to choose like the select dropdown below.
First, we are going to create our form and select using the Reactive Forms API. To use the Reactive Forms API we need to import the ReactiveFormsModule
into our application module.
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReactiveFormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
@NgModule({
imports: [BrowserModule, ReactiveFormsModule],
declarations: [AppComponent],
bootstrap: [AppComponent]
})
export class AppModule {}
Next, we need to create our form. To do this, we will use the FormBuilder
service which provides a convenient way to create forms.
import { Component } from '@angular/core';
import {
FormBuilder,
FormGroup,
FormArray,
FormControl,
ValidatorFn
} from '@angular/forms';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
form: FormGroup;
constructor(private formBuilder: FormBuilder) {
this.form = this.formBuilder.group({
orders: ['']
});
}
submit() {
console.log(this.form.value);
}
}
The form builder creates a FormGroup
instance. A form group contains one to many FormControl
s or individual user inputs. Our single input in this form will be our orders
select input.
For now, we are going to hard code our data into our component we will use to create our select dropdown.
import { Component } from '@angular/core';
import { FormBuilder, FormGroup, FormArray, FormControl, ValidatorFn } from '@angular/forms';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
form: FormGroup;
orders = [];
constructor(private formBuilder: FormBuilder) {
this.form = this.formBuilder.group({
orders: ['']
});
this.orders = this.getOrders();
}
getOrders() {
return [
{ id: '1', name: 'order 1' },
{ id: '2', name: 'order 2' },
{ id: '3', name: 'order 3' },
{ id: '4', name: 'order 4' }
];
}
submit() { ... }
}
We will loop over our orders data to build the select options in the HTML template.
<form [formGroup]="form" (ngSubmit)="submit()">
<label for="orders">Order</label>
<select formControlName="orders" id="orders">
<option *ngFor="let order of orders; let i = index" [value]="orders[i].id">
{{orders[i].name}}
</option>
</select>
<button>submit</button>
</form>
For each order in our data set, we create an option for the select. For each select, we bind the order id as the option value. One thing to note whenever you bind data to a select option the value will always be converted to a string.
Now that we have built our select from data dynamically let's update our code to handle updating the select from asynchronous data. We usually get data from an API request of some kind. In our example, we will simulate an API request using an Observable to emit our data.
import { Component } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { of } from 'rxjs';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
form: FormGroup;
orders = [];
constructor(private formBuilder: FormBuilder) {
this.form = this.formBuilder.group({
orders: ['']
});
// async orders
of(this.getOrders()).subscribe(orders => {
this.orders = orders;
});
}
getOrders() {
return [
{ id: '1', name: 'order 1' },
{ id: '2', name: 'order 2' },
{ id: '3', name: 'order 3' },
{ id: '4', name: 'order 4' }
];
}
submit() { ... }
}
Using the RxJS of()
operator we can convert our data to by async similar to if we requested the data via Angular's HTTP Service. We can subscribe to the async data and assign it to our orders property. We also need to update the select to have an initial default value once our async data has loaded. We can do this by using the patchValue
method on our form control.
of(this.getOrders()).subscribe(orders => {
this.orders = orders;
this.form.controls.orders.patchValue(this.orders[0].id);
});
You can find the full working example in the link below!