Leverage an external JavaScript library within your Angular 8 project

Looking to create a funnel-graph in Angular using the amazing funnel-graph-js library, but facing some challenges in making it work correctly.

Below 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';
  }
}

Include these lines in your 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"
]

Encountering an error like this https://i.sstatic.net/zt9BQ.png

Answer №1

Make sure to have the javascript file linked in the HTML for it to work properly.

UPDATE:

An improved method to add an additional javascript file is by placing it in the "scripts" section of the angular.json file. Additionally, include

declare const FunnelGraph: any

to avoid compilation errors. This information was sourced from a stackoverflow response and this guide. Don't forget to incorporate the CSS files within the json as well!

END OF UPDATE

The error occurs because the code attempts to find an HTML element with a class called "funnel", which cannot be located. To enhance this directive, consider making it more generalized.

To start, move the graph generation code inside the constructor where the directive logic resides. For better generalization, assign a unique id to the element and adjust the code accordingly. Here's a suggested approach:

HTML:

<div id="funnel-graph-1" appFunnelGraph></div>

Javascript:

import { Directive, ElementRef } from '@angular/core';

// It should be sufficient to import this in the html using 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({
      // Update the container selector with the element id for generalization
      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();
  }
}

Answer №2

I decided to create a service instead of opting for the directive approach.

  • Initially, I generated a service named dynamic-script-loader-service within 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/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="5a3c2f34343f36773d283b2a32">[email protected]</a>/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"
            ],

inserted a div with class funnel in dashboard.component.html

<div class="funnel"></div>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Press the button to reveal the table - jQuery/JavaScript

Can you please provide guidance on how to hide the inner HTML table when the page loads and then display the table with results only after clicking the search button? I do not want to show an empty table. Below is the code snippet that I have tried: Howev ...

Executing a JavaScript function within an EJS template using Express, EJS, and UIkit

Why do I keep getting the error UIkit is not defined even though the JS is loaded in the header? Additionally, alert doesn't seem to work either. How can I resolve this issue? This is the EJS index template: <!DOCTYPE html> <html lang=" ...

What is the best way to use element.appendChild to generate a link?

I am currently utilizing the following snippet of Javascript to extract information from the current webpage using a browser extension. I have only included a portion of the code that is relevant, as the full script is quite lengthy. The code works perfect ...

Comparison of jQuery, AngularJS, and Node.js

I'm a beginner in web development and I have some basic knowledge: HTML - structure of websites CSS - design aspect JavaScript - for adding interactivity Now, what exactly is jQuery, AngularJS, and Node.js? Upon researching, I discovered that jQue ...

In React, I am currently working on implementing a feature that will allow the scene camera to move as the user scrolls

I'm currently working on incorporating a feature to enable movement of the scene camera when scrolling in React. However, as I am relatively new to using this library, I am unsure which implementation approach would be most suitable for achieving this ...

Creating a textured path using Threejs

Having trouble drawing a path in my 3D world, as the line class is not helpful. Can anyone offer assistance? See this image I've updated my question I want to draw a path and fill it with texture. var SUBDIVISIONS = 20; geometry = new THREE.Geo ...

Attempting to spread a non-iterable instance is invalid. For non-array objects to be iterable, they must have a [Symbol.iterator]() method

data(){ return { tables:[] } }, mounted(){ this.fetchData() }, methods:{ fetchData(){ var subscription = web3.eth.subscribe('logs', { address: '0x123456..', topics: ['0x12345. ...

How much worth does an unfilled text input box hold?

I've been working on integrating a search feature and I'm having an issue with the functionality. Below is the code snippets for both HTML and JS: HTML <form> <input type="text" ng-model="searchVar" class="searchbox"> <in ...

Issues arising when using December in a JavaScript function to determine weekends

My function is used to determine if a day falls on the weekend or not, and it works perfectly for most months except December. What could be causing this issue? weekEnd: function(date) { // Determine if it's a weekend var date1 = new Dat ...

Utilizing globalProperties in Vue 3 Web Components: A Comprehensive Guide

I'm having trouble understanding how to implement globalProperties within a web component. Here's a snippet from my main.js file: import { defineCustomElement } from 'vue' import axios from 'axios' import VueAxios from ' ...

Understanding TypeScript: Utilizing type intersection and the powerful 'this' keyword

Can you explain the contrast between the following: interface MyType { f<T>(other: T): this & T; } versus interface MyType { f<T>(other: T): MyType & T; } ? Your insights would be highly appreciated! ...

What could be the reason for Object.assign failing to update a key in my new object?

Function handleSave @bind private handleSave() { const { coin, balance } = this.state; console.log('coin', coin); console.log('balance', balance); const updatedCoin = Object.assign({ ...coin, position: balance }, coi ...

Exploring Cross-Module Provider Integration in Angular2 RC5

I'm currently working on a project using Angular RC5 and the latest Material Design. Although the latter may not be relevant. To improve organization, I am creating separate modules instead of keeping everything in the main Module. However, I need to ...

Choosing comparable choices from the drop-down menu

I am working on a dropdown menu that contains different options. I need to use jQuery to automatically select the option that corresponds to the branch of the currently logged in user. However, when some option text is very similar, it causes an issue. // ...

Encountered a ZoneAwareError while trying to import the InfiniteScrollModule

Upon importing the InfiniteScrollModule from 'angular2-infinite-scroll' into my module, a ZoneAwareError is displayed https://i.sstatic.net/G2T94.png ...

manipulate the form information in node.js

My form.html is very simple: <form id="login-form" action=" ?? " method="post">Email <br/> <input type="text" name="Email" id="em" /> <br/>password <br/> <input type="text" name="password" id="pas" /> ...

Is it acceptable to operate a live website with a hybrid implementation of Angular and AngularJS?

I have an existing AngularJS app that I am eager to update to the newest version of Angular. However, a major hurdle is that the app relies on numerous third-party libraries that are not compatible with Angular v.2. Is it feasible to operate a combined An ...

Troubleshooting: Empty Rows displayed in PrimeNG Table

Experimenting with Angular 8 and the primeNG package, I'm facing an issue where I cannot retrieve values. Despite using the {{staff[col.field]}} syntax, I only get empty rows of data. However, when I utilize the interface definition like {{staff.Emplo ...

Navigating through Express Js

Here is the API route for retrieving specific information: /Token?l=Spanish&n=NameofTheServer&m0=Email&m1=&m2=&m3=&m4=&m5=&m6=&m7=&m8=&m9=&m10=&m11=&m12=&m13=&m14=&m15=&m16=&m ...

Vuejs Namespaced Mixins

Creating a namespaced mixin is something I am interested in achieving. Let's use the example of a notification mixin: (function() { 'use strict'; window.mixins = window.mixins || {} window.mixins.notification = { met ...