Data source binding and custom menu items in Angular Menu component

12 Sep 202519 minutes to read

This section covers binding the Angular Menu component to hierarchical or self-referential data sources and customizing menu items using templates.

Data binding

The Menu component supports data source bindings such as arrays of JavaScript objects structured as either hierarchical or self-referential data.

Hierarchical data

The Menu can be populated with a hierarchical data source by assigning it to the items property, and mapping fields with corresponding keys to the fields property, which includes sub options like itemId, text, and children to align data source properties with the Menu’s structure.

JSON data

The Menu can generate its menu items from an array of complex data sources by mapping fields from the fields property.

import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { MenuModule } from '@syncfusion/ej2-angular-navigations'



import { Component } from '@angular/core';
import { enableRipple } from '@syncfusion/ej2-base';
import { FieldSettingsModel } from '@syncfusion/ej2-angular-navigations';

// Import an array of JSON data from datasource.ts
import { dataSource } from './datasource';

enableRipple(true);

@Component({
imports: [ MenuModule],


standalone: true,
    selector: 'app-root',
    template: `<div class="e-section-control">
    <ejs-menu [items]="data" [fields]='menuFields'></ejs-menu></div>`
})

export class AppComponent {
    public menuFields: FieldSettingsModel = {
        text: ['continent', 'country', 'language'],
        children: ['countries', 'languages']
    };

    public data: { [key: string]: Object }[] = dataSource;
}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

Data Service

In application level, remote data binding can be achieved using DataManager.
To create Menu, assign items property with resultant data from callback function.

The following example displays five employees’ FirstName from the Employees table and ShipName details from the Orders table of the Northwind Data Service.

import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { MenuModule } from '@syncfusion/ej2-angular-navigations'



import { Component, OnInit } from '@angular/core';
import { enableRipple } from '@syncfusion/ej2-base';
import { DataManager, Query, ODataV4Adaptor, ReturnOption } from '@syncfusion/ej2-data';
import { FieldSettingsModel } from '@syncfusion/ej2-angular-navigations';

enableRipple(true);

@Component({
imports: [ MenuModule],


standalone: true,
    selector: 'app-root',
    template: `<div class="e-section-control">
    <ejs-menu *ngIf='menuItems' [items]='menuItems' [fields]='menuFields'></ejs-menu></div>`
})

export class AppComponent implements OnInit {

    private SERVICE_URI: string = 'https://services.odata.org/V4/Northwind/Northwind.svc/';

    // Menu fields definition.
    public menuFields: FieldSettingsModel = {
        text: ['FirstName', 'ShipName'],
        children: ['Orders']
    };

    public menuItems?: { [key: string]: Object }[];

    public ngOnInit(): void {
        // Getting remote data using DataManager.
        new DataManager({ url: this.SERVICE_URI, adaptor: new ODataV4Adaptor(), crossDomain: true })
            .executeQuery(
                new Query().from('Employees').take(5).hierarchy(
                    new Query()
                        .foreignKey('EmployeeID')
                        .from('Orders').take(13),
                    function () {
                        return [1, 2, 3, 4, 5]
                    }
                ))
            .then((e: ReturnOption) => {
                //Assign result data to menu items
                this.menuItems = e.result as { [key: string]: Object }[];
                const loaderElement = document.getElementById('loader') as HTMLElement;
                if (loaderElement) {
                    loaderElement.style.display = "none";
                }
            });
    }
}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

Self-referential data

The Menu can be populated from a self-referential data structure, consisting of an array of JSON objects with parentId mapping.

Directly assign self-referential data to the items property, and map all field members with corresponding keys to the fields property, where itemId uniquely identifies each menu item and parentId links child items to their parent.

To render root-level nodes, specify parentId as null or omit the parentId for root-level nodes in the data source.

In the following example, the id, pId, and text columns from self-referential data have been mapped to the itemId, parentId, and text fields, respectively.

import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { MenuModule } from '@syncfusion/ej2-angular-navigations'



import { Component } from '@angular/core';
import { enableRipple } from '@syncfusion/ej2-base';
import { FieldSettingsModel } from '@syncfusion/ej2-angular-navigations';

enableRipple(true);

@Component({
imports: [ MenuModule],


standalone: true,
    selector: 'app-root',
    template: `<div class="e-section-control">
            <ejs-menu [items]='data' [fields]='menuFields'></ejs-menu>
            </div>`
})

export class AppComponent {
    //Menu datasource
    public data: { [key: string]: Object }[] = [
        { id: 'parent1', text: 'Events' },
        { id: 'parent2', text: 'Movies' },
        { id: 'parent3', text: 'Directory' },
        { id: 'parent4', text: 'Queries', pId: null as any },
        { id: 'parent5', text: 'Services', pId: null as any },

        { id: 'parent6', text: 'Conferences', pId: 'parent1' },
        { id: 'parent7', text: 'Music', pId: 'parent1' },
        { id: 'parent8', text: 'Workshops', pId: 'parent1' },

        { id: 'parent9', text: 'Now Showing', pId: 'parent2' },
        { id: 'parent10', text: 'Coming Soon', pId: 'parent2' },

        { id: 'parent10', text: 'Media Gallery', pId: 'parent3' },
        { id: 'parent11', text: 'Newsletters', pId: 'parent3' },

        { id: 'parent12', text: 'Our Policy', pId: 'parent4' },
        { id: 'parent13', text: 'Site Map', pId: 'parent4' },
        { id: 'parent14', text: 'Pop', pId: 'parent7' },
        { id: 'parent15', text: 'Folk', pId: 'parent7' },
        { id: 'parent16', text: 'Classical', pId: 'parent7' }
    ];

    //Menu fields definition
    public menuFields: FieldSettingsModel = {
        itemId: 'id',
        text: 'text',
        parentId: 'pId'
    };
}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

Custom menu items

The Menu can be customized using Essential® JS2 Template engine to render the elements.

To customize menu items in your application, set your customized template string to the template property.
In the following example, the menu has been rendered with customized menu items.

import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { MenuModule } from '@syncfusion/ej2-angular-navigations'
import { ButtonModule } from '@syncfusion/ej2-angular-buttons'



import { Component, Inject } from '@angular/core';
import { FieldSettingsModel } from '@syncfusion/ej2-angular-navigations';
import { enableRipple } from '@syncfusion/ej2-base';

enableRipple(false);

@Component({
imports: [ MenuModule, ButtonModule],

providers: [
    { provide: 'sourceFiles', useValue: {files: []
standalone: true,
    selector: 'app-root',
    styleUrls: ['./template.css'],
    template: `<div class="e-section-control">
    <div id="menuTemplate" class="menu-section">
    <div class="menu-control">
        <ejs-menu [items]='dataSource' [fields]='menuFields' [animationSettings]="animation" cssClass="e-template-menu">
            <ng-template #template let-dataSource="">
                
                <div *ngIf="dataSource.value" style="width:100%;display:flex;justify-content:space-between;">
                    <img *ngIf="dataSource.url" class="e-avatar e-avatar-small"
                        src="src/images/platforms/.png" />
                    <span style="width:100%;"></span>
                    <span *ngIf="dataSource.count" class='e-badge e-badge-success'></span>
                </div>
                <div *ngIf="dataSource.about" tabindex="0" class="e-card">
                    <div class="e-card-header">
                        <div class="e-card-header-caption">
                            <div class="e-card-header-title">About Us</div>
                        </div>
                    </div>
                    <div class="e-card-content">
                        
                    </div>
                    <div class="e-card-actions">
                        <button class="e-btn e-outline" style="pointer-events: auto;">
                            Read More
                        </button>
                    </div>
                </div>
            </ng-template>
        </ejs-menu>
    </div>
</div>
</div>`
})

export class AppComponent {
animation: any;
      constructor(@Inject('sourceFiles') public sourceFiles: any) {
        sourceFiles.files = ['template.css'];
    }
    //Template datasource
    public dataSource: { [key: string]: Object }[] = [
     {
            category: 'Products',
            options: [
                { value: 'JavaScript', url: 'https://ej2.syncfusion.com/angular/documentation/../../../../../menu/images/javascript' },
                { value: 'Angular', url: '../../../../../../menu/images/angular' },
                { value: 'ASP.NET Core', url: '../../../../../../menu/images/core' },
                { value: 'ASP.NET MVC', url: '../../../../../../menu/images/mvc' }
            ]
        },
        {
            category: 'Services',
            options: [
                { value: 'Application Development', count: '1200+' },
                { value: 'Maintenance & Support', count: '3700+' },
                { value: 'Quality Assurance' },
                { value: 'Cloud Integration', count: '900+' }
            ]
        },
        {
            category: 'About Us',
            options: [
                {
                    id: 'about',
                    about: {
                        value: "We are on a mission to provide world-class best software solutions for web, mobile and desktop platforms. Around 900+ applications are desgined and delivered to our customers to make digital & strengthen their businesses."
                    }
                }
            ]
        },
        { category: 'Careers' },
        { category: 'Sign In' }
    ];

    // Menu fields definition
    public menuFields: object = {
        text: ['category', 'value'],
        children: ['options']
    };
}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

To prevent submenu closing, set args.cancel to true in the beforeClose event handler to enable custom interaction logic.

See Also