Charts & JS Plugins

AdminLTE ships no charting library of its own — instead the demo shows the canonical Blazor pattern for driving any third-party JS plugin (ApexCharts, FullCalendar, SortableJS, Quill, jsvectormap) through JS interop.

The pattern in three steps

  • Load the plugin from a CDN in App.razor (CSS in <head>, JS before </body>), alongside a small interop module of your own.
  • Capture the target element in your page with @ref into an ElementReference.
  • Initialize the plugin on first render from OnAfterRenderAsync(firstRender) via @inject IJSRuntime, passing the element reference to your interop function.

1. Load plugin CDNs in App.razor

@* <head> — plugin CSS *@
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.snow.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jsvectormap@1.5.3/dist/css/jsvectormap.min.css">

@* before </body>, after blazor.web.js and <AdminLteScripts /> *@
<script src="https://cdn.jsdelivr.net/npm/apexcharts@3.37.1/dist/apexcharts.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.20/index.global.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.7/Sortable.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jsvectormap@1.5.3/dist/js/jsvectormap.min.js"></script>
<script src="js/demo-interop.js"></script>

2. A small interop module

Put one function per plugin on a namespaced global. Each takes the DOM element(s) passed from Blazor:

// wwwroot/js/demo-interop.js
window.adminlteDemo = {
  salesAreaChart(el) {
    if (!el) return;
    new ApexCharts(el, {
      series: [{ name: 'Digital Goods', data: [28, 48, 40, 19, 86, 27, 90] }],
      chart: { height: 300, type: 'area', toolbar: { show: false } },
      colors: ['#0d6efd'],
      stroke: { curve: 'smooth' },
    }).render();
  },
};

3. Initialize from the page

Capture the element with @ref, inject IJSRuntime, and call your function once on first render:

@page "/"
@inject IJSRuntime JS

<AppContent Title="Dashboard">
    <Card Title="Sales Value">
        <ChildContent>
            <div @ref="_chartRef"></div>
        </ChildContent>
    </Card>
</AppContent>

@code {
    private ElementReference _chartRef;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await JS.InvokeVoidAsync("adminlteDemo.salesAreaChart", _chartRef);
        }
    }
}

Always guard plugin init with if (firstRender). JS interop is not available during prerendering — it only works after the component is interactive — so initialise in OnAfterRenderAsync, never in OnInitialized.

Passing multiple elements

An interop function can take several element references. The demo's FullCalendar page passes the calendar element, an external-events container, and a checkbox:

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (firstRender)
    {
        await JS.InvokeVoidAsync("adminlteDemo.calendar", _cal, _externalEvents, _removeAfterDrop);
    }
}

The same approach drives SortableJS (Kanban lanes), Quill (rich-text editor) and jsvectormap (world map + sparklines) in the demo — see samples/AdminLte.Demo/wwwroot/js/demo-interop.js for the full set of verbatim option objects.


AdminLTE 4 · ASP.NET port Edit on GitHub