Creating Dynamic Radio Lists with Angular Forms
When building real-world applications with complex forms, often we need to create the forms dynamically. Commonly, we need to load in data and construct the form based on that data. In this tutorial, we will see how to create a radio list from an asynchronous data source.
Our example we will create a simple radio list that iterates over a list of orders for a user to choose like the radio list below.
First, we need to create our form in Angular. The first step is to import the ReactiveFormsModule
into our application module. By importing the Reactive Forms Module into our application module we enable the Reactive Forms API for our entire application.
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 the form using the FormBuilder
service. This service makes it easy to create complex Angular forms and manage validation. To use the FormBuilder
service we use Angular's dependency injection system by listing the dependency in the constructor of our component. Angular instantiates and passes in an instance of the FormBuilder
service for the component to use.
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. The form group contains one to many FormControl
s. Our single input in this form is our orders
radio list input. Each control takes a default initial value and a list of one to many optional validation rules.
For now, we are going to hard code our data into our component we will use to create our radio list input.
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 loop over our orders data using ngFor to build the radio list items in the HTML template.
<form [formGroup]="form" (ngSubmit)="submit()">
<label *ngFor="let order of orders;">
<input
formControlName="orders"
type="radio"
name="orders"
[value]="order.id"
/>
{{order.name}}
</label>
<button>submit</button>
</form>
For each order in our data, we create a single radio input and bind the order id as the value for the individual radio input. One thing to note when you bind data to a value attribute in HTML the value is always treated as a string.
Now that we have our dynamic radio list created, let's make it get the data asynchronously. We usually get data from an API request. In this example, we simulate an API request using an Observable to emit our data.
import { Component } from '@angular/core';
import {
FormBuilder,
FormGroup,
FormArray,
FormControl,
ValidatorFn
} 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: ['']
});
// mimic async orders
of(this.getOrders()).subscribe(orders => {
this.orders = orders;
this.form.controls.orders.patchValue(this.orders[0].id);
});
}
getOrders() {
return [
{ id: 100, name: 'order 1' },
{ id: 200, name: 'order 2' },
{ id: 300, name: 'order 3' },
{ id: 400, name: 'order 4' }
];
}
submit() {
console.log(this.form.value);
}
}
Using the RxJS of()
operator, we can convert our data to by async similar to if we requested the data via Angular's HTTP Client Service. We subscribe to the async data and assign it to our orders property. We need to update the radio list 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);
});
If we log out the value of our form, we should see the id
of the selected order from the radio list.
{ orders: 100 }
You can find the full working example in the link below!