Refresh the Angular directive in order to update the Morris.js charts following the API request

I am currently utilizing Morris charts with Angular to present graphical reports that fetch data from our backend server via a REST API call.

Although I can see the retrieved data in the console log, it is not being displayed in the charts. Upon investigation, I discovered that the directive barchart loads before the API call, resulting in the display of data available in $scope.myModel.

I am attempting to determine if there is a way in Angular to reload the directive when data is received from the API call. Can anyone assist me with this?

Below is the code for generating the Bar Chart:

This is my code

var sampleApp = angular.module('sample',[]);

sampleApp.directive('barchart', function() {

return {

    // required to make it work as an element
    restrict: 'E',
    template: '<div></div>',
    replace: true,
    // observe and manipulate the DOM
    link: function($scope, element, attrs) {

        var data = $scope[attrs.data],
            xkey = $scope[attrs.xkey],
            ykeys= $scope[attrs.ykeys],
            labels= $scope[attrs.labels];

        Morris.Bar({
                element: element,
                data: data,
                xkey: xkey,
                ykeys: ykeys,
                labels: labels
            });

    }

};

});

sampleApp.controller('sampleController',function($scope, $http){
$scope.values = []

$scope.xkey = 'range';

$scope.ykeys = ['total_tasks',     'total_overdue'];

$scope.labels = ['Total Tasks', 'Out of Budget Tasks'];
$http.get('http://api.*******.com/api/getAppID/?parameter=whatsapp').success( function(res) {
        if(!res.error) {
            if(res.status==1) res.status=true
            else res.status=false
    $scope.values[0] = res.metrices.total_shares
    $scope.values[1] = res.metrices.unique_share_count  
    $scope.values[2] = res.metrices.total_clicks
    $scope.values[3] = res.metrices.total_downloads
}
})
$scope.myModel = [
{ range: 'January', total_tasks: $scope.values[0], total_overdue: 5 },
{ range: 'January', total_tasks: $scope.values[1], total_overdue: 8 },
{ range: 'January', total_tasks: $scope.values[2], total_overdue: 1 },
{ range: 'January', total_tasks: $scope.values[3], total_overdue: 6 }
];

});

HTML PART:

<!DOCTYPE html>
<html>
<head>
    <link rel="stylesheet" href="http://cdn.oesmith.co.uk/morris-0.4.3.min.css">
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script>
    <script src="http://cdn.oesmith.co.uk/morris-0.4.3.min.js"></script>
    <script src="js/sample.js"></script>
    <meta charset=utf-8 />

</head>
<body ng-app="sample" ng-controller="sampleController">

    <barchart xkey="xkey" ykeys="ykeys" labels="labels" data="myModel"></barchart>


</body>

Answer №1

The 'flag' variable is unnecessary in this scenario. Instead, focus on watching the 'myModel' variable and avoid recreating a new Morris chart every time the model changes. Simply call the Morris setData method when the model changes for a second time.

Starting from your directive, I customized it to create code that effectively redraws the chart on window resize events. Perhaps someone else can benefit from this implementation.

(function () {
    'use strict';
    var module = angular.module('app.charts', []);
    module.directive('areachart', function ($window) {

        return {
            restrict: 'E',
            template: '<div></div>',
            replace: true,
            link: function ($scope, element, attrs) {
                var morris;
                angular.element($window).bind('resize', function () {
                    if (morris) {
                        console.log('morris resized');
                        morris.redraw();
                    }
                });

                attrs.$observe('value', function (val) {
                    if (!morris) {
                        console.log('creating chart');
                        morris = Morris.Area({
                            element: element,
                            data: angular.fromJson(val),
                            xkey: $scope[attrs.xkey],
                            ykeys: $scope[attrs.ykeys],
                            labels: $scope[attrs.labels]
                        });
                    } else {
                        console.log('setting chart values');
                        morris.setData(angular.fromJson(val));
                    }
                });
            }
        };
    });
}).call(this);

HTML

<areachart xkey="xkey" ykeys="ykeys" labels="labels" data-value="{{myModel}}"></areachart>

In your controller:

        $scope.xkey = 'y';

        $scope.ykeys = ['a', 'b'];

        $scope.labels = ['Series A', 'Series B'];

        $scope.myModel = [
                { y: '2006', a: 100, b: 90 },
                { y: '2007', a: 75, b: 65 },
                { y: '2008', a: 50, b: 40 },
                { y: '2009', a: 75, b: 65 },
                { y: '2010', a: 50, b: 40 },
                { y: '2011', a: 75, b: 65 },
                { y: '2012', a: 100, b: parseInt((Math.random() * 10000) / 10) }

Answer №2

After putting in a lot of effort, I was able to successfully figure this out. Sharing my solution here to assist other coders.

var sampleApp = angular.module('sample',[]);

sampleApp.directive('barchart', function() {

return {

    // necessary for functioning as an element
    restrict: 'E',
    template: '<div></div>',
    replace: true,
    // observe and manipulate the DOM
    link: function($scope, element, attrs) {
        $scope.$watch('flag', function() {

    $scope.myModel = [
       { range: 'January', total_tasks: $scope.values[0], total_overdue: 5 },
       { range: 'January', total_tasks: $scope.values[1], total_overdue: 8 },
       { range: 'January', total_tasks: $scope.values[2], total_overdue: 1 },
       { range: 'January', total_tasks: $scope.values[3], total_overdue: 6 }
     ];

    console.log($scope.flag + $scope.values+' The one we want watch')

    $scope.xkey = 'range';

    $scope.ykeys = ['total_tasks',     'total_overdue'];

    $scope.labels = ['Total Tasks', 'Out of Budget Tasks'];

        var data = $scope[attrs.data],
            xkey = $scope[attrs.xkey],
            ykeys= $scope[attrs.ykeys],
            labels= $scope[attrs.labels];

            var setData = function(){
            console.log('inside setData function');

        Morris.Bar({
                element: element,
                data: data,
                xkey: xkey,
                ykeys: ykeys,
                labels: labels
            });
            };
        if ($scope.flag == 1) {    
            attrs.$observe('data',setData)  
            }               
            });

    }

};

});


sampleApp.controller('sampleController',function($scope, $http){
$scope.flag = 0;
$scope.values = [];



$http.get('http://api.*******/api/*****/?appname=whatsapp').success( function(res) {
        if(!res.error) {
            if(res.status==1) res.status=true
            else res.status=false

    $scope.values[0] = res.metrices.total_shares
    $scope.values[1] = res.metrices.unique_share_count  
    $scope.values[2] = res.metrices.total_clicks
    $scope.values[3] = res.metrices.total_downloads
    $scope.flag = 1;
    console.log($scope.flag+"in api call"+$scope.values)

}

})


});

This method worked well for me. However, removing the (flag==1) condition in $watch causes the chart to redraw twice with overlapping. I welcome any suggestions for enhancing this solution.

I found this link to be extremely informative and valuable. [1]:

Answer №3

All that is required to refresh the display is to invoke the $apply method, which in turn triggers the $digest method. The $digest method then examines whether any of the $watchers have been modified and updates the object accordingly.

setTimeout(function() {
  $scope.$apply(function (){
    morris = Morris.Area({
      element: element,
      data: angular.fromJson(val),
      xkey: $scope[attrs.xkey],
      ykeys: $scope[attrs.ykeys],
      labels: $scope[attrs.labels]
    });
  });
},1500);

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

Node js presenting outdated versions of altered angular documents (rather than the latest versions)

I have been working on a project with the MEAN stack and everything has been going smoothly, except for one issue I am facing with the static files. In my Node router file, I have set up some rules to serve the static files. Everything was fine until I ma ...

Effortless bug tracking in Chrome developer tools

When I'm debugging, I want the code to be displayed in Chrome browser (or another browser like Edge) exactly as it was written. Even when using pretty print, the code still appears unreadable. For example, a block of code written in my IDE: {provideD ...

The scrolling speed of the mousewheel in Firefox is notably slower compared to that of Google Chrome

Kindly review this sample link: When I test the above example in Chrome and scroll using the mouse wheel, the page moves up by 100px each time. The Y position is displayed as well. However, if I try the same page in Firefox 26.0 and scroll with the mouse ...

Any suggestions on MySQL auto-inserted apostrophes?

Having trouble updating a value in my Database, as it's resulting in an error. Backend: router.patch("/toggleState", (req, res) => { const todoId = req.body.todoId; const attribute = req.body.attribute; const newValue = req.body.newValue ...

What is the best way to trigger my web scraper within an express route?

Within my Nodejs server's root directory, I have implemented a web scraper using needle to handle the HTTP requests for retrieving HTML data. This scraper returns an Array of data upon completion. In addition, there is an index.js file containing expr ...

"Using a separate JavaScript file in NodeJS without the 'require' defined will result

Apologies if this question seems basic, but I am new to NodeJS and have been struggling with this issue. I have installed requireJS using the command: npm install requirejs I used express project to set up the project directory structure as follows: ...

Check to see if the variable is present in LocalStorage using Javascript

Currently working on a chat system where I create a Localstorage variable whenever a new chat is initiated. Here's how I do it: localStorage.setItem("chat_"+varemail, data); My next step is to figure out how many of these chat variables exist. Somet ...

Is there a way to adjust the height of a turnjs page?

I am currently utilizing the Turn.js flip library and I need to adjust the height of the turnjs page. The current setup calculates the height of the page based on the client's height, but now I want to change it to a specific value like 700px. How can ...

Error: ng-messages syntax issue with the field parameter

Encountering the following error: Syntax Error: Token '{' invalid key at column 2 of the expression [{{field}}.$error] starting at [{field}}.$error]. when attempting to execute the code below (form-field.html) <div class='row form-grou ...

No file was chosen for uploading. Utilizing CodeIgniter with AJAX

Details : public function addNewFile($file) { $data = array('name' => $this->input->post('filename'), 'size' => $this->input->post('filesize'), ...

Cease / Cancel Ajax request without activating an error signal

I am looking for a way to intercept all ajax requests on a page and stop/abort some of the requests based on certain criteria. Although initially using jqXHR.abort(); worked, it caused the error event of all the aborted requests to be triggered, which is n ...

Divergent behavior of SVG elements in Firefox versus Chrome with HTML5

I am trying to position a 'div' under a 'textarea' within an 'svg' and 'g' element. This works fine in Firefox, but not in Chrome. When I apply a transform on the 'g' element, it moves correctly to the des ...

Redirecting pages without a hash portion

In my Express.js app.js file, there is a get route that looks like this: app.get('/admin', function(req, res, next){ if(req.isAuthenticated()) { return next(); } res.redirect('/admin/login'); },Routes.Admin.Index); When a ...

Press the "submit" button to perform an onclick event just before the form's "action" is executed

Here is a form that allows you to select a CSV file and upload it to a MySQL server: <form class="ui input" enctype="multipart/form-data" method = "POST" action="trend_upload_csv.php" role = "form"> <input type = "file" name ="file" id="file" ...

The parameter 'data' is assumed to have an 'any' type in React hooks, according to ts(7006)

It's perplexing to me how the 7006 error underlines "data," while in the test environment on the main page of React Hooks (https://react-hook-form.com/get-started#Quickstart), everything works perfectly. I'm wondering if I need to include anothe ...

Is it possible for Kendo UI to be integrated with an existing AngularJS 1.6 project?

Currently focused on a project using AngularJS 1.6.6, not the newer versions like Angular 2 or Angular 4. Wondering if purchasing a KendoUI license will integrate seamlessly with this classic version of AngularJS. ...

A Promise-based value returned by a Typescript decorator with universal methods

I am currently working on creating a method decorator that can be applied to both prototype and instance methods. Referenced from: Typescript decorators not working with arrow functions In the code provided below, the instanceMethod() is returning a Prom ...

Tips for customizing bootstrap code for registration form to validate against duplicate emails?

The contact_me form utilizes default code to handle success or failure responses from the server. The code calls msend_form.php for communication with the database and always returns true. It allows checking for pre-existing email addresses in the database ...

What steps should be followed to construct a window identical to the one depicted in the attached image using HTML and CSS?

Check out this link to see the window style I'm trying to recreate using HTML, CSS, and Javascript. No Jquery needed. Thank you in advance. ...

What is the correct way to test history.push and history.replace in React applications?

When attempting to test the replace or push functions, I am encountering issues. When using mock, it is not being called at all and results in an empty object representing history. The error message I receive states: "Cannot spy the replace property beca ...