Cory Rylan

My name is , Google Developer Expert, Speaker, Software Developer. Building Design Systems and Web Components.

Follow @coryrylan
Angular

Creating Dynamic Radio Lists with Angular Forms

Cory Rylan

- 3 minutes

Updated

This article has been updated to the latest version Angular 17 and tested with Angular 16. The content is likely still applicable for all Angular 2 + versions.

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 FormControls. 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!

View Demo Code   
Twitter Facebook LinkedIn Email
 

No spam. Short occasional updates on Web Development articles, videos, and new courses in your inbox.

Related Posts

Angular

Creating Dynamic Tables in Angular

Learn how to easily create HTML tables in Angular from dynamic data sources.

Read Article
Web Components

Reusable Component Patterns - Default Slots

Learn about how to use default slots in Web Components for a more flexible API design.

Read Article
Web Components

Reusable Component Anti-Patterns - Semantic Obfuscation

Learn about UI Component API design and one of the common anti-patterns, Semantic Obfuscation.

Read Article