Create dual list from ListView in Angular ListView component
25 Sep 202620 minutes to read
A dual list interface consists of two Angular ListView components that enable users to transfer items between two collections through intuitive controls. This pattern is commonly used for selection scenarios where users need to move items from an available list to a selected list and vice versa. The Angular ListView component provides robust support for implementing dual list functionality with built-in events and data manipulation capabilities.
Use cases
Dual list interfaces are particularly effective for:
Stock exchanges management: Moving stocks between different country portfolios or market categories
Job application systems: Transferring skills between available competencies and selected qualifications
User permission management: Assigning and removing user roles or access rights
Product catalog organization: Moving products between different categories or collections
Resource allocation: Distributing resources between available and assigned pools
Integration of Dual List
The dual list implementation uses two Angular ListView components positioned side-by-side with transfer controls between them. An ej2-button component provides the transfer functionality, while a TextBox enables filtering capabilities for improved user experience with large datasets.
The dual list architecture supports:
Bulk transfer operations: Moving entire datasets from one list to another
Selective item transfer: Moving individually selected items between lists
Real-time filtering: Dynamically filtering list content based on user input
Bidirectional data flow: Supporting transfers in both directions with appropriate validation
In the Angular ListView component, sorting is enabled using the sortOrder property to maintain consistent item ordering across transfers. The select event triggers when users interact with items, enabling dynamic button state management and transfer validation.
Data Transfer Operations
Moving complete dataset from first to second list (»)
The bulk forward transfer moves all items from the source Angular ListView to the destination ListView. When users click the forward button, the system extracts all items from the first list using array slicing operations and concatenates them with the second ListView’s existing data. This button remains active only when the source ListView contains data, preventing unnecessary operations on empty collections.
Moving complete dataset from second to first list («)
The bulk backward transfer operates identically to the forward transfer but moves data from the second Angular ListView back to the first. This operation maintains data integrity by transferring all items while preserving their original structure and sorting order. The button activation depends on the second ListView containing transferable items.
Moving selected items between lists (>) and (<)
Individual item transfers rely on the ListView’s select event to identify user-chosen items. When users select specific items from either list, the corresponding transfer buttons become active, allowing precise control over which items move between collections. This selective approach provides granular control while maintaining the overall list organization.
Filtering Implementation
The filtering functionality uses a TextBox input to capture user queries and dynamically filter Angular ListView content. The implementation leverages the dataManager to query the underlying data source, applying text-based filters that update the ListView display in real-time. This approach ensures efficient data handling while providing responsive user feedback during filtering operations.
Sorting Behavior
List item sorting in dual list implementations uses the Angular ListView’s sortOrder property to maintain consistent ordering across both components. When sorting is enabled in one ListView, transferred items retain the same sort order in the destination list, ensuring data consistency and predictable user experience throughout the transfer process.
import{NgModule}from'@angular/core'import{BrowserModule}from'@angular/platform-browser'import{ListViewModule}from'@syncfusion/ej2-angular-lists'import{ButtonModule}from'@syncfusion/ej2-angular-buttons'import{Component,ViewChild}from"@angular/core";import{enableRipple}from"@syncfusion/ej2-base";import{DataManager,Query,ODataV4Adaptor}from"@syncfusion/ej2-data";import{ListViewComponent}from"@syncfusion/ej2-angular-lists";import{ButtonComponent}from"@syncfusion/ej2-angular-buttons"enableRipple(true);@Component({imports:[ListViewModule,ButtonModule],standalone: true,selector:'my-app',template:`<div id="text1"> <input #textbox class="e-input" type="text" id="firstInput" placeholder="Filter" title="Type in a name" (keyup)="onFirstKeyUp($event)" /> </div> <ejs-listview #list1 id='list-1' [dataSource]='firstListData' [fields]='fields' [sortOrder]='Ascending' (select)="onFirstListSelect()"></ejs-listview> <div id="btn"> <button ejs-button #btn1 id="firstBtn" (click)="firstbtnclick()"> >> </button> <button ejs-button #btn2 id="secondBtn" [disabled]=true (click)="secondbtnclick()"> > </button> <button ejs-button #btn3 id="thirdBtn" [disabled]=true (click)="thirdbtnclick()"> < </button> <button ejs-button #btn4 id="fourthBtn" (click)="fourthbtnclick()"> << </button> </div> <div id="text2"> <input #text class="e-input" type="text" id="secondInput" placeholder="Filter" title="Type in a name" (keyup)="onSecondKeyUp($event)" /> </div> <ejs-listview #list2 id='list-2' [dataSource]='secondListData' [fields]='fields' [sortOrder]='Ascending' (select)="onSecondListSelect()"></ejs-listview>`,})exportclassAppComponent{publicfields?: Object;publicfirstListData?: any;secondListData: any;Ascending: any;constructor(){this.firstListData=[{text:"Hennessey Venom",id:"list-01"},{text:"Bugatti Chiron",id:"list-02"},{text:"Bugatti Veyron Super Sport",id:"list-03"},{text:"SSC Ultimate Aero",id:"list-04"},{text:"Koenigsegg CCR",id:"list-05"},{text:"McLaren F1",id:"list-06"}];this.secondListData=[{text:'Aston Martin One- 77',id:'list-07'},{text:'Jaguar XJ220',id:'list-08'},{text:'McLaren P1',id:'list-09'},{text:'Ferrari LaFerrari',id:'list-10'},];this.fields={text:"text",id:"id"};}@ViewChild('list1')firstListObj?: ListViewComponent;@ViewChild('list2')secondListObj?: ListViewComponent;@ViewChild('btn1')firstBtnObj?: ButtonComponent;@ViewChild('btn2')secondBtnObj?: ButtonComponent;@ViewChild('btn3')thirdBtnObj?: ButtonComponent;@ViewChild('btn4')fourthBtnObj?: ButtonComponent;@ViewChild('textbox')textboxEle: any;@ViewChild('text')textEle: any;ngAfterViewInit(){this.firstListData=(this.firstListObjasListViewComponent|any).dataSource.slice();this.secondListData=(this.secondListObjasListViewComponent|any).dataSource.slice();}//Here, all list items are moved to the second list on clicking move all buttonfirstbtnclick(){(this.secondListObjasListViewComponent).dataSource=Array.prototype.concat.call((this.firstListObjasListViewComponent).dataSource,(this.secondListObjasListViewComponent).dataSource);this.updateFirstListData();this.firstListObj?.removeMultipleItems((this.firstListObjasListViewComponent|any).liCollection);this.firstListData=this.firstListData.concat((this.firstListObjasListViewComponent).dataSource);this.secondListData=(this.secondListObjasListViewComponent|any).dataSource.slice();(this.firstBtnObjasButtonComponent).disabled=true;this.onFirstKeyUp((e: any)=>{});;this.setButtonState();}//Here, the selected list items are moved to the second list on clicking move buttonsecondbtnclick(){lete=this.firstListObj?.getSelectedItems();(this.secondListObjasListViewComponent).dataSource=Array.prototype.concat.call((this.secondListObjasListViewComponent).dataSource,(easany).data);this.firstListObj?.removeItem((easany).item);this.firstListData=(this.firstListObjasListViewComponent).dataSource;this.secondListData=(this.secondListObjasListViewComponent|any).dataSource.slice();this.onFirstKeyUp((e: any)=>{});;(this.secondBtnObjasButtonComponent).disabled=true;this.setButtonState();}//Here, the selected list items are moved to the first list on clicking move buttonthirdbtnclick(){lete=this.secondListObj?.getSelectedItems();(this.firstListObjasListViewComponent).dataSource=Array.prototype.concat.call((this.firstListObjasListViewComponent).dataSource,(easany).data);this.secondListObj?.removeItem((easany).item);this.secondListData=(this.secondListObjasListViewComponent).dataSource;this.firstListData=(this.firstListObjasListViewComponent|any).dataSource.slice();this.onSecondKeyUp((e: any)=>{});(this.thirdBtnObjasButtonComponent).disabled=true;this.setButtonState();}//Here, all list items are moved to the first list on clicking move all buttonfourthbtnclick(){(this.firstListObjasListViewComponent).dataSource=Array.prototype.concat.call((this.firstListObjasListViewComponent).dataSource,(this.secondListObjasListViewComponent).dataSource);this.updateSecondListData();this.secondListObj?.removeMultipleItems((this.secondListObjasListViewComponent|any).liCollection);this.secondListData=this.secondListData.concat((this.secondListObjasListViewComponent).dataSource);this.firstListData=(this.firstListObjasListViewComponent|any).dataSource.slice();this.onSecondKeyUp((e: any)=>{});this.setButtonState();}//Here, the ListView data source is updated to the first listupdateFirstListData(){Array.prototype.forEach.call((this.firstListObjasListViewComponent|any).liCollection,(list)=>{this.firstListData.forEach((data: any,index: any)=>{if(list.innerText.trim()===data.text){deletethis.firstListData[index];}});});this.textboxEle.nativeElement.value='';letds: any[]=[];this.firstListData.forEach((data: any)=>{ds.push(data);})this.firstListData=ds;}//Here, the ListView dataSource is updated for the second listupdateSecondListData(){Array.prototype.forEach.call((this.secondListObjasListViewComponent|any).liCollection,(list)=>{this.secondListData.forEach((data: any,index: string|number)=>{if(list.innerText.trim()===data.text){deletethis.secondListData[index];}});});this.textEle.nativeElement.value='';letds: any=[];this.secondListData.forEach((data: any)=>{ds.push(data);})this.secondListData=ds;}onFirstListSelect(){(this.secondBtnObjasButtonComponent).disabled=false;}onSecondListSelect(){(this.thirdBtnObjasButtonComponent).disabled=false;}//Here, filtering is handled using the dataManager for the first listonFirstKeyUp(e: any){letvalue=this.textboxEle.nativeElement.value;letdata=newDataManager(this.firstListData).executeLocal(newQuery().where('text','startswith',value,true));if(!value){(this.firstListObjasListViewComponent).dataSource=this.firstListData.slice();}else{(this.firstListObjasListViewComponent|any).dataSource=data;}}//Here, filtering is handled using the dataManager for the second listonSecondKeyUp(e: any){letvalue=this.textEle.nativeElement.value;letdata=newDataManager(this.secondListData).executeLocal(newQuery().where('text','startswith',value,true));if(!value){(this.secondListObjasListViewComponent).dataSource=this.secondListData.slice();}else{(this.secondListObjasListViewComponent|any).dataSource=data;}}//Here, the state of the button is changedsetButtonState(){if((this.firstListObjasListViewComponent|any).dataSource.length){(this.firstBtnObjasButtonComponent).disabled=false;}else{(this.firstBtnObjasButtonComponent).disabled=true;(this.secondBtnObjasButtonComponent).disabled=true;}if((this.secondListObjasListViewComponent|any).dataSource.length){(this.fourthBtnObjasButtonComponent).disabled=false;}else{(this.fourthBtnObjasButtonComponent).disabled=true;(this.thirdBtnObjasButtonComponent).disabled=true;}}}