npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@apps24/ng-select-pro

v0.1.2

Published

A production-ready Angular multiselect dropdown component with search, virtual scroll, keyboard navigation, and full theming support.

Downloads

233

Readme

ng-select-pro

npm version License: MIT Angular Build Status

A production-ready Angular multiselect dropdown component with search, virtual scroll, keyboard navigation, group support, and full theming.


Features

  • Multi-select and single-select modes
  • Search/filter with configurable placeholder
  • Virtual scrolling (CDK) auto-activated for lists > 100 items
  • Full keyboard navigation — ArrowUp/Down, Enter, Escape, Tab
  • ControlValueAccessor — works with ngModel and ReactiveFormsModule
  • Select all / Deselect all
  • Badge display with overflow indicator (+N)
  • Group headers from option.group
  • Disabled options and disabled component state
  • Tooltip support via option.tooltip
  • Loading spinner state
  • Dropdown position — auto (smart), top, or bottom
  • Angular animations for smooth open/close
  • ARIA roles and attributes for accessibility
  • CSS custom properties for complete theming
  • Tailwind CSS integration support
  • Angular Material theme built-in
  • Angular 8 – 20+ compatible (partial-Ivy compilation)

Installation

npm install @apps24/ng-select-pro

⚠️ @angular/cdk required peer dependency

This library uses @angular/cdk for virtual scrolling. If your project does not already have @angular/cdk installed, install it first at the same major version as your Angular to avoid peer dependency conflicts:

npm install @angular/cdk@16   # for Angular 16
npm install @angular/cdk@17   # for Angular 17
npm install @angular/cdk@18   # for Angular 18
npm install @angular/cdk@19   # for Angular 19
npm install @angular/cdk@20   # for Angular 20

Not sure which version you have? Run ng version in your project directory.


Quick Start

Step 1 — Import the module

Module-based app (Angular 8–16):

// app.module.ts
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MultiSelectModule } from '@apps24/ng-select-pro';

@NgModule({
  imports: [
    BrowserAnimationsModule,  // required for dropdown animation
    FormsModule,
    MultiSelectModule
  ]
})
export class AppModule {}

Standalone app (Angular 14+):

// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { provideAnimations } from '@angular/platform-browser/animations';
import { importProvidersFrom } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MultiSelectModule } from '@apps24/ng-select-pro';

bootstrapApplication(AppComponent, {
  providers: [
    provideAnimations(),
    importProvidersFrom(FormsModule, MultiSelectModule)
  ]
});

Step 2 — Use in your component

// app.component.ts
import { Component } from '@angular/core';
import { IMultiSelectOption } from '@apps24/ng-select-pro';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent {
  selected: IMultiSelectOption[] = [];

  options: IMultiSelectOption[] = [
    { id: 1, label: 'Angular' },
    { id: 2, label: 'React' },
    { id: 3, label: 'Vue' }
  ];
}
<!-- app.component.html -->
<ng-multiselect
  [(ngModel)]="selected"
  [options]="options"
></ng-multiselect>

<p>Selected: {{ selected | json }}</p>

Complete Real-World Demo

A complete example showing multi-select, single-select, grouped options, reactive forms, max selection, disabled items, and event handling — all in one component.

Component TypeScript

// user-form.component.ts
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { IMultiSelectOption, IMultiSelectConfig } from '@apps24/ng-select-pro';

@Component({
  selector: 'app-user-form',
  templateUrl: './user-form.component.html',
  styleUrls: ['./user-form.component.scss']
})
export class UserFormComponent implements OnInit {

  form!: FormGroup;

  // ── Skills multi-select ──────────────────────────────────────────────────
  skillOptions: IMultiSelectOption[] = [
    { id: 'angular',     label: 'Angular',     group: 'Frontend' },
    { id: 'react',       label: 'React',       group: 'Frontend' },
    { id: 'vue',         label: 'Vue.js',      group: 'Frontend' },
    { id: 'nodejs',      label: 'Node.js',     group: 'Backend'  },
    { id: 'python',      label: 'Python',      group: 'Backend'  },
    { id: 'go',          label: 'Go',          group: 'Backend'  },
    { id: 'postgres',    label: 'PostgreSQL',  group: 'Database' },
    { id: 'mongo',       label: 'MongoDB',     group: 'Database' },
    { id: 'redis',       label: 'Redis',       group: 'Database' },
    { id: 'docker',      label: 'Docker',      group: 'DevOps'   },
    { id: 'k8s',         label: 'Kubernetes',  group: 'DevOps'   },
  ];

  skillConfig: Partial<IMultiSelectConfig> = {
    placeholder: 'Select your skills',
    searchPlaceholder: 'Search skills...',
    maxSelect: 5,
    maxDisplayed: 3,
    showSelectAll: false,
    showClearAll: true
  };

  // ── Country single-select ────────────────────────────────────────────────
  countryOptions: IMultiSelectOption[] = [
    { id: 'us', label: 'United States', group: 'Americas' },
    { id: 'ca', label: 'Canada',        group: 'Americas' },
    { id: 'gb', label: 'United Kingdom',group: 'Europe'   },
    { id: 'de', label: 'Germany',       group: 'Europe'   },
    { id: 'fr', label: 'France',        group: 'Europe'   },
    { id: 'jp', label: 'Japan',         group: 'Asia'     },
    { id: 'in', label: 'India',         group: 'Asia'     },
  ];

  countryConfig: Partial<IMultiSelectConfig> = {
    mode: 'single',
    placeholder: 'Select country',
    closeOnSelect: true,
    showSelectAll: false,
    showClearAll: true
  };

  // ── Role select (disabled items) ─────────────────────────────────────────
  roleOptions: IMultiSelectOption[] = [
    { id: 'dev',     label: 'Developer'                                    },
    { id: 'lead',    label: 'Tech Lead'                                    },
    { id: 'mgr',     label: 'Engineering Manager'                          },
    { id: 'arch',    label: 'Architect'                                    },
    { id: 'cto',     label: 'CTO',     disabled: true, tooltip: 'Contact sales' },
    { id: 'vp',      label: 'VP Eng',  disabled: true, tooltip: 'Contact sales' },
  ];

  roleConfig: Partial<IMultiSelectConfig> = {
    mode: 'single',
    placeholder: 'Select your role',
    closeOnSelect: true,
    showSelectAll: false
  };

  // ── Event log ─────────────────────────────────────────────────────────────
  events: string[] = [];

  constructor(private fb: FormBuilder) {}

  ngOnInit(): void {
    this.form = this.fb.group({
      name:    ['', Validators.required],
      skills:  [[], Validators.required],
      country: [null, Validators.required],
      role:    [null, Validators.required]
    });
  }

  onSkillsChange(selected: IMultiSelectOption[]): void {
    this.events.unshift(`Skills changed → ${selected.map(s => s.label).join(', ') || 'none'}`);
  }

  onCountryChange(selected: IMultiSelectOption[]): void {
    this.events.unshift(`Country changed → ${selected[0]?.label || 'none'}`);
  }

  onSubmit(): void {
    if (this.form.valid) {
      console.log('Form value:', this.form.value);
      this.events.unshift('Form submitted ✓');
    }
  }
}

Component Template

<!-- user-form.component.html -->
<form [formGroup]="form" (ngSubmit)="onSubmit()" class="form">

  <!-- Name field -->
  <div class="field">
    <label>Full Name</label>
    <input formControlName="name" type="text" placeholder="John Doe" />
  </div>

  <!-- Skills — multi-select, max 5, grouped, with search -->
  <div class="field">
    <label>Skills <small>(max 5)</small></label>
    <ng-multiselect
      formControlName="skills"
      [options]="skillOptions"
      [config]="skillConfig"
      (selectionChange)="onSkillsChange($event)"
    ></ng-multiselect>
    <small *ngIf="form.get('skills')?.invalid && form.get('skills')?.touched">
      Please select at least one skill.
    </small>
  </div>

  <!-- Country — single-select with groups -->
  <div class="field">
    <label>Country</label>
    <ng-multiselect
      formControlName="country"
      [options]="countryOptions"
      [config]="countryConfig"
      (selectionChange)="onCountryChange($event)"
    ></ng-multiselect>
  </div>

  <!-- Role — single-select with some disabled options -->
  <div class="field">
    <label>Role</label>
    <ng-multiselect
      formControlName="role"
      [options]="roleOptions"
      [config]="roleConfig"
    ></ng-multiselect>
  </div>

  <button type="submit" [disabled]="form.invalid">Submit</button>

</form>

<!-- Event log -->
<div class="event-log" *ngIf="events.length">
  <h4>Events</h4>
  <p *ngFor="let e of events">{{ e }}</p>
</div>

Component Styles

/* user-form.component.scss */
.form {
  max-width: 480px;
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.field {
  display: flex;
  flex-direction: column;
  gap: 6px;

  label {
    font-weight: 600;
    font-size: 14px;
    color: #374151;
  }

  small {
    color: #ef4444;
    font-size: 12px;
  }

  input {
    padding: 8px 12px;
    border: 1px solid #d1d5db;
    border-radius: 8px;
    font-size: 14px;
    outline: none;

    &:focus { border-color: #3b82f6; }
  }
}

button[type="submit"] {
  padding: 10px 24px;
  background: #3b82f6;
  color: white;
  border: none;
  border-radius: 8px;
  font-size: 14px;
  cursor: pointer;

  &:disabled { opacity: 0.5; cursor: not-allowed; }
  &:hover:not(:disabled) { background: #2563eb; }
}

.event-log {
  margin-top: 24px;
  padding: 12px;
  background: #f8fafc;
  border-radius: 8px;
  font-size: 13px;
  color: #475569;
}

Module Registration

// user-form.module.ts  (or add to AppModule)
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ReactiveFormsModule } from '@angular/forms';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MultiSelectModule } from '@apps24/ng-select-pro';
import { UserFormComponent } from './user-form.component';

@NgModule({
  declarations: [UserFormComponent],
  imports: [
    CommonModule,
    ReactiveFormsModule,
    BrowserAnimationsModule,
    MultiSelectModule
  ],
  exports: [UserFormComponent]
})
export class UserFormModule {}

More Usage Examples

ngModel (template-driven form)

selectedFruits: IMultiSelectOption[] = [];
fruits: IMultiSelectOption[] = [
  { id: 1, label: 'Apple' },
  { id: 2, label: 'Banana' },
  { id: 3, label: 'Cherry' }
];
<ng-multiselect [(ngModel)]="selectedFruits" [options]="fruits"></ng-multiselect>
<p>You selected: {{ selectedFruits.map(o => o.label).join(', ') }}</p>

Single select mode

<ng-multiselect
  [(ngModel)]="selectedCountry"
  [options]="countryOptions"
  [config]="{ mode: 'single', closeOnSelect: true, showSelectAll: false }"
></ng-multiselect>

Max selections (e.g. pick up to 3 tags)

<ng-multiselect
  [(ngModel)]="selectedTags"
  [options]="tagOptions"
  [config]="{ maxSelect: 3, placeholder: 'Pick up to 3 tags' }"
></ng-multiselect>

Loading state (async options)

isLoading = true;
options: IMultiSelectOption[] = [];

ngOnInit() {
  this.api.getOptions().subscribe(data => {
    this.options = data;
    this.isLoading = false;
  });
}
<ng-multiselect
  [(ngModel)]="selected"
  [options]="options"
  [config]="{ loading: isLoading, loadingText: 'Fetching options...' }"
></ng-multiselect>

Custom item renderer (with HTML)

config: Partial<IMultiSelectConfig> = {
  itemRenderer: (opt) => `<strong>${opt.label}</strong> <small style="color:#9ca3af">#${opt.id}</small>`
};
<ng-multiselect [(ngModel)]="selected" [options]="options" [config]="config"></ng-multiselect>

Listen to events

<ng-multiselect
  [(ngModel)]="selected"
  [options]="options"
  (selectionChange)="onSelectionChange($event)"
  (searchChange)="onSearch($event)"
  (dropdownOpen)="onOpen()"
  (dropdownClose)="onClose()"
></ng-multiselect>
onSelectionChange(selected: IMultiSelectOption[]) {
  console.log('Selected:', selected);
}

onSearch(query: string) {
  // Can be used to trigger remote search
  console.log('Search query:', query);
}

Option Interface

interface IMultiSelectOption {
  id: any;           // Unique identifier
  label: string;     // Display text
  disabled?: boolean;// Prevent selection (greyed out)
  group?: string;    // Group header label
  image?: string;    // Image URL shown in option row
  tooltip?: string;  // Native browser tooltip on hover
  data?: any;        // Store any extra data (not rendered)
}

Configuration Reference

All options are passed via [config]="{ ... }":

| Option | Type | Default | Description | |---|---|---|---| | mode | 'multi' \| 'single' | 'multi' | Selection mode | | searchEnabled | boolean | true | Show search input | | searchPlaceholder | string | 'Search...' | Search input placeholder | | placeholder | string | 'Select options' | Placeholder when empty | | maxSelect | number \| null | null | Max selections (null = unlimited) | | maxDisplayed | number | 3 | Max badges shown before +N overflow | | disabled | boolean | false | Disable entire component | | closeOnSelect | boolean | false | Auto-close after each selection | | showSelectAll | boolean | true | Show Select All link (multi only) | | showClearAll | boolean | true | Show × clear button | | labelRenderer | ((opt) => string) \| null | null | Custom badge label function | | itemRenderer | ((opt) => string) \| null | null | Custom option HTML (supports HTML) | | noResultsText | string | 'No results found' | Empty search state text | | loadingText | string | 'Loading...' | Loading state text | | loading | boolean | false | Show loading spinner | | autoClose | boolean | true | Close on outside click | | dropdownPosition | 'auto' \| 'top' \| 'bottom' | 'auto' | Dropdown direction | | theme | IMultiSelectTheme | — | CSS class overrides per slot |

Theme object

| Key | Applied to | |---|---| | containerClass | Trigger container div | | dropdownClass | Dropdown panel div | | searchClass | Search <input> | | itemClass | Each option row | | selectedItemClass | Selected option rows | | disabledItemClass | Disabled option rows | | badgeClass | Each selected badge | | overflowBadgeClass | The +N overflow badge | | clearBtnClass | Clear all × button | | selectAllClass | Select all row |


Outputs

| Output | Type | When | |---|---|---| | selectionChange | EventEmitter<IMultiSelectOption[]> | Selection changes | | searchChange | EventEmitter<string> | User types in search | | dropdownOpen | EventEmitter<void> | Dropdown opens | | dropdownClose | EventEmitter<void> | Dropdown closes |


Global defaults via injection token

Set defaults once for the entire app:

import { MULTI_SELECT_DEFAULT_CONFIG } from '@apps24/ng-select-pro';

// app.module.ts
providers: [
  {
    provide: MULTI_SELECT_DEFAULT_CONFIG,
    useValue: {
      placeholder: 'Choose...',
      maxDisplayed: 2,
      showSelectAll: false
    }
  }
]

Or use forRoot():

MultiSelectModule.forRoot({
  placeholder: 'Choose...',
  maxDisplayed: 2,
  showSelectAll: false
})

Theming

CSS Custom Properties

Override any visual value on :root or a parent selector:

:root {
  --ng-select-border: 1px solid #6366f1;
  --ng-select-border-radius: 12px;
  --ng-select-badge-bg: #eef2ff;
  --ng-select-badge-text: #4f46e5;
  --ng-select-item-hover-bg: #eef2ff;
  --ng-select-item-selected-bg: #e0e7ff;
  --ng-select-item-selected-text: #3730a3;
  --ng-select-font-size: 13px;
  --ng-select-max-height: 320px;
}

| Variable | Default | Purpose | |---|---|---| | --ng-select-border | 1px solid #d1d5db | Container border | | --ng-select-border-radius | 8px | Corner radius | | --ng-select-bg | #ffffff | Container background | | --ng-select-text | #111827 | Text color | | --ng-select-placeholder | #9ca3af | Placeholder color | | --ng-select-dropdown-bg | #ffffff | Dropdown background | | --ng-select-dropdown-shadow | subtle shadow | Dropdown elevation | | --ng-select-item-hover-bg | #f9fafb | Hovered option bg | | --ng-select-item-selected-bg | #eff6ff | Selected option bg | | --ng-select-item-selected-text | #1d4ed8 | Selected option text | | --ng-select-badge-bg | #dbeafe | Badge background | | --ng-select-badge-text | #1e40af | Badge text | | --ng-select-badge-radius | 4px | Badge corner radius | | --ng-select-search-border | 1px solid #e5e7eb | Search input border | | --ng-select-disabled-opacity | 0.5 | Disabled opacity | | --ng-select-font-size | 14px | Base font size | | --ng-select-max-height | 280px | Dropdown max height |

Tailwind CSS

See Tailwind Integration Guide.

Angular Material

Add .ng-select-material class to the host:

<ng-multiselect class="ng-select-material" ...></ng-multiselect>

See Angular Material Integration Guide.


Keyboard Navigation

| Key | Action | |---|---| | ArrowDown | Open dropdown / move focus down | | ArrowUp | Move focus up | | Enter | Open dropdown / select focused option | | Escape | Close dropdown | | Tab | Close dropdown and move focus |


Contributing

  1. Fork the repo
  2. Create a feature branch: git checkout -b feature/my-feature
  3. Make your changes with tests
  4. Run tests: npm test
  5. Run lint: npm run lint
  6. Submit a PR against main

Development Setup

git clone https://github.com/apps24/ng-select-pro.git
cd ng-select-pro
npm install
npm run build:lib    # Build the library
npm start            # Start demo app at localhost:4200
npm test             # Run library unit tests

License

MIT © apps24