Templates
ng-select lets you customize every part of the component — labels, options, headers, footers, and more — using ng-template directives.
Custom label template
Section titled “Custom label template”import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';import { FormsModule } from '@angular/forms';import { NgLabelTemplateDirective, NgSelectComponent } from '@ng-select/ng-select';
@Component({ selector: 'ng-template-label-example', templateUrl: './template-label-example.component.html', styleUrls: ['./template-label-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule, NgLabelTemplateDirective],})export class TemplateLabelExampleComponent implements OnInit { cities = [ { id: 1, name: 'New York', avatar: '//www.gravatar.com/avatar/b0d8c6e5ea589e6fc3d3e08afb1873bb?d=retro&r=g&s=30 2x', }, { id: 2, name: 'London', avatar: '//www.gravatar.com/avatar/ddac2aa63ce82315b513be9dc93336e5?d=retro&r=g&s=15' }, { id: 3, name: 'Beijing', avatar: '//www.gravatar.com/avatar/6acb7abf486516ab7fb0a6efa372042b?d=retro&r=g&s=15', }, { id: 4, name: 'New Delhi', avatar: '//www.gravatar.com/avatar/b0d8c6e5ea589e6fc3d3e08afb1873bb?d=retro&r=g&s=30 2x', }, { id: 5, name: 'Paris', avatar: '//www.gravatar.com/avatar/ddac2aa63ce82315b513be9dc93336e5?d=retro&r=g&s=15', }, ];
selectedCity = this.cities[0].name;
ngOnInit() {}}<p>Custom selected item label using <b>ng-label-tmp</b></p>
<ng-select [items]="cities" [(ngModel)]="selectedCity" bindLabel="name" bindValue="name"> <ng-template ng-label-tmp let-item="item"> <img height="15" width="15" [src]="item.avatar" /> {{ item.name }} </ng-template></ng-select>Custom placeholder template
Section titled “Custom placeholder template”import { Component, ChangeDetectionStrategy } from '@angular/core';import { FormsModule } from '@angular/forms';import { NgLabelTemplateDirective, NgPlaceholderTemplateDirective, NgSelectComponent } from '@ng-select/ng-select';
@Component({ selector: 'ng-template-placeholder-example', templateUrl: './template-placeholder-example.component.html', styleUrls: ['./template-placeholder-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule, NgPlaceholderTemplateDirective, NgLabelTemplateDirective],})export class TemplatePlaceholderExampleComponent { cities = [ { id: 1, name: 'New York', avatar: '//www.gravatar.com/avatar/b0d8c6e5ea589e6fc3d3e08afb1873bb?d=retro&r=g&s=30 2x', }, { id: 2, name: 'London', avatar: '//www.gravatar.com/avatar/ddac2aa63ce82315b513be9dc93336e5?d=retro&r=g&s=15' }, { id: 3, name: 'Beijing', avatar: '//www.gravatar.com/avatar/6acb7abf486516ab7fb0a6efa372042b?d=retro&r=g&s=15', }, { id: 4, name: 'New Delhi', avatar: '//www.gravatar.com/avatar/b0d8c6e5ea589e6fc3d3e08afb1873bb?d=retro&r=g&s=30 2x', }, { id: 5, name: 'Paris', avatar: '//www.gravatar.com/avatar/ddac2aa63ce82315b513be9dc93336e5?d=retro&r=g&s=15', }, ];
selectedCity = undefined;
placeholderAvatar = '//www.gravatar.com/avatar/b0d8c6e5ea589e6fc3d3e08afb1873bb?d=retro&r=g&s=30 2x';}<p>Custom placeholder using <b>ng-placeholder-tmp</b></p>
<ng-select [items]="cities" [(ngModel)]="selectedCity" bindLabel="name" bindValue="name" searchable="true"> <ng-template ng-placeholder-tmp> <img height="15" width="15" [src]="placeholderAvatar" /> Select your city </ng-template> <ng-template ng-label-tmp let-item="item"> <img height="15" width="15" [src]="item.avatar" /> {{ item.name }} </ng-template></ng-select>Custom option template
Section titled “Custom option template”import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';import { FormsModule } from '@angular/forms';import { NgOptionHighlightDirective } from '@ng-select/ng-option-highlight';import { NgOptionTemplateDirective, NgSelectComponent } from '@ng-select/ng-select';
@Component({ selector: 'ng-template-option-example', templateUrl: './template-option-example.component.html', styleUrls: ['./template-option-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule, NgOptionTemplateDirective, NgOptionHighlightDirective],})export class TemplateOptionExampleComponent implements OnInit { cities = [ { id: 1, name: 'New York', avatar: '//www.gravatar.com/avatar/b0d8c6e5ea589e6fc3d3e08afb1873bb?d=retro&r=g&s=30 2x', }, { id: 2, name: 'London', avatar: '//www.gravatar.com/avatar/ddac2aa63ce82315b513be9dc93336e5?d=retro&r=g&s=15' }, { id: 3, name: 'Beijing', avatar: '//www.gravatar.com/avatar/6acb7abf486516ab7fb0a6efa372042b?d=retro&r=g&s=15', }, { id: 4, name: 'New Delhi', avatar: '//www.gravatar.com/avatar/b0d8c6e5ea589e6fc3d3e08afb1873bb?d=retro&r=g&s=30 2x', }, { id: 5, name: 'Paris', avatar: '//www.gravatar.com/avatar/ddac2aa63ce82315b513be9dc93336e5?d=retro&r=g&s=15', }, ];
selectedCity = this.cities[1].name;
constructor() {}
ngOnInit() {}}<p>Custom dropdown panel option template using <b>ng-option-tmp</b></p>
<ng-select [items]="cities" [(ngModel)]="selectedCity" bindLabel="name" bindValue="name"> <ng-template ng-option-tmp let-item="item" let-index="index" let-search="searchTerm"> @if (item.name === 'London') { <div>{{ item.name }}</div> } @if (item.name !== 'London') { <div style="margin: 10px 0" class="card"> <div class="card-body"> <h5 class="card-title" [ngOptionHighlight]="search">{{ item.name }}</h5> <h6 class="card-subtitle mb-2 text-muted">Card subtitle</h6> <p class="card-text"> <img height="15" width="15" [src]="item.avatar" /> Some quick example text to build </p> @if (item.name === 'Beijing') { <div> <a href="#" class="card-link">Card link</a> <a href="#" class="card-link">Another link</a> </div> } </div> </div> } </ng-template></ng-select>Custom optgroup template
Section titled “Custom optgroup template”import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';import { FormsModule } from '@angular/forms';import { NgLabelTemplateDirective, NgOptgroupTemplateDirective, NgOptionTemplateDirective, NgSelectComponent } from '@ng-select/ng-select';import { NgOptionHighlightDirective } from '@ng-select/ng-option-highlight';
@Component({ selector: 'ng-template-optgroup-example', templateUrl: './template-optgroup-example.component.html', styleUrls: ['./template-optgroup-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule, NgLabelTemplateDirective, NgOptgroupTemplateDirective, NgOptionTemplateDirective, NgOptionHighlightDirective],})export class TemplateOptgroupExampleComponent implements OnInit { cities = [ { id: 1, name: 'New York', avatar: '//www.gravatar.com/avatar/b0d8c6e5ea589e6fc3d3e08afb1873bb?d=retro&r=g&s=30 2x', }, { id: 2, name: 'London', avatar: '//www.gravatar.com/avatar/ddac2aa63ce82315b513be9dc93336e5?d=retro&r=g&s=15' }, { id: 3, name: 'Beijing', avatar: '//www.gravatar.com/avatar/6acb7abf486516ab7fb0a6efa372042b?d=retro&r=g&s=15', }, { id: 4, name: 'New Delhi', avatar: '//www.gravatar.com/avatar/b0d8c6e5ea589e6fc3d3e08afb1873bb?d=retro&r=g&s=30 2x', }, { id: 5, name: 'Paris', avatar: '//www.gravatar.com/avatar/ddac2aa63ce82315b513be9dc93336e5?d=retro&r=g&s=15', }, ];
selectedCity = this.cities[2].name;
ngOnInit() {}}<p>Custom label option and optgroup templates</p>
<ng-select [items]="cities" groupBy="avatar" [(ngModel)]="selectedCity" bindLabel="name" bindValue="name"> <ng-template ng-label-tmp let-item="item"> <img height="15" width="15" [src]="item.avatar" /> <b>{{ item.name }}</b> </ng-template> <ng-template ng-optgroup-tmp let-item="item" let-index="index"> City group logo <img height="15" width="15" [src]="item.avatar" /> </ng-template> <ng-template ng-option-tmp let-item="item" let-index="index" let-search="searchTerm"> <b [ngOptionHighlight]="search">{{ item.name }}</b> </ng-template></ng-select>Custom header and footer template
Section titled “Custom header and footer template”Custom info display templates
Section titled “Custom info display templates”import { Component, EventEmitter, OnInit, inject, ChangeDetectionStrategy } from '@angular/core';import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';import { DataService } from '../data.service';import { FormsModule } from '@angular/forms';import { NgLoadingTextTemplateDirective, NgNotFoundTemplateDirective, NgSelectComponent, NgTypeToSearchTemplateDirective } from '@ng-select/ng-select';
@Component({ selector: 'ng-template-display-example', templateUrl: './template-display-example.component.html', styleUrls: ['./template-display-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule, NgTypeToSearchTemplateDirective, NgNotFoundTemplateDirective, NgLoadingTextTemplateDirective],})export class TemplateDisplayExampleComponent implements OnInit { private dataService = inject(DataService);
peopleTypeahead = new EventEmitter<string>(); serverSideFilterItems = []; selectedPeople;
ngOnInit() { this.serverSideSearch(); }
private serverSideSearch() { this.peopleTypeahead .pipe( distinctUntilChanged(), debounceTime(300), switchMap((term) => this.dataService.getPeople(term)), ) .subscribe( (x) => { this.serverSideFilterItems = x; }, (err) => { console.log(err); this.serverSideFilterItems = []; }, ); }}<p>Custom not found, type to search and loading templates</p><ng-select [multiple]="true" [items]="serverSideFilterItems" [(ngModel)]="selectedPeople" placeholder="Select people" bindLabel="name" bindValue="name" [typeahead]="peopleTypeahead"> <ng-template ng-typetosearch-tmp> <div class="ng-option disabled">Start typing...</div> </ng-template> <ng-template ng-notfound-tmp let-searchTerm="searchTerm"> <div class="ng-option disabled">No data found for "{{ searchTerm }}"</div> </ng-template> <ng-template ng-loadingtext-tmp let-searchTerm="searchTerm"> <div class="ng-option disabled">Fetching data for "{{ searchTerm }}"</div> </ng-template></ng-select>
<br />
Selected people: {{ selectedPeople }}Custom search control
Section titled “Custom search control”import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';import { FormsModule } from '@angular/forms';import { NgHeaderTemplateDirective, NgSelectComponent } from '@ng-select/ng-select';
@Component({ selector: 'ng-template-search-example', templateUrl: './template-search-example.component.html', styleUrls: ['./template-search-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule, NgHeaderTemplateDirective],})export class TemplateSearchExampleComponent implements OnInit { cities = [ { id: 1, name: 'New York', avatar: '//www.gravatar.com/avatar/b0d8c6e5ea589e6fc3d3e08afb1873bb?d=retro&r=g&s=30 2x', }, { id: 2, name: 'London', avatar: '//www.gravatar.com/avatar/ddac2aa63ce82315b513be9dc93336e5?d=retro&r=g&s=15' }, { id: 3, name: 'Beijing', avatar: '//www.gravatar.com/avatar/6acb7abf486516ab7fb0a6efa372042b?d=retro&r=g&s=15', }, { id: 4, name: 'New Delhi', avatar: '//www.gravatar.com/avatar/b0d8c6e5ea589e6fc3d3e08afb1873bb?d=retro&r=g&s=30 2x', }, { id: 5, name: 'Paris', avatar: '//www.gravatar.com/avatar/ddac2aa63ce82315b513be9dc93336e5?d=retro&r=g&s=15', }, ];
selectedCity = this.cities[0].name;
constructor() {}
ngOnInit() {}}<label>Custom search control inside of dropdown</label>
<ng-select #select [items]="cities" [searchable]="false" [(ngModel)]="selectedCity" bindLabel="name" bindValue="name"> <ng-template ng-header-tmp> <input style="width: 100%; line-height: 24px" type="text" (input)="select.filter($event.target.value)" /> </ng-template></ng-select>Custom loading spinner
Section titled “Custom loading spinner”import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';import { NgLoadingSpinnerTemplateDirective, NgSelectComponent } from '@ng-select/ng-select';
@Component({ selector: 'ng-template-loading-example', templateUrl: './template-loading-example.component.html', styleUrls: ['./template-loading-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, NgLoadingSpinnerTemplateDirective],})export class TemplateLoadingExampleComponent implements OnInit { cities = [ { id: 1, name: 'New York', avatar: '//www.gravatar.com/avatar/b0d8c6e5ea589e6fc3d3e08afb1873bb?d=retro&r=g&s=30 2x', }, { id: 2, name: 'London', avatar: '//www.gravatar.com/avatar/ddac2aa63ce82315b513be9dc93336e5?d=retro&r=g&s=15' }, { id: 3, name: 'Beijing', avatar: '//www.gravatar.com/avatar/6acb7abf486516ab7fb0a6efa372042b?d=retro&r=g&s=15', }, { id: 4, name: 'New Delhi', avatar: '//www.gravatar.com/avatar/b0d8c6e5ea589e6fc3d3e08afb1873bb?d=retro&r=g&s=30 2x', }, { id: 5, name: 'Paris', avatar: '//www.gravatar.com/avatar/ddac2aa63ce82315b513be9dc93336e5?d=retro&r=g&s=15', }, ];
ngOnInit() {}}<p>Custom loading spinner using <b>ng-loadingspinner-tmp</b></p>
<ng-select [loading]="true" [items]="cities" bindLabel="name" bindValue="name"> <ng-template ng-loadingspinner-tmp> <div class="lds-ellipsis"> <div></div> <div></div> <div></div> <div></div> </div> </ng-template></ng-select>.lds-ellipsis { display: inline-block; position: relative; width: 32px; height: 32px; margin-right: 10px;}
.lds-ellipsis div { position: absolute; top: 14px; width: 8px; height: 8px; border-radius: 50%; background: #c2c2c2; animation-timing-function: cubic-bezier(0, 1, 1, 0);}
.lds-ellipsis div:nth-child(1) { left: -9px; animation: lds-ellipsis1 0.6s infinite;}
.lds-ellipsis div:nth-child(2) { left: -10px; animation: lds-ellipsis2 0.6s infinite;}
.lds-ellipsis div:nth-child(3) { left: 2px; animation: lds-ellipsis2 0.6s infinite;}
.lds-ellipsis div:nth-child(4) { left: 24px; animation: lds-ellipsis3 0.6s infinite;}
@keyframes lds-ellipsis1 { 0% { transform: scale(0); } 100% { transform: scale(1); }}
@keyframes lds-ellipsis3 { 0% { transform: scale(1); } 100% { transform: scale(0); }}
@keyframes lds-ellipsis2 { 0% { transform: translate(0, 0); } 100% { transform: translate(19px, 0); }}Custom clear button
Section titled “Custom clear button”import { Component, ChangeDetectionStrategy } from '@angular/core';import { FormsModule } from '@angular/forms';import { NgClearButtonTemplateDirective, NgSelectComponent } from '@ng-select/ng-select';
@Component({ selector: 'ng-template-clear-example', templateUrl: './template-clear-example.component.html', styleUrls: ['./template-clear-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule, NgClearButtonTemplateDirective],})export class TemplateClearExampleComponent { cities = [ { id: 1, name: 'Clermont-Ferrand', }, { id: 2, name: 'Chamalières', }, { id: 3, name: 'Lyon', }, { id: 4, name: 'Compiègne', }, ];
selectedCity = this.cities[0].name;}<p>Custom clear button using <b>ng-clearbutton-tmp</b></p>
<ng-select [(ngModel)]="selectedCity" [items]="cities" bindLabel="name" bindValue="name" class="clb-select"> <ng-template ng-clearbutton-tmp> <span class="ng-clear">Clear</span> </ng-template></ng-select>.clb-select ::ng-deep .ng-clear-wrapper { margin-right: 8px; width: 50px;}| Template directive | Purpose |
|---|---|
ng-label-tmp |
Customizes how the selected item's label is rendered in the select input |
ng-placeholder-tmp |
Customizes the placeholder shown when no item is selected |
ng-option-tmp |
Customizes how each option is rendered in the dropdown panel; exposes item, index and searchTerm |
ng-optgroup-tmp |
Customizes how group headers are rendered when groupBy is used |
ng-header-tmp |
Renders custom content at the top of the dropdown panel (e.g. select/unselect all buttons or a custom search control) |
ng-footer-tmp |
Renders custom content at the bottom of the dropdown panel (e.g. selected count) |
ng-typetosearch-tmp |
Customizes the message shown before the user starts typing when using typeahead |
ng-notfound-tmp |
Customizes the message shown when the search returns no results; exposes searchTerm |
ng-loadingtext-tmp |
Customizes the text shown while items are being loaded; exposes searchTerm |
ng-loadingspinner-tmp |
Replaces the default loading spinner with custom markup |
ng-clearbutton-tmp |
Replaces the default clear button with custom markup |
Inputs
Section titled “Inputs”| Input | Type | Default | Description |
|---|---|---|---|
| [items] | Array<any> |
[] |
Items array |
| bindLabel | string |
label |
Object property to use for label. Default label |
| bindValue | string |
- |
Object property to use for selected model. By default binds to whole object. |
| [multiple] | boolean |
false |
Allows to select multiple items. |
| [groupBy] | string | Function |
null | Allow to group items by key or function expression |
| [searchable] | boolean |
true |
Allow to search for value. Default true |
| [loading] | boolean |
- |
You can set the loading state from the outside (e.g. async items loading) |
| [typeahead] | Subject |
- |
Custom autocomplete or advanced filter. |
| placeholder | string |
- |
Placeholder text. |
See the NgSelectComponent API reference for the complete list of inputs and outputs.