Skip to main content

NgBootstrap: Dropdown Example

NgBootstrap Drop-down

export interface DropdownConfig {
dropdownClassSelector?: string; // CSS class name(s) to apply to the dropdown - same level as vms-dropdown class
toggleClassSelector?: string; // CSS class name(s) to apply to the dropdown toggle button
toggleLabelWidth?: string; // A width to apply to the dropdown toggle's label (e.g. '170px')
toggleTitle?: string; // Title to apply to the dropdown toggle button
toggleLabel?: string; // Label to apply to the dropdown toggle button when no item is selected
toggleDisabledFn?: () => boolean; // Return true if the dropdown's toggle button should be disabled
placement?: string; // Dropdown placement
}
export interface DropdownMenuItem {
label: string;
value: any;
badgeLabel?: string;
disabled?: boolean;
showIcon?: boolean;
showBadge?: boolean;
itemClassSelector?: string; // CSS class name(s) to apply to menu item (e.g. 'bg-brand-blue-300 text-brand-gray-600')
labelClassSelector?: string; // CSS class name(s) to apply to label (e.g. 'text-brand-gray-600')
iconClassSelector?: string; // CSS class name(s) to apply to icon (e.g. 'mr-3')
badgeClassSelector?: string; // CSS class name(s) to apply to badge (e.g. 'ml-3')
labelFn?: (item?: any) => string;
badgeLabelFn?: (item?: any) => string;
disabledFn?: (item?: any) => boolean;
itemClassSelectorFn?: (item?: any) => string; // CSS class name(s) to apply to menu item dynamically
labelClassSelectorFn?: (item?: any) => string; // CSS class name(s) to apply to label dynamically
iconClassSelectorFn?: (item?: any) => string; // CSS class name(s) to apply to icon dynamically
badgeClassSelectorFn?: (item?: any) => string; // CSS class name(s) to apply to badge dynamically
}

Component .ts

import { Component, EventEmitter, Input, Output, OnChanges } from '@angular/core';
import { isFunction } from 'util';

import { DropdownConfig } from '../model/dropdownConfig';
import { DropdownMenuItem } from '../model/dropdownMenuItem';

@Component({
selector: 'app-dropdown',
templateUrl: './dropdown.component.html',
styleUrls: ['./dropdown.component.scss']
})
export class DropdownComponent implements OnChanges {
@Input() config: DropdownConfig;

@Input() items: DropdownMenuItem[];

@Input() selectedItem: DropdownMenuItem;

@Output() selectedItemChanged: EventEmitter<DropdownMenuItem> = new EventEmitter();

toggleLabel: string;
toggleTitle: string;
defaultToggleLabel: string;
dropdownClassSelector: string;
toggleClassSelector: string;
toggleLabelWidth: string;
menuItems: DropdownMenuItem[];

constructor() {
this.toggleLabel = this.defaultToggleLabel = '- Select -';
this.toggleTitle = '';
this.dropdownClassSelector = '';
this.toggleClassSelector = '';
this.toggleLabelWidth = '100%';
this.menuItems = [];
}

ngOnChanges() {
this.menuItems = this.items;
this.toggleLabel = this.selectedItem ? this.selectedItem.label : (this.config && this.config.toggleLabel ? this.config.toggleLabel : this.defaultToggleLabel);
this.toggleTitle = this.selectedItem ? this.selectedItem.label : (this.config && this.config.toggleTitle ? this.config.toggleTitle : '');

if (this.config) {
this.dropdownClassSelector = this.config.dropdownClassSelector ? this.config.dropdownClassSelector : '';
this.toggleClassSelector = this.config.toggleClassSelector ? this.config.toggleClassSelector : '';
this.toggleLabelWidth = this.config.toggleLabelWidth ? this.config.toggleLabelWidth : '100%';
}
}

toggleDisabled() {
if (this.config && isFunction(this.config.toggleDisabledFn)) {
return this.config.toggleDisabledFn();
}

return null;
}

menuItemLabelFn(menuItem: DropdownMenuItem) {
if (isFunction(menuItem.labelFn)) {
return menuItem.labelFn();
}

return menuItem.label || '';
}

menuItemBadgeLabelFn(menuItem: DropdownMenuItem) {
if (isFunction(menuItem.badgeLabelFn)) {
return menuItem.badgeLabelFn();
}

return menuItem.badgeLabel || '';
}

menuItemClass(menuItem: DropdownMenuItem) {
if (isFunction(menuItem.itemClassSelectorFn)) {
return menuItem.itemClassSelectorFn();
}

return menuItem.itemClassSelector || null;
}

menuItemLabelClass(menuItem: DropdownMenuItem) {
if (isFunction(menuItem.labelClassSelectorFn)) {
return menuItem.labelClassSelectorFn();
}

return menuItem.labelClassSelector || null;
}

menuItemIconClass(menuItem: DropdownMenuItem) {
if (isFunction(menuItem.iconClassSelectorFn)) {
return menuItem.iconClassSelectorFn();
}

return menuItem.iconClassSelector || null;
}

menuItemBadgeClass(menuItem: DropdownMenuItem) {
if (isFunction(menuItem.badgeClassSelectorFn)) {
return menuItem.badgeClassSelectorFn();
}

return menuItem.badgeClassSelector || null;
}

menuItemDisabled(menuItem: DropdownMenuItem) {
if (isFunction(menuItem.disabledFn)) {
return menuItem.disabledFn();
} else if (menuItem.hasOwnProperty('disabled')) {
return menuItem.disabled;
}

return null;
}

onMenuItemClick(menuItem: DropdownMenuItem) {
this.selectedItem = menuItem;
this.toggleLabel = this.toggleTitle = menuItem.label;
this.selectedItemChanged.emit(menuItem);
}
}

Component .html

<div ngbDropdown class="common-dropdown" [ngClass]="dropdownClassSelector" data-placement="{{(config && config.placement ? config.placement : 'bottom-right')}}">
<button ngbDropdownToggle type="button" class="btn d-flex align-items-center" [ngClass]="toggleClassSelector" [attr.title]="toggleTitle" [disabled]="toggleDisabled()">
<span class="dropdown-toggle-label" [style.width]="toggleLabelWidth">{{ toggleLabel }}</span>
</button>


<div ngbDropdownMenu aria-label="Dropdown menu">
<button ngbDropdownItem class="dropdown-item" *ngFor="let menuItem of menuItems"
[ngClass]="menuItemClass(menuItem)"
[class.active]="selectedItem && selectedItem.value === menuItem.value"
[disabled]="menuItemDisabled(menuItem)"
(click)="onMenuItemClick(menuItem)"
(keydown.space)="onMenuItemClick(menuItem)">
<i class="dg-menu-icon" *ngIf="menuItem.showIcon" [ngClass]="menuItemIconClass(menuItem)"></i>
<span class="dg-menu-label" [ngClass]="menuItemLabelClass(menuItem)">{{menuItemLabelFn(menuItem)}}</span>
<span class="dg-menu-badge" *ngIf="menuItem.showBadge" [ngClass]="menuItemBadgeClass(menuItem)">{{menuItemBadgeLabelFn(menuItem)}}</span>
</button>
<div class="dg-menu-label px-3" *ngIf="!menuItems || menuItems.length === 0">No Items Available</div>
</div>
</div>

Component .scss

:host {
display: block;
}

Component .spec.ts

import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CommonModule } from '@angular/common';
import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';

import { DropdownComponent } from './dropdown.component';

describe('DropdownComponent', () => {
let component: DropdownComponent;
let fixture: ComponentFixture<DropdownComponent>;

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ DropdownComponent ],
imports: [ CommonModule, NgbDropdownModule ]
})
.compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(DropdownComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});

App common styling (main app .scss)

.common-dropdown {
&.with-border {
border: 1px solid $brand-gray-border;
border-radius: 3px;
}

&.fixed-width {
.dropdown-toggle-label {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-align: left;
}
}

&.menu-items-wrap {
.dropdown-menu .dropdown-item {
white-space: normal;
}
}

&.full-width-menu .dropdown-menu {
width: 100%;
}

.dropdown-toggle {
width: 100%;

&:disabled {
background-color: $brand-form-control-disabled-bg;
color: $brand-form-control-disabled-color;
opacity: 1;
}

&::after {
margin-left: 0.5rem;
}

&.slim {
padding: 0;
}
}
}

.dropdown-item.active {
color: $brand-white;
background-color: $brand-primary;
}