13

I can't seem to get angular2 view to be updated on an array.push function, called upon from a setInterval async operation.

the code is from this angular plunkr example of setInterval:

What i'm trying to do is as follows:

import {View, Component, bootstrap, Directive, ChangeDetectionStrategy, ChangeDetectorRef} from 'angular2/angular2'

@Component({selector: 'cmp', changeDetection: ChangeDetectionStrategy.OnPush})
@View({template: `Number of ticks: {{numberOfTicks}}`})
class Cmp {
  numberOfTicks = [];
  
  constructor(private ref: ChangeDetectorRef) {
    setInterval(() => {
      this.numberOfTicks.push(3);
      this.ref.markForCheck();
    }, 1000);
  }
}

@Component({
  selector: 'app',
  changeDetection: ChangeDetectionStrategy.OnPush
})
@View({
  template: `
    <cmp><cmp>
  `,
  directives: [Cmp]
})
class App {
}

bootstrap(App);
<!DOCTYPE html>
<html>

<head>
  <title>angular2 playground</title>
  <script src="https://code.angularjs.org/tools/traceur-runtime.js"></script>
  <script src="https://code.angularjs.org/tools/system.js"></script>
  <script src="https://code.angularjs.org/tools/typescript.js"></script>
  <script data-require="jasmine" data-semver="2.2.1" src="http://cdnjs.cloudflare.com/ajax/libs/jasmine/2.2.1/jasmine.js"></script>
  <script data-require="jasmine" data-semver="2.2.1" src="http://cdnjs.cloudflare.com/ajax/libs/jasmine/2.2.1/jasmine-html.js"></script>
  <script data-require="jasmine@*" data-semver="2.2.1" src="http://cdnjs.cloudflare.com/ajax/libs/jasmine/2.2.1/boot.js"></script>
  
  <script src="config.js"></script>
  <script src="https://code.angularjs.org/2.0.0-alpha.37/angular2.min.js"></script>
  <script>
    System.import('app')
      .catch(console.error.bind(console));
  </script>

</head>

<body>
  <app></app>
</body>

</html>

The above code will work properly if "numberOfTicks" is just a number, (as the plunker original example shows) but once I change it to an array and push data, it won't update.

I can't seem to understand why is that.

The following behaviour is similar to an issue I have when trying to update a graph in angular2 with new data points when using setInterval / setTimeout.

Thanks for the help.

1
  • I'll elaborate - I'm trying to update a graph in realtime by updating it's data array using the above setInterval methodoligy, this plunker might be a good example to try and achieve this: plnkr.co/edit/vdgKVJOymMYhiyqZrPma?p=preview Commented Mar 27, 2016 at 13:21

2 Answers 2

20

You need to update the whole reference of your array after adding an element in it:

  constructor(private ref: ChangeDetectorRef) {
    setInterval(() => {
      this.numberOfTicks.push(3);
      this.numberOfTicks = this.numberOfTicks.slice();
      this.ref.markForCheck();
    }, 1000);
  }
}

Edit

The DoCheck interface could also interest you since it allows you to plug your own change detection algorithm.

See this link for more details:

Here is a sample:

@Component({
  selector: 'custom-check',
  template: `
    <ul>
      <li *ngFor="#line of logs">{{line}}</li>
    </ul>`
})
class CustomCheckComponent implements DoCheck {
  @Input() list: any[];
  differ: any;
  logs = [];

  constructor(differs: IterableDiffers) {
    this.differ = differs.find([]).create(null);
  }

  ngDoCheck() {
    var changes = this.differ.diff(this.list);
    if (changes) {
      changes.forEachAddedItem(r => this.logs.push('added ' + r.item));
      changes.forEachRemovedItem(r => this.logs.push('removed ' + r.item))
    }
  }
}
Sign up to request clarification or add additional context in comments.

7 Comments

Thanks Thierry, while this indeed solves the case that i've described here, i'll elaborate a bit more. I'm trying to update a graph dynamically by updating its value array, things is, if I slice the array the graph will be redrawn entirely. In angular 1.x i did this using $timeout/$interval which run the $apply and make angular to detect changes. I'm trying to achieve the same 'realtime' affect using angular 2
I'll rephrase - using the method you suggested in a graph will work, only the graph would 'flash' rather then just drawing a new point on the existing graph
Okay I understand. Perhaps you could inject a ChangeDetectorRef instance in your component and call its markForCheck method after added an element in the array.
Yeah thats exactly what i'm trying to do, unfortunately it seems like angular still requires the array reference to change. However, the behavior i'm seeing is very strange. Angular will update the graph if I do any action on the browser. Things like resizing browser size / opening & closing debugger. As long as I do these, the graph magically updates with the same behavior as in angular 1.
|
9

The above code will work properly if "numberOfTicks" is just a number, but once I change it to an array and push data, it won't update. I can't seem to understand why is that.

It is because a number is a JavaScript primitive type, and an array is a JavaScript reference type. When Angular change detection runs, it uses === to determine if a template binding has changed.

If {{numberOfTicks}} is a primitive type, === compares the current and previous values. So the values 0 and 1 are different.

If {{numberOfTicks}} is a reference type, === compares the current and previous (reference) values. So if the array reference didn't change, Angular won't detect a change. In your case, using push(), the array reference isn't changing.

If you put the following in your template instead

<div *ngFor="#tick of numberOfTicks">{{tick}}</div>

then Angular would update the view because there is a binding to each array element, rather then just the array itself. So if a new item is push()ed, or an existing item is deleted or changed, all of these changes will be detected.

So in your chart plunker, the following should update when the contents of the from and to arrays change:

<span *ngFor="#item of from">{{item}}</span>
<span *ngFor="#item of to">{{item}}</span>

Well, it will update if each item is a primitive type.

2 Comments

This does not seem to work anymore, I got "Unexpected token #" error.
Though it seems angular2 does not really use === anymore, since pushing to array works just fine. Guess they do full object comparison including traversing arrays.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.