Customize ListView with dynamic tags in Angular ListView component
12 Sep 202516 minutes to read
The ListView component can be customized to display items with dynamic tags using the template
property. This implementation allows users to add and remove tags dynamically from list items through interaction with a secondary ListView rendered within a Dialog component.
Implementation approach
The dynamic tag functionality requires coordination between a parent ListView, a Dialog component, and a secondary ListView that serves as a tag selector. The following steps outline the complete implementation:
-
Initialize a dynamic ListView with the required properties to hold available tags for the parent ListView items. Bind the
select
event handler to capture user selections and add the chosen values as tags to the corresponding parent ListView item.//Select event handler for the tag selector ListView rendered within the dialog addTag(e) { let listTag = document.createElement('span'); listTag.className = 'advanced-option'; let labelElem = document.createElement('span'); labelElem.className = 'label'; let deleteElem = document.createElement('span'); deleteElem.className = 'delete'; deleteElem.onclick = this.removeTag; labelElem.innerHTML = e.text; listTag.appendChild(labelElem); listTag.appendChild(deleteElem); let tag = document.createElement('span'); tag.className = 'advanced-option-list'; tag.appendChild(listTag); this.listviewInstance.element.querySelector('.e-active').appendChild(tag); }
-
Configure the Dialog component with appropriate content and append the dynamically created ListView to the dialog during the
created
event. This ensures the tag selector ListView is properly initialized when the dialog becomes available. -
Implement click event handling for the add button icon (+) to update the ListView data source with available tags and display the dialog containing the dynamic ListView selector. This method manages both the data binding and dialog visibility states.
//Method to hide/show the dialog and update the ListView data source renderDialog(id) { if (document.getElementsByClassName('e-popup-open').length != 0) { this.dialog.hide(); } else { this.listObj.dataSource = this.datasource[id]; this.listObj.dataBind(); this.dialog.show(); } }
-
Bind click event handlers to dynamically added tags to enable removal functionality. This allows users to delete tags after they have been added to ListView items.
//Method to remove individual tag elements from list items removeTag() { this.parentNode.parentNode.remove(); }
import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { ListViewModule } from '@syncfusion/ej2-angular-lists'
import { DialogModule } from '@syncfusion/ej2-angular-popups'
import { ButtonModule } from '@syncfusion/ej2-angular-buttons'
import { Component, ViewChild } from '@angular/core';
import { ListViewComponent } from "@syncfusion/ej2-angular-lists";
import { DialogComponent } from "@syncfusion/ej2-angular-popups";
import { CommonModule } from '@angular/common';
@Component({
imports: [
ListViewModule,
DialogModule,
ButtonModule,
CommonModule
],
standalone: true,
selector: 'my-app',
template: `
<div id="sample">
<ejs-listview #list id='templatelist' [dataSource]='data' [fields]='fields' width=350>
<ng-template #template let-data="">
<div>
<span class="templatetext"></span>
<span class="designationstyle">
<button ejs-button [id]="data.Id" class="e-but" iconCss='e-icons e-add-icon' cssClass='e-small e-round' (click)='onClick($event)'></button>
</span>
<span class="advanced-option-list">
<span *ngFor="let tag of data.tags" class="advanced-option">
<span class="label"></span>
<span class="e-icons e-close" (click)="removeTag(data, tag)"></span>
</span>
</span>
</div>
</ng-template>
</ejs-listview>
<ejs-dialog id='dialog' #ejDialog width='200px' [animationSettings]='animation' [visible]='false' showCloseIcon='true' [position]='position'>
<ng-template #content>
<ejs-listview #List id="list" showHeader=true headerTitle='Favorite' width='200px' [dataSource]='datasource[currentSelectedId]' [fields]='fields' (select)='addTag($event)'></ejs-listview>
</ng-template>
</ejs-dialog>
</div>
`
})
export class AppComponent {
@ViewChild('list') listViewInstance?: ListViewComponent;
@ViewChild('List') listObj?: ListViewComponent;
@ViewChild('ejDialog') dialog?: DialogComponent;
public data: any[] = [
{ "Id": "Brooke", "Name": "Brooke", tags: [] },
{ "Id": "Claire", "Name": "Claire", tags: [] },
{ "Id": "Erik", "Name": "Erik", tags: [] },
{ "Id": "Grace", "Name": "Grace", tags: [] },
{ "Id": "Jacob", "Name": "Jacob", tags: [] }
];
public fields: Object = { text: "Name" };
public position?: Object;
public animation: Object = { effect: 'None' };
public currentSelectedId: string = '';
public brookeTag: Object = [
{ "id": "list11", "Name": "Discover Music" },
{ "id": "list12", "Name": "Sales and Events" },
{ "id": "list13", "Name": "Categories" },
{ "id": "list14", "Name": "MP3 Albums" },
{ "id": "list15", "Name": "More in Music" },
];
public claireTag: Object = [
{ "id": "list21", "Name": "Songs" },
{ "id": "list22", "Name": "Bestselling Albums" },
{ "id": "list23", "Name": "New Releases" },
{ "id": "list24", "Name": "Bestselling Songs" },
];
public erikTag: Object = [
{ "id": "list31", "Name": "Artwork" },
{ "id": "list32", "Name": "Abstract" },
{ "id": "list33", "Name": "Acrylic Mediums" },
{ "id": "list34", "Name": "Creative Acrylic" },
{ "id": "list35", "Name": "Canvas Art" }
];
public graceTag: Object = [
{ "id": "list41", "Name": "Rock" },
{ "id": "list42", "Name": "Gospel" },
{ "id": "list43", "Name": "Latin Music" },
{ "id": "list44", "Name": "Jazz" },
];
public jacobTag: Object = [
{ "id": "list51", "Name": "100 Albums" },
{ "id": "list52", "Name": "Hip-Hop and R&B Sale" },
{ "id": "list53", "Name": "CD Deals" }
];
public datasource: any = {
"Brooke": this.brookeTag,
"Claire": this.claireTag,
"Erik": this.erikTag,
"Grace": this.graceTag,
"Jacob": this.jacobTag
};
ngAfterViewChecked() {
setTimeout(() => {
this.position = {
X: (document.querySelector('.e-add-icon') as HTMLElement | any).getBoundingClientRect().left + 50,
Y: (document.querySelector('.e-add-icon') as any).getBoundingClientRect().top - 5
};
}, 1000);
}
onClick(e: any) {
this.currentSelectedId = e.currentTarget.id;
this.renderDialog(this.currentSelectedId);
}
renderDialog(id: string | number) {
if (document.getElementsByClassName('e-popup-open').length != 0) {
(this.dialog as DialogComponent).hide();
} else {
(this.listObj as ListViewComponent).dataSource = this.datasource[id];
this.listObj?.dataBind();
(this.dialog as DialogComponent).show();
}
}
addTag(e: any) {
const selectedItem = this.data.find(item => item.Id === this.currentSelectedId);
if (selectedItem && !selectedItem.tags.includes(e.text)) {
selectedItem.tags.push(e.text);
this.listViewInstance?.refresh();
}
this.dialog?.hide();
}
removeTag(item: any, tag: string) {
const index = item.tags.indexOf(tag);
if (index > -1) {
item.tags.splice(index, 1);
this.listViewInstance?.refresh();
}
}
}
@import 'node_modules/@syncfusion/ej2-base/styles/material.css';
@import 'node_modules/@syncfusion/ej2-angular-base/styles/material.css';
@import 'node_modules/@syncfusion/ej2-angular-lists/styles/material.css';
@import 'node_modules/@syncfusion/ej2-angular-buttons/styles/material.css';
@import 'node_modules/@syncfusion/ej2-angular-popups/styles/material.css';
#sample {
padding: 40px;
}
.advanced-option-list {
display: inline-flex;
list-style: none;
margin-left: 5px;
padding: 0;
margin-top: 4px;
overflow: auto;
}
.advanced-option {
border: 1px solid #0078d7;
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
border-radius: 2px;
padding: 0 20px 0 5px;
margin: 0 3px 1px 0;
float: left;
position: relative;
background: white;
line-height: 21px;
}
.advanced-option .label {
color: #0078d7;
font-size: 10px;
}
.advanced-option .e-icons.e-close {
right: 0;
position: absolute;
top: 6px;
cursor: pointer;
width: 14px;
font-size: xx-small;
}
#templatelist .designationstyle {
float: right;
position: relative;
right: 10px;
}
.cont-bg {
font-size: 17px;
height: 46px;
padding-top: 10px;
width: 100%;
}
.e-add-icon::before {
content: '\e823';
}
#templatelist .e-list-item {
height: auto;
}
.e-dialog .e-dlg-header-content+.e-dlg-content {
margin-top: -18px;
}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));