Skip to content

DomEventPlugin forces event handlers to run in Angular Zone #18753

@heathkit

Description

@heathkit

I'm submitting a...


[X] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report  
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Current behavior

In #18107 the DomEventsPlugin was changed to bypass Zone.js when adding event listeners. This improves performance when scheduling these events, but it forces their handler to run in the Angular Zone, regardless of which Zone the addEventListener call originated from.

Expected behavior

In ngUpgrade, a $rootScope.$digest is called every time the NgZone is invoked. For certain events that fire frequently (scroll, mouseover, mouseout, etc), these handlers would trigger potentially expensive $digest calls frequently.

We worked around this by running Angular in a custom zone. Our custom zone would intercept when these event handlers were scheduled and instead schedule them in the root zone, thus running their handler outside of NgZone and avoiding the expensive $digest call.

This workaround is no longer an option. By default, the EventManagerPlugin will schedule events without Zone.js and we have no way to alter this behavior. This isn't an issue for us as ngUpgradeLite has solved our performance problems, but other teams using ngUpgrade might have had a similar solution.

Example of old workaround

const customZone = Zone.current.fork({
  name: 'event blacklist',
  onScheduleTask:
      (delegate: ZoneDelegate, current: Zone, target: Zone, task: Task):
          Task => {
            // Blacklist scroll, mouse, and request animation frame events.
            if ((task.type === 'eventTask' || task.type === 'macroTask') &&
                isBlacklistedEvent(task.source)) {
              task.cancelScheduleRequest();

              // Schedule task in root zone.
              return Zone.root.scheduleTask(task);
            } else {
              return delegate.scheduleTask(target, task);
            }
          }
});

then, during bootstrap...

customZone.run(() => {
    platformBrowser().bootstrapModuleFactory(AppModuleNgFactory).then((ref) => {
      const adapter = ref.injector.get(UpgradeModule) as UpgradeModule;
      const zone = ref.injector.get(NgZone);
      zone.run(() => {
        adapter.bootstrap(el, modules);
     });
  });
});

New workaround

We still want to control how handlers for these events are run. However, it seems that since EventManagerPlugin is not intended to be a public API, our only option is to make a custom RendererFactory.

I don't know if this is a change in behavior that needs to be fixed, or just something that needs to be documented.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: zonesIssues related to zone.jsregressionIndicates than the issue relates to something that worked in a previous versiontype: bug/fix

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions