Angular Select Option using Template-Driven Form

By Arvind Rai, January 08, 2024
This page will walk through Angular select dropdown using template-driven form. In Angular select element, the ngValue attribute of <option> element binds an object using property binding. For validation, we need to use validation attribute such as required. We can get validation state using local template variable. Select element is created using <select> tag and multiple select element uses multiple attribute.
Now find the complete example step-by-step.

1. Select Option Dropdown

Angular provides following directive to use <select> element in our form.
SelectControlValueAccessor 
The above directive works for both module 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) {
    }	
} 
Now create an array of Profile class.
allProfiles = [
    new Profile('dev', 'Developer'),
    new Profile('man', 'Manager'),
    new Profile('dir', 'Director')
] 
Step-3: Now 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. 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> 
We need to provide name attribute in <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); 
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 two-way binding with ngModel.
<select name="profile" [(ngModel)]="userProfile"> 
userProfile will give selected value.

2. 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 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);
} 
We can also fetch selected values using two-way binding with ngModel.
<select multiple name="selectedTechs" [(ngModel)]="technologies"> 
technologies will be an array of all selected values.

3. Select Option Binding and Default Value

Select option binding can be achieved by one/two way binding with ngModel. Suppose we want following value selected as default.
this.user.profile = this.allProfiles[2]; 
Then perform select option binding as follows.
<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> 
We can achieve select option binding for default value in <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> 

4. 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 use required 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> 
In the same way we can use local template variable with <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> 

5. 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> 
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 and we can see that our compareTech() method will be called.

6. 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> 
Now find the method that will be called on change event.
onProfileChange() {
   console.log('Profile Changed: '+ this.user.profile.prName);
} 


7. 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> 
user.component.ts
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;
	}
} 
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 interface 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()
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 { 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 { } 

8. Output

Find the print-screen of the output.
Angular Select Option using Template-Driven Form

9. References

SelectControlValueAccessor
SelectMultipleControlValueAccessor

10. Download Source Code

POSTED BY
ARVIND RAI
ARVIND RAI







©2024 concretepage.com | Privacy Policy | Contact Us