Skip to content

feat(platform-browser): can config zone/once/passive/capture in event listener#38236

Closed
JiaLiPassion wants to merge 1 commit intoangular:masterfrom
JiaLiPassion:event-modifier
Closed

feat(platform-browser): can config zone/once/passive/capture in event listener#38236
JiaLiPassion wants to merge 1 commit intoangular:masterfrom
JiaLiPassion:event-modifier

Conversation

@JiaLiPassion
Copy link
Copy Markdown
Contributor

Close #19878

PR Checklist

Please check if your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • angular.io application / infrastructure changes
  • Other... Please describe:

What is the current behavior?

Issue Number: 19878

What is the new behavior?

can't pass passive, capture, once parameters to addEventListener.

not easy to config use ngzone or noopzone with HostListener

user need to explicitly call runOutsideOfAngular to make sure eventhandler run outsideofngZone

@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');
    });
  }

not easy to run event handler outsideOfAngular in template

<div (mouseover)="mouseover();"></div>
mouseover() {
  this.ngZone.runOfAngular(() => {
    ...
  });
}

What is the new behavior?

Can config which zone will event handler will run into in @HostListener decorator.

can config passive, capture, once parameters to addEventListener.

  • in template, can config those parameter like below.
<div (mouseover.capture.once.passive)="mouseover();"></div>

can config ngzone which will guarantee eventhandler run in ngZone even the handler was

added not in ngZone.

 @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 if config outsideOfAngular, DOMEventManager will bypass zone.js and directly use native addEventListener to improve performance.

And it can also work with #20672 to get better performance.

 @HostListener('window:resize.noopzone', ['$event.target'])
  onResize(target: any) {
    console.log('resize triggered');
  }

Copy link
Copy Markdown
Contributor

@mhevery mhevery left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The approach seems reasonable:

  • Needs tests
  • Needs documentation (so that developers can discover the feature)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, you are right, I am following the way key_events_plugin does, I will update this part. Thanks.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would use !NgZone.isInAngularZone() to determine that.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, I will update this part.

@JiaLiPassion
Copy link
Copy Markdown
Contributor Author

@mhevery , I will continue to work on this PR and add tests and doc. thanks!

@mhevery mhevery added the area: zones Issues related to zone.js label Jul 28, 2020
@ngbot ngbot Bot added this to the needsTriage milestone Jul 28, 2020
@JiaLiPassion JiaLiPassion force-pushed the event-modifier branch 3 times, most recently from 0efb517 to b7cfed9 Compare July 29, 2020 13:53
@JiaLiPassion JiaLiPassion marked this pull request as ready for review July 29, 2020 13:54
@pullapprove pullapprove Bot requested a review from kyliau July 29, 2020 13:54
@JiaLiPassion JiaLiPassion added the target: major This PR is targeted for the next major release label Jul 29, 2020
@mary-poppins
Copy link
Copy Markdown

… 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');
  }
```
@mary-poppins
Copy link
Copy Markdown

import {Item} from './item';

currentItem = { name: 'teapot'} ;
@Component(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- `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.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- `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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  • noCD
  • manual (I am leaning towards this one)
  • !CD
  • raw
  • exclude
  • hidden
  • mask

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In summary, I'm guessing "Passing DOM event handling options in Angular templates" and "Controlling zones on event binding" are able to separate.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this text should include instructions on syntax of combining multiple options.

getZone(): NgZone;
}

export declare interface EventManagerPluginOptions {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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';

/**
*
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs jsdocs

}

/** @internal */
_splitEventNameAndOptions(eventName: string):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  • eventName
  • window:evenName

Proposed syntax is:

  • eventName.option1.option2
  • window: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:eventName
  • window,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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@JiaLiPassion JiaLiPassion changed the title feat(platform-browser): can config zone/once/passive/capture in event listenre feat(platform-browser): can config zone/once/passive/capture in event listener Jul 30, 2020
@JiaLiPassion JiaLiPassion marked this pull request as draft August 22, 2020 14:40
@mhevery
Copy link
Copy Markdown
Contributor

mhevery commented Nov 18, 2020

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.

@mhevery mhevery closed this Nov 18, 2020
@angular-automatic-lock-bot
Copy link
Copy Markdown

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot Bot locked and limited conversation to collaborators Dec 19, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area: zones Issues related to zone.js cla: yes target: major This PR is targeted for the next major release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

HostListener should have option to always run in ngZone

5 participants