Working with Data in Angular Chart

31 Aug 202624 minutes to read

The Angular Chart component supports multiple data binding approaches to suit different application scenarios. Data can be bound from local JSON arrays, dynamically loaded on demand with lazy loading, or fetched from remote services using various adaptor patterns. This guide covers all available data binding methods, helping you choose the right approach for your use case based on data size, performance requirements, and backend architecture.

Choosing a data binding approach

Method Best For Advantages Considerations
Local data Small to medium datasets Simple setup, no network latency, instant rendering All data must be in memory
Common datasource Multiple series sharing data Reduces redundancy, single update point Limited to data common across series
Lazy loading Large datasets with scrolling Loads only visible data, better performance Requires server-side pagination
Remote data (OData/WebAPI) Backend-driven data Scalable, centralized data management, real-time updates Network latency, requires service setup
Offline mode Data caching with client-side actions Eliminates repeated requests, faster interactions Initial load time, memory constraints

Local data

Bind simple JSON data to the chart using the dataSource property in the series configuration. Map the JSON fields to the xName and yName properties to specify which data fields represent the x and y axis values.

import { ChartModule } from '@syncfusion/ej2-angular-charts'
import { CategoryService, DateTimeService, ScrollBarService, ColumnSeriesService, LineSeriesService,
    ChartAnnotationService, RangeColumnSeriesService, StackingColumnSeriesService,LegendService, TooltipService
 } from '@syncfusion/ej2-angular-charts'


import { Component, OnInit } from '@angular/core';

@Component({
imports: [
         ChartModule
    ],

providers: [ CategoryService, DateTimeService, ScrollBarService, LineSeriesService, ColumnSeriesService,
        ChartAnnotationService, RangeColumnSeriesService, StackingColumnSeriesService, LegendService, TooltipService,],
standalone: true,
    selector: 'app-container',
    template: `<ejs-chart id="chart-container" [primaryXAxis]='primaryXAxis'>
        <e-series-collection>
            <e-series [dataSource]='chartData' type='Column' xName='month' yName='sales' name='Sales'></e-series>
        </e-series-collection>
    </ejs-chart>`
})
export class AppComponent implements OnInit {
    public primaryXAxis?: Object;
    public chartData?: Object[];
    ngOnInit(): void {
        this.chartData = [
            { month: 'Jan', sales: 35 }, { month: 'Feb', sales: 28 },
            { month: 'Mar', sales: 34 }, { month: 'Apr', sales: 32 },
            { month: 'May', sales: 40 }, { month: 'Jun', sales: 32 },
            { month: 'Jul', sales: 35 }, { month: 'Aug', sales: 55 },
            { month: 'Sep', sales: 38 }, { month: 'Oct', sales: 30 },
            { month: 'Nov', sales: 25 }, { month: 'Dec', sales: 32 }
        ];
        this.primaryXAxis = {
            valueType: 'Category'
        };
    }

}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

Common datasource

Bind the same JSON data to all series by setting the dataSource property at the chart level instead of the series level. This approach is useful when multiple series share the same dataset and you want to update data from a single source. Each series independently maps its own xName and yName properties to the common data fields.

import { ChartModule } from '@syncfusion/ej2-angular-charts'
import { CategoryService, DateTimeService, ScrollBarService, ColumnSeriesService, LineSeriesService,
    ChartAnnotationService, RangeColumnSeriesService, StackingColumnSeriesService,LegendService, TooltipService
 } from '@syncfusion/ej2-angular-charts'


import { Component, OnInit } from '@angular/core';

@Component({
imports: [
         ChartModule
    ],

providers: [ CategoryService, DateTimeService, ScrollBarService, LineSeriesService, ColumnSeriesService,
        ChartAnnotationService, RangeColumnSeriesService, StackingColumnSeriesService, LegendService, TooltipService,],
standalone: true,
    selector: 'app-container',
    template: `<ejs-chart id="chart-container" [primaryXAxis]='primaryXAxis' [dataSource]='chartData'>
        <e-series-collection>
            <e-series type='Column' xName='month' yName='sales' name='Sales'></e-series>
            <e-series type='Column' xName='month' yName='sales1' name='Sales'></e-series>
        </e-series-collection>
    </ejs-chart>`
})
export class AppComponent implements OnInit {
    public primaryXAxis?: Object;
    public chartData?: Object[];
    ngOnInit(): void {
        this.chartData = [
      { month: 'Jan', sales: 35, sales1: 28 }, { month: 'Feb', sales: 28, sales1: 35 },
      { month: 'Mar', sales: 34, sales1: 32 }, { month: 'Apr', sales: 32, sales1: 34 },
      { month: 'May', sales: 40, sales1: 32 }, { month: 'Jun', sales: 32, sales1: 40 },
      { month: 'Jul', sales: 35, sales1: 55 }, { month: 'Aug', sales: 55, sales1: 35 },
      { month: 'Sep', sales: 38, sales1: 30 }, { month: 'Oct', sales: 30, sales1: 38 },
      { month: 'Nov', sales: 25, sales1: 32 }, { month: 'Dec', sales: 32, sales1: 25 }

        ];
        this.primaryXAxis = {
            valueType: 'Category'
        };
    }

}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

Lazy loading

Lazy loading enables on-demand data retrieval, loading only the data required for the currently visible range. This approach is ideal for large datasets that would be expensive to load entirely into memory.

Prerequisite: Enable scroll on the axis (e.g., enableScrollOn: true on the primaryXAxis) so the scrollEnd event can fire. Lazy loading is supported on numeric, datetime, and category axes.

How lazy loading works:

  1. The user scrolls the chart to view a different data range.
  2. The scrollEnd event fires with current axis range information.
  3. In the handler, retrieve the minimum and maximum values from the scrolled axis and request the corresponding data from your server.
  4. Append the new data to the existing dataset.
  5. The chart automatically re-renders with the updated data.
import { ChartModule } from '@syncfusion/ej2-angular-charts'
import { CategoryService, DateTimeService, ScrollBarService, ColumnSeriesService, LineSeriesService,
    ChartAnnotationService, RangeColumnSeriesService, StackingColumnSeriesService,LegendService, TooltipService
 } from '@syncfusion/ej2-angular-charts'


import { Component, OnInit, ViewChild } from '@angular/core';
import { ChartComponent } from '@syncfusion/ej2-angular-charts';
import { Internationalization } from '@syncfusion/ej2-base';
import { NumericTextBoxComponent } from '@syncfusion/ej2-angular-inputs';
import { IScrollEventArgs } from '@syncfusion/ej2-charts';

@Component({
imports: [
         ChartModule
    ],

providers: [ CategoryService, DateTimeService, ScrollBarService, LineSeriesService, ColumnSeriesService,
        ChartAnnotationService, RangeColumnSeriesService, StackingColumnSeriesService, LegendService, TooltipService,],
standalone: true,
    selector: 'app-container',
    template: `<ejs-chart style='display:block;' #chart [legendSettings]='legend' id='container' [primaryXAxis]='primaryXAxis'
            [tooltip]='tooltip' [height]='height' [width]='width' (scrollEnd)='scrollEnd($event)'
            [primaryYAxis]='primaryYAxis' [crosshair]='crosshair' [chartArea]='chartArea' [title]='title'>
            <e-series-collection>
                <e-series [dataSource]='data' [animation]='animation' type='Line' xName='x' yName='y'>
                </e-series>
            </e-series-collection>
        </ejs-chart>`
})
export class AppComponent implements OnInit {
    ngOnInit(): void {
    }
    public intl: Internationalization = new Internationalization();
    @ViewChild('point')
    private pointslength?: NumericTextBoxComponent;
    public value: number = 1000;
    public step: number = 100;
    public enabled: boolean = false;
    public format: string = 'n';
    public dropValue: string = 'Range';
    public minValue: Date = new Date(2009, 0, 1);
    public maxValue: Date = new Date(2014, 0, 1);
    public dropDownData: Object = [
        { value: 'Range' },
        { value: 'Points Length' }

    ];
    public fields: Object = { text: 'value', value: 'value' };
    public data: Object[] = this.GetNumericData(new Date(2009, 0, 1));
    @ViewChild('chart')
    public chart?: ChartComponent;
    // Initializing Primary X Axis
    public primaryXAxis: Object = {
        title: 'Day',
        valueType: 'DateTime',
        edgeLabelPlacement: 'Shift',
        skeleton: 'yMMM',
        skeletonType: 'Date',
        scrollbarSettings: {
            range: {
                minimum: new Date(2009, 0, 1),
                maximum: new Date(2014, 0, 1)
            },
            enable: true,
            pointsLength: 1000
        }
    };
    public height: string = '450';
    public width: string = '100%';
    //Initializing Primary Y Axis
    public primaryYAxis: Object = {
        title: 'Server Load',
        labelFormat: '{value}MB'
    };
    public tooltip: Object = {
        enable: true, shared: true,
        header : "<b>${point.x}</b>", format : "Server load : <b>${point.y}</b>"
    };
    public legend: Object = {
        visible: false
    };
    public title: string = 'Network Load';
    public animation: Object = { enable: false };
    public chartArea: Object = {
        border: {
            width: 0
        }
    };
crosshair: any;
    public scrollEnd(args: IScrollEventArgs | any): void {
        (this.chart as ChartComponent).series[0].dataSource = this.GetNumericData(new Date(args.currentRange.maximum));
        (this.chart as ChartComponent).dataBind();
    };
    public GetNumericData(date: Date): {x: Date, y: number}[] {
        var series1 = [];
        var value = 30;
        for (var i = 0; i <= 60; i++) {
            if (Math.random() > .5) {
                value += (Math.random() * 10 - 5);
            }
            else {
                value -= (Math.random() * 10 - 5);
            }
            if (value < 0) {
                value = this.getRandomInt(20, 40);
            }
            date = new Date(date.setMinutes(date.getMinutes() + 1));
            var point = { x: date, y: Math.round(value) };
            series1.push(point);
        }
        return series1;
    }
    public getRandomInt(min: number, max: number) {
        return Math.floor(Math.random() * (max - min + 1)) + min;
    }
};
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

Remote data

Bind remote data from a web service by using the DataManager class. The DataManager simplifies communication with REST APIs, OData services, and custom web endpoints. It requires minimal configuration—typically just the service URL and an appropriate adaptor—then handles all request/response processing.

Assign the DataManager instance to the dataSource property in the series, and map the response fields to xName and yName.

Use the optional query property to filter, sort, or paginate data on the server. For example:

import { DataManager, Query } from '@syncfusion/ej2-data';

const dataManager = new DataManager({
  url: 'https://services.odata.org/V4/Northwind/Northwind.svc/Orders',
  adaptor: new ODataV4Adaptor()
});

const query = new Query().take(50).page(2).select(['OrderID', 'Freight']);

CORS note: When the chart and the web service run on different origins, ensure the server enables CORS and returns the appropriate Access-Control-Allow-Origin headers.

import { ChartModule } from '@syncfusion/ej2-angular-charts'
import { CategoryService, DateTimeService, ScrollBarService, ColumnSeriesService, LineSeriesService,
    ChartAnnotationService, RangeColumnSeriesService, StackingColumnSeriesService,LegendService, TooltipService
 } from '@syncfusion/ej2-angular-charts'


import { Component, OnInit } from '@angular/core';
import { DataManager, Query } from '@syncfusion/ej2-data';

@Component({
imports: [
         ChartModule
    ],

providers: [ CategoryService, DateTimeService, ScrollBarService, LineSeriesService, ColumnSeriesService,
        ChartAnnotationService, RangeColumnSeriesService, StackingColumnSeriesService, LegendService, TooltipService,],
standalone: true,
    selector: 'app-container',
    template: `<ejs-chart id="chart-container" [primaryXAxis]='primaryXAxis' [primaryYAxis]='primaryYAxis'>
        <e-series-collection>
            <e-series [dataSource]='dataManager' type='Column' [query]='query' xName='CustomerID' yName='Freight' name='Sales'></e-series>
        </e-series-collection>
    </ejs-chart>`
})
export class AppComponent implements OnInit {
    public primaryXAxis?: Object;
    public primaryYAxis?: Object;
    public dataManager: DataManager = new DataManager({
    url: 'https://services.syncfusion.com/angular/production/api/orders'
    });
    public query: Query = new Query().take(5).where('Estimate', 'lessThan', 3, false);
    ngOnInit(): void {
        this.primaryXAxis = {
            rangePadding: 'Additional',
            valueType: 'Category',
            title: 'Assignee'
        };
        this.primaryYAxis = {
            title: 'Estimate'
        };
    }

}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

Binding data using ODataAdaptor

OData is a standardized protocol for creating and consuming data. You can retrieve data from an OData v3 service using the DataManager. Refer to the following code example for remote data binding using an OData service.

import { ChartModule } from '@syncfusion/ej2-angular-charts'
import { CategoryService, ColumnSeriesService } from '@syncfusion/ej2-angular-charts'
import { Component, OnInit } from '@angular/core';
import { DataManager, Query, ODataAdaptor } from '@syncfusion/ej2-data';
@Component({
imports: [
        ChartModule
    ],

providers: [ CategoryService, ColumnSeriesService ],
standalone: true,
    selector: 'app-container',
    template: `<ejs-chart id="chart-container" [primaryXAxis]='primaryXAxis' [title]='title'>
        <e-series-collection>
            <e-series [dataSource]='dataManager' type='Column' [query]='query' xName='CustomerID' yName='Freight'></e-series>
        </e-series-collection>
    </ejs-chart>`
})
export class AppComponent implements OnInit {
    public primaryXAxis?: Object;
    public title?: string;
    public dataManager: DataManager = new DataManager({
        url: 'https://services.odata.org/V3/Northwind/Northwind.svc/Orders/',
        adaptor: new ODataAdaptor(),
        crossDomain: true
    });
    public query: Query = new Query();
    ngOnInit(): void {
        this.primaryXAxis = {
            valueType: 'Category'
        };
        this.title = 'Order Details';
    }

}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

Binding data using ODataV4Adaptor

ODataV4 is an improved and more standardized version of the OData protocol, with enhanced query capabilities and better JSON support. Use the ODataV4Adaptor to consume ODataV4 services. For more information on ODataV4 specifications, refer to the odata documentation.
When to use ODataV4Adaptor: If your backend service implements OData v4.0, prefer this adaptor over the older ODataAdaptor for better compliance and features.

import { ChartModule } from '@syncfusion/ej2-angular-charts'
import { CategoryService, ColumnSeriesService } from '@syncfusion/ej2-angular-charts'
import { Component, OnInit } from '@angular/core';
import { DataManager, Query, ODataV4Adaptor } from '@syncfusion/ej2-data';
@Component({
imports: [
        ChartModule
    ],

providers: [ CategoryService, ColumnSeriesService ],
standalone: true,
    selector: 'app-container',
    template: `<ejs-chart id="chart-container" [primaryXAxis]='primaryXAxis' [title]='title'>
        <e-series-collection>
            <e-series [dataSource]='dataManager' type='Column' [query]='query' xName='CustomerID' yName='Freight'></e-series>
        </e-series-collection>
    </ejs-chart>`
})
export class AppComponent implements OnInit {
    public primaryXAxis?: Object;
    public title?: string;
    public dataManager: DataManager = new DataManager({
        url: 'https://services.odata.org/V4/Northwind/Northwind.svc/Orders',
        adaptor: new ODataV4Adaptor()
    });
    public query: Query = new Query();
    ngOnInit(): void {
        this.primaryXAxis = {
            valueType: 'Category'
        };
        this.title = 'Order Details';
    }

}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

Web API adaptor

Use the WebApiAdaptor to consume custom REST APIs that follow a standard response format. This adaptor is ideal for backends that do not implement OData but provide REST endpoints returning JSON data.

Expected response format:

The Web API must return a JSON object with two properties:

  • Items: Array of data objects for the chart
  • Count: Total number of records (useful for pagination)

The Items array contains the actual data records, and Count indicates the total available records (supporting server-side pagination).

{
    Items: [{..}, {..}, {..}, ...],
    Count: 830
}

Sample server endpoint (ASP.NET Core):

[HttpGet("api/orders")]
public object Get([FromQuery] DataManagerRequest dm)
{
    var data = _context.Orders
        .Skip(dm.Skip)
        .Take(dm.Take)
        .ToList();
    var count = _context.Orders.Count();
    return new { Items = data, Count = count };
}
import { ChartModule } from '@syncfusion/ej2-angular-charts'
import { CategoryService, ColumnSeriesService } from '@syncfusion/ej2-angular-charts'
import { Component, OnInit } from '@angular/core';
import { DataManager, Query, WebApiAdaptor } from '@syncfusion/ej2-data';
@Component({
imports: [
        ChartModule
    ],

providers: [ CategoryService, ColumnSeriesService ],
standalone: true,
    selector: 'app-container',
    template: `<ejs-chart id="chart-container" [primaryXAxis]='primaryXAxis' [title]='title'>
        <e-series-collection>
            <e-series [dataSource]='dataManager' type='Column' [query]='query' xName='CustomerID' yName='Freight'></e-series>
        </e-series-collection>
    </ejs-chart>`
})
export class AppComponent implements OnInit {
    public primaryXAxis?: Object;
    public title?: string;
    public dataManager: DataManager = new DataManager({
        url: 'https://services.syncfusion.com/angular/production/api/orders',
        adaptor: new WebApiAdaptor()
    });
    public query: Query = new Query();
    ngOnInit(): void {
        this.primaryXAxis = {
            valueType: 'Category'
        };
        this.title = 'Order Details';
    }

}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

Custom adaptor

Create a custom adaptor by extending one of the built-in adaptors (typically ODataAdaptor) to add custom logic for request/response handling. Common use cases include adding serial numbers, reformatting dates, or adding computed fields before the chart renders the data.

Basic skeleton:

import { ODataAdaptor } from '@syncfusion/ej2-data';

export class CustomAdaptor extends ODataAdaptor {
  override processResponse(data: any, ds: any, query: any, xhr: any): any {
    // Transform the response here
    return data;
  }
}

Commonly overridden methods:

  • processResponse – transform the response before binding.
  • processQuery – modify the URL or query before the request is sent.
  • beforeSend – add custom headers or handle errors before the request.
  • insert, update, remove – customize CRUD operations.

For the full list and signatures, see the DataManager adaptor documentation.

import { ChartModule } from '@syncfusion/ej2-angular-charts'
import { CategoryService, ColumnSeriesService } from '@syncfusion/ej2-angular-charts'
import { Component, OnInit } from '@angular/core';
import { DataManager, Query, ODataAdaptor } from '@syncfusion/ej2-data';
@Component({
imports: [
        ChartModule
    ],

providers: [ CategoryService, ColumnSeriesService ],
standalone: true,
    selector: 'app-container',
    template: `<ejs-chart id="chart-container" [primaryXAxis]='primaryXAxis' [title]='title'>
        <e-series-collection>
            <e-series [dataSource]='data' type='Column' [query]='query' xName='CustomerID' yName='Sno'></e-series>
        </e-series-collection>
    </ejs-chart>`
})

export class AppComponent implements OnInit {
    public primaryXAxis?: Object;
    public title?: string;
    public data?: DataManager;
    public query: Query = new Query();
    ngOnInit(): void {
        class SerialNoAdaptor extends ODataAdaptor {
            public override processResponse(): Object[] {
                let i: number = 0;
                // calling base class processResponse function
                let original: Object[] | any = super.processResponse.apply(this, arguments as any);
                // adding serial number
                original.forEach((item: Object | any) => (item['Sno'] = ++i));
                return original;
            }
        }
        this.data = new DataManager({
            url: 'https://services.syncfusion.com/angular/production/api/orders',
            adaptor: new SerialNoAdaptor(),
            offline: true
        });
        this.primaryXAxis = {
            valueType: 'Category',
        };
        this.title = 'Order Details';
    }

}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

Offline mode

When using remote data binding, all filtering, sorting, and pagination normally happen on the server. To improve responsiveness and reduce server load, enable offline mode: the chart loads all data once during initialization, then handles all interactions client-side. Set the offline property of DataManager to true to activate this behavior.
Use offline mode when:

  • Your dataset is relatively small (fits comfortably in browser memory)
  • You want instant filtering and sorting without server round-trips
  • Network latency is a significant usability concern
  • You prefer to minimize server requests during user interactions
    Caution: Offline mode loads the entire dataset at once, which may impact initial load time and memory usage for large datasets.
import { ChartModule } from '@syncfusion/ej2-angular-charts'
import { CategoryService, ColumnSeriesService } from '@syncfusion/ej2-angular-charts'
import { Component, OnInit } from '@angular/core';
import { DataManager, Query, ODataAdaptor } from '@syncfusion/ej2-data';
@Component({
imports: [
        ChartModule
    ],

providers: [ CategoryService, ColumnSeriesService ],
standalone: true,
    selector: 'app-container',
    template: `<ejs-chart id="chart-container" [primaryXAxis]='primaryXAxis' [title]='title'>
        <e-series-collection>
            <e-series [dataSource]='dataManager' type='Column' [query]='query' xName='CustomerID' yName='Freight'></e-series>
        </e-series-collection>
    </ejs-chart>`
})
export class AppComponent implements OnInit {
    public primaryXAxis?: Object;
    public title?: string;
    public dataManager: DataManager = new DataManager({
        url: 'https://services.syncfusion.com/angular/production/api/orders',
        adaptor: new ODataAdaptor(),
        offline: true
    });
    public query: Query = new Query();
    ngOnInit(): void {
        this.primaryXAxis = {
            valueType: 'Category'
        };
        this.title = 'Order Details';
    }

}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

Empty points

Data points with null or undefined values are treated as empty points. By default, empty points are not rendered as data — they create a gap in the series line, bar, or column. You can customize this behavior with the emptyPointSettings property in the series configuration.

Default behavior: Empty points use the Gap mode, which leaves a blank space in the chart visualization.

Available emptyPointSettings.mode values:

Mode Description
Gap Leaves a blank space at the empty point (default).
Zero Treats the empty point as a zero value.
Average Replaces the empty point with the average of the surrounding data points.
Drop Drops (skips) the empty point entirely.
import { ChartModule } from '@syncfusion/ej2-angular-charts'
import { CategoryService, DateTimeService, ScrollBarService, ColumnSeriesService, LineSeriesService,
    ChartAnnotationService, RangeColumnSeriesService, StackingColumnSeriesService,LegendService, TooltipService
 } from '@syncfusion/ej2-angular-charts'


import { Component, OnInit } from '@angular/core';

@Component({
imports: [
         ChartModule
    ],

providers: [ CategoryService, DateTimeService, ScrollBarService, LineSeriesService, ColumnSeriesService,
        ChartAnnotationService, RangeColumnSeriesService, StackingColumnSeriesService, LegendService, TooltipService,],
standalone: true,
    selector: 'app-container',
    template: `<ejs-chart id="chart-container" [primaryXAxis]='primaryXAxis'>
        <e-series-collection>
            <e-series [dataSource]='chartData' type='Column' xName='month' yName='sales' name='Sales' [emptyPointSettings]='emptySeries1'></e-series>
        </e-series-collection>
    </ejs-chart>`
})
export class AppComponent implements OnInit {
    public primaryXAxis?: Object;
    public chartData?: Object[];
    public marker?: Object;
    public emptySeries1?: Object;
    public emptySeries2?: Object;
        ngOnInit(): void {
        this.chartData = [
            { month: 'Jan', sales: 35 }, { month: 'Feb', sales: null },
            { month: 'Mar', sales: 34 }, { month: 'Apr', sales: 32 },
            { month: 'May', sales: 40 }, { month: 'Jun', sales: 32 },
            { month: 'Jul', sales: undefined }, { month: 'Aug', sales: 55 },
            { month: 'Sep', sales: 38 }, { month: 'Oct', sales: 30 },
            { month: 'Nov', sales: 25 }, { month: 'Dec', sales: 32 }
        ];
        this.primaryXAxis = {
            valueType: 'Category'
        }
        this.marker = {
            visible : true
        }
        this.emptySeries1 = {
            mode: 'Gap'
        }
        this.emptySeries2 = {
            mode:'Average'
        }
    }

}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

Customizing empty point

Assign a specific color to empty points by setting the fill property in the emptyPointSettings object. This allows you to visually distinguish empty data points from regular data in your chart.

import { ChartModule } from '@syncfusion/ej2-angular-charts'
import { DateFormatOptions } from '@syncfusion/ej2-base'
import { CategoryService, DateTimeService, ScrollBarService, ColumnSeriesService, LineSeriesService,
    ChartAnnotationService, RangeColumnSeriesService, StackingColumnSeriesService,LegendService, TooltipService
 } from '@syncfusion/ej2-angular-charts'


import { Component, OnInit } from '@angular/core';

@Component({
imports: [
         ChartModule
    ],

providers: [ CategoryService, DateTimeService, ScrollBarService, LineSeriesService, ColumnSeriesService,
        ChartAnnotationService, RangeColumnSeriesService, StackingColumnSeriesService, LegendService, TooltipService,],
standalone: true,
    selector: 'app-container',
    template: `<ejs-chart id="chart-container" [primaryXAxis]='primaryXAxis'>
        <e-series-collection>
            <e-series [dataSource]='chartData' type='Column' xName='month' yName='sales' name='Sales' [emptyPointSettings]='emptySeries1'></e-series>
        </e-series-collection>
    </ejs-chart>`
})
export class AppComponent implements OnInit {
    public primaryXAxis?: Object;
    public chartData?: Object[];
    public marker?: Object;
    public emptySeries1?: Object;
    public emptySeries2?: Object;
        ngOnInit(): void {
        this.chartData = [
            { month: 'Jan', sales: 35 }, { month: 'Feb', sales: null },
            { month: 'Mar', sales: 34 }, { month: 'Apr', sales: 32 },
            { month: 'May', sales: 40 }, { month: 'Jun', sales: 32 },
            { month: 'Jul', sales: undefined }, { month: 'Aug', sales: 55 },
            { month: 'Sep', sales: 38 }, { month: 'Oct', sales: 30 },
            { month: 'Nov', sales: 25 }, { month: 'Dec', sales: 32 }
        ];
        this.primaryXAxis = {
            valueType: 'Category'
        }
        this.marker = {
            visible : true
        }
        this.emptySeries1 = {
            mode:'Average',
            fill: 'red',
            border: { width: 2, color: 'violet'}
        }
    }

}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

Handling no data

When the chart has no data available to render, use the noDataTemplate property to display a custom layout within the chart area. This template can include messages, images, icons, or interactive elements (such as a load button) to guide the user. The template maintains design consistency and improves user experience when data is unavailable. Once data becomes available, the chart automatically updates and replaces the template with the visualization.

Template content:

<ng-template #noDataTemplate>
  <div class="no-data">
    <p>No data available. Click below to load data.</p>
    <button (click)="loadData()">Load Data</button>
  </div>
</ng-template>

<ejs-chart [noDataTemplate]="noDataTemplate">
  <e-series-collection>
    <e-series [dataSource]="chartData" xName="x" yName="y" type="Column"></e-series>
  </e-series-collection>
</ejs-chart>
import { ViewChild } from '@angular/core'
import { ChartModule } from '@syncfusion/ej2-angular-charts'
import { CategoryService, DateTimeService, ScrollBarService, ColumnSeriesService, LineSeriesService,
    ChartAnnotationService, RangeColumnSeriesService, StackingColumnSeriesService,LegendService, TooltipService
 } from '@syncfusion/ej2-angular-charts'


import { Component, OnInit } from '@angular/core';
import { ChartComponent } from '@syncfusion/ej2-angular-charts'

@Component({
imports: [
         ChartModule
    ],

providers: [ CategoryService, DateTimeService, ScrollBarService, LineSeriesService, ColumnSeriesService,
        ChartAnnotationService, RangeColumnSeriesService, StackingColumnSeriesService, LegendService, TooltipService,],
standalone: true,
    selector: 'app-container',
    template: `<ejs-chart id="chart-container" [noDataTemplate]='noDataTemplate' #chart [primaryXAxis]='primaryXAxis'>
        <e-series-collection>
            <e-series [dataSource]='chartData' type='Column' xName='x' yName='y' name='Sales'></e-series>
        </e-series-collection>
        <ng-template #noDataTemplate>
                <div id="noDataTemplateContainer">
                    <div class="template-align">
                        <img src="./no-data.png" alt="No Data"/>
                    </div>
                    <div class="template-align">
                        <p style="font-size: 15px; margin: 10px 0 0;"><strong>No data available to display.</strong></p>
                    </div>
                    <div class="template-align" style="margin-top: 15px;">
                        <button ejs-button class="load-data-btn" (click)="loadData()" iconCss="e-icons e-refresh">Load Data</button>
                    </div>
                </div>
            </ng-template>
    </ejs-chart>`
})
export class AppComponent implements OnInit {
    @ViewChild('chart')
    public chart?: ChartComponent;
    public primaryXAxis?: Object;
    public data: Object[] = [];
    public chartData?: Object[];

    ngOnInit(): void {
        this.chartData = [];
        this.primaryXAxis = {
            valueType: 'Category'
        };
    }

    public loadData(): void {
        this.chartData = [
            { x: 'January', y: 19173 },
            { x: 'February', y: 17726 },
            { x: 'March', y: 19874 },
            { x: 'April', y: 19391 },
            { x: 'May', y: 20072 },
            { x: 'June', y: 19233 }
        ];
        this.chart?.refresh();
    }
}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

Troubleshooting

Issue Likely cause Suggested fix
Chart renders with no data when using DataManager. CORS is not enabled on the server, or the URL/adaptor combination is wrong. Verify the endpoint in a browser/Postman, then add CORS headers and confirm the adaptor matches the response.
scrollEnd event never fires. Scroll/zoom is not enabled on the axis. Set enableScrollOn: true on the relevant axis (or enable zoom selection).
WebApiAdaptor returns no items. The response does not contain Items/Count properties. Ensure the response matches the expected JSON format shown in the Web API adaptor section.
offline: true causes slow initial render. The dataset is too large to fit comfortably in browser memory. Keep offline mode for small datasets only; otherwise process filtering/sorting on the server.
Empty points appear as gaps unexpectedly. The mode is left at the default Gap. Set emptyPointSettings.mode to Zero, Average, or Drop as needed.