Angular Select Option + Multiple Select Option + Validation Example using Template-Driven Form
October 18, 2021
This page will walk through Angular select option, multiple select option and its validation example using template-driven form. Here we will provide end-to-end demo for how to populate select option and multiple select options and to validate them. In Angular select element, the ngValue
attribute of <option>
element binds an object using property binding. For validation of select element, we need to use validation attribute such as required
. We can get validation state like valid and invalid using local template variable. Select element is created using <select>
tag and multiple select element uses multiple
attribute as <select multiple>
. By default, select element compares two objects using object identity. If we want to change the default behavior, we need to use compareWith
input in select element. Now find the complete example step-by-step.
Contents
Technologies Used
Find the technologies being used in our example.1. Angular 12.1.0
2. Node.js 12.14.1
3. NPM 7.20.3
Project Structure
Find the project structure of our example.angular-demo | |--src | | | |--app | | | | | |--domain | | | | | | | |--profile.ts | | | |--technology.ts | | | |--user.ts | | | | | |--services | | | | | | | |--user-service.ts | | | | | |--user-component | | | | | | | |--user.component.ts | | | |--user.component.html | | | |--user.component.css | | | | | |--app.component.ts | | |--app.module.ts | | | |--main.ts | |--index.html | |--styles.css | |--node_modules |--package.json
Select Option Dropdown
Angular provides following directive to use<select>
element in our form.
SelectControlValueAccessor
FormsModule
and ReactiveFormsModule
. It writes values and listen changes for select element. Now find the following steps to use <select>
element.
Step-1: Import
FormsModule
using imports
metadata of @NgModule
decorator in application module.
Step-2: Create an array of data that will be displayed in
<select>
dropdown. For example we have created a class to store data.
export class Profile { constructor(public prId:string, public prName:string) { } }
Profile
class.
allProfiles = [ new Profile('dev', 'Developer'), new Profile('man', 'Manager'), new Profile('dir', 'Director') ]
<select>
element. Here we will iterate our array allProfiles
to populate data in dropdown. To assign value to option
element, we need to use ngValue
property. We can assign our object to ngValue
using property binding. Now find the code snippet to use <select>
element.
<select name="profile" ngModel> <option *ngFor="let prf of allProfiles" [ngValue]="prf"> {{ prf.prName }} </option> </select>
<select>
tag using which we will access its selected value on form submit. To register our <select>
element with NgForm
, <select>
must have ngModel
attribute or one/two way binding with ngModel
.
Step-4: On form submit we can fetch the value of selected data using the instance of
NgForm
. Suppose the form
is the instance of NgForm
, now we can access selected value as follows.
let userProfile: Profile = form.controls['profile'].value; console.log("Profile Id: " + userProfile.prId); console.log("Profile Name: " + userProfile.prName);
<select>
element we have populated complete object of Profile
class. So when we select data, we get complete Profile
object.
We can also fetch selected value using two-way binding with
ngModel
.
<select name="profile" [(ngModel)]="userProfile">
userProfile
will give selected value.
Multiple Select Option
Angular provides following directive to handle multiple selection option.SelectMultipleControlValueAccessor
FormsModule
and ReactiveFormsModule
. To enable multiple selection, we need to use multiple
attribute as <select multiple>
. Find the steps to use it.
Step-1: Create an array of data that will be populated in multiple select option. For example we have a class as follows.
export class Technology { constructor(public techId:number, public techName:string) { } }
Technology
class.
allTechnologies = [ new Technology(100, 'Java'), new Technology(101, 'Angular'), new Technology(102, 'Hibernate'), new Technology(103, 'Spring') ]
<select multiple>
element.
<select multiple name="selectedTechs" ngModel> <option *ngFor="let tech of allTechnologies" [ngValue]="tech"> {{ tech.techName }} </option> </select>
<select multiple>
must have ngModel
attribute or one/two way binding with ngModel
to register with NgForm
. The elements from allTechnologies
array will be populated and a user can select multiple values at one time.
Step-3: On form submit we can fetch multiple selected values. Suppose
form
is the instance of NgForm
. Now find the code snippet.
let userTechnologies: Technology[] = form.controls['selectedTechs'].value; for (let i=0; i < user.technologies.length; i++) { console.log("Technology Id: " + user.technologies[i].techId); console.log("Technology Name: " + user.technologies[i].techName); }
ngModel
.
<select multiple name="selectedTechs" [(ngModel)]="technologies">
technologies
will be an array of all selected values.
Select Option Binding and Default Value
Select option binding can be achieved by one/two way binding withngModel
. Suppose we want following value selected as default.
this.user.profile = this.allProfiles[2];
<select name="profile" [ngModel]="user.profile"> <option [ngValue]="null" disabled>Choose your profile</option> <option *ngFor="let prf of allProfiles" [ngValue]="prf"> {{ prf.prName }} </option> </select>
<select multiple>
element in the same way as above using one/two way binding with ngModel
. Find the code snippet.
<select multiple name="selectedTechs" [ngModel]="user.technologies"> <option *ngFor="let tech of allTechnologies" [ngValue]="tech"> {{ tech.techName }} </option> </select>
Select Option Validation
To validate select and multiple select options, we need to use validation attribute with them such as for required validation we need to userequired
attribute in <select>
element and <select muliple>
element.
We can use local template variable to fetch validation state. Local template variable can call following boolean properties to know validation state.
1. When
touched
returns true then untouched
returns false.
2. When
dirty
returns true then pristine
returns false.
3. When
valid
returns true then invalid
returns false.
We can use above values with local template variable. Now create the local template variable in select and multiple select options. Find the code snippet for
<select>
element with required
attribute.
<select name="profile" ngModel required #profile="ngModel">
#profile="ngModel"
is creating a local template variable named as profile
. We can get validation state with local template variable such as profile.touched
, profile.invalid
etc and they will return boolean values that can be used for validation message. The validation message can be printed as follows.
<div *ngIf="profile.invalid"> Profile required. </div>
<select multiple>
element. Find the code snippet of <select multiple>
element with required
attribute.
<select multiple name="selectedTechs" ngModel required #selectedTechs="ngModel">
#selectedTechs="ngModel"
is creating a local template variable named as selectedTechs
. We can get validation state with local template variable such as selectedTechs.pristine
, selectedTechs.valid
etc and they will return boolean values that can be used for validation message. The validation message can be printed as follows.
<div *ngIf="selectedTechs.invalid"> Technologies required. </div>
Using compareWith in Select Option
Angular uses object identity by default to select options but in some scenarios there can be discrepancies when object values remains same but object identity is changed.From the Angular Doc,
"Angular uses object identity to select options. It's possible for the identities of items to change while the data does not. This can happen, for example, if the items are produced from an RPC to the server, and that RPC is re-run. Even if the data hasn't changed, the second response will produce objects with different identities."
To handle such scenarios, angular provides
compareWith
input with <select>
and <select multiple>
element. We will bind a method with compareWith
to change default logic of object comparison. Find the code snippet.
<select multiple name="selectedTechs" [compareWith]="compareTech" [ngModel]="user.technologies"> <option *ngFor="let tech of allTechnologies" [ngValue]="tech"> {{ tech.techName }} </option> </select>
compareTech(t1: Technology, t2: Technology): boolean { return t1 && t2 ? t1.techId === t2.techId : t1 === t2; }
<select multiple>
element and we can see that our compareTech()
method will be called.
Select Option Change Event
Select option change event is called when selection of item is changed. Change event is used as follows.<select name="profile" [(ngModel)]="user.profile" (change)="onProfileChange()"> --- </select>
onProfileChange() { console.log('Profile Changed: '+ this.user.profile.prName); }
Complete Example
user.component.html<h3>User Information</h3> <p *ngIf="isValidFormSubmitted" [ngClass] = "'success'"> Form submitted successfully. </p> <form #userForm="ngForm" (ngSubmit)="onFormSubmit(userForm)"> <table> <tr> <td>Select Profile: </td> <td> <select name="profile" [(ngModel)]="user.profile" required #profile="ngModel" (change)="onProfileChange()"> <option [ngValue]="null" disabled>Choose your profile</option> <option *ngFor="let prf of allProfiles" [ngValue]="prf"> {{ prf.prName }} </option> </select> <div *ngIf="profile.invalid && userForm.submitted && !isValidFormSubmitted" [ngClass] = "'error'"> Profile required. </div> </td> </tr> <tr> <td>Name:</td> <td> <input name="name" [ngModel]="user.userName" required #name="ngModel"> <div *ngIf="name.invalid && userForm.submitted && !isValidFormSubmitted" [ngClass] = "'error'"> Name required. </div> </td> </tr> <tr> <td>Select Technologies: </td> <td> <select multiple name="selectedTechs" [compareWith]="compareTech" [ngModel]="user.technologies" required #selectedTechs="ngModel"> <option *ngFor="let tech of allTechnologies" [ngValue]="tech"> {{ tech.techName }} </option> </select> <div *ngIf="selectedTechs.invalid && userForm.submitted && !isValidFormSubmitted" [ngClass] = "'error'"> Technologies required. </div> </td> </tr> <tr> <td colspan="2"> <button>Submit</button> <button type="button" (click)="setDefaultValues()">Set Default</button> <button type="button" (click)="resetForm(userForm)">Reset</button> </td> </tr> </table> </form>
import { Component, OnInit } from '@angular/core'; import { NgForm } from '@angular/forms'; import { UserService } from '../services/user-service'; import { Profile } from '../domain/profile'; import { Technology } from '../domain/technology'; import { User } from '../domain/user'; @Component({ selector: 'app-template', templateUrl: './user.component.html', styleUrls: ['./user.component.css'] }) export class UserComponent implements OnInit { isValidFormSubmitted = false; allProfiles: Profile[] = []; allTechnologies: Technology[] = []; user = {} as User; constructor(private userService: UserService) { } ngOnInit(): void { this.allProfiles = this.userService.getPofiles(); this.allTechnologies = this.userService.getTechnologies(); } onFormSubmit(form: NgForm) { this.isValidFormSubmitted = false; if (form.valid) { this.isValidFormSubmitted = true; } else { return; } let userName = form.controls['name'].value; let userProfile: Profile = form.controls['profile'].value; let userTechnologies: Technology[] = form.controls['selectedTechs'].value; let newUser = {} as User; newUser.userName = userName; newUser.profile = userProfile; newUser.technologies = userTechnologies; this.userService.createUser(newUser); this.resetForm(form); } resetForm(form: NgForm) { form.resetForm(); this.user.profile = null; this.user.userName = ''; } setDefaultValues() { this.user.userName = "Narendra Modi"; this.user.profile = this.allProfiles[2]; this.user.technologies = [ this.allTechnologies[1], this.allTechnologies[3] ]; } onProfileChange() { console.log('Profile Changed: ' + this.user.profile?.prName); } compareTech(t1: Technology, t2: Technology): boolean { console.log(t1.techId + '-' + t2.techId); return t1 && t2 ? t1.techId === t2.techId : t1 === t2; } }
table { border-collapse: collapse; } table, th, td { border: 1px solid black; } .error { color: red; } .success { color: green; }
export class Profile { constructor(public prId:string, public prName:string) { } }
export class Technology { constructor(public techId:number, public techName:string) { } }
import { Profile } from './profile'; import { Technology } from './technology'; export interface User { userName: string; profile: Profile | null; technologies: Technology[]; }
import { Injectable } from '@angular/core'; import { Profile } from '../domain/profile'; import { Technology } from '../domain/technology'; import { User } from '../domain/user'; @Injectable() export class UserService { getPofiles(): Profile[] { let profiles = [ new Profile('dev', 'Developer'), new Profile('man', 'Manager'), new Profile('dir', 'Director') ] return profiles; } getTechnologies(): Technology[] { let technologies = [ new Technology(100, 'Java'), new Technology(101, 'Angular'), new Technology(102, 'Hibernate'), new Technology(103, 'Spring') ] return technologies; } createUser(user: User) { //Log user data in console console.log("User Name: " + user.userName); console.log("Profile Id: " + user.profile?.prId); console.log("Profile Name: " + user.profile?.prName); for (let i=0; i < user.technologies.length; i++) { console.log("Technology Id: " + user.technologies[i].techId); console.log("Technology Name: " + user.technologies[i].techName); } } }
import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: ` <app-template></app-template> ` }) export class AppComponent { }
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { AppComponent } from './app.component'; import { UserComponent } from './user-component/user.component'; import { UserService } from './services/user-service'; @NgModule({ imports: [ BrowserModule, FormsModule ], declarations: [ AppComponent, UserComponent ], providers: [ UserService ], bootstrap: [ AppComponent ] }) export class AppModule { }
Run Application
To run the application, find the following steps.1. Download source code using download link given below on this page.
2. Use downloaded src in your Angular CLI application. To install Angular CLI, find the link.
3. Run ng serve using command prompt.
4. Now access the URL http://localhost:4200
Find the print screen.

References
SelectControlValueAccessorSelectMultipleControlValueAccessor