fix(elements): initialization should run in ngZone (#24181)#24185
fix(elements): initialization should run in ngZone (#24181)#24185remackgeek wants to merge 11 commits intoangular:masterfrom
Conversation
|
@remackgeek , sure, I think a demo to describe this issue will be helpful. So your use case is you will create the |
gkalpak
left a comment
There was a problem hiding this comment.
Wouldn't the same issue affect things like detectChanges() and destroy()?
|
@gkalpak, yes. Please see the latest update -- if you choose zone behavior, this wraps all the strategy methods. |
| this.insureAngularZone(() => { super.connect(element); }); | ||
| } | ||
|
|
||
| disconnect() { this.insureAngularZone(super.disconnect); } |
There was a problem hiding this comment.
Wouldn't this cause this to be undefined inside super.disconnect?
There was a problem hiding this comment.
yeah, and its inconsistent with the line above it.
| } | ||
|
|
||
| /** | ||
| * extends ComponentNgElementStrategy to insure callbacks run in the Angular zone |
| this.insureAngularZone(() => { super.setInputValue(propName, value); }); | ||
| } | ||
|
|
||
| private insureAngularZone(fn: () => any) { |
There was a problem hiding this comment.
*ensure
would this be better named as runInZone or similar?
| this.insureAngularZone(() => { super.connect(element); }); | ||
| } | ||
|
|
||
| disconnect() { this.insureAngularZone(super.disconnect); } |
There was a problem hiding this comment.
yeah, and its inconsistent with the line above it.
|
@gkalpak, @robwormald thanks for the review. Updated. |
|
I picked |
|
@robwormald @remackgeek @gkalpak what are your plans for this change? My company has built many Angular Components and having the opportunity to export them as WebComponent to integrate in other non Angular applications (with Zones/Change Detection working out of the box) would be a huge use case and argument to move a lot of development focus into Angular Components. |
| export class ComponentNgElementZoneStrategy extends ComponentNgElementStrategy { | ||
| private ngZone: NgZone; | ||
|
|
||
| constructor(protected componentFactory: ComponentFactory<any>, protected injector: Injector) { |
There was a problem hiding this comment.
No need for these to be protected here - the superclass already takes care of assigning them to itself.
| private readonly uninitializedInputs = new Set<string>(); | ||
|
|
||
| constructor(private componentFactory: ComponentFactory<any>, private injector: Injector) {} | ||
| constructor(protected componentFactory: ComponentFactory<any>, protected injector: Injector) {} |
| * @experimental | ||
| */ | ||
| export class ComponentNgElementZoneStrategy extends ComponentNgElementStrategy { | ||
| private ngZone: NgZone; |
|
To clarify my output runOutsideAngular suggestion, it would look something like this: |
|
@kseamon, wouldn't there be cases where you want the event to be in the zone? Say a third-party element being used in an Angular SPA. |
|
In that case, the element would have already started up in the angular zone?
I’d say that if we have to manually join the angular zone then we should leave it on output. Otherwise we can stay.
…Sent from my iPhone
On Aug 19, 2018, at 8:01 PM, remackgeek ***@***.***> wrote:
@kseamon, wouldn't there be cases where you want the event to be in the zone? Say a third-party element being used in an Angular SPA.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.
|
| this.runInZone(() => { super.setInputValue(propName, value); }); | ||
| } | ||
|
|
||
| private runInZone(fn: () => any) { return NgZone.isInAngularZone() ? fn() : this.ngZone.run(fn); } |
There was a problem hiding this comment.
Remove NgZone.isInAngularZone() check. If we are are in a micro-frondend situation (an Angular shell app plus some angular elements imported from different js angular bundles), this condition will return true, because we always are in a AngularZone, but the wrong one to perfom change detection: the shell, we need to run the code in the element zone instead, in order to get the change detection ( AppReference.tick()) working for the element.
On the other hand, in a normal situation with just one angular shell and elements created from the inside, it won't be a problem to call ngZone.run(), it should cost zero, so the "zone check" could be safely removed.
You can find the PoC and more details in this PR: remackgeek/elements-zone-strategy#2.
|
Per @tnicola's comment, each child app would have it's own ngZone, so leaving it should be fine and would not cause the parent app to leave its own zone. |
|
@remackgeek, yeah, I am working on a |
|
So, to summarize the various threads to make sure I'm following everything:
This should handle the 3 broad use cases:
|
|
@tnicola, I updated the PR to check the specific ngZone while waiting for PR 1126 land @kseamon, I did some testing -- the event handlers for the outputs already run in the zone they were created in, so I'm not sure where there would be a performance gain changing zones in the Observable. But the mechanics of zones are black magic to me, so let me know if I missed something. |
|
Can you include a unit test to verify that going forward? Thanks.
…Sent from my iPhone
On Sep 3, 2018, at 10:56 PM, remackgeek ***@***.***> wrote:
@tnicola, I updated the PR to check the specific ngZone while waiting for PR 1126 land
@kseamon, I did some testing -- the event handlers for the outputs already run in the zone they were created in, so I'm not sure where there would be a performance gain changing zones in the Observable. But the mechanics of zones are black magic to me, so let me know if I missed something.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.
|
|
I created a PR in |
|
@JiaLiPassion 's PR has landed in zone.js, so I'd appreciate people testing against that and letting us know about any changes |
|
@robwormald, sure, I will test it! |
|
I ran my examples and everything worked as expected. Thanks, @JiaLiPassion ! Will the next version of zone.js be released with Angular 7? |
|
@remackgeek, thanks for the confirmation, I am not sure the |
|
@JiaLiPassion , I did some more testing and I did find an issue. Your PR doesn't seem to also fix #23841, while this one does. I have example code here. A live example is here |
|
@remackgeek, thank you for the testing, yes, it will not work because we still need to update the |
gkalpak
left a comment
There was a problem hiding this comment.
@remackgeek, as mentioned in #37416 (comment) we have decided this is a valueable fix to get into @angular/elements. Are you still interested in working on this?
To be discussed/determined:
- I noticed the implementation here is different than the one in remackgeek/elements-zone-strategy/.../element-zone-strategy.ts. Am I correct to assume that the implementation in this PR is more performant (when already in the zone) and covers more usecases? Is there any downside (once we address
Zonebeing potentially undefined)? - If we can ensure there is no extra overhead when already inside the zone, then I would be tempted to incorporate the fix into
ComponentNgElementStrategy.
| constructor(componentFactory: ComponentFactory<any>, injector: Injector) { | ||
| super(componentFactory, injector); | ||
| this.ngZone = this.injector.get<NgZone>(NgZone); | ||
| this.elementZone = this.ngZone.run(() => { return Zone.current; }); |
There was a problem hiding this comment.
This also needs to account for the possibility that Zone.js has not been loaded at all (similar to how we do in core/src/zone/ng_zone.ts#L123 and other places). Same in runInZone() below.
|
@gkalpak, I'd be happy to work on this! I'd guess that the PR version is more performant and handles the same cases, but I'm not a zone wizard. I shamelessly lifted the basic pattern from @JiaLiPassion in PR23885.. It would be cool to put this in ComponentNgElementStrategy, but then don't you lose the ability to tree-shake out the zone stuff for zoneless users? |
Not sure 🤔 Why would that be the case? What part would prevent the tree-shaking? |
|
I did some quick-and-dirty performance checking -- on my machine, the overhead for wrapping in ngZone.run() is about 20ms for 1 million calls. The overhead for comparing zones in the "already in zone" case is lost in the noise. |
|
I don't think |
|
The new PR is here: #37814. |
|
Thx, @remackgeek ✨ |
|
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
PR Checklist
Please check if your PR fulfills the following requirements:
PR Type
What kind of change does this PR introduce?
What is the current behavior?
Issue Number: #24181
When an angular element is created outside the angular zone, such as in setTimeout(), automatic change detection no longer works
What is the new behavior?
during connect(), initializeComponent is always called in the angular zone
Does this PR introduce a breaking change?
Other information
@JiaLiPassion this is based/extends on your PR 23885, please review