Skip to documentation

guide

Migrate angular2-multiselect-dropdown to Angular 22

Map imports, settings, events and forms to the maintained Angular 22 package and verify asynchronous selection.

By Alexandro · Updated 2026-11-06

Confirm the package you are migrating

This fork originates from angular2-multiselect-dropdown. It is not ng-multiselect-dropdown; that project's selectors and settings must not be assumed compatible. This guide targets @stackline/angular-multiselect-dropdown 22.1.6 and the Angular 22 documentation line. Historical Angular lines remain accessible and are not upgraded by this guide.

npm ls angular2-multiselect-dropdown ng-multiselect-dropdown @stackline/angular-multiselect-dropdown @angular/core
npm install --save-exact @stackline/[email protected]

The package declares Angular core/common/forms >=22.0.0 <24.0.0; a declared range is separate from tested framework versions. Use matching Angular core/common/forms versions and the Node range recorded in the package reference. The executable application source and its dependency declarations are linked below.

Map the actual API

Existing angular2-multiselect-dropdown usage Stackline integration
AngularMultiSelectModule import Import it from @stackline/angular-multiselect-dropdown
angular2-multiselect selector Retained
[data] and [(ngModel)] Retained; import Angular FormsModule
[settings] Review the versioned settings type; do not copy settings from ng-multiselect-dropdown
onSelect, onDeSelect, onSelectAll, onDeSelectAll Bind the actual events and verify payload shape
Reactive form integration Import ReactiveFormsModule; use the demonstrated formControlName flow

Forms, asynchronous data and selection

Start with stable item IDs and initialize both data and selected items to arrays. When an asynchronous response replaces the list, reconcile selected IDs against the new objects deliberately; do not rely on object identity across requests. Decide whether unavailable selections are retained or cleared. Keep server errors separate from a valid empty result and leave controls labeled while loading.

The running Angular 22 example and its actual source files are preserved. They demonstrate forms, event handlers, asynchronous data, grouping and keyboard interactions. The reference describes the current package independently of older examples.

Executable forms and asynchronous selection example

Open the Angular 22.2.4 guide example. It imports the real 22.1.6 module, binds ngModel, handles selection events and reconciles selected IDs after an asynchronous dataset change. Build it with npm ci --prefix scripts/docs/angular-example followed by npm run build --prefix scripts/docs/angular-example.

import '@angular/compiler';
import {Component,ChangeDetectorRef,inject,provideZonelessChangeDetection} from '@angular/core';
import {bootstrapApplication} from '@angular/platform-browser';
import {CommonModule} from '@angular/common';
import {FormsModule} from '@angular/forms';
import {DataTableModule} from '@stackline/angular-data-table-component';
import {AngularMultiSelectModule} from '@stackline/angular-multiselect-dropdown';
import {createTableState,simulatedAPI} from '../examples/table-state.mjs';

@Component({selector:'guide-example',standalone:true,imports:[CommonModule,FormsModule,DataTableModule,AngularMultiSelectModule],template:`
<h1>Angular 22.2.4: server table and asynchronous multiselect</h1>
<p>Table 22.1.7 · Multiselect 22.1.6 · generated local data only.</p>
<p><a href="/docs/guides/angular-table/">Server-table guide</a> · <a href="/docs/guides/angular-multiselect/">Multiselect migration</a> · <a href="main.ts">Running source</a></p>
<section aria-labelledby="table-title"><h2 id="table-title">Server paging, sorting and persistent ID selection</h2>
<button (click)="load(1)">First page</button> <button (click)="load(page+1)">Next page</button>
<button (click)="direction=direction==='asc'?'desc':'asc';load(1)">Toggle server sort</button>
<button (click)="load(99)">Empty page</button> <button (click)="load(1,true)">Simulate failure</button>
<button (click)="race()">Simulate overlapping requests</button>
<p role="status">{{table.state.loading?'Loading…':table.state.error||('Page '+page+' · '+table.state.total+' total rows')}}</p>
<stackline-data-table title="Server response" [columns]="columns" [data]="table.state.rows" [pagination]="true" [paginationServer]="true" [paginationTotalRows]="table.state.total" [paginationDefaultPage]="page" [paginationPerPage]="size" (pageChange)="load($event.page)" (rowsPerPageChange)="size=$event.rowsPerPage;load(1)"></stackline-data-table>
<fieldset><legend>Selection across pages (stable IDs)</legend><label *ngFor="let row of table.state.rows"><input type="checkbox" [checked]="table.state.selected.has(row.id)" (change)="select(row.id,$event)">{{row.name}}</label></fieldset>
<p id="selection">Selected IDs: {{selectedIDs()}}</p><p *ngIf="!table.state.loading && !table.state.error && table.state.rows.length===0">No rows on this page.</p></section>
<section aria-labelledby="multi-title"><h2 id="multi-title">Asynchronous multiselect with ngModel</h2>
<button (click)="refresh()">Load another dataset</button><p role="status">{{multiLoading?'Loading options…':'Options ready'}}</p>
<angular2-multiselect [data]="items" [(ngModel)]="selected" [settings]="settings" (onSelect)="event='Selected '+$event.itemName" (onDeSelect)="event='Deselected '+$event.itemName" (onSelectAll)="event='Selected all'" (onDeSelectAll)="event='Cleared all'"></angular2-multiselect>
<p role="status">{{event}}</p><p id="multi-selection">Form value: {{selected|json}}</p>
</section>`})
class GuideExample {
  private change=inject(ChangeDetectorRef);
  table=createTableState(simulatedAPI);page=1;size=10;direction='asc';columns=[{name:'ID',selector:'id'},{name:'Name',selector:'name'}];
  items=[{id:1,itemName:'Canada'},{id:2,itemName:'France'},{id:3,itemName:'Brazil'}];selected:any[]=[];multiLoading=false;event='';
  settings={text:'Select countries',singleSelection:false,enableSearchFilter:true,selectAllText:'Select all',unSelectAllText:'Clear all'};
  constructor(){this.load(1);}
  async load(page:number,fail=false){this.page=page;await this.table.load({page,size:this.size,sort:'id',direction:this.direction,fail});this.change.markForCheck();}
  race(){this.direction='asc';this.load(1);this.load(2);}
  select(id:number,event:Event){this.table.select(id,(event.target as HTMLInputElement).checked);}
  selectedIDs(){return [...this.table.state.selected].join(', ')||'none';}
  async refresh(){this.multiLoading=true;await new Promise(resolve=>setTimeout(resolve,300));this.items=[{id:1,itemName:'Canada'},{id:4,itemName:'Japan'}];const ids=new Set(this.selected.map(x=>x.id));this.selected=this.items.filter(x=>ids.has(x.id));this.multiLoading=false;this.change.markForCheck();}
}
bootstrapApplication(GuideExample,{providers:[provideZonelessChangeDetection()]}).catch(error=>{document.body.textContent=String(error);throw error;});

Migration acceptance checks

Run the application build with the chosen Angular version. Exercise initial selection, single and multiple selection, search, select all, deselect all, disabled state, reset and validation. Replace data after an asynchronous load and verify submitted IDs. Use Tab to focus, keyboard arrows to navigate and Escape to close; verify the selected state and focus after closing, including inside a dialog.

Do not treat a successful compile as proof of keyboard behavior. The operational verification report records which runtime and browser checks were executed. Copy the linked running source to reproduce its behavior rather than maintaining a second snippet that can drift.

Package installation and API reference · All guides