feat(platform-browser): can config zone/once/passive/capture in event listener#38236
feat(platform-browser): can config zone/once/passive/capture in event listener#38236JiaLiPassion wants to merge 1 commit intoangular:masterfrom
Conversation
There was a problem hiding this comment.
If I understand correctly the getZoneEventHandler will create a wrapper for the zone to run it int correct zone. If that is the case I don't think that is right approach, because the event will still get registered current zone. When event fires it will fire in the current zone and than execute the event handler in the target zone. What I think you want instead is just to register the event in the correct zone to begin with. (In other words the registration should be done in the correct zone, not the execution)
There was a problem hiding this comment.
Yeah, you are right, I am following the way key_events_plugin does, I will update this part. Thanks.
There was a problem hiding this comment.
I would use !NgZone.isInAngularZone() to determine that.
There was a problem hiding this comment.
Got it, I will update this part.
|
@mhevery , I will continue to work on this PR and add tests and doc. thanks! |
0efb517 to
b7cfed9
Compare
|
You can preview 86b974c at https://pr38236-86b974c.ngbuilds.io/. |
|
You can preview b7cfed9 at https://pr38236-b7cfed9.ngbuilds.io/. |
… listener. Close angular#19878 1. Can't pass `passive`, `capture`, `once` parameters to `addEventListener`. 2. Not easy to config use ngzone or noopzone with HostListener user need to explicitly call `runOutsideOfAngular` to make sure `eventhandler` run outsideof`ngZone` ``` @HostListener('window:resize', ['$event.target']) onResize(target: any) { this.ngZone.runOutsideOfAngular(() => { console.log('resize triggered'); }); } ``` or call `ngZone.run` explicitly make sure `eventhandler` run into `ngZone`. ``` @HostListener('window:resize', ['$event.target']) onResize(target: any) { this.ngZone.run(() => { console.log('resize triggered'); }); } ``` ``` <div (mouseover)="mouseover();"></div> ``` ``` mouseover() { this.ngZone.runOfAngular(() => { ... }); } ``` 3. Can config which `zone` will event handler will run into in `@HostListener` decorator. 4. can config `passive`, `capture`, `once` parameters to `addEventListener`. - in template, can config those parameter like below. ``` <div (mouseover.capture.once.passive)="mouseover();"></div> ``` even the handler was added not in `ngZone`. ``` <div (mouseover.ngZone)="mouseover();"></div> ``` ``` @HostListener('window:resize.ngZone', ['$event.target']) onResize(target: any) { console.log('resize triggered'); } ``` - Can config `noopZone` which will guarantee eventhandler run outside of `ngZone` even the handler was added in `ngZone`. And it can also work with angular#20672 to get better performance. ``` <div (mouseover.noopZone)="mouseover();"></div> ``` ``` @HostListener('window:resize.noopZone', ['$event.target']) onResize(target: any) { console.log('resize triggered'); } ```
b7cfed9 to
d20b7d0
Compare
| import {Item} from './item'; | ||
|
|
||
| currentItem = { name: 'teapot'} ; | ||
| @Component( |
There was a problem hiding this comment.
This is example code. It should include comments explaining what you are demonstrating in each of the cases. Otherwise the reader does not know why you are showing this code to them.
|
|
||
| And it is also possible to apply `ngZone` or `noopZone` option to make sure the event handler run into the specified `zone`. | ||
|
|
||
| - `ngZone`: The event handler always runs into the `angular zone`, so it triggers the change detection automatically. |
There was a problem hiding this comment.
| - `ngZone`: The event handler always runs into the `angular zone`, so it triggers the change detection automatically. | |
| - `ngZone`: The event handler always runs in the `angular zone`, therefore change detection is triggered automatically. |
Since everything is in ngZone already why would you ever use this option?
| And it is also possible to apply `ngZone` or `noopZone` option to make sure the event handler run into the specified `zone`. | ||
|
|
||
| - `ngZone`: The event handler always runs into the `angular zone`, so it triggers the change detection automatically. | ||
| - `noopZone`: The event handler always runs outside of the `angular zone`, so it does not trigger the change detection automatically. |
There was a problem hiding this comment.
| - `noopZone`: The event handler always runs outside of the `angular zone`, so it does not trigger the change detection automatically. | |
| - `noopZone`: The event handler always runs outside of the `angular zone`, therefore no change detection is triggered. |
There was a problem hiding this comment.
Thinking more about this, I don't think we want to tie the options to the zone keywords as zones are implementation details not intention. So I think a better thing would be to say something like click.noCD or something like that to convey the intention that I don't want CD to happen here.
Possible candidates:
noCDmanual(I am leaning towards this one)!CDrawexcludehiddenmask
There was a problem hiding this comment.
I agree with Misko and in addition, I prefer adding ng prefix for Angular-specific flags which isn't standards in DOM event context.
Or, I'm guessing maybe it shouldn't address Zone's concern in this feature because it is actually not needed to solve the problem in a single way. For example, scoping events inside/outside Zones, I think there are other approaches like the below. How do you think?
<ng-container *ngOutsideZone>
<div (click)="clickInNoopZone()"></div>
</ng-container>
There was a problem hiding this comment.
In summary, I'm guessing "Passing DOM event handling options in Angular templates" and "Controlling zones on event binding" are able to separate.
There was a problem hiding this comment.
@mhevery, @lacolaco , yeah, I agree that zone is the implementation detail, we need to consider another names.
And this idea is interesting,
<ng-container *ngOutsideZone>
<div (click)="clickInNoopZone()"></div>
</ng-container>
But it may be difficult if I want to only make some event outside of ngZone.
<ng-container *ngOutsideZone>
<div (scroll)="scrollOutsideOfZone()" (click)="clickInsideNgZone()"></div>
</ng-container>
There was a problem hiding this comment.
But it may be difficult if I want to only make some event outside of ngZone.
A-ha! Totally agreed. I understood that Zone configuration must be an event binding syntax.
|
|
||
| And it is also possible to apply `ngZone` or `noopZone` option to make sure the event handler run into the specified `zone`. | ||
|
|
||
| - `ngZone`: The event handler always runs into the `angular zone`, so it triggers the change detection automatically. |
There was a problem hiding this comment.
you should enumerate all of the possible options including capture, passive, once in this list.
|
|
||
| ## Apply `capture`, `passive`, `once`, `ngZone`, `noopZone` options | ||
|
|
||
| It is possible to apply [AddEventListenerOptions](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) `capture`, `passive`, `once` by combining the event name with the option. |
There was a problem hiding this comment.
this text should include instructions on syntax of combining multiple options.
| getZone(): NgZone; | ||
| } | ||
|
|
||
| export declare interface EventManagerPluginOptions { |
There was a problem hiding this comment.
| export declare interface EventManagerPluginOptions { | |
| export declare interface EventOptions { |
I think shorter names are better. In this case the options are for the event (or maybe event handler?)
| import {EventManagerPluginOptions} from './event_manager'; | ||
|
|
||
| /** | ||
| * |
| } | ||
|
|
||
| /** @internal */ | ||
| _splitEventNameAndOptions(eventName: string): |
There was a problem hiding this comment.
I would like to see a unit test specifically for this function where we feed it different event names and see how they get parsed.
Current syntax is:
eventNamewindow:evenName
Proposed syntax is:
eventName.option1.option2window:evenName.option1.option2
It does not seem very consistent that window is a prefix but the other option are suffix. I think we should unified it.
option1,option2:eventNamewindow,option1,option2:eventName
Example:
window,passive,noCD:scroll would mean register a passive scroll listener on window with no change-detection.
We should change the signature of ɵɵlistener
export function ɵɵlistener(
eventName: string, listenerFn: (e?: any) => any, useCapture = false,
eventTargetResolver?: GlobalTargetResolver): typeof ɵɵlistener {
from useCapture=false to a bit flags representing the options. This would require a compiler change but it would be consistent and would allow for possible tree shaking benefits.
There was a problem hiding this comment.
How is (eventTarget:eventName:options)="..." instead of . chaining? Because its signature order is similar to JavaScript API. And existing pipe parameters syntax is also like that.
window:resize:option1,option2
// JS API
window.addEventListener(resize, ..., {option1,option2})
// Pipe parameters
{{ prop | date:'short' }}
Optional arguments are typically placed at the last position in many languages and contexts. I guess inserting in the middle looks complex and not intuitive a little.
There was a problem hiding this comment.
I totally get where you are coming from and agree with you. However given that we already have window:event which is a form of an option I would prefer syntax which is consistent with existing syntax. But we will have an official RFC to decide on the syntax to come.
|
I am going to close this PR in the interest of keeping our PR queue clean. Per discussions in team meetings, we want this feature, but the current approach is not ideal, as we would like a more encompassing feature. |
|
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. |
Close #19878
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: 19878
What is the new behavior?
can't pass
passive,capture,onceparameters toaddEventListener.not easy to config use ngzone or noopzone with HostListener
user need to explicitly call
runOutsideOfAngularto make sureeventhandlerrun outsideofngZoneor call
ngZone.runexplicitly make sureeventhandlerrun intongZone.not easy to run event handler outsideOfAngular in template
What is the new behavior?
Can config which
zonewill event handler will run into in@HostListenerdecorator.can config
passive,capture,onceparameters toaddEventListener.can config
ngzonewhich will guarantee eventhandler run inngZoneeven the handler wasadded not in
ngZone.noopzonewhich will guarantee eventhandler run outside ofngZoneeven the handler was added inngZone. and if configoutsideOfAngular, DOMEventManager will bypass zone.js and directly use native addEventListener to improve performance.And it can also work with #20672 to get better performance.