Filter/Function causing infdig

83 Views Asked by At

I am making a pie chart for my data. I am using Angular Chart (and subsequently, charts.js).

My data looks like this (vm being the controller):

vm.persons = [
  {
    name:'smith',
    cart: [
      {
        id: 1,
        category: 'food'
      },
      {
        id: 2,
        category: 'clothes'
      }
    ]
  },
  {
    name: 'adams',
    cart: [
      {
        id: 3,
        category: 'automobile'
      },
      {
        id:1, category: 'food'
      }
    ]
  }
]

As such, my template looks like:

<div ng-repeat="person in vm.persons">
  <div class="person-header">{{person.name}}</div>
  <!-- chart goes here -->
  <canvas class="chart chart-pie" chart-data="person.cart | chart : 'category' : 'data'" chart-labels="person.cart | chart : 'category' : 'labels'"></canvas>
  <div class="person-data" ng-repeat="item in person.cart">
    <div>{{item.category}}</div>
  </div>
</div>

I decided to go with a filter for the chart as I thought that would be appropriate, DRY and reusable:

angular.module('myModule').filter('chartFilter', function() {
  return function(input, datum, key) {
    const copy = JSON.parse(JSON.stringify([...input.slice()])); // maybe a bit overboard on preventing mutation...
    const entries = Object.entries(copy.reduce((o,n) => {o[n[datum]] = (o[n[datum]] || 0) + 1}, {}));
    const out = {
      labels: entries.map(entry => entry[0]);
      data: entries.map(entry => entry[1]);
    };
    return out[key];
  }
});

THIS WORKS, and the chart does show up, with the proper data. However per the console, it throws an infdig error every time. Per the docs, it's because I am returning a new array, which I am because it is almost a different set of data. Even if I get rid of copy (which is meant to be a separate object entirely) and use input directly (input.reduce(o,n), etc.) it still throws the error.

I tried also making it into a function (in the controller):

vm.chartBy = (input, datum, key) => {
  const copy = JSON.parse(JSON.stringify([...input.slice()])); // maybe a bit overboard on preventing mutation...
  const entries = Object.entries(copy.reduce((o,n) => {o[n[datum]] = (o[n[datum]] || 0) + 1}, {}));
  const out = {
    labels: entries.map(entry => entry[0]);
    data: entries.map(entry => entry[1]);
  };
  return out[key];
};

and in the template:

<canvas class="chart chart-pie" chart-data="vm.chartBy(person.cart, 'category', 'data')" chart-labels="vm.chartBy(person.cart, 'category', 'labels')"></canvas>

However this is throwing an infdig error as well.

Does anyone know how to not get it to through an infdig error each time? That is what I am trying to solve.

1

There are 1 best solutions below

0
On

As you pointed out, you can't bind to a function which produces a new array or the digest cycle will never be satisfied that the new value matches the old, because the array reference changes each time.

Instead, bind only to the data and then implement the filter in the directive, so that the filtered data is never bound, just shown in the directive's template.

HTML

<canvas class="chart chart-pie" chart-data="person.cart" chart-labels="person.cart"></canvas>

JavaScript

app.directive('chartData', function(){
    return {
        template: '{{chartData | chart : "category" : "data"}}',
        scope: {
            'chartData': '='
        }
    }
});

app.directive('chartLabels', function(){
    return {
        template: '{{chartLabels | chart : "category" : "labels"}}',
        scope: {
            'chartLabels': '='
        }
    }
});

app.filter('chart', function() {
    return function(input, datum, key) {
        ...
        return out[key];
    }
});

I've hardcoded the datum/key strings in the directives but you could pass those in as additional bindings if needed.

Simple Mock-up Fiddle