0

I feel there are some things that are missing in Angular 2 that Angular 1 had, one of those things being the $watch function.

OnChanges and DoCheck are supposed to replace the $watch but I haven't been able to use either to get the desired result. OnChanges can't listen to objects so that's of no use in this scenario, and while DoCheck does the job, it causes massive lag since it seems to go bananas as soon as a change happens.

Here I've got a component which has a bunch of checkboxes, each checkbox toggle causes a value to be added to an array, rather than the default true/false functionality.

What I need to watch for in this component is the isDisabled state of a checkbox, so each time it changes I need to update the isChecked state of the checkbox.

Like I said, it works, but it has 2-3 seconds of delay on every checkbox toggle, despite only having 5 checkboxes. Preferably I wouldn't want the loop to run every time you toggle a checkbox, but instead only if the isDisabled state of a checkbox changes.

EDIT: I was told an Observable could do the job here, but I can't find any examples of this being applied to an array of objects like this. It also needs to trigger without calling its next() method manually, since it has to listen for outside changes to the array that is being sent in.

EDIT 2: I created a plunkr which doesn't show the lag, probably because in my app I have other things that might have an affect on the delay as well. However, the plunkr clearly shows that there seems to be something wrong because I put a console.log(true) within the ngDoCheck() function and it runs a looot of times. Which can't be intended, if it is, it's really odd.

Here's the plunkr: https://plnkr.co/edit/dJVnqbrredDyWTXNDzDK?p=preview

How can I do this?

export class FormCheckboxMultipleComponent implements OnInit, DoCheck {
  @Input() model: Array<any>;
  @Output('modelChange') onModelChange: EventEmitter<any> = new EventEmitter();
  @Output() callback: EventEmitter<any> = new EventEmitter();

  constructor(private _globals: GlobalVariablesService) {
    this.ns = _globals.ns;
  }

  ngOnInit() {
    this.model = this.model || [];
  }

  ngDoCheck() {

    for (let checkbox of this.checkboxes) {

      if (checkbox.isDisabled) {
        this.untoggle(checkbox);
      }
    }
  }

  findIndex(checkbox) {

    return this.model.reduce(function(cur, val, index) {

      if (val.name === checkbox.name && cur === -1) {
        return index;
      }

      return cur;
    }, -1);
  }

  addToModel(checkbox) {
    this.model.push(checkbox);
  }

  removeFromModel(i) {
    this.model.splice(i, 1);
  }

  toggle(checkbox) {

    if (checkbox.isDisabled) {
      return false;
    }

    let existingIndex = this.findIndex(checkbox);

    if (existingIndex === -1) {
      this.addToModel(checkbox);
    }
    else {
      this.removeFromModel(existingIndex);
    }

    checkbox.isChecked = !checkbox.isChecked;

    this.onModelChange.emit(this.model);

    this.callback ? this.callback.emit() : false;
  }

  untoggle(checkbox) {

    let existingIndex = this.findIndex(checkbox);

    this.removeFromModel(existingIndex);

    checkbox.isChecked = false;

    this.onModelChange.emit(this.model);
  }
}
Chrillewoodz
  • 27,055
  • 21
  • 92
  • 175
  • You could just us an `Observable` to notify about changes instead or in addition to the array change. This is the way in Angular2 to avoid expensive deep value compairsons. – Günter Zöchbauer Jun 06 '16 at 10:56
  • @GünterZöchbauer I'm having problems understanding how to use Observables despite looking at countless examples, could you provide an example that would work in this component? Would really appreciate it. – Chrillewoodz Jun 06 '16 at 10:57
  • Maybe you are running into https://github.com/angular/angular/issues/3406 otherwise with 5 checkboxes a delay should hardly be measureable. – Günter Zöchbauer Jun 06 '16 at 11:04
  • @GünterZöchbauer Can an Observable's subscribe method run without me manually calling .next()? – Chrillewoodz Jun 06 '16 at 12:23
  • I don't know rxjs observable very well because I use Dart which has streams for this. AFAIK you can create an observable from events which then emits values automatically. Don't know details though. – Günter Zöchbauer Jun 06 '16 at 12:26
  • "it causes massive lag since it seems to go bananas as soon as a change happens" -- can you create a minimal plunker that demonstrates this? Like Günter said, I don't see how checking 5 objects could cause a multi-second delay. – Mark Rajcok Jun 06 '16 at 19:08
  • @MarkRajcok I will try whenever I find the time. – Chrillewoodz Jun 08 '16 at 09:17
  • @MarkRajcok Look in the plunkr and open the console, and you can see it "go bananas", without lag however, but in a larger application such as the demo app I got it will cause lag. – Chrillewoodz Jun 08 '16 at 16:05
  • @GünterZöchbauer Can you take a look? https://plnkr.co/edit/dJVnqbrredDyWTXNDzDK?p=preview It doesn't show the lag but it does show the ngDoCheck going bonkers. – Chrillewoodz Jun 08 '16 at 16:06

1 Answers1

1

That's caused by this.onModelChange.emit(this.model); in this.untoggle(checkbox) in ngDoCheck. This is a perfect endless loop. this.onModelChange.emit(this.model); causes change detection and therefore ngDoCheck() to run and the loop begins again.

Ensure that change detection doesn't cause change detection. ngDoCheck is called by change detection and must not invoke another change detection otherwise you get an endless loop.

Plunker

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567