Angular Select Option using Reactive Form

By Arvind Rai, January 08, 2024
This page will walk through Angular select dropdown example using reactive form. We will provide end to end demo for how to populate select dropdown and multiple select options. In Angular select element, the value attribute of <option> element can bind an object using property binding. For validation of select element, we need to use validation attribute such as required as select element attribute or Validators.required in FormControl. 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.

1. Create a FormGroup

We will create a FormGroup using FormBuilder.
userForm: FormGroup; 
this.userForm = this.formBuilder.group({
	profile: [null, [ Validators.required ] ],
	userName: ['', [ Validators.required ] ],
	technologies:  [null, [ Validators.required ] ]
});	
In the above form, profile form control will be used for single select dropdown and technologies form control will be used for multiple select options in our demo.

2. Single Select Option

In Angular, SelectControlValueAccessor writes values and listens changes for select element. This directive works for both FormsModule and ReactiveFormsModule.
Find the steps to use <select> element for single selection.
Step-1: Import ReactiveFormsModule 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) {
    }	
} 
Now create an array of Profile class.
allProfiles = [
    new Profile('dev', 'Developer'),
    new Profile('man', 'Manager'),
    new Profile('dir', 'Director')
] 
Step-3: Find the <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. Find the code snippet to use <select> element.
<select formControlName="profile">
  <option [ngValue]="null" disabled>Choose your profile</option>
  <option *ngFor="let prf of allProfiles" [ngValue]="prf">
   {{ prf.prName }}
  </option>
</select> 
Assign form control name to formControlName which will store selected value.
Step-4: On form submit we can fetch the value of selected data using the FormGroup instance i.e. userForm.
let newUser: User = this.userForm.value; 
In the <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 userForm.get('profile').

3. Multiple Select Option

Angular provides following directive to handle multiple selection option.
SelectMultipleControlValueAccessor 
The role of above directive is writing a value and listening to changes. It works for both module 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) {
    }
} 
Now create an array of Technology class.
allTechnologies = [
    new Technology(100, 'Java'),
    new Technology(101, 'Angular'),
    new Technology(102, 'Hibernate'),
    new Technology(103, 'Spring')		 
] 
Step-2: Find the code snippet for <select multiple> element.
<select multiple formControlName="technologies">
  <option *ngFor="let tech of allTechnologies" [ngValue]="tech">
   {{ tech.techName }}
  </option>
</select> 
Assign form control name to formControlName which will store all selected values. The elements from allTechnologies array will be populated and a user can select multiple values at one time. The selected values can be fetched using userForm.get('technologies').

4. Default Selected Value

We can set values to select options as usual by using setValue or patchValue of FormGroup.
setDefaultValues() {
	let user = new User();
	user.userName = "Narendra Modi";
	user.profile = this.allProfiles[2];
	user.technologies = [
		this.allTechnologies[1],
		this.allTechnologies[3]
	];
	this.userForm.setValue(user);
} 
Call the above method by any user action to set default values.

5. Select Option Validation

For validation of select element, we need to use either validation attribute such as required as select element attribute or Validators such as Validators.required in FormControl. Suppose we want to validate select box using Validators. Our select box form control will look like as below.
this.userForm = this.formBuilder.group({
	profile: [null, [ Validators.required ] ],
	technologies:  [null, [ Validators.required ] ],
	------
});	
Now display error messages.
<select formControlName="profile">
	<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.dirty" class="error">
	Profile required.
</div>

<select multiple formControlName="technologies">
	<option *ngFor="let tech of allTechnologies" [ngValue]="tech">
		{{ tech.techName }}
	</option>
</select>
<div *ngIf="technologies.invalid && userForm.dirty" class="error">
	Technologies required.
</div> 

6. 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. 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 formControlName="technologies" [compareWith]="compareTech">
	<option *ngFor="let tech of allTechnologies" [ngValue]="tech">
		{{ tech.techName }}
	</option>
</select> 
Find our custom object comparison logic, normally id is unique, so we can build our identity comparison logic using data id as follows.
compareTech(t1: Technology, t2: Technology): boolean {
  return t1 && t2 ? t1.techId === t2.techId : t1 === t2;
} 
To test this feature in our example, set values in <select multiple> element using setValue or patchValue and we can see that our compareTech() method will be called.

7. Select Option Change Event

Select option change event is called when selection of item is changed. Change event is used as follows.
<select formControlName="profile" (change)="onProfileChange()">
  ---
</select> 
Now find the method that will be called on change event.
onProfileChange() {
   let profile: Profile = this.profile.value;	
   console.log('Profile Changed: ' + profile.prName);
} 

8. Complete Example

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 
Now find the complete code.
user.component.ts
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, Validators } 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[];
	userForm: FormGroup;

	constructor(private formBuilder: FormBuilder, private userService: UserService) { }
	ngOnInit(): void {
		this.userForm = this.formBuilder.group({
			profile: [null, [Validators.required]],
			userName: ['', [Validators.required]],
			technologies: [null, [Validators.required]]
		});
		this.allProfiles = this.userService.getPofiles();
		this.allTechnologies = this.userService.getTechnologies();
	}
	get profile() {
		return this.userForm.get('profile');
	}
	get userName() {
		return this.userForm.get('userName');
	}
	get technologies() {
		return this.userForm.get('technologies');
	}
	onFormSubmit() {
		this.isValidFormSubmitted = false;
		if (this.userForm.valid) {
			this.isValidFormSubmitted = true;
		} else {
			return;
		}
		let newUser: User = this.userForm.value;
		this.userService.createUser(newUser);
		this.resetForm(this.userForm);
	}
	resetForm(form: FormGroup) {
		form.reset();
	}
	setDefaultValues() {
		let user = new User();
		user.userName = "Narendra Modi";
		user.profile = this.allProfiles[2];
		user.technologies = [
			this.allTechnologies[1],
			this.allTechnologies[3]
		];
		this.userForm.setValue(user);
	}
	onProfileChange() {
		let profile: Profile = this.profile.value;
		console.log('Profile Changed: ' + profile.prName);
	}
	compareTech(t1: Technology, t2: Technology): boolean {
		console.log(t1.techId + '-' + t2.techId);
		return t1 && t2 ? t1.techId === t2.techId : t1 === t2;
	}
} 
user.component.html
<h2>User Information</h2>
<p *ngIf="isValidFormSubmitted" class="success">
	Form submitted successfully.
</p>
<form [formGroup]="userForm" (ngSubmit)="onFormSubmit()">
	<table>
		<tr>
			<td>Select Profile: </td>
			<td>
				<select formControlName="profile" (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.dirty" class="error">
					Profile required.
				</div>
			</td>
		</tr>
		<tr>
			<td>Name:</td>
			<td>
				<input formControlName="userName">
				<div *ngIf="userName.invalid && userForm.dirty" class="error">
					User Name required.
				</div>
			</td>
		</tr>
		<tr>
			<td>Select Technologies: </td>
			<td>
				<select multiple formControlName="technologies" [compareWith]="compareTech">
					<option *ngFor="let tech of allTechnologies" [ngValue]="tech">
						{{ tech.techName }}
					</option>
				</select>
				<div *ngIf="technologies.invalid && userForm.dirty" class="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> 
user.component.css
table {
    border-collapse: collapse;
}
table, th, td {
    border: 1px solid black;
}
.error {
    color: red;
}
.success {
    color: green;
} 
profile.ts
export class Profile { 
    constructor(public prId:string, public prName:string) {
    }	
} 
technology.ts
export class Technology { 
    constructor(public techId:number, public techName:string) {
    }
} 
user.ts
import { Profile } from './profile';
import { Technology } from './technology';

export class User { 
    userName: string;
	profile: Profile = null;
	technologies: Technology[];
} 
user-service.ts
import { Injectable } from '@angular/core';
import { Profile } from '../domain/profile';
import { Technology } from '../domain/technology';
import { User } from '../domain/user';

@Injectable({
    providedIn: 'root'
})
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);
        }
    }
} 
app.component.ts
import { Component } from '@angular/core';

@Component({
   selector: 'app-root',
   template: `
				<app-template></app-template>
             `
})
export class AppComponent { 
} 
app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReactiveFormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { UserComponent } from './user-component/user.component';
import { UserService } from './services/user-service';

@NgModule({
      imports: [
            BrowserModule,
            ReactiveFormsModule
      ],
      declarations: [
            AppComponent,
            UserComponent
      ],
      providers: [
      ],
      bootstrap: [
            AppComponent
      ]
})
export class AppModule { } 

9. Output

Find the print screen of the output.
Angular Select Option using Reactive Form

10. References

SelectControlValueAccessor
SelectMultipleControlValueAccessor

11. Download Source Code

POSTED BY
ARVIND RAI
ARVIND RAI







©2024 concretepage.com | Privacy Policy | Contact Us