Use external JavaScript library in Angular 8 application - javascript

I am new to Angular and i want to develop a funnel-graph. i like the funnel-graph-js library. i tried a lot but haven't succeed.
here is my funnel-graph-directive.ts
import { Directive, ElementRef } from '#angular/core';
// import * as graph from '../../../assets/js/funnel-graph.js';
import * as graph from 'funnel-graph-js/dist/js/funnel-graph.js';
var graph = new FunnelGraph({
container: '.funnel',
gradientDirection: 'horizontal',
data: {
labels: ['Impressions', 'Add To Cart', 'Buy'],
subLabels: ['Direct', 'Social Media', 'Ads'],
colors: [
['#FFB178', '#FF78B1', '#FF3C8E'],
['#A0BBFF', '#EC77FF'],
['#A0F9FF', '#7795FF']
],
values: [
[3500, 2500, 6500],
[3300, 1400, 1000],
[600, 200, 130]
]
},
displayPercent: true,
direction: 'horizontal'
});
graph.draw();
#Directive({
selector: '[appFunnelGraph]'
})
export class FunnelGraphDirective {
style: any;
constructor(el: ElementRef) {
el.nativeElement.style.backgroundColor = 'yellow';
}
}
I have added these lines in my angular.json
"styles": [
"src/styles.scss",
"./node_modules/funnel-graph-js/dist/css/main.css",
"./node_modules/funnel-graph-js/dist/css/theme.css"
],
"scripts": [
"./node_modules/funnel-graph-js/dist/js/funnel-graph.js"
]
Here is the error i am getting

As long as you linked the javascript file in the html, it will work fine.
EDIT:
A better way to include an addition javascript file is to put it into the "scripts" section in the angular.json file. You can also add
declare const FunnelGraph: any
in order to compile without errors. This has been taken from an answer to a stackoverflow question and this guide. Remember to include the css files in that json too!
EDIT END
You get that error because the code tries to look for an HTML element with a class named "funnel", but cannot find it. Since this is a directive, it would be better if it was a little more generalized.
First of all, you should move your graph-generating code inside the constructor, since that's were the directive logic resides. To better generalize this directive, it would be best if you gave a unique id to that element and change the code accordingly. This is how I would do it:
HTML:
<div id="funnel-graph-1" appFunnelGraph></div>
JS:
import { Directive, ElementRef } from '#angular/core';
// It should be fine to just import this in the html with a script tag
// import * as graph from 'funnel-graph-js/dist/js/funnel-graph.js';
#Directive({
selector: '[appFunnelGraph]'
})
export class FunnelGraphDirective {
style: any;
constructor(el: ElementRef) {
el.nativeElement.style.backgroundColor = 'yellow';
var graph = new FunnelGraph({
// Generalize the container selector with the element id
container: '#' + el.nativeElement.id,
gradientDirection: 'horizontal',
data: {
labels: ['Impressions', 'Add To Cart', 'Buy'],
subLabels: ['Direct', 'Social Media', 'Ads'],
colors: [
['#FFB178', '#FF78B1', '#FF3C8E'],
['#A0BBFF', '#EC77FF'],
['#A0F9FF', '#7795FF']
],
values: [
[3500, 2500, 6500],
[3300, 1400, 1000],
[600, 200, 130]
]
},
displayPercent: true,
direction: 'horizontal'
});
graph.draw();
}
}

I ended up by creating service instead of using directive approach.
First i generated a service called dynamic-script-loader-service in
my dashboard module.
dynamic-service-loader.service.service.ts
import { Injectable } from '#angular/core';
interface Scripts {
name: string;
src: string;
}
export const ScriptStore: Scripts[] = [
{ name: 'chartjs', src: 'https://unpkg.com/funnel-graph-js#1.3.9/dist/js/funnel-graph.min.js' },
];
declare var document: any;
#Injectable()
export class DynamicScriptLoaderServiceService {
private scripts: any = {};
constructor() {
ScriptStore.forEach((script: any) => {
this.scripts[script.name] = {
loaded: false,
src: script.src
};
});
}
load(...scripts: string[]) {
const promises: any[] = [];
scripts.forEach((script) => promises.push(this.loadScript(script)));
return Promise.all(promises);
}
loadScript(name: string) {
return new Promise((resolve, reject) => {
if (!this.scripts[name].loaded) {
//load script
let script = document.createElement('script');
script.type = 'text/javascript';
script.src = this.scripts[name].src;
if (script.readyState) { //IE
script.onreadystatechange = () => {
if (script.readyState === 'loaded' || script.readyState === 'complete') {
script.onreadystatechange = null;
this.scripts[name].loaded = true;
resolve({ script: name, loaded: true, status: 'Loaded' });
}
};
} else { //Others
script.onload = () => {
this.scripts[name].loaded = true;
resolve({ script: name, loaded: true, status: 'Loaded' });
};
}
script.onerror = (error: any) => resolve({ script: name, loaded: false, status: 'Loaded' });
document.getElementsByTagName('head')[0].appendChild(script);
} else {
resolve({ script: name, loaded: true, status: 'Already Loaded' });
}
});
}
}
dashboard.component.ts
import { Component, OnInit, ViewEncapsulation } from '#angular/core';
import { DynamicScriptLoaderServiceService } from '../dynamic-script-loader-service.service';
import * as FunnelGraph from 'funnel-graph-js';
function dashboardFunnel() {
const graph = new FunnelGraph({
container: '.funnel',
// gradientDirection: 'horizontal',
data: {
labels: ['Label 7', 'Label 1', 'Label 2', 'Label 3', 'Label 4', 'Label 5', 'Label 6'],
colors: ['#00A8FF', '#00A8FF', '#00A8FF', '#00A8FF', '#00A8FF', '#00A8FF', '#00A8FF'],
// color: '#00A8FF',
values: [12000, 11000, 10000, 9000, 8000, 7000, 6000]
},
displayPercent: true,
direction: 'horizontal',
});
graph.draw();
}
#Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class DashboardComponent implements OnInit {
constructor(
private dynamicScriptLoader: DynamicScriptLoaderServiceService
) {}
ngOnInit() {
this.loadScripts();
dashboardFunnel();
}
private loadScripts() {
// You can load multiple scripts by just providing the key as argument into load method of the service
this.dynamicScriptLoader.load('chartjs', 'random-num').then(data => {
// Script Loaded Successfully
}).catch(error => console.log(error));
}
}
added providers in my dashboard.module.ts
providers: [DynamicScriptLoaderServiceService],
added css in my angular.json
"styles": [
"src/styles.scss",
"./node_modules/funnel-graph-js/dist/css/main.css",
"./node_modules/funnel-graph-js/dist/css/theme.css"
],
added div with class funnel in dashboard.component.html
<div class="funnel"></div>

Related

Error with Angular ag-grid: "Could not find component class XXX, did you forget to configure this component"

I am getting this error from Chrome when running an ag-grid application. Basically, I have following
export class CustomHeader implements IHeaderAngularComp {
private params: any;
#ViewChild('menuButton', { read: ElementRef }) public menuButton;
agInit(params): void {
this.params = params;
}
onMenuClicked() {
this.params.showColumnMenu(this.menuButton.nativeElement);
}
refresh(params: IHeaderParams): boolean {
return true;
}
}
...
ColumnDefs = {
...
{
field: "A column", editable: false, sortable: false, width: 90,
type: 'stringColumn', centered: true, pinned: 'left',
headerComponent: CustomHeader,
headerComponentParams: {
template: `
<div ref="eLabel" class="lmn-form-group">
<input ref="eCheck" type="checkbox">
<span>Use This</span>
</div>
`
}
},
...
}
The Chrome says it does not recognize this CustomHeader:
Could not find component class CustomHeader {
agInit(params) {
this.params = params;
}
onMenuClicked() {
this.params.showColumnMenu(this.menuButton.nativeElement);
}
refresh(params) {
return true;
}
}, did you forget to configure this component?"
Is there anything I am missing?
Have you seen the example in the documentation?
Note that you also need to provide the [frameworkComponents] grid property so that the grid knows to use the header component:
this.frameworkComponents = { agColumnHeader: CustomHeader };
If you come here in 2022 and you are not using the latest version ag-grid, do check the correct version documents here:
https://www.ag-grid.com/archive/
For me at version 25.3, still need to use frameworkComponents as #Shuheb said, but the latest version 27.3 docs do not have this property any more.
As the 25.3 doc says, There are two ways to register custom components:
By name.
Direct reference.
By Name
export class AppComponent {
frameworkComponents: [
'cubeComponent': CubeComponent
];
columnDefs: [
{
headerName: "Cube",
field: "value",
cellRenderer: 'cubeComponent',
}
]
//...other properties & methods
}
By Direct Reference
export class AppComponent {
columnDefs: [
{
headerName: "Cube",
field: "value",
cellRendererFramework: CubeComponent,
}
]
//...other properties & methods
}

How to assign HTML element ID, through an Angular component, to a JavaScript function?

I found a JavaScript library and a function, I struggled, but did include this JavaScript function in my Angular application.
The JavaScript code prints takes in an object (myData: two arrays of numbers), and passes this data to a function, which draws two charts, each chart corresponds to one of the two arrays of numbers.
First thing I had to do is figure out how to include this JavaScript in my Angular project, which wasn't easy. And I'm even still not sure if it worked or not, because I haven't finished connecting everything together.
So here's the JavaScript code:
<!DOCTYPE html>
<html>
<head>
<!-- This page was last built at 2020-10-16 19:40 -->
<meta charset="utf-8"/>
<!-- Plotly.js -->
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
</head>
<body>
<div class="myDiv" id="myDiv">
</div>
<div class="myDiv" id="myDiv2">
</div>
<script>
myData = {
"loss": [
0.6437230565968681,
.
.
.
0.015016184188425541
],
"accuracy": [
0.6740923523902893,
.
.
.
0.9943200945854187
]
}
dataPlot(myData)
function dataPlot(myData){
function range(start, end) {
var ans = [];
for (let i = start; i <= end; i++) {
ans.push(i);
}
return ans;
}
yLoss = myData['loss'];
yAccuracy = myData['accuracy'];
traceLoss = {
type: 'scatter',
x: range(yLoss.length>0?1:0,yLoss.length),
y: yLoss,
mode: 'lines',
name: 'Red',
line: {
color: 'rgb(219, 64, 82)',
width: 1
}
};
traceAccuracy = {
type: 'scatter',
x: range(yAccuracy.length>0?1:0,yAccuracy.length),
y: yAccuracy,
mode: 'lines',
name: 'Blue',
line: {
color: 'rgb(55, 128, 191)',
width: 1
}
};
var layoutLoss = {
title: 'Model Binary Crossentropy Loss',
xaxis: {
title: 'Epochs',
showgrid: false,
zeroline: false
},
yaxis: {
title: 'Loss',
showline: false
}
// for static size
,width: 900,
height: 500
};
var layoutAccuracy = {
title: 'Model Training Data Accuracy',
xaxis: {
title: 'Epochs',
showgrid: false,
zeroline: false
},
yaxis: {
title: 'Accuracy',
showline: false
}
// for static size
,width: 900,
height: 500
};
var dataLoss = [traceLoss];
var dataAccuracy = [traceAccuracy];
divs('myDiv','myDiv2')
function divs(div1, div2) {
Plotly.newPlot(div1, dataLoss, layoutLoss);
Plotly.newPlot(div2, dataAccuracy, layoutAccuracy);
}
}
</script>
</body>
</html>
If you copy that code to a .html file it'll work.
See at the bottom of the code this function:
function divs(div1, div2) {
Plotly.newPlot(div1, dataLoss, layoutLoss);
Plotly.newPlot(div2, dataAccuracy, layoutAccuracy);
}
This function takes div1 and div2 values, those are the IDs for the 2 divs containing the charts.
What I need is, first, to pass myData to the function, which is easy and doable, not a problem.
The problem is, how am I supposed to give the IDs of the divs to the JavaScript function, and have it know where those divs are to begin with?
I've been trying with #ViewChildren and ElementRef. But no success.
Here's my HTML template so far:
<div #divs *ngFor="let item of [1,2]" [id]="'myDiv-' + item">Div {{item}}+{{whatevs}}</div>
<button (click)="getDivs()">Get divs</button>
and the component:
import { Component, ElementRef, OnInit, QueryList, ViewChildren } from '#angular/core';
declare var dataPlot: any;
#Component({
selector: 'jhi-ml-model-data',
templateUrl: './ml-model-data.component.html',
})
export class MlModelDataComponent implements OnInit {
#ViewChildren("divs", { read: ElementRef }) divs!: QueryList<ElementRef>;
myData: any;
constructor() { }
getDivs(): void {
// this.divs.forEach((div: ElementRef) => div.nativeElement);
this.divs.forEach((div: ElementRef) => console.log(div.nativeElement.getAttribute('id')));
// this^ gets the IDs for both divs, not each one individually.
}
ngOnInit(): void {
this.myData = {
"loss": [0.6437230565968681,
.
.
.
0.39040950554258685],
"accuracy": [0.6740923523902893,
.
.
.
0.9943200945854187]
}
dataPlot(this.myData);
dataPlot.divs();
}
}
I feel like I've put myself in an untangleable mess, and feel like there must be

How to create,edit and delete data in Ng2 smart table

I used [ng2 smart table] in my angular 2 project, I need to send an API request using http.post() method but the problem happened when I click on the button to confirm data face this error in console:
ERROR TypeError: _co.addClient is not a function.
this is code in service.ts :
import { Injectable } from '#angular/core';
import { Clients } from './clients.model';
import { HttpClient, HttpErrorResponse } from '#angular/common/http';
import { Observable} from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class ClientsService {
url="http://localhost:21063/api/clints"
clients:Clients[];
client:Clients;
constructor(private http:HttpClient) { }
getAllClients(): Observable<Clients[]>{
return this.http.get<Clients[]>(this.url);
}
addClient(event){
this.http.post<Clients>(this.url,this.client)
.subscribe(
res=>{
console.log(res);
event.confirm.resolve(event.Clients);
},
(err: HttpErrorResponse) => {
if (err.error instanceof Error) {
console.log("Client-side error occurred.");
} else {
console.log("Server-side error occurred.");
}
}
)
}
and this my template :
<div class="mainTbl">
<ng2-smart-table
[settings]="settMain"
[source]="this.Service.clients"
(createConfirm)="addClient($event)"
(editConfirm)="onEditConfirm($event)"
(deleteConfirm)="onDeleteConfirm($event)"
></ng2-smart-table>
</div>
compontent .ts
settMain = {
noDataMessage: 'عفوا لا توجد بيانات',
actions: {
columnTitle: 'إجراءات',
position: 'right',
},
pager: {
perPage: 5,
},
add: {
addButtonContent: ' إضافة جديد ',
createButtonContent: '',
cancelButtonContent: '',
confirmCreate: true,
},
edit: {
editButtonContent: '',
saveButtonContent: '',
cancelButtonContent: '',
confirmSave: true,
},
delete: {
deleteButtonContent: '',
confirmDelete: true,
},
columns: {
id: {
title: 'كود العميل',
width: '80px',
},
name: {
title: 'اسم العميل',
width: '160px'
},
phone: {
title: ' الهاتف'
},
address: {
title: ' العنوان'
},
account: {
title: 'الرصيد '
},
notes: {
title: 'ملاحظات'
}
}
};
private myForm: FormGroup;
constructor(private formBuilder: FormBuilder, private Service: ClientsService) { }
ngOnInit() {
this.Service.getAllClients().subscribe(data => this.Service.clients = data);
this.Service.client={
id:0,
name:null,
phone:null,
address:null,
type:null,
account:0,
nots:null,
branchId:0,
};
so how can I find my mistake also what the best way to handle create, edit and delete operations?
thanks in advance
That is because addClient is a method on service.ts, whereas the ng2-smart-table is instantiated on that component, and you shouldn't directly call your service method on the template.
Therefore, the right way of doing things would be to create a method on your component.ts which calls the addClient method.
On your component.html template, we bind the editConfirm event to another method onAddClient
<div class="mainTbl">
<ng2-smart-table
[settings]="settMain"
[source]="this.Service.clients"
(createConfirm)="onAddClient($event)"
(editConfirm)="onEditConfirm($event)"
(deleteConfirm)="onDeleteConfirm($event)"
></ng2-smart-table>
</div>
On your component.ts,
onAddClient(event) {
this.Service.addClient(event).subscribe(
(res) => {
// handle success
}, (error) => {
// handle error
});
}
And in addition, on your service.ts, you will pass the data from the compnoent, and return the response from the http request from the HTTP Client.
addClient(data){
console.log(data);
return this.http.post<Clients>(this.url, data);
}

Why show this error,",Uncaught ReferenceError: c3 is not defined"

I have created one calculator in that calculator i have use pie chart to show data dynamically,but chart throw error.The code is given below,
custom.min.js in this js file i have usen c3 chart,
var chart = c3.generate({
data: {
columns: [
["Interest Payable", 3900519],
["Principal", 3e6]
],
type: "pie"
},
axis: { x: { label: "Sepal.Width" }, y: { label: "Petal.Width" } },
legend: { show: !1 },
tooltip: { format: { title: function(t) { return "" }, value: function(t, e, n) { return t } }, contents: tooltip_contents }
});
I have usen this plugin to done this : c3.min.js,c3_renderers.min.js,d3.min.js,d3.v3.min.js,pivot.min.js.
I have downloaded this plugin and put inside the asset folder and I am call this js file by scriptloaderService, and this scriptloaderService imported inside component.
DynamicScriptLoaderServiceService this is scriptloaderServise code.
import { Injectable } from '#angular/core';
interface Scripts {
name: string;
src: string;
}
export const ScriptStore: Scripts[] = [
{ name: 'd3.v3.min.js', src: './assets/Scripts/d3.v3.min.js' },
{ name: 'd3.min.js', src: './assets/Scripts/d3.min.js'},
{ name: 'c3.min.js', src: './assets/Scripts/c3.min.js' },
{ name: 'custom.min.js', src: './assets/Scripts/custom.min.js' },
];
declare var document: any;
#Injectable()
export class DynamicScriptLoaderServiceService {
private scripts: any = {};
constructor() {
ScriptStore.forEach((script: any) => {
this.scripts[script.name] = {
loaded: false,
src: script.src
};
});
}
load(...scripts: string[]) {
const promises: any[] = [];
scripts.forEach((script) => promises.push(this.loadScript(script)));
return Promise.all(promises);
}
loadScript(name: string) {
return new Promise((resolve, reject) => {
if (!this.scripts[name].loaded) {
//load script
let script = document.createElement('script');
script.type = 'text/javascript';
script.src = this.scripts[name].src;
if (script.readyState) { //IE
script.onreadystatechange = () => {
if (script.readyState === "loaded" || script.readyState === "complete") {
script.onreadystatechange = null;
this.scripts[name].loaded = true;
resolve({script: name, loaded: true, status: 'Loaded'});
}
};
} else { //Others
script.onload = () => {
this.scripts[name].loaded = true;
resolve({script: name, loaded: true, status: 'Loaded'});
};
}
script.onerror = (error: any) => resolve({script: name, loaded: false, status: 'Loaded'});
document.getElementsByTagName('head')[0].appendChild(script);
} else {
resolve({ script: name, loaded: true, status: 'Already Loaded' });
}
});
}
}
CalcyComponent this is component in which service and JS file was called to show pie chart.
import { Component, OnInit, AfterViewInit } from '#angular/core';
import { DynamicScriptLoaderServiceService } from '../../../ScriptLoaderService/dynamic-script-loader-service.service';
#Component({
selector: 'app-calcy',
templateUrl: './calcy.component.html',
styleUrls: ['./calcy.component.css']
})
export class CalcyComponent implements OnInit, AfterViewInit {
constructor(private dynamicScriptLoader:DynamicScriptLoaderServiceService) { }
ngOnInit() {
this.loadScripts();
}
ngAfterViewInit() { }
loadScripts() {
// You can load multiple scripts by just providing the key as argument into load method of the service
this.dynamicScriptLoader.load(
'd3.v3.min.js','d3.min.js','c3.min.js','custom.min.js',).then(data => {
// Script Loaded Successfully
}).catch(error => console.log(error));
}
}
calcy.component.html this is template where chart should be show, I have given small code of that.
<div class="abc">
<div id="chart" class="chart" style="width: 320px; height: 285px;"></div>
<div style="display: none;">
<div title="" style="width: 454px;
height: 319px;">
<!-- <div id="Graph"></div> -->
</div>
</div>
</div>
Main error shown by browser is this. "Uncaught ReferenceError: c3 is not defined"
any one can help in this code????

How do I implement zingchart into angular2

I have an existing project that I want to implement zingcharts on.
I have tried 3 different tutorials mainly from " https://blog.zingchart.com/2016/07/19/zingchart-and-angular-2-charts-back-at-it-again/ "blog.
However I can not get that working in my project.
So I decided I will try and implement it the most basic way first and later on try better ways. This is what I did but it does not shop up yet.
Being new to angular2 I am not quite sure if this will work.
I went to the ZingChart website and tried to implement this basic example ->https://www.zingchart.com/docs/getting-started/your-first-chart/
So I built 2 files chart.ts and chart.component.html and implemented the
"<script src="https://cdn.zingchart.com/zingchart.min.js"></script>"
in the index.html
//chart.ts
import { Component } from '#angular/core';
#Component({
selector: 'rt-chart',
templateUrl: './chart.component.html'
})
export class Chart
{
}
//chart.component.html
<!--Chart Placement[2]-->
<div id="chartDiv"></div>
<script>
var chartData = {
type: "bar", // Specify your chart type here.
title: {
text: "My First Chart" // Adds a title to your chart
},
legend: {}, // Creates an interactive legend
series: [ // Insert your series data here.
{ values: [35, 42, 67, 89]},
{ values: [28, 40, 39, 36]}
]
};
zingchart.render({ // Render Method[3]
id: "chartDiv",
data: chartData,
height: 400,
width: 600
});
</script>
I called it in my already working website as
It does not show up. What am I doing wrong? Is there something I am missing.
Angular2 is quite new to me.
Thanks
With latest angular2 version (#2.2.3) you can leverage special ZingChart directive like this:
zing-chart.directive.ts
declare var zingchart: any;
#Directive({
selector : 'zing-chart'
})
export class ZingChartDirective implements AfterViewInit, OnDestroy {
#Input()
chart : ZingChartModel;
#HostBinding('id')
get something() {
return this.chart.id;
}
constructor(private zone: NgZone) {}
ngAfterViewInit() {
this.zone.runOutsideAngular(() => {
zingchart.render({
id : this.chart.id,
data : this.chart.data,
width : this.chart.width,
height: this.chart.height
});
});
}
ngOnDestroy() {
zingchart.exec(this.chart.id, 'destroy');
}
}
where ZingChartModel is just a model of your chart:
zing-chart.model.ts
export class ZingChartModel {
id: String;
data: Object;
height: any;
width: any;
constructor(config: Object) {
this.id = config['id'];
this.data = config['data'];
this.height = config['height'] || 300;
this.width = config['width'] || 600;
}
}
Here's completed Plunker Example

Categories

Resources