Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
af68210
Introduced DeepPartial type for use in Ng Charts Configuration
legrottagliegionata Nov 20, 2025
6ae6cfe
chore(version): added angular20 support
lexasq Feb 26, 2026
9041082
Merge branch 'master' into alex-migrate-angular20
lexasq Feb 26, 2026
0c49e45
Merge branch 'alex-migrate-angular20' into patch-deep-partial
lexasq Feb 26, 2026
5bbb191
Merge pull request #2020 from legrottagliegionata/patch-deep-partial
lexasq Feb 26, 2026
1291244
chore(version): added angular20 support
lexasq Feb 26, 2026
1e50a54
chore(version): added angular20 support
lexasq Feb 26, 2026
c7350b7
chore(version): added angular20 support
lexasq Feb 26, 2026
9b757d6
chore(version): added angular20 support
lexasq Feb 26, 2026
b370f3c
chore(version): added angular20 support
lexasq Feb 26, 2026
e8ddc28
chore(version): added angular20 support
lexasq Feb 26, 2026
9ede5f1
chore(version): added angular20 support
lexasq Feb 26, 2026
2162c16
chore(version): added angular20 support
lexasq Feb 26, 2026
d4ba702
chore(version): added angular20 support
lexasq Feb 26, 2026
d3201b1
chore(version): added angular20 support
lexasq Feb 26, 2026
4e479e3
chore(version): added angular20 support
lexasq Feb 26, 2026
c82027a
chore(version): added angular20 support
lexasq Feb 26, 2026
4330edd
chore(version): added angular20 support
lexasq Feb 26, 2026
908f58b
chore(version): added angular20 support
lexasq Feb 26, 2026
5d6a927
chore(version): added angular20 support
lexasq Feb 26, 2026
d1989cd
chore(version): added angular20 support
lexasq Feb 26, 2026
bff421f
chore(version): added angular20 support
lexasq Feb 26, 2026
8a3d983
chore(version): added angular20 support
lexasq Feb 26, 2026
773f7cc
chore(version): added angular20 support
lexasq Feb 26, 2026
f157d97
chore(version): added angular20 support
lexasq Feb 26, 2026
bfe04a5
chore(version): added angular20 support
lexasq Feb 26, 2026
c49b254
chore(version): added angular20 support
lexasq Feb 26, 2026
3d6e712
chore(version): added angular20 support
lexasq Feb 26, 2026
15486a8
chore(version): added angular20 support
lexasq Feb 26, 2026
dddeb2f
chore(version): added angular20 support
lexasq Feb 26, 2026
044619d
chore(version): added angular20 support
lexasq Feb 26, 2026
3c236c1
chore(version): added angular20 support
lexasq Feb 26, 2026
77abb06
chore(version): added angular20 support
lexasq Feb 26, 2026
361ba6f
chore(version): added angular20 support
lexasq Feb 26, 2026
2d53373
chore(version): added angular20 support
lexasq Feb 26, 2026
22b7e59
chore(version): added angular20 support
lexasq Feb 26, 2026
0a161b6
chore(version): added angular20 support
lexasq Feb 26, 2026
f14cbc9
chore(version): added angular20 support
lexasq Feb 26, 2026
9c5299d
chore(version): added angular20 support
lexasq Feb 26, 2026
a2abac6
chore(version): added angular20 support
lexasq Feb 26, 2026
7ad2137
chore(version): added angular20 support
lexasq Feb 26, 2026
ca6c527
chore(version): added angular20 support
lexasq Feb 26, 2026
d3b6a5b
chore(version): added angular20 support
lexasq Feb 26, 2026
9a78353
chore(version): added angular20 support
lexasq Feb 26, 2026
0e68453
chore(version): added angular20 support
lexasq Feb 26, 2026
08a074c
chore(version): added angular20 support
lexasq Feb 26, 2026
3334b39
chore(version): added angular20 support
lexasq Feb 27, 2026
6c22eb5
chore(version): added angular20 support
lexasq Feb 27, 2026
69b48f8
chore(version): added angular20 support
lexasq Feb 27, 2026
1936fc2
chore(version): added angular20 support
lexasq Feb 27, 2026
bc8288e
fix: prettier formatting for base-chart.directive.ts
lexasq Feb 27, 2026
363bfd9
chore(version): added angular20 support
lexasq Feb 27, 2026
87c375a
chore(version): added angular20 support
lexasq Feb 27, 2026
a600e3e
chore(version): added angular20 support
lexasq Feb 27, 2026
54c2888
chore(version): added angular20 support
lexasq Feb 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
cache: npm
node-version: 18
node-version: 20.19.0
- run: npm ci
- run: npx prettier --check .
- run: npm run lint
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
- uses: actions/setup-node@v4
with:
cache: npm
node-version: 18
node-version: 20.19
- run: npm ci
- run: npx prettier --check .
- run: npm run lint
Expand Down Expand Up @@ -64,7 +64,7 @@ jobs:
- uses: actions/setup-node@v4
with:
cache: npm
node-version: 18
node-version: 20.19

- uses: actions/download-artifact@v4
with:
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions apps/ng2-charts-demo/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ export function app(): express.Express {
publicPath: browserDistFolder,
providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }],
})
.then((html: any) => res.send(html))
.catch((err: any) => next(err));
.then((html: string) => res.send(html))
.catch((err: Error) => next(err));
});

return server;
Expand Down
12 changes: 5 additions & 7 deletions apps/ng2-charts-demo/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import {
AfterViewInit,
Component,
ElementRef,
Inject,
QueryList,
Renderer2,
ViewChild,
ViewChildren,
inject,
} from '@angular/core';
import { DOCUMENT } from '@angular/common';
import { Router, RouterLink, RouterOutlet } from '@angular/router';
Expand Down Expand Up @@ -99,12 +99,10 @@ export class AppComponent implements AfterViewInit {
| undefined;
tabLabels: string[] = [];

constructor(
@Inject(DOCUMENT) private document: Document,
private renderer: Renderer2,
private themeService: ThemeService,
private router: Router,
) {}
private document = inject(DOCUMENT);
private renderer = inject(Renderer2);
private themeService = inject(ThemeService);
private router = inject(Router);

ngAfterViewInit(): void {
if (this.tabElements) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<app-chart-host chartType="line">
<app-chart-host chartType="line" ngSkipHydration>
<canvas
baseChart
[data]="lineChartData"
Expand All @@ -7,7 +7,7 @@
(chartHover)="chartHovered($event)"
(chartClick)="chartClicked($event)"
></canvas>
<table>
<table ngSkipHydration>
<tr>
@for (label of lineChartData.labels; track label) {
<th>{{ label }}</th>
Expand Down
37 changes: 19 additions & 18 deletions copy-sources.mjs
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
import glob from 'glob';
import fs from 'fs';
import { glob } from 'glob';
import fs from 'fs/promises';

glob(
`apps/ng2-charts-demo/src/**/*.component.ts`,
{ ignore: 'man.css' },
(err, files) => {
if (err) {
// Handle the error
}
async function copyComponentFiles() {
try {
const files = await glob('apps/ng2-charts-demo/src/**/*.component.ts');

// Iterate over the list of files
files.forEach((srcPath) => {
for (const srcPath of files) {
// Construct the full path to the destination file
const destPath = srcPath.replace('.ts', '.txt');

fs.copyFile(srcPath, destPath, (err) => {
if (err) {
// Handle the error
}
});
});
},
);
try {
await fs.copyFile(srcPath, destPath);
console.log(`Copied ${srcPath} to ${destPath}`);
} catch (err) {
console.error(`Error copying ${srcPath} to ${destPath}:`, err);
}
}
} catch (err) {
console.error('Error finding files:', err);
}
}

copyComponentFiles();
2 changes: 1 addition & 1 deletion libs/ng2-charts-schematics/src/ng-add/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { Schema } from './schema';
import * as messages from './messages';
import { addPackageToPackageJson } from '../utils/package-config';

const NG2_CHARTS_VERSION = '5.0.0';
const NG2_CHARTS_VERSION = '9.0.0';
const CHARTJS_VERSION = '4.3.0';

/**
Expand Down
10 changes: 5 additions & 5 deletions libs/ng2-charts/package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"version": "8.0.1",
"version": "9.0.0",
"name": "ng2-charts",
"description": "Reactive, responsive, beautiful charts for Angular based on Chart.js",
"peerDependencies": {
"@angular/platform-browser": ">=19.0.0",
"@angular/common": ">=19.0.0",
"@angular/core": ">=19.0.0",
"@angular/cdk": ">=19.0.0",
"@angular/platform-browser": ">=20.0.0",
"@angular/common": ">=20.0.0",
"@angular/core": ">=20.0.0",
"@angular/cdk": ">=20.0.0",
"chart.js": "^3.4.0 || ^4.0.0",
"rxjs": "^6.5.3 || ^7.4.0"
},
Expand Down
1 change: 1 addition & 0 deletions libs/ng2-charts/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
export * from './lib/ng-charts.provider';
export * from './lib/base-chart.directive';
export * from './lib/theme.service';
export * from './lib/utils';
137 changes: 85 additions & 52 deletions libs/ng2-charts/src/lib/base-chart.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ import {
Directive,
ElementRef,
EventEmitter,
Inject,
Input,
NgZone,
OnChanges,
OnDestroy,
Optional,
Output,
SimpleChanges,
inject,
PLATFORM_ID,
} from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
import {
Chart,
ChartConfiguration,
Expand All @@ -37,10 +38,10 @@ import {
standalone: true,
})
export class BaseChartDirective<
TType extends ChartType = ChartType,
TData = DefaultDataPoint<TType>,
TLabel = unknown,
>
TType extends ChartType = ChartType,
TData = DefaultDataPoint<TType>,
TLabel = unknown,
>
implements OnDestroy, OnChanges
{
@Input() public type: ChartConfiguration<TType, TData, TLabel>['type'] =
Expand Down Expand Up @@ -70,27 +71,34 @@ export class BaseChartDirective<
active: object[];
}> = new EventEmitter();

public ctx: string;
public ctx: CanvasRenderingContext2D | null = null;
public chart?: Chart<TType, TData, TLabel>;

private subs: Subscription[] = [];
private themeOverrides: ChartConfiguration['options'] = {};
private element = inject(ElementRef);
private zone = inject(NgZone);
private themeService = inject(ThemeService);
private config = inject(NG_CHARTS_CONFIGURATION, { optional: true }) as
| NgChartsConfiguration
| undefined;
private platformId = inject(PLATFORM_ID);
private isBrowser = isPlatformBrowser(this.platformId);

public constructor(
element: ElementRef,
private zone: NgZone,
private themeService: ThemeService,
@Optional() @Inject(NG_CHARTS_CONFIGURATION) config?: NgChartsConfiguration,
) {
if (config?.registerables) {
Chart.register(...config.registerables);
public constructor() {
if (this.config?.registerables) {
Chart.register(...this.config.registerables);
}

if (config?.defaults) {
defaults.set(config.defaults);
if (this.config?.defaults) {
defaults.set(this.config.defaults);
}

// Only get canvas context in browser environment
if (this.isBrowser) {
this.ctx = this.element.nativeElement.getContext('2d');
}

this.ctx = element.nativeElement.getContext('2d');
this.subs.push(
this.themeService.colorschemesOptions
.pipe(distinctUntilChanged())
Expand All @@ -99,6 +107,10 @@ export class BaseChartDirective<
}

ngOnChanges(changes: SimpleChanges): void {
if (!this.isBrowser) {
return; // Skip chart operations in SSR
}

const requireRender = ['type'];
const propertyNames = Object.getOwnPropertyNames(changes);

Expand All @@ -108,20 +120,29 @@ export class BaseChartDirective<
) {
this.render();
} else {
const config = this.getChartConfiguration();
// For legend changes, we need to update the chart options and re-render
if (this.chart && changes['legend']) {
const config = this.getChartConfiguration();
if (config.options) {
this.chart.options = config.options;
}
this.update();
} else if (this.chart) {
const config = this.getChartConfiguration();

// Using assign to avoid changing the original object reference
if (this.chart) {
Object.assign(this.chart.config.data, config.data);
if (this.chart.config.plugins) {
// Using assign to avoid changing the original object reference
if (config.data && this.chart.config.data) {
Object.assign(this.chart.config.data, config.data);
}
if (config.plugins && this.chart.config.plugins) {
Object.assign(this.chart.config.plugins, config.plugins);
}
if (this.chart.config.options) {
if (config.options && this.chart.config.options) {
Object.assign(this.chart.config.options, config.options);
}
}

this.update();
this.update();
}
}
}

Expand All @@ -133,24 +154,32 @@ export class BaseChartDirective<
this.subs.forEach((s) => s.unsubscribe());
}

public render(): Chart<TType, TData, TLabel> {
public render(): Chart<TType, TData, TLabel> | undefined {
if (!this.isBrowser || !this.ctx) {
return undefined;
}

if (this.chart) {
this.chart.destroy();
}

return this.zone.runOutsideAngular(
() => (this.chart = new Chart(this.ctx, this.getChartConfiguration())),
() =>
(this.chart = new Chart(
this.ctx as CanvasRenderingContext2D,
this.getChartConfiguration(),
)),
);
}

public update(mode?: UpdateMode): void {
if (this.chart) {
if (this.chart && this.isBrowser) {
this.zone.runOutsideAngular(() => this.chart?.update(mode));
}
}

public hideDataset(index: number, hidden: boolean): void {
if (this.chart) {
if (this.chart && this.isBrowser) {
this.chart.getDatasetMeta(index).hidden = hidden;
this.update();
}
Expand Down Expand Up @@ -180,33 +209,37 @@ export class BaseChartDirective<
TData,
TLabel
>['options'] {
return merge(
{
onHover: (event: ChartEvent, active: object[]) => {
if (!this.chartHover.observed && !this.chartHover.observers?.length) {
return;
}
const baseOptions = {
onHover: (event: ChartEvent, active: object[]) => {
if (!this.chartHover.observed && !this.chartHover.observers?.length) {
return;
}

this.zone.run(() => this.chartHover.emit({ event, active }));
},
onClick: (event?: ChartEvent, active?: object[]) => {
if (!this.chartClick.observed && !this.chartClick.observers?.length) {
return;
}
this.zone.run(() => this.chartHover.emit({ event, active }));
},
onClick: (event?: ChartEvent, active?: object[]) => {
if (!this.chartClick.observed && !this.chartClick.observers?.length) {
return;
}

this.zone.run(() => this.chartClick.emit({ event, active }));
},
this.zone.run(() => this.chartClick.emit({ event, active }));
},
this.themeOverrides,
this.options,
{
plugins: {
legend: {
display: this.legend,
},
};

const legendOptions = {
plugins: {
legend: {
display: this.legend,
},
},
);
};

return merge(
baseOptions,
this.themeOverrides || {},
this.options || {},
legendOptions,
) as ChartConfiguration<TType, TData, TLabel>['options'];
}

private getChartConfiguration(): ChartConfiguration<TType, TData, TLabel> {
Expand Down
2 changes: 1 addition & 1 deletion libs/ng2-charts/src/lib/ng-charts.provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
Defaults,
registerables as defaultRegisterables,
} from 'chart.js';
import { DeepPartial } from 'chart.js/dist/types/utils';
import { DeepPartial } from './utils';
import { merge } from 'lodash-es';

export const NG_CHARTS_CONFIGURATION =
Expand Down
Loading
Loading