LaraNewDev's avatar

Chartjs through npm graph not displayed. Works with CDN

Hello everybody,

I am starting with chartjs. Already tried to do something through CDN and it worked perfectly. Then, I decided to include it in project through NPM (following https://www.chartjs.org/docs/latest/getting-started/integration.html) and the graphs are not visible and get the following error:

Uncaught ReferenceError: Chart is not defined 

To install it, I did the following:

npm install chartjs

Installation went OK. My dependancies look like this in package.json:

"dependencies": {
        "alpinejs": "^3.7.1",
        "chart.js": "^3.7.0"
    }

Then, in resources/js/app.js, included the import statements. I show you the whole file so you can have context. (The alpine stuff was there before and it works)

import { Chart, registerables } from 'chart.js';
Chart.register(...registerables);

import Alpine from 'alpinejs'
window.Alpine = Alpine
 
Alpine.start()

I then ran

npm run dev

which worked fine. On the CLI I can see (but it passes quite fast) some text with the word chartjs. This makes me think that I am doing fine. Also, went to app.js but in public directory and doing ctrl+f chartjs returns a number of results, so everything looks good. I am calling the app.js in the <head> of a blade layout named "twapp" in the following manner:

 <script src="{{ asset('js/app.js') }}" defer></script>

Finally, I want to use the charts in a view. The view contains the following (is the code from https://www.chartjs.org/docs/latest/getting-started/usage.html)

@extends('layouts.twapp')
@section('content')

<canvas id="myChart" width="400" height="400"></canvas>
<script>
const ctx = document.getElementById('myChart');
const myChart = new Chart(ctx, {
    type: 'bar',
    data: {
        labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
        datasets: [{
            label: '# of Votes',
            data: [12, 19, 3, 5, 2, 3],
            backgroundColor: [
                'rgba(255, 99, 132, 0.2)',
                'rgba(54, 162, 235, 0.2)',
                'rgba(255, 206, 86, 0.2)',
                'rgba(75, 192, 192, 0.2)',
                'rgba(153, 102, 255, 0.2)',
                'rgba(255, 159, 64, 0.2)'
            ],
            borderColor: [
                'rgba(255, 99, 132, 1)',
                'rgba(54, 162, 235, 1)',
                'rgba(255, 206, 86, 1)',
                'rgba(75, 192, 192, 1)',
                'rgba(153, 102, 255, 1)',
                'rgba(255, 159, 64, 1)'
            ],
            borderWidth: 1
        }]
    },
    options: {
        scales: {
            y: {
                beginAtZero: true
            }
        }
    }
});
</script>

@endsection

I think that I must be very close. After all, the process of installing AlpineJS (and I guess other JS stuff) is 99% the same and managed to make it work quite fast. I have a few hipothesis: -The graph wants to be created before the script is loaded (makes sense?) -I am doing something wrong in the install process (my favourite) -There is a bug somewhere: chartjs, laravel... (I don't think so...)

Thanks a lot and happy new year.

EDIT: I want to thank a lot Sinnbeck for the help. I make this edit to add an alternative solution to the one he provided. I did a lot of google research before asking the original question and could not find a summarized answer, so here you go:

There is a "conflict" (or at least it happens to me (Laravel 8, AlpineJS 3.X and Livewire 2.x)) between AlpineJS and chartJS. The reason is that AlpineJS requires to be included with "defer" attribute. If you put the code to load chartJS in the same file as Alpine JS, when loading the script file you will be applying "defer" attribute to chartJS as well. This casuses (at least to me) that Chart is not found. So ideally, what you want is to have chartJS and AlpineJS in different .js files and include them as separate lines. Then, you will need to mix them. What I did is to place, in resources\js, two files:

app.js
charts.js

The first one has the code needed to load AlpineJS, and the second one chartsJS. Then, at the head of my HTMLs (I have it in a blade layout, but this is not important for the solution) you call both scripts the following way:

   <!-- Scripts -->
    <script src="{{ asset('js/charts.js') }}"></script> 
    <script src="{{ asset('js/app.js') }}" defer></script> 

Finally, you need to tell Laravel to pick the new js file (charts.js) from resources directory and move it to public directory. To do that, you have to modify the mix script. Mine looks like this now:

mix.js('resources/js/app.js', 'public/js')
   .js('resources/js/charts.js', 'public/js')
      .postCss('resources/css/main.css', 'public/css', [
         require('tailwindcss'),
      ])

Notice how there are two js lines. Now you run NPM with

npm run dev

And that's all. Hope its helpful for anybody.

0 likes
12 replies
Sinnbeck's avatar

You need to expose chart to window. By default you cannot use any npm module outside "mix"

import { Chart, registerables } from 'chart.js';
Chart.register(...registerables);
window.Chart = Chart; //this allows you to use chart outside of "mix" 
1 like
LaraNewDev's avatar

@Sinnbeck Thanks a lot from the prompt response. Unfortunally, it did not work. My app.js looks like this now:

import { Chart, registerables } from 'chart.js';
import Alpine from 'alpinejs'
Chart.register(...registerables);
window.Chart = Chart; //this allows you to use chart outside of "mix" 
window.Alpine = Alpine
Alpine.start()

Looks right to you?

Thanks

Sinnbeck's avatar

@LaraNewDev yeah. Remeber to recompile. Now Chart should be available just like alpine

If it fails then check the browser console for errors

1 like
LaraNewDev's avatar

@Sinnbeck Thanks again. Sadly, it did not work. However, I found something quite interesting. If I move the JS code that is in the view to app.js (in resources) and then recompile, it works. This is what I put in app.js:

import Chart from 'chart.js/auto';
import Alpine from 'alpinejs'

window.Chart = Chart //this allows you to use chart outside of "mix" 
window.Alpine = Alpine;
Alpine.start()

const ctx = document.getElementById('myChart');
const myChart = new Chart(ctx, {
    type: 'bar',
    data: {
        labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
        datasets: [{
            label: '# of Votes',
            data: [12, 19, 3, 5, 2, 3],
            backgroundColor: [
                'rgba(255, 99, 132, 0.2)',
                'rgba(54, 162, 235, 0.2)',
                'rgba(255, 206, 86, 0.2)',
                'rgba(75, 192, 192, 0.2)',
                'rgba(153, 102, 255, 0.2)',
                'rgba(255, 159, 64, 0.2)'
            ],
            borderColor: [
                'rgba(255, 99, 132, 1)',
                'rgba(54, 162, 235, 1)',
                'rgba(255, 206, 86, 1)',
                'rgba(75, 192, 192, 1)',
                'rgba(153, 102, 255, 1)',
                'rgba(255, 159, 64, 1)'
            ],
            borderWidth: 1
        }]
    },
    options: {
        scales: {
            y: {
                beginAtZero: true
            }
        }
    }
});

and this is what is left of my view:

@extends('layouts.twapp')
@section('content')

<canvas id="myChart" width="400" height="400"></canvas>
<script>

</script>

@endsection

Obviously, I would like to put the JS code that is in app.js in my view, but that causes the error Uncaught ReferenceError: Chart is not defined So, the problem seems to be including the JS code in the view.

BTW, my webpack.mix.js looks like this

mix.js('resources/js/app.js', 'public/js')
      .postCss('resources/css/main.css', 'public/css', [
         require('tailwindcss'),
      ])

Maybe I should add something there? I am using Laravel 8.

Thanks again for the great support.

Sinnbeck's avatar
Sinnbeck
Best Answer
Level 102

@LaraNewDev that makes sense. As you are using it inside of mix. As I said, the problem is that Chart isn't exported to outside of mix (blade). This line will do it, but it needs to be run before your js in blade (blade is using window for Javascript)

window.Chart = Chart; 
LaraNewDev's avatar

@Sinnbeck I have great news. I put everything back where it should be (JS code in view) and failed again. The difference is that this time I changed the script call in my layout from this:

<script src="{{ asset('js/app.js') }}" defer></script>

To this:

<script src="{{ asset('js/app.js') }}" ></script>

So I removed the "defer" attribute. It made it work. I guess that the HTML was loading before JS, which made "Chart" to be undefined. The problem I face now (this never ends :-)) is that AlpineJS requires (https://alpinejs.dev/essentials/installation) the "defer" attribute. Can I somehow make it compatible for both? Like having a script with defer for AlpineJS and another for charts? I am afraid this would "work" but it would not be doing stuff "the Laravel way".

Sinnbeck's avatar

@LaraNewDev yeah that makes sense. You an probably fix it to work with defer if you wrap your chart code in a listener

document.addEventListener('DOMContentLoaded', function () {
    //your chart code 
});

Or add defer to your script tag

3 likes
LaraNewDev's avatar

@Sinnbeck Worked perfectly. Thanks a lot for the support, you helped me to learn a lot of this side of web development.

pedestrianlove's avatar

For anyone using a newer Laravel version(9 or 10) that uses Vite instead of mix, take the following steps:

  • This guide assumes that you have an app.js and have imported into your blade view using the following directive:
@vite(['resources/css/app.css', 'resources/js/app.js'])

(or some similar files, name doesn't matter)

1. Use npm to install chart.js.

npm install chart.js // You can find this in official documentation.

2. Import it inside your app.js.

import Chart from 'chart.js/auto';    // You can find this in official documentation.

window.Chart = Chart;            // Expose chart to Blade file as stated by Sinnbeck.

3. Wrap your chart stuff with the following to avoid the content load before it grabs the dependencies inside app.js: (credit to @sinnbeck )

document.addEventListener('DOMContentLoaded', function () {
    //your chart code 
});

4. Your code is working as if you import chart.js using CDN!

2 likes
Hyppolitak's avatar

Adding type="module" in the script tag works for me:

<script type="module">
// your JS code
</script>

Please or to participate in this conversation.