angular: NgModel works incorrectly when using onPush ChangeDetection strategy
I’m submitting a …
[x] bug report - related issue https://github.com/angular/angular/issues/10148, but it's about testing.
Current behavior
I have two components: the first one is a simple child component with some input and basic support of [ngModel]
directive via ControlValueAccesor
interface. The second one is a parent component has onPush
change detection strategy and populates a value into child component via [ngModel]
directive (right after that it’s marked for changes via ChangeDetectorRef
class). When [ngModel]
is updated child component still displays an old value in the template while the component object has actual value in target property.
As I can see NgModel directive updates target component asynchronously - https://github.com/angular/angular/blob/2.0.0/modules/%40angular/forms/src/directives/ng_model.ts#L191-L193 so the following scenario occurs
- Change detection executes for parent component.
- Change detection updates [ngModel] value
- [ngModel] schedules async task that updates child component
- Change detection executes for child component
- Actual value arrives in child component.
Expected/desired behavior Change detection should work with already updated component to ensure that new value has been populated into UI.
So it looks like that last two steps should be swapped (or even better - [ngModel] can update child component synchronously).
Reproduction of the problem Here is a tiny example with the issue. I’m expecting here that right after 2 seconds App Component should display “new value”. ~~http://plnkr.co/edit/IabIsT3323GpZmQSPd4z?p=preview~~ new example can be found here http://plnkr.co/edit/KowgH96EtDbVJao13Hz5?p=preview, thanks to @andreialecu
if I use the following markup in the example above
<my-child [value]="value"></my-child>
instead of
<my-child [ngModel]="value"></my-child>
all work as expected
Please tell us about your environment:
- Angular version: 2.0.0-rc.05
- Browser: [ Chrome 52]
- Language: [TypeScript 1.8 | ES5]
About this issue
- Original URL
- State: closed
- Created 8 years ago
- Reactions: 79
- Comments: 39 (14 by maintainers)
Commits related to this issue
- OnPush reverted, see https://github.com/angular/angular/issues/10816 — committed to Squidex/squidex by SebastianStehle 7 years ago
Doesn’t look like it will be fixed in the near future.
Don’t use
OnPush
with form components4 years and still going nowhere with this…
This issue is really big flaw of Angular IMO. OnPush strategy helps us build fully controllable, predictable and unidirectional applications with great performance. But various third parties which are using two-way data binding is forcing us to workaround child components by manually detecting changes.
If you can modify the source code of the component that implements the
ControlValueAccessor
, a workaround is to callmarkForCheck
eg:Here’s the plunker from the original poster, using this technique: http://plnkr.co/edit/fy0gZdT5qpBZ3nKVOAzo?p=preview
Bumping this issue. I just spent hours debugging why change detection isn’t working until I found this thread 😕
Closing this issue since PR #44886 is now merged, the fix will be available in v13.2.1 release.
@m-przybylski I know it’s 8 months later and you’ve probably already fixed the problem one way or another, but maybe someone else will come here with the similar issue so I wanted to explain what’s happening.
Problem is that with
OnPush
change detection changes incontrol.errors
done by async validators aren’t spotted, so you can’t just do*ngIf="control.errors"
. Solution is to wrap errors into Observable and useasync
pipe.In component you’d need to do sth like this:
and in template:
startWith
is needed to trigger an initial validation.See working example here: https://stackblitz.com/edit/angular-nmrtjx
I think the angular architecture does not permit this. If proper way to fix the issue is to force the component library author to make it work with OnPush change detection, maybe there should be a guideline for them. Currently I am unable to make all my form components work with OnPush.
Also it will be better in future to remove the “default” change detection altogether. This will force everyone to use OnPush, which will result in better applications.
This worked for me:
ngAfterViewInit() { this.changeDectorRef.markForCheck() }
Have the same issue. Cannot use onPush in my components because I use 3rd party components (value accessors) as child components that cannot be pinged outside to markForCheck() themselves and trigger change detection.
I would also add explicitly that the
ngOnChanges()
life-cycle event method should be fired. I didn’t see that mentioned in this version of the ticket.Any update on this issue? or a temporary workaround? I need this for my project too. I have a parent component (with
OnPush
strategy &ControlValueAccessor
) that wraps a third party component (with default strategy &ControlValueAccessor
). When the parent component changes the value, the child component is never updated, unless it receives an user event (ie: hover, click).onPush
strategy is not meant to be used with component that has its own state. But for the component that receives data from outside. Form control does have internal state.Otherwise you need to work manually with
ChangeDetectorRef
as @christianacca shows, which is not convenient way.I think I can get around this by subscribing to
statusChanges
outside the validator:@mlc-mlapis sorry, but you are wrong. Change detection strategy to default and it works as expected…
@m-przybylski … I would prefer to not talk about
OnPush
at all in that case … because it’s not really related … and it’ll always be just confusing. Your case is just about async-validators and nothing more.Updated plunker to work with Angular 2.0 final: http://plnkr.co/edit/KowgH96EtDbVJao13Hz5?p=preview Old one wasn’t working any more.
This seems pretty severe, I cannot use
ngModel
because of this and have to resort to avoiding ControlValueAccesor and using separate explicit@Input
and@Output
properties with a different name thanngModel
.Another good repro is available here: https://github.com/angular/angular/issues/12607
@DoanVanThuong This is a simple example of
OnPush
effect on CD: https://stackblitz.com/edit/angular-parent-child-onpush?file=src%2Fapp%2Fparent%2Fparent.component.tsHere’s my cheesy work-around…
Write a Directive with the following code:
Attach the directive to your third-party components that need it, and then when you would normally trigger a ‘detectChanges’ from the parent component, cycle through your view children that have this directive, and run the ‘manuallyDetectChanges’ function.