DEV Community

Cory Rylan
Cory Rylan

Posted on • Originally published at coryrylan.com

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.

<label><input type="radio" name="orders" value="1" checked />order 1</label>
<label><input type="radio" name="orders" value="2" />order 2</label>
<label><input type="radio" name="orders" value="3" />order 3</label>
<label><input type="radio" name="orders" value="4" />order 4</label>
Enter fullscreen mode Exit fullscreen mode

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 { }
Enter fullscreen mode Exit fullscreen mode

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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() { ... }
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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) }
}
Enter fullscreen mode Exit fullscreen mode

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);
});
Enter fullscreen mode Exit fullscreen mode

If we log out the value of our form, we should see the id of the selected order
from the radio list.

{ orders: 100 }
Enter fullscreen mode Exit fullscreen mode

You can find the full working demo here!

Top comments (0)