ng2-charts update labels and data - javascript
I'm trying to create dynamically a chart using ng2-chart,
I get information from an angular 2 service, when I change only labels of chart it works and when I change data only it works, but When I change both just data are updated in the chart. have any one an explication for this strange behavior.
my template :
<canvas baseChart height="130" width="180"
[data]="doughnutChartData"
[labels]="doughnutChartLabels"
[chartType]="doughnutChartType"
(chartHover)="chartHovered($event)"
(chartClick)="chartClicked($event)">
</canvas>
my class :
export class PlDoughnutComponent implements OnInit {
constructor(private homeService: TileServiceService) { }
ngOnInit() {
this.updatePLdoughnut();
}
public util : UtilService = new UtilService();
public doughnutChartLabels:string[] = ['Download Sales'];
public doughnutChartData:number[] = [0,0,100];
public doughnutChartType:string = 'doughnut';
public updatePLdoughnut(){
this.homeService.getTile().
then(res => {
this.doughnutChartLabels = res.PLtypes;
this.doughnutChartData = this.util.objectToIntArray(res.PLByTypes);
})
}
}
Apparently, if you do not modify the original reference to the labels array, it seems to work, at least for me. I mean, if you want a completely different set of labels, you should do something like this:
In the template:
<canvas baseChart
[datasets]="lineChartData"
[labels]="lineChartLabels"
[options]="lineChartOptions"
[chartType]="'line'"></canvas>
In the ts component:
this.lineChartLabels.length = 0;
for (let i = tempLabels.length - 1; i >= 0; i--) {
this.lineChartLabels.push(tempLabels[i]);
}
Or, using new ECMAScript syntax:
this.lineChartLabels.length = 0;
this.lineChartLabels.push(...tempLabels);
The key is maybe the this.lineChartLabels.length = 0; statement, which practically 'empties' your array by setting its length to 0, without modifying the reference.
Hope this helps!
Recently i had to use ng2-charts and i was having a very big issues with updating my data untill i found this sollution:
<div class="chart">
<canvas baseChart [datasets]="datasets_lines" [labels]="labels_line" [colors]="chartColors" [options]="options" [chartType]="lineChartType">
</canvas>
</div>
and here what i have in my component :
import { Component, OnInit, Pipe, ViewChild, ElementRef } from '#angular/core';
import { BaseChartDirective } from 'ng2-charts/ng2-charts';
#Component({
moduleId: module.id,
selector: 'product-detail',
templateUrl: 'product-detail.component.html'
})
export class ProductDetailComponent {
#ViewChild(BaseChartDirective) chart: BaseChartDirective;
private datasets_lines: { label: string, backgroundColor: string, borderColor: string, data: Array<any> }[] = [
{
label: "Quantities",
data: Array<any>()
}
];
private labels_line = Array<any>();
private options = {
scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}]
}
};
constructor() { }
ngOnInit() {
this.getStats();
}
getStats() {
this._statsService.getStatistics(this.startDate, this.endDate, 'comparaison')
.subscribe(
res => {
console.log('getStats success');
this.stats = res;
this.labels_line = this.getDates();
this.datasets_lines = [];
let arr: any[];
arr = [];
for (let stat of this.stats) {
arr.push(stat.quantity);
}
this.datasets_lines.push({
label: 'title',
data: arr
});
this.refresh_chart();
},
err => {
console.log("getStats failed from component");
},
() => {
console.log('getStats finished');
});
}
refresh_chart() {
setTimeout(() => {
console.log(this.datasets_lines_copy);
console.log(this.datasets_lines);
if (this.chart && this.chart.chart && this.chart.chart.config) {
this.chart.chart.config.data.labels = this.labels_line;
this.chart.chart.config.data.datasets = this.datasets_lines;
this.chart.chart.update();
}
});
}
getDates() {
let dateArray: string[] = [];
let currentDate: Date = new Date();
currentDate.setTime(this.startDate.getTime());
let pushed: string;
for (let i = 1; i < this.daysNum; i++) {
pushed = currentDate == null ? '' : this._datePipe.transform(currentDate, 'dd/MM/yyyy');
dateArray.push(pushed);
currentDate.setTime(currentDate.getTime() + 24 * 60 * 60 * 1000);
}
re
turn dateArray;
}
}
i m sure this is the right way to do it, and hope this would be helpfull
Like Deyd pointed out before, this is caused by a combination of Angular 2+'s change detection and a bug in ng2-charts.
According to my own observations (correct me if I'm wrong), Angular merges several changes within a very short timeframe into a single collection (changes: SimpleChanges) when ngOnChanges is called.
Unfortunately, ng2-charts only checks if the dataset has been changed with this collection and updates it. Otherwise it completely rebuilds the entire chart. However, because of the way the change detection works, more than one property might have been changed. Then, only the dataset gets updated even if the labels and possibly other properties have been updated as well. See ngOnChanges in ng2-charts:
valor-software/ng2-charts/src/charts/charts.ts
And if you don't want to have a separate copy of ng2-charts in your app and fix the problem yourself, a possible workaround for this problem is to set the dataset with a short delay using JavaScript's built-in function setTimeout(callback: () => void, delay: number).
Before:
#Component({
selector: 'app-root',
template: `
<select (change)="onChange($event.target.value)">
<option value="" disabled selected>Select your option</option>
<option value="0">Option 0</option>
<option value="1">Option 1</option>
</select>
<canvas baseChart
chartType="bar"
[datasets]="barChartData"
[labels]="barChartLabels"
[colors]="barChartColors">
</canvas>
`
})
export class AppComponent implements OnInit {
chartData: string[];
chartLabels: string[];
chartColors: string[];
onChange(id: string) {
getFromApiById(id)
.then(result => this._setChart(result.data, result.labels, result.colors));
}
private _setChart(data: string[], labels: string[], colors: string[]) {
this.chartData = data;
this.chartLabels = labels;
this.chartColors = colors;
}
}
After:
#Component({
selector: 'app-root',
template: `
<select (change)="onChange($event.target.value)">
<option value="" disabled selected>Select your option</option>
<option value="0">Option 0</option>
<option value="1">Option 1</option>
</select>
<canvas baseChart
chartType="bar"
[datasets]="barChartData"
[labels]="barChartLabels"
[colors]="barChartColors">
</canvas>
`
})
export class AppComponent implements OnInit {
chartData: string[];
chartLabels: string[];
chartColors: string[];
onChange(id: string) {
getFromApiById(id)
.then(result => this._setChart(result.data, result.labels, result.colors));
}
private _setChart(data: string[], labels: string[], colors: string[]) {
this.chartLabels = labels;
this.chartColors = colors;
setTimeout(() => {
this.chartData = data;
}, 50);
}
}
Using BaseChartDirective i did chart update and it served the purpose. Sample below:
import { BaseChartDirective } from 'ng2-charts/ng2-charts';
inside the class add as below
#ViewChild(BaseChartDirective) chart: BaseChartDirective;
While you have the values to be changed, add as below
setTimeout(() => {
if (this.chart && this.chart.chart && this.chart.chart.config) {
this.chart.chart.config.data.labels = this.labels_pie;
this.chart.chart.update();
}
});
The trick is in clearing the label and data array, the below code didnt work for me :(
```
clearCharts() {
this.barChartLabels= [];
this.barChartData= [
{data: [], label: 'label1'},
{data: [], label: 'label2'}
];
}
However when I changed the way I cleared the data helped me (Using object reference)
clearCharts() {
this.barChartLabels= [];
this.emptyChartData(this.barChartData);
}
emptyChartData(obj) {
obj[0].data = [];
obj[1].data = [];
obj[0].label = 'label1';
obj[1].label = 'label2';
}
```
Using BaseChartDirective i did chart update and it served the purpose. Sample below:
import { BaseChartDirective } from 'ng2-charts';
inside the class add as below
#ViewChild(BaseChartDirective) chart: BaseChartDirective;
While you have the values to be changed, add as below
this.chart.ngOnChanges({});
This is an issue in the library ng2-charts, to resolve it I have cloned the github of ng2-charts in my app directory and have done following steps :
npm install
in appmodule import ng-2charts.ts from src directory.
add this updateChartLabels function to chart.ts file
call it in the onChanges function.
public ngOnChanges(changes: SimpleChanges): void {
if (this.initFlag) {
if(changes.hasOwnProperty('labels')){
console.log('labels changes ...');
this.updateChartLabels(changes['labels'].currentValue);
}
//..
//...
}
private updateChartLabels(newLabelsValues: string[] | any[]): void {
this.chart.data.labels = newLabelsValues;
}
For those looking for a walk around, for now you can put your labels and data in an object and put that object in an array and just loop through the array in your html. This will redraw the element every time your array changes.
in your type script every time there's a change.
data = [...]; labels = [...]; chartArray = [{data , labels }]
in your html
<canvas *ngFor="let chartData of chartArray " [datasets]="chartData.data" [labels]="chartData.labels" > </canvas>
This is an issue with the current ng2-charts library.
Try the new ng4-charts library which has fixed this issue.
https://www.npmjs.com/package/ng4-charts
There is another way to do it:
In your HTML you have
<canvas baseChart
[datasets]="ChartData"
//...other stuff >
</canvas>
and in the component I have a function which update the chart with new data, and then I clone the datasets and re-assign it
drawChart(){
this.ChartData=[{data: this.dataX, label: 'X'}]; // this.dataX has new values from some place in my code
//nothing happened with my chart yet, until I add this lines:
let clone = JSON.parse(JSON.stringify(this.ChartData));
this.ChartData=clone;
//other stuff like labels etc.
}
this works for me, hope it works for you too
I was able to fix this issue by turning the handler into an arrow function
export class HistogramChartComponent implements OnInit {
constructor(private dataService: MyFruitService ) { }
barChartOptions: ChartOptions = { responsive: true };
barChartLabels: Label[] = ['Apple', 'Banana', 'Kiwifruit', 'Blueberry', 'Orange', 'Grapes'];
barChartType: ChartType = 'bar';
barChartLegend = true;
barChartPlugins = [];
barChartData: ChartDataSets[] = [
{ data: [45, 37, 60, 70, 46, 33], label: 'Best Fruits' }
];
ngOnInit() {
this.dataService
.getDocument("Foobar")
.subscribe(this.handleResponse);
}
handleResponse = (doc: MyFruitDocument) => {
console.log('document: ', doc);
let labels = doc.dataPoints.map(p => p.fruitName);
let data = { data: doc.dataPoints.map(p => p.value), label: 'Best Fruits' };
this.barChartLabels = labels;
this.barChartData = [ data ];
}
}
I have also faced this issue when trying to update the labels, (specifically when trying to send a shorter array), And this solved it:
#ViewChild(BaseChartDirective) chart!: BaseChartDirective;
and then, when updating the labels:
this.chart.chart!.config.data.labels = [...]
No need to call the update() method.
Since I didn't manage to get one of the above solutions to work properly, I want to contribute my solution, in case someone stumbles across this post and also got stuck with the present approaches.
I have the HTML similar to #mustafa918:
<div>
<canvas #canvas id="canvas"
baseChart [datasets]="lineChartData"
[labels]="lineChartLabels"
[colors]="lineChartColors"
[options]="lineChartOptions"
[chartType]="lineChartType"
[legend]="lineChartLegend"
(chartHover)="chartHovered($event)"
(chartClick)="chartClicked($event)">
</canvas>
</div>
And for the initialisation of the charts data in typescript i have:
public lineChartData: Array<any> = [
{ data: this.heights, label: 'Heights History', type: 'line', fill: false},
{ data: this.widths, label: 'Widths History', type: 'line', fill: false }];
And for me it worked only by setting the data and labels at the same time and don't use chart.update() - chart is the reference to the BaseChartDirective.
I loaded the respective data and labels beforehand, so that in this.heights, this.width and this.lineChartLabels are corresponding data.
E.g. : The entries on heights[i], widths[i] and lineChartLabels[i] match with the element in my elementArray at index i => element ={ "height":30, "width":20, "label":"box"}
setDatasets() {
//store data in chart references
var arrHeights = [];
for (var i in this.heights) {
arrHeights.push({ x: this.lineChartLabels[i], y: this.heights[i] });
}
var arrWidths= [];
for (var i in this.widths) {
arrWidths.push({ x: this.lineChartLabels[i], y: this.widths[i] });
}
this.lineChartData[0].data = arrHeights;
this.lineChartData[1].data = arrWidths;
}
I hope this helps someone :) Good Luck!
Today i struggled with similar problem, it appears there is a huge bug inside the updateChartData function of ng2-charts library version 1.6.0.
Here is the original function:
updateChartData = function (newDataValues) {
if (Array.isArray(newDataValues[0].data)) {
this.chart.data.datasets.forEach(function (dataset, i) {
dataset.data = newDataValues[i].data;
if (newDataValues[i].label) {
dataset.label = newDataValues[i].label;
}
});
}
else {
this.chart.data.datasets[0].data = newDataValues;
}
}
As you can see this updates only the data and the label, but all other properties are left behind. In my case i wanted to update also the pointBorderColor so i decided to override this.
First i get a reference to the ng2-charts library:
import { BaseChartDirective } from 'ng2-charts';
#ViewChild(BaseChartDirective) chart: any;
It is very important the type is "any", because otherwise typescript will not allow me to override a private function.
Then i fix the bug in the function and override it in afterVIew init:
ngAfterViewInit(){
if (this.chart) {
this.chart.updateChartData = function (newDataValues) {
if (Array.isArray(newDataValues[0].data)) {
this.chart.data.datasets.forEach(function (dataset, i) {
dataset.data = newDataValues[i].data;
if (newDataValues[i].pointBorderColor) {
dataset.pointBorderColor = newDataValues[i].pointBorderColor;
}
if (newDataValues[i].label) {
dataset.label = newDataValues[i].label;
}
});
}
else {
this.chart.data.datasets[0].data = newDataValues;
}
}.bind(this.chart);
}
}
Based on above answers, I extended this function and everything works fine now!
TS Code:
Declare : import { BaseChartDirective } from 'ng2-charts';
#ViewChild(BaseChartDirective) chart: BaseChartDirective;
public lineChartData: ChartDataSets[] = [
{ data: [0, 0, 0, 0, 0, 0, 0], label: 'Data 1' },
{ data: [0, 0, 0, 0, 0, 0, 0], label: 'Data 2' }
];
public lineChartLabels: Label[] = ['Label1', 'Label2', 'Label3', 'Label4',
'Label5', 'Label6';
TS Function:
refresh_chart(){
setTimeout(() => {
if (this.chart && this.chart.chart && this.chart.chart.config) {
this.chart.chart.config.data.datasets.forEach(x => {
x.data = [];
});
let index = 0;
this.chart.chart.config.data.datasets.forEach(x => {
x.data = this.lineChartData[index].data;
index++;
});
this.chart.chart.update();
}
}, 500);
}
HTML Code:
<canvas baseChart [datasets]="lineChartData" class="canvas-wh" [labels]="lineChartLabels"
[options]="lineChartOptions" [colors]="lineChartColors" [legend]="lineChartLegend"
[chartType]="lineChartType" [plugins]="lineChartPlugins">
For me, it worked only after using ViewChildren and not ViewChild.
TS:
#ViewChildren('baseLineChart1') chart !: QueryList<BaseChartDirective>;
this.chart.forEach((child) => { child.update() })
HTML:
<canvas class="card-line-chart" baseChart #baseLineChart1>
Related
Create dynamically updating graph with Angular 6 on API calls
I am trying to implement angular chart.js graph dynamically update based on api calls Currently i have implemented it to be a static chart, as follows. service import { Injectable } from '#angular/core'; import {HttpClient,HttpHeaders} from '#angular/common/http'; import 'rxjs/add/operator/map'; #Injectable({ providedIn: 'root' }) export class WeatherService { constructor(private _http:HttpClient) { } dailyForecast(){ return this._http.get("http://samples.openweathermap.org/data/2.5/history/city?q=Warren,OH&appid=b6907d289e10d714a6e88b30761fae22") .map(result => result); } } component ngOnInit(){ this._weather.dailyForecast() .subscribe(res=>{ let temp_max = res['list'].map(res=> res.main.temp_max) let temp_min = res['list'].map(res=> res.main.temp_min) let alldates = res['list'].map(res=> res.dt) let weatherDates = [] alldates.forEach((res) => { let jsdate = new Date(res*1000) weatherDates.push(jsdate.toLocaleTimeString('en',{year:'numeric',month:'short',day:'numeric'})) }); this.chart = new Chart('canvas',{ type: 'line', data : { labels:weatherDates, datasets: [ { data:temp_max, borderColor: '#3cba9f', fill:false }, { data:temp_min, borderColor: '#ffcc00', fill:false } ] }, options:{ legend:{ display:false }, scales: { xAxes: [{ display:true }], yAxes: [{ display:true }] } } }); }) } But i wanted it to make , look like IQ Option like graph(To update that graph component only) like this, dynamically updating graph what are the options that i can take? and how can i implement those?
You can do it through polling service, where you make each request after a specific interval, You can make a request based on the interval using $interval, Observable.interval(3000) or let intervalId = setInterval(() => { this.updateData(); }, 3000);
Ag-Grid cellRender with Button Click
I am using an angular 5 with ag-grid data table i cant able to trigger a click event from cell using cellRenderer here how am using my ag-grid --> colDefs this.columnDefs = [ {headerName: '#', rowDrag: true, width: 75}, {headerName: 'One', field: 'fieldName', cellRenderer : function(params){ return '<div><button (click)="drop()">Click</button></div>' } } ]; drop() { alert("BUTTON CLICKEFD") } if am using onClick="alert("123")" --> it works, but i cant able to use onClick="drop()" it throws drop of undefined, i tried this too inside of cellRenderer --> params = params.$scope.drop = this.drop; if am using gridOptions with angularCompileRows : true it throws an error Cannot read property '$apply' of undefined. Do i need to install ag-grid enterprise ??
You can use cellRenderer with a button component. If you want to get the click event on the button from the user on the table, just declare the callback function you want to cellRendererParams. // app.component.ts columnDefs = [ { headerName: 'Button Col 1', cellRenderer: 'buttonRenderer', cellRendererParams: { onClick: this.onBtnClick.bind(this), label: 'Click' } }, ... ] The above code is just a small part, check out full example on Stackblitz
Angular. Here we create the button cell renderer as an Angular component that implements the ICellRendererAngularComp interface. Access to the params object can be found on the agInit hook. // app/button-cell-renderer.component.ts #Component({ selector: 'btn-cell-renderer', template: ` <button (click)="btnClickedHandler($event)">Click me!</button> `, }) export class BtnCellRenderer implements ICellRendererAngularComp, OnDestroy { private params: any; agInit(params: any): void { this.params = params; } btnClickedHandler() { this.params.clicked(this.params.value); } ngOnDestroy() { // no need to remove the button click handler as angular does this under the hood } } The renderer is registered to ag-Grid via gridOptions.frameworkComponents. Note that we’re passing the button click handler dynamically to our renderer via cellRendererParams - allowing for a more flexible and reusable renderer. // app/app.component.ts this.columnDefs = [ { field: 'athlete', cellRenderer: 'btnCellRenderer', cellRendererParams: { clicked: function(field: any) { alert(`${field} was clicked`); } }, minWidth: 150, } // [...] ]; this.frameworkComponents = { btnCellRenderer: BtnCellRenderer }; It is also necessary to pass our renderer to our #NgModule decorator to allow for dependency injection. // app/app.modules.ts #NgModule({ imports: [ BrowserModule, FormsModule, HttpClientModule, AgGridModule.withComponents([BtnCellRenderer]), ], declarations: [AppComponent, BtnCellRenderer], bootstrap: [AppComponent], }) See demo. Learn more about Angular Cell Renderer. Vanilla JavaScript. A DOM element is created in the init method, which is then returned in the getGui method. The optional destroy hook has also included to do some cleanup (removing the click listener from our component). // btn-cell-renderer.js function BtnCellRenderer() {} BtnCellRenderer.prototype.init = function(params) { this.params = params; this.eGui = document.createElement('button'); this.eGui.innerHTML = 'Click me!'; this.btnClickedHandler = this.btnClickedHandler.bind(this); this.eGui.addEventListener('click', this.btnClickedHandler); } BtnCellRenderer.prototype.getGui = function() { return this.eGui; } BtnCellRenderer.prototype.destroy = function() { this.eGui.removeEventListener('click', this.btnClickedHandler); } BtnCellRenderer.prototype.btnClickedHandler = function(event) { this.params.clicked(this.params.value); } The renderer is registered to ag-Grid in gridOptions.components and is used on the athlete column. Note that we’re passing the button click handler dynamically to our renderer via cellRendererParams - this makes for a more flexible and reusable renderer. // main.js var gridOptions = { columnDefs: [ { field: 'athlete', cellRenderer: 'btnCellRenderer', cellRendererParams: { clicked: function(field) { alert(`${field} was clicked`); } }, minWidth: 150 }, // [...] components: { btnCellRenderer: BtnCellRenderer } }; See demo. Learn more about JavaScript Cell Renderers. React. Here our button cell renderer is constructed as a React component. The only thing to take note of here is that cell params will be available on the component via props. // BtnCellRenderer.jsx class BtnCellRenderer extends Component { constructor(props) { super(props); this.btnClickedHandler = this.btnClickedHandler.bind(this); } btnClickedHandler() { this.props.clicked(this.props.value); } render() { return ( <button onClick={this.btnClickedHandler}>Click Me!</button> ) } } The renderer is registered to ag-Grid via gridOptions.frameworkComponents. The button click handler is passed to our renderer at run time via cellRendererParams - allowing for a more flexible and reusable renderer. // index.jsx columnDefs: [ { field: 'athlete', cellRenderer: 'btnCellRenderer', cellRendererParams: { clicked: function(field) { alert(`${field} was clicked`); }, }, // [...] } ]; frameworkComponents: { btnCellRenderer: BtnCellRenderer, } See demo. Learn more about React Cell Renderers. Vue.js. Configuring the renderer in Vue.js is simple: // btn-cell-renderer.js export default Vue.extend({ template: ` <span> <button #click="btnClickedHandler()">Click me!</button> </span> `, methods: { btnClickedHandler() { this.params.clicked(this.params.value); } }, }); As with the other frameworks, the renderer is registered to ag-Grid via gridOptions.frameworkComponents and the button click handler is passed to our renderer at run time via cellRendererParams - allowing for a more flexible and reusable renderer. // main.js this.columnDefs = [ { field: 'athlete', cellRenderer: 'btnCellRenderer', cellRendererParams: { clicked: function(field) { alert(`${field} was clicked`); } }, // [...] ], this.frameworkComponents = { btnCellRenderer: BtnCellRenderer } See demo. Learn more about Vue.js Cell Renderers. Read the full blog post on our website or check out our documentation for a great variety of scenarios you can implement with ag-Grid. Ahmed Gadir | Developer # ag-Grid
To expand on the answer from #T4professor, I will post some code to also have a dynamic label on that Click button. // Author: T4professor import { Component, OnInit, AfterContentInit } from '#angular/core'; import { ICellRendererAngularComp } from 'ag-grid-angular'; #Component({ selector: 'app-button-renderer', template: ` <button class="{{btnClass}}" type="button" (click)="onClick($event)">{{label}}</button> ` }) export class ButtonRendererComponent implements ICellRendererAngularComp { //https://stackblitz.com/edit/angular-ag-grid-button-renderer?file=src%2Fapp%2Fapp.component.ts params: any; label: string; getLabelFunction: any; btnClass: string; agInit(params: any): void { this.params = params; this.label = this.params.label || null; this.btnClass = this.params.btnClass || 'btn btn-primary'; this.getLabelFunction = this.params.getLabelFunction; if(this.getLabelFunction && this.getLabelFunction instanceof Function) { console.log(this.params); this.label = this.getLabelFunction(params.data); } } refresh(params?: any): boolean { return true; } onClick($event) { if (this.params.onClick instanceof Function) { // put anything into params u want pass into parents component const params = { event: $event, rowData: this.params.node.data // ...something } this.params.onClick(params); } } } Then, in the component with the grid you do the following: columnDefs = [ { headerName: 'Publish', cellRenderer: 'buttonRenderer', cellRendererParams: { onClick: this.onRowPublishBtnClick.bind(this), label: 'Publish', getLabelFunction: this.getLabel.bind(this), btnClass: 'btn btn-primary btn-sm' } } ] onRowPublishBtnClick(e) { this.rowDataClicked = e.rowData; } getLabel(rowData) { console.log(rowData); if(rowData && rowData.hasIndicator) return 'Republish'; else return 'Publish'; }
You have this issue because you invoke drop() incorrectly you should change it to this.drop() In general you should use cellRenderer property with simple logic. More convenient way for complex logic renderer you should use cellRendererFramework: YourCustomRendererAngularComponent. columnDefs = [ { headerName: 'Col Name', cellRendererFramwork: MyAngularRendererComponent, // RendererComponent suffix it is naming convention cellRendererParams: { onClick: (params) => this.click(params); } }, ... ] MyAngularRendererComponent should implements AgRendererComponent. Also in angular module where you use MyAngualrRendererComponent don`t forget put this code: #NgModule({ imports: [ AgGridModule.withCompoennts([ MyAngualrRendererComponent ]) ] })
I was looking for a solution to this but for multiple buttons in the same column. I couldn't find an answer anywhere so I wrote up this Plain Javascript solution. I hope it helps other people looking for the solution I was looking for. Also open to suggestions on how to make the javascript less hacky. // multi-btn-cell-renderer.js function multiBtnCellRenderer() {} multiBtnCellRenderer.prototype.init = function(params) { var self = this; self.params = params; self.num_buttons = parseInt(this.params.num_buttons); self.btnClickedHandlers = {}; let outerDiv = document.createElement('div') for(let i = 0; i < self.num_buttons; i++) { let button = document.createElement('button'); button.innerHTML = self.params.button_html[i]; outerDiv.appendChild(button); self.btnClickedHandlers[i] = function(event) { self.params.clicked[i](self.params.get_data_id()); }.bind(i, self); button.addEventListener('click', self.btnClickedHandlers[i]); } self.eGui = outerDiv; }; multiBtnCellRenderer.prototype.getGui = function() { return this.eGui; }; multiBtnCellRenderer.prototype.destroy = function() { for(let i = 0; i < this.num_buttons; i++) { this.eGui.removeEventListener('click', this.btnClickedHandlers[i]); } }; // main.js var columnDefs = [ { headerName: "Action", maxWidth: 60, filter: false, floatingFilter: false, suppressMenu: true, sortable: false, cellRenderer: multiBtnCellRenderer, cellRendererParams: { num_buttons: 2, button_html: ["<i class='fa fa-pencil'></i>","<i class='fa fa-trash'></i>"], get_data_id: function() { return this.data.id; }, clicked: { 0: function(data_id) { $.get(`/employee/${data_id}/edit`) }, 1: function(data_id) { $.delete(`/employee/${data_id}`) } } } } ]
Angular create Dynamic Component recursively
I am trying to build a dynamic component based on a Config. The component would read the config recursively and create the component. It is found that the method ngAfterViewInit() would only be called twice. #Component({ selector: "dynamic-container-component", template: ` <div #container draggable="true" (dragstart)="dragstart($event)" (drop)="drop($event)" (dragover)="dragover($event)" style="border: 1px solid; min-height: 30px"></div> ` }) export default class DynamicContainerComponent { #Input() dynamicConfig: DynamicConfig; #ViewChild("container", {read: ElementRef}) private elementRef: ElementRef; private isContainer: boolean; private componentRef: ComponentRef<any>; private componentRefs: ComponentRef<any>[] = []; constructor( private componentFactoryResolver: ComponentFactoryResolver, private injector: Injector, private viewContainer: ViewContainerRef, private render: Renderer2 ){ console.log("running"); } ngAfterViewInit(){ if (this.dynamicConfig){ console.log(this.dynamicConfig) if (this.dynamicConfig.getType() == ComponentType.INPUT){ this.isContainer = false; let componetFactory: ComponentFactory<InputComponent> = this.componentFactoryResolver.resolveComponentFactory(InputComponent); this.componentRef = this.viewContainer.createComponent(componetFactory); this.render.appendChild(this.elementRef.nativeElement, this.componentRef.location.nativeElement); }else { this.isContainer = true; let items: DynamicConfig[] = this.dynamicConfig.getItems(); if (items){ for (var i=0; i<items.length; i++){ let item: DynamicConfig = items[i]; let componetFactory: ComponentFactory<DynamicContainerComponent> = this.componentFactoryResolver.resolveComponentFactory(DynamicContainerComponent); let componentRef: ComponentRef<DynamicContainerComponent> = this.viewContainer.createComponent(componetFactory); componentRef.instance.dynamicConfig = item; this.componentRefs.push(componentRef); this.render.appendChild(this.elementRef.nativeElement, componentRef.location.nativeElement); } } } }else { console.log("config does not exist"); } } dragstart(event){ debugger; } drop(event){ debugger; } dragover(event){ debugger; event.preventDefault(); } } The Component would be created by other component by the following code. If The Dynamic Component would create another Dynamic Component by componentFactoryResolver. var configJson = { type: ComponentType.CONTAINER, items: [ { type: ComponentType.CONTAINER, items: [{ type: ComponentType.CONTAINER, items: [{ type: ComponentType.CONTAINER, items: [{ type: ComponentType.INPUT }] }] }] } ] } this.config = new DynamicConfig(); this.config.assign(configJson); console.log(this.config); Update I found a similar issue in github: https://github.com/angular/angular/issues/10762 I have done something suggested by other people. but I think it is just a dirty fix. ngAfterViewInit(){ setTimeout(function(){ if (this.dynamicConfig){ console.log(this.dynamicConfig) if (this.dynamicConfig.getType() == ComponentType.INPUT){ this.isContainer = false; let componetFactory: ComponentFactory<InputComponent> = this.componentFactoryResolver.resolveComponentFactory(InputComponent); this.componentRef = this.viewContainer.createComponent(componetFactory); this.render.appendChild(this.elementRef.nativeElement, this.componentRef.location.nativeElement); }else { this.isContainer = true; let items: DynamicConfig[] = this.dynamicConfig.getItems(); if (items){ for (var i=0; i<items.length; i++){ let item: DynamicConfig = items[i]; let componetFactory: ComponentFactory<DynamicContainerComponent> = this.componentFactoryResolver.resolveComponentFactory(DynamicContainerComponent); let componentRef: ComponentRef<DynamicContainerComponent> = this.viewContainer.createComponent(componetFactory); componentRef.instance.dynamicConfig = item; this.componentRefs.push(componentRef); this.render.appendChild(this.elementRef.nativeElement, componentRef.location.nativeElement); } } } }else { console.log("config does not exist"); } }.bind(this)) }
By the time you create your dynamic component angular has almost finished change detection cycle. This way you can either run: componentRef.changeDetectorRef.detectChanges() Note: setTimeout has similar effect but fires change detection cycle on the whole app or rename lifecycle hook to ngOnInit Also you're passing wrong input to dynamic component: let item: DynamicConfig = items[i]; ^^^^^^^^^^^^^ but it is not DynamicConfig instance but rather plain object ... componentRef.instance.dynamicConfig = item; it should be: let item: any = items[i]; const config = new DynamicConfig(); config.assign(item); componentRef.instance.dynamicConfig = config; Ng-run Example
Update/re-render Charts.js in Angular 4
Trying to re-render charts every time when i have some change in API, here is the method: ngOnChanges(changes: SimpleChanges) { const data = changes['data'].currentValue; this.draw(data); } and draw function private draw(charts) { this.charts = charts.map((chart, i) => { this.options.title.text = chart.title; const options = { type: 'bar', data: chart, options: this.options }; return new Chart(`chart${i}`, options); }); this.charts.forEach(chart => { chart.update(); }); } you can see that i try to update/re-render charts in draw function. In console log i can see that chart object was updated with new data, but they just disappear from page. What's wrong in my update method?
I found a solution, i know it's a hack, but for the moment it's ok ))) So, basically i check if charts already exist in DOM, and if exist remove them and create new canvas elements. private removeAction() { const block = document.querySelector('#chartsBlock'); //remove while (block.firstChild) { block.removeChild(block.firstChild); } // create this.charts.forEach((chart, i) => { let canvas = document.createElement('canvas'); canvas.id = `chart${i}`; block.appendChild(canvas); }); }
You could try using ChangeDetectorRef: import { ChangeDetectorRef } from '#angular/core'; constructor(private detector: ChangeDetectorRef) {} ngOnChanges(changes: SimpleChanges) { const data = changes['data'].currentValue; this.draw(data); this.detector.detectChanges() // if detectChanges() doesn't work, try markForCheck() }
Vuejs chart not displaying after page reload
I created a chart using vue-chartjs which renders some data from a JSON endpoint. The problem is that the chart is not rendered after the page reloads. This is the HTML that calls the chart component. <absentee-chart v-if="isLoaded==true" :chart-data="this.chartData"></absentee-chart> This is the absenteeChart.js file that renders the chart data. import { Bar, mixins } from 'vue-chartjs' export default { extends: Bar, mixins: [mixins.reactiveProp], data: () => ({ options: { responsive: true, maintainAspectRatio: false } }), mounted () { this.renderChart(this.chartData, this.options) } } And finally my .vue file. created () { axios .get(constants.baseURL + "absentee-reports/graph", auth.getAuthHeader()) .then(response => { this.graph = response.data; var males = { data: [] }; var females = { data: [] }; var len = this.graph.length; for (var i = 0; i < len; i++) { if (this.graph[i].year == "2009") { this.labels.push(this.graph[i].month); //push to males this.datasets[0].data.push(this.graph[i].males); //push to females this.datasets[1].data.push(this.graph[i].females); } } this.isLoaded = true; this.chartData.labels = this.labels; this.chartData.datasets = this.datasets; }); } UPDATE: The chart appears after I resize my browser page.
The solution to this was separating all my chart logic from my .vue file and omitting the use of mixins and reactiveProp. From what I could tell, this issue lies deep within the core of chart.js Hope this helps somebody. Cheers :)