3

I have a small doubt about ng-init, actually I have some charts.

  1. Trendline on top
  2. Piecharts below.
  3. HTML Table below.

on page load I am not displaying anything, but once I click on particular button for the first time then my entire charts and table is getting displayed for the data received by the http request. but afterwards if I click on the other button and executing same http request then its updating the trendline and table but not the piecharts. My piecharts are being rendered inside the below div with the ng-init, and when I am testing that ng-init="myFunction(index)" method its not updating on the subsequent click.

<form>
  <md-input-container>
    <label for="myInput">Search Division</label>
    <md-icon md-svg-icon="~/Content/myIcons/searchicon/ic_search_black_36px.svg"></md-icon>
    <input type="text" id="myInput" ng-model="searchDiv" md-autofocus>
  </md-input-container>
  <md-content ng-repeat="prod in listofProd | filter: searchDiv">
    <md-button class="md-whiteframe-1dp card" ng-click="generateChart(prod)" flex-sm="100" flex-gt-sm="100" flex-gt-md="100">
    {{prod}}
    </md-button>
  </md-content>
</form>

<div layout="row" layout-wrap>
  <div flex="25" style="width:1000px;height:300px; border: 1px solid skyblue;" ng-repeat="year in years track by $index" id="piechart{{$index}}" ng-init="myFunction($index)">
  </div>
</div>

And this is my angularjs code for the ng-init function.

(function () {
  'use strict'
  angular.module("GAiiNSApp").controller("ProdPerDash", ['$http', '$scope', '$log', function ($http, $scope, $log) {
    $scope.isLoading = false;
    $scope.hideme = true;
    var currentdate = new Date();
    var startdd = "01";
    var startmonth = "00";
    var lastyear = currentdate.getFullYear() - 3;
    var enddd = "31";
    var endmonth = "11";
    var endyear = currentdate.getFullYear();
    $scope.StartDate = new Date(lastyear, startmonth, startdd);
    $scope.EndDate = new Date(endyear, endmonth, enddd);
    $scope.Region = "Local";
    $scope.Country = "";
    $scope.ProductLineSelected = false;
    $scope.ButtonText = "CUSTOMIZE";

    $scope.clicked = false;
    $scope.Yes = function () {
      if (!$scope.clicked == true) {
        $scope.ButtonText = "CANCEL";
        $scope.clicked = true;
      }
      else {
        $scope.ButtonText = "CUSTOMIZE";
        $scope.clicked = false;
      }
    }
    $scope.alert = function (message) {
      $window.alert(message);
    }

    $scope.Regions = ["Export", "Local"];
    $http.get('/General/API/GetProductDivision').then(function (res) {
      $scope.listofProd = res.data;
    });
    $scope.selectedProduct = "";
    $scope.generateChart = function (prodname) {
      $scope.selectedProduct = prodname;
      $scope.isLoading = true;
      $http.get('/Marketing/MarketingAPI/ProdPerformance', {
        params: {
          StartDate: $scope.StartDate,
          EndDate: $scope.EndDate,
          ProductDivision: prodname,
          Division: $scope.Region,
          Area: $scope.Country
        }
      }).then(function (res) {
        var resArray = res.data;
        var trendarray = [];
        $scope.TableRecords = resArray;
        $scope.hideme = false;
        var json = resArray;
        // console.log("Array of Objects: " + JSON.stringify($scope.TableRecords));
        var groupedData = {};
        var result = [];
        resArray.forEach(function (item) {
          var year = item.SecuredYear;
          var value = item.ValueInDhs;
          if (groupedData.hasOwnProperty(year)) {
            groupedData[year] += value;
          } else {
            groupedData[year] = value;
          }
        });
        //Pie chart data starts here
        var years = [];
        json.forEach(function (obj) {
          if (years.indexOf(obj.SecuredYear) == -1)
            years.push(obj.SecuredYear);
        });
        //$scope.years = years;
        $scope.years = angular.copy(years);
        $scope.pichartsview = true;
        $scope.myFunction = function (index) {

          var data = [['Product', 'ValueInDhs']];
          json.forEach(function (obj) {
            if (obj.SecuredYear == years[index]) {
              data.push([String(obj.GroupName).trim(), obj.ValueInDhs]);
            }
          });
          $scope.displayChart(data, index);
        }
        $scope.displayChart = function (dataForChart, index) {
          var chartData = dataForChart;
          google.charts.load('current', { 'packages': ['corechart'] });
          google.charts.setOnLoadCallback(drawChartPie);
          function drawChartPie() {
            var data = google.visualization.arrayToDataTable(chartData);
            var options = {
              chartArea: { left: 10, top: 20, width: "80%", height: "80%" },
              legend: 'bottom',
              is3D: true,
              pieSliceText: 'percentage',
              pieSliceTextStyle: {
                fontSize: 8
              },
              title: $scope.selectedProduct + " - " + $scope.years[index]
            };
            var chart = new google.visualization.PieChart(document.getElementById('piechart' + index));
            $scope.$apply(function () {
              chart.draw(data, options);
            });
          }
        }
        //Pie chart data ends here
        for (var year in groupedData) {
          var tmp = {};
          tmp[year] = groupedData[year];
          result.push(tmp);
        }
        result.forEach(function (obj, index) {
          var key = Object.keys(obj)[0];
          trendarray.push([parseInt(key, 10), obj[key]]);
        });
        trendarray.splice(0, 0, [{ label: 'SecuredYear', type: 'number' }, { label: 'ValueInDhs', type: 'number' }]);
        google.charts.load('current', { 'packages': ['corechart'] });
        google.charts.setOnLoadCallback(drawChart);
        $scope.isLoading = false;
          function drawChart() {
            var data = google.visualization.arrayToDataTable(
              trendarray
              );
            var options = {
              legend: 'none',
              title: 'Product Performance Trendline - ' + $scope.selectedProduct + " - " + $scope.Region,
              hAxis: { title: 'Secured Year', format: '0' },
              vAxis: { title: 'Secured Value in DHS' },
              trendlines: {
                0: {
                  lineWidth: 5,
                  type: 'polynomial',
                  visibleInLegend: true,
                  color: 'green',
                }
              }
            };
            var chart = new google.visualization.ScatterChart(document.getElementById('chart_div2'));
            chart.draw(data, options);
          }
        })
    $scope.ProductLineSelected = true;
    };
  }]); //this is ctrl closing
})(); //this is function closing
Aruna
  • 11,959
  • 3
  • 28
  • 42
Khan
  • 271
  • 1
  • 5
  • 13

2 Answers2

1

Since Angular 1.2, it has an option track by to prevent the repeater from re-rendering all the items to improve the performance.

More information can be found at: http://www.codelord.net/2014/04/15/improving-ng-repeat-performance-with-track-by/

That's why ng-init is not fired again for the same years rendered already since you have used the track by here ng-repeat="year in years track by $index".

Even if you remove this, the ng-init is again not fired for the second time since years here is the array where Angular is brilliant enough to track by its value though there is no explicit track by.

Hence, we have to change the years from simple array to object array like, [{year: obj.SecuredYear}] to let Angular to generate a $$hashKey which is not identical when the object changed every time and Angular will redraw the repeater by firing ng-init.

So the changes related to this are,

Html

<div flex="25" style="width:1000px;height:300px; border: 1px solid skyblue;" 
  ng-repeat="year in years" id="piechart{{$index}}" ng-init="myFunction($index)">
</div>

JS (populating years)

    var years = [];
    json.forEach(function (obj) {
      var exists = years.filter(function(y) {
        return y.year === obj.SecuredYear;
      });

      if (exists.length === 0) { // This will replace the `indexOf` check
        years.push({year: obj.SecuredYear});  // change here
      }
    });
    $scope.years = years;

JS (myFunction())

      $scope.myFunction = function (index) {
      var data = [['Product', 'ValueInDhs']];
      json.forEach(function (obj) {
        if (obj.SecuredYear == years[index].year) {   // change here at years[index].year
          data.push([String(obj.GroupName).trim(), obj.ValueInDhs]);
        }
      });
      $scope.displayChart(data, index);
    };

I just created a sample snippet as below to check this with a console.log(years[index].year); inside the $scope.myFunction() which logs successfully with ng-init, every time the button Generate Chart is clicked.

angular.module('myApp', []);

function MyCtrl($scope) {
    var json = [{SecuredYear: 1965},{SecuredYear: 1988}, {SecuredYear: 2016}, {SecuredYear: 2012}];
    $scope.generateChart = function(item) {
      
        var years = [];
        json.forEach(function (obj) {
          var exists = years.filter(function(y) {
            return y.year === obj.SecuredYear;
          });
          
          if (exists.length === 0) {
            years.push({year: obj.SecuredYear});
          }
        });
        $scope.years = years;
      
        
        $scope.myFunction = function (index) {
          var data = [['Product', 'ValueInDhs']];
          json.forEach(function (obj) {
            if (obj.SecuredYear == years[index].year) {
              //data.push([String(obj.GroupName).trim(), obj.ValueInDhs]);
              console.log(years[index].year);
            }
          });
          //$scope.displayChart(data, index);
        };
    };
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
    <div ng-controller="MyCtrl">
        <button ng-click="generateChart()">Generate Chart</button><br/>
        <div style="border: 1px solid skyblue;" ng-repeat="year in years" id="piechart{{$index}}" ng-init="myFunction($index)" >{{year.year}}
        </div>
    </div>
</div>
Aruna
  • 11,959
  • 3
  • 28
  • 42
  • **Aruna**, all of a sudden it started giving error on another part of the draw chart function. The error is **Error: $rootScope:inprog**. The charts are being rendered properly for the first time, but once I click for second time on the button then it give the above error. – Khan Dec 12 '16 at 04:55
  • @Saleem Can you gimme the full error and there will be a angular url with the error which you can gimme as well. I will have a look. – Aruna Dec 12 '16 at 05:05
  • **Aruna**, this is the link for the error. **https://docs.angularjs.org/error/$rootScope/inprog?p0=$digest** the error is coming inside the **$scope.displayChart = function (dataForChart, index) {** function. you can have the TeamViewer if you want. – Khan Dec 12 '16 at 05:07
  • okay, this is because of this line inside the `$scope.displayChart` function, `$scope.$apply(function () { chart.draw(data, options); });`. You can remove the `$scope.$apply(function () { })` wrapper and you can change this line simply to `chart.draw(data, options);`. – Aruna Dec 12 '16 at 05:09
  • So change from `$scope.$apply(function () { chart.draw(data, options); });` to `chart.draw(data, options);`. – Aruna Dec 12 '16 at 05:11
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/130371/discussion-between-saleem-and-aruna). – Khan Dec 12 '16 at 05:16
  • **Aruna**, Can we discuss about the issue now? – Khan Dec 28 '16 at 11:53
  • **Aruna**, did you get the solution. – Khan Jan 04 '17 at 06:58
  • Can anybody attempt this question? – Khan Jan 12 '17 at 11:10
0

angular.module('app', [
    'googlechart'
  ])
  .controller('appCtrl', function() {
    let vm = this;
    vm.charts = [];

    vm.addChart = function() {
      vm.charts.push({
        type: "PieChart",
        data: {
          "cols": [{
            id: "t",
            label: "Topping",
            type: "string"
          }, {
            id: "s",
            label: "Slices",
            type: "number"
          }],
          "rows": [{
            c: [{
              v: "Mushrooms"
            }, {
              v: 3
            }, ]
          }, {
            c: [{
              v: vm.input.name
            }, {
              v: vm.input.quantity
            }]
          }, {
            c: [{
              v: "Olives"
            }, {
              v: 31
            }]
          }, {
            c: [{
              v: "Zucchini"
            }, {
              v: 1
            }, ]
          }, {
            c: [{
              v: "Pepperoni"
            }, {
              v: 2
            }, ]
          }]
        }
      });
    };
    return vm;
  });
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.9/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-google-chart/0.1.0/ng-google-chart.min.js" type="text/javascript"></script>
<div ng-app="app" ng-controller="appCtrl as vm">
  <div ng-repeat="chart in vm.charts track by $index">
    <div google-chart chart="chart" style="height:150px; width:100%;"></div>
  </div>
  <br>
  <input type="text" required ng-model="vm.input.name" placeholder="Enter name">
  <input type="number" required ng-model="vm.input.quantity" placeholder="Enter quantity">
  <button ng-click="vm.addChart()">Add chart</button>
</div>

I've noticed you're using google charts. Your problem derives from using a non-compatible library (google charts is unaware of the changes in the model - angular in that case).

You could write your own directive but why do that if someone else already did the heavy lifting? Instead I suggest you consider using the following directive.

Here's a live example.

Edit: ng-init does not work the way you want it to. ng-init simply fires a function when the controller loads, not when the element is being rendered.

Muli Yulzary
  • 2,559
  • 3
  • 21
  • 39