BizzyDizzy BizzyDizzy - 1 month ago 9
TypeScript Question

Angular 2 child component not detecting changes correctly

Plunker with an example:
https://plnkr.co/edit/aWUE54EUMIm8rnw3PZOO?p=preview

First button increments the value. If the value is less than 10 the button should be enabled otherwise it should disable itself.
The second button resets the value to 0.

this.button1 = new ButtonAction(
"info",
"check",
"right",
"Click me!",
"medium",
true,
true,
new Command(
() => this.val += 1,
() => {
return this.val < 10;
}
),
false
);

this.button2 = new ButtonAction(
"info",
"check",
"right",
"Reset first button!",
"medium",
true,
true,
new Command(
() => this.val = 0,
null
),
false
);


When the value reaches 10 the first button indeed disables itself but when I reset the value to 0 it doesn't get updated even though everything is looking ok in the output:

Marked for check with value 7
btnClass: btn btn-outline-info btn-block
isDisabled: false
Marked for check with value 8
btnClass: btn btn-outline-info btn-block
isDisabled: false
Marked for check with value 9
btnClass: btn btn-outline-info btn-block
isDisabled: false
Marked for check with value 10
btnClass: btn btn-outline-info btn-block disabled
isDisabled: true
Marked for check with value 0
btnClass: btn btn-outline-info btn-block
isDisabled: false


This is how the value is set:

set val(v: number){
this._val = v;
console.log("Marked for check with value "+this._val);
//this.cd.markForCheck();
this.cd.detectChanges();
}


markForCheck doesn't work either so I really don't know what to do except handle the DOM myself through ElementRef (already tried it and it works), but I don't really want to do this =)

I have been trying to detect the problem and I guess the generated factory class compares the previous value of buttonClasses with the current and it works when it goes from
btn btn-outline-info btn-block
to
btn btn-outline-info btn-block disabled
but not the other way around.

The picture shows what happens when I reset the value with the second button and when buttonClasses first time get's called after that.
Debugging after reset button click

self._expr_2 is the previous value and it's
btn btn-outline-info btn-block
but it should also have
disabled
. Current value does not have
disabled
class which is correct.

Thanks in advance for the help. I really don't know what to do here...

Answer

With ChangeDetectionStrategy.OnPush strategy you have to call cd.markForCheck API method.

The most important thing:

It marks the path from our component until root to be checked for the next change detection run.

So you need to fire it on your UIButton ChangeDetectorRefs otherwise change detection will work only on one button after your action.

Whenever angular2 does change detection it marks view as checked after changes. enter image description here

Here is how angular2 handles click event:

UIButton.ngfactory.js

_View_UIButton0.prototype._handle_click_1_0 = function($event) {
  var self = this;
  self.markPathToRootAsCheckOnce();
  self.debug(1,3,14);
  var pd_0 = (self.context.clicked() !== false);
  return (true && pd_0);
};

Pay attention at the line:

self.markPathToRootAsCheckOnce();

It marks current button to be checked, but second button will remain with ChangeDetectorStatus.Checked status and omitted when checking will happen.

So your solution might look like this:

1) UIButton component

constructor(public cd: ChangeDetectorRef) {}

2) App component

@ViewChildren(UIButton) buttons: QueryList<UIButton>;

set val(v: number){
  this._val = v;
  console.log("Marked for check!");
  this.buttons.toArray().forEach(btn => btn.cd.markForCheck());
}

This way both buttons will be checked

See it in action Plunker

Hope this helps you!