Building Dynamic Forms in Ionic 2

At Bendyworks we've been working on growing our in-house expertise in Ionic. In this blog I'm going to go over one of the more complex form structures available in Ionic 2, displaying dynamic options, and show you how to implement it!

Form Needs

  • We want to be able to use icon-only buttons as an input option.
  • We need our form to be able to dynamically display options in a dropdown select based on previous selections.

Set up a Form

First we're going to set up a basic form in my-car.html:

<ion-list>
  <form [formGroup]="carForm">
    <ion-item>
      <ion-label>My Car</ion-label>
    </ion-item>
    <ion-item>
      <ion-label>Make</ion-label>
      <ion-select formControlName="carMake">
        <ion-option value="ford">Ford</ion-option>
        <ion-option value="toyota">Toyota</ion-option>
        <ion-option value="tesla">Tesla</ion-option>
      </ion-select>
    </ion-item>
    <ion-item>
      <ion-label>Model</ion-label>
      <ion-select formControlName="carModel">
        <ion-option value="focus">Focus</ion-option>
        <ion-option value="escape">Escape</ion-option>
        <ion-option value="mustang">Mustang</ion-option>
        <ion-option value="fusion">Fusion</ion-option>
        <ion-option value="models">Model S</ion-option>
        <ion-option value="camry">Camry</ion-option>
        <ion-option value="prius">Prius</ion-option>
        <ion-option value="f150">F-150</ion-option>
        <ion-option value="model3">Model 3</ion-option>
        <ion-option value="corolla">Corolla</ion-option>
        <ion-option value="highlander">Highlander</ion-option>
        <ion-option value="yaris">Yaris</ion-option>
        <ion-option value="modelx">Model X</ion-option>
      </ion-select>
    </ion-item>
  </form>
</ion-list>
<button ion-button (click)="save()">Submit</button>

Next let's set up my-car.ts:

import { FormBuilder, FormGroup, Validators } from [email protected]/forms';
...
export class MyCarPage {
  carForm: FormGroup;
  make: string;
  model: string;

  constructor(public formBuilder: FormBuilder) {
    this.carForm = formBuilder.group({
      make: [''],
      model: ['']
    });
  }

  save(){
    console.log(this.carForm.value);
  }
}

As you can see, this basic form has 2 dropdown selects. First you are to select the make of your car and then the model. But the list of models is long and they are mixed between the various makes so its difficult to tell which model is a Ford, which is a Toyota and which is a Tesla. We can fix this and make this a more interesting form!

Icon-only Button Input

To make the form a little more interesting, let's change the selection of the car make to a button format with the car company logos on the buttons:

<ion-item>
  <ion-label>Make</ion-label>
  <ion-buttons>
    <button ion-button icon-only (click)="setMake('Ford')">
      <img src="assets/img/ford-logo.png" alt="Ford"/>
    </button>
    <button ion-button icon-only (click)="setMake('Toyota')">
      <img src="assets/img/toyota-logo.png" alt="Toyota"/>
    </button>
    <button ion-button icon-only (click)="setMake('Tesla')">
      <img src="assets/img/tesla-logo.png" alt="Tesla"/>
    </button>
  </ion-buttons>
</ion-item>

Dynamic Dropdown

Since we don't have a database right now, we'll create a mock database in a file called data-store.ts:

...
import { Car } from '../app/models/car';
import { Storage } from [email protected]/storage';

@Injectable()
export class DataStore {
  cars: Car[] = [];

  constructor(public http: Http, public storage: Storage){
    this.mockCars();
  }

  getCars(){
    return this.storage.get('cars');
  }

  mockCars(){
    let cars = [
      {id: '1', make: 'Ford', model: 'Focus'},
      {id: '2', make: 'Ford', model: 'Escape'},
      ...
    ]
    this.storage.set('cars', JSON.stringify(cars));
  }
}

Create a model for our car car.ts:

export class Car {
  id: string;
  make: string;
  model: string;

  constructor(id, make, model){
    this.id = id;
    this.make = make;
    this.model = model;
  }
}

Then import that into a car.service.ts:

import { Injectable, Component } from [email protected]/core';
import { DataStore } from '../../providers/data-store';
import { Car } from '../models/car';

@Injectable()
export class CarService {
  cars: Car[] = [];

  constructor(private dataStore: DataStore){}
}

Add the following to our my-car.ts. In the setMake() function, we are saving the value to a variable we can use later as well as setting the form value, creating a list of model options based on the make selected and creating a list of cars that match the make. Similarly, in the setModel() function, we're setting the model to a local variable and setting the form value.

  cars: Car[] = this.carService.cars;
  carList: Car[] = [];

setMake(m){
  this.make = m;
  this[carForm][make] = m;
  this.carList = [];
  this.setModelOptions();
  this.cars.forEach(function(car){
    if (car.make == m){
      carList.push(car)
    }
  });
}

setModel(m){
  this.model = m;
  this[carForm][model] = m;
}

setModelOptions(){
  let carModels = [];
  let make = this.make;
  if (this.make != null){
    this.cars.forEach(function(car){
      if (car.make == make && carModels.indexOf(car.model) < 0){
        carModels.push(car.model);
      }
    });
    return carModels;
  }
}

Lastly, change the dropdown select portion of the form in my-car.html to:

<ion-item>
  <ion-label>Model</ion-label>
  <ion-select formControlName="carModel" (ionChange)="setModel($event)">
    <ion-option *ngFor="let model of setModelOptions()" value="{{model}}">{{model}}</ion-option>
  </ion-select>
</ion-item>

Now when you load my-car.html you should see 3 buttons at the top with the car logos of the 3 companies and when you tap a button the car make is set in the form and the dropdown for models gets populated with the appropriate models for that make. Select your make and hit Submit!