Giter VIP home page Giter VIP logo

tinkoff / ng-event-plugins Goto Github PK

View Code? Open in Web Editor NEW
233.0 10.0 12.0 2.05 MB

This is an Angular library for optimizing performance sensitive events and declarative preventDefault, stopPropagation and capture phase listeners.

Home Page: https://codesandbox.io/s/github/TinkoffCreditSystems/ng-event-plugins/tree/master/projects/demo

License: Apache License 2.0

JavaScript 15.65% TypeScript 71.01% HTML 7.09% CSS 1.62% Less 4.15% Shell 0.47%
angular events optimization preventdefault stoppropagation capture angular-library trigger-change-detection

ng-event-plugins's Introduction

Angular Event Plugins

npm version npm bundle size Coverage Status angular-open-source-starter telegram chat

@tinkoff/ng-event-plugins is a tiny (1KB gzip) library for optimizing change detection cycles for performance sensitive events (such as touchmove, scroll, drag etc.) and declarative preventDefault() and stopPropagation().

How to use

  1. Add EventPluginsModule to your app module:

    import {NgModule} from '@angular/core';
    import {BrowserModule} from '@angular/platform-browser';
    import {EventPluginsModule} from '@tinkoff/ng-event-plugins'; // <-- THIS
    
    @NgModule({
      bootstrap: [
        /*...*/
      ],
      imports: [
        /*...*/
        BrowserModule,
        EventPluginsModule, // <-- GOES HERE
      ],
      declarations: [
        /*...*/
      ],
    })
    export class AppModule {}

BrowserModule or BrowserAnimationsModule must go first. You will see a warning if you mess the order.

  1. Use new modifiers for events in templates and in @HostListener:

    • .stop to call stopPropagation() on event
    • .prevent to call preventDefault() on event
    • .self to skip bubbled events
    • .silent to call event handler outside Angular's NgZone
    • .capture to listen to events in capture phase
    • .passive to add passive event listener
    • .once to remove event listener after first callback

    For example:

    <div (mousedown.prevent)="onMouseDown()">Clicking on this DIV will not move focus</div>
    <div (click.stop)="onClick()">Clicks on this DIV will not bubble up</div>
    <div (mousemove.silent)="onMouseMove()">Callbacks to mousemove will not trigger change detection</div>
    <div (click.capture.stop)="onClick()">
      <div (click)="never()">Clicks will be stopped before reaching this DIV</div>
    </div>
  2. You can also re-enter NgZone and trigger change detection, using @shouldCall decorator that takes a predicate function as argument:

<div (scroll.silent)="onScroll($event.currentTarget)">
  Scrolling this DIV will only trigger change detection and onScroll callback if it is scrolled to bottom
</div>
import {shouldCall} from '@tinkoff/ng-event-plugins';

export function scrollFilter({
 scrollTop, scrollHeight, clientHeight
}: HTMLElement): boolean {
    return scrollTop === scrollHeight - clientHeight;
}

// ...

@shouldCall(scrollFilter)
onScroll(_element: HTMLElement): void {
    this.someService.requestMoreData();
}

All examples above work the same when used with @HostListener and CustomEvent

Important notes

  • Predicate is called with the same arguments as the decorated method and in the context of class instance (has access to this)

  • Decorated method will be called and change detection triggered if predicate returns true.

  • Predicates must be exported named function for AOT, arrow functions will trigger build error.

  • .silent modifier will not work with built-in keyboard pseudo-events, such as keydown.enter or keydown.arrowDown since Angular re-enters NgZone inside internal handlers.

Observable host bindings

In this library there's also a plugin that enables observable host bindings. Sounds weird to do host binding with event plugin, but the code is actually pretty simple. You can read more about it in this article.

To use it you need to couple @HostListener and @HostBinding on the same Observable property with following syntax:

@HostBinding('$.disabled')
@HostListener('$.disabled')
readonly disabled$ = asCallable(this.service.loading$)

This supports all the native Angular syntax, such as class.class-name or style.width.px.

IMPORTANT NOTES:

  • Until this issue is resolved you would have to use NO_ERRORS_SCHEMA in your module in order to bind to arbitrary properties
  • asCallable is a utility function from this library that simply adds Function to the type so Angular thinks it could be a host listener
  • To bind attributes you need to add .attr modifier in the end, not the beginning like in basic Angular binding. This is due to Angular using regexp to match for attr. string in @HostBinding decorator:
@HostBinding('$.aria-label.attr')
@HostListener('$.aria-label.attr')
readonly label$ = asCallable(this.translations.get$('label'));

Demo

You can try this interactive demo

You can also read this detailed article explaining how this library works

Maintained

@tinkoff/ng-event-plugins is a part of Taiga UI libraries family which is backed and used by a large enterprise. This means you can rely on timely support and continuous development.

License

πŸ†“ Feel free to use our library in your commercial and private applications

All @tinkoff/ng-event-plugins packages are covered by Apache 2.0

Read more about this license here

Open-source

Do you also want to open-source something, but hate the collateral work? Check out this Angular Open-source Library Starter we’ve created for our projects. It got you covered on continuous integration, pre-commit checks, linting, versioning + changelog, code coverage and all that jazz.

ng-event-plugins's People

Contributors

dependabot[bot] avatar marsibarsi avatar waterplea avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ng-event-plugins's Issues

[BUG] Upgrade to version 2.4.0 made @shouldCall not working

🐞 Bug report

Description

Upgrade to [email protected] caused ShouldCalls in our application to throw this error:

ERROR TypeError: zone.run is not a function
    value tinkoff-ng-event-plugins.js:580
    ResizableSidebarComponent_mousemove_silent_HostBindingHandler ResizableSidebarComponent.js:8
    Angular 15
    addEventListener tinkoff-ng-event-plugins.js:344
    Angular 3
    addEventListener tinkoff-ng-event-plugins.js:341
    Angular 5
    ResizableSidebarComponent_HostBindings ResizableSidebarComponent.js:7
    Angular 4
    ClassificationSchemesComponent_Template template.html:1
    Angular 22
    RxJS 70

seems to be caused by should-call.ts:13, as if its associated init event did not pass Angular ZoneJS instance to it.

Reproduction

In a controller of Angular component:

@HostListener('document:mousemove.silent', ['$event'])
@HostListener('document:init.mousemove', ['$event'])
@ShouldCall(resizeInProgress)
onSideResize(event: MouseEvent) { ... }

Expected behavior

Nothing should be thrown.

Versions

  • OS: MacOS 12
  • Browser Firefox and Chrome, both latest
  • Angular 13.0.2

Additional context

Angular 16 - Ivy

Angular 16 will fully deprecate ngcc, making this librairy no longer usable in Angular 16+.

Request: Make ng-event-plugins work on Angular 16+

Thanks

[BUG] An issue with Angular Language Service

🐞 Bug report

Description

.stop and .prevent syntax is not correctly handled by angular language service. This leads to errors being displayed by IDE when such syntax is used:
Screenshot 2020-11-26 at 17 24 25
Compare it to the standard click event:
Screenshot 2020-11-26 at 17 24 46

Expected behavior

No error is displayed, type of $event is displayed on hover, description of event is displayed on hover.

Versions

  • OS: MacOS
  • Angular 11
  • JetBrains IDE (WebStorm/Rider/RubyMine etc) (not sure if the issue persists in VSCode, but i would think so)

Additional context

I'm not sure that this can be fixed on the plugin side, but still creating an issue, as this is very annoying to have these errors all around the IDE.

Added by @waterplea:
Also exact type of the $event variable is lost when using .stop and .prevent. This leads to compilation errors like this:

Argument of type 'Event' is not assignable to parameter of type 'MouseEvent'.

[BUG] false error "EventManagerModule must come after BrowserModule in imports" when using lazy loaded modules

🐞 Bug report

Description

After setting up lazy loaded modules, I get the following error in console:
EventManagerModule must come after BrowserModule in imports

Note that I do not import EventManagerModule directly, only @taiga-ui modules, and I do import them after BrowserModule so I'm a bit confused.

Something weird: I had lazy loaded modules without errors and in my case the error occurred as soon as I set up the ssr with Angular Universal. I don't really understand why but working on the reproduction I realized that it had nothing to do with ssr but rather with lazy loaded modules. But again I had no error before using ssr :/

I think that the assertion may not be right?
When I log plugins inside the constructor, we pass twice:

class EventPluginsModule {
    /**
     * @param {?} plugins
     */
    constructor(plugins) {
        console.log('!!!!!!!!!!!!!!!');
        console.log(plugins);
        console.log('!!!!!!!!!!!!!!!');
        console.assert(!(plugins[0] instanceof SilentEventPlugin), 'EventManagerModule must come after BrowserModule in imports');
    }
}

!!!!!!!!!!!!!!!

[
  DomEventsPlugin {
    _doc: <ref *1> {
      parentNode: null,
      _previousSibling: [Circular *1],
      _nextSibling: [Circular *1],
      _index: undefined,
      _childNodes: null,
      _firstChild: [Object],
      nodeType: 9,
      isHTML: true,
      _address: 'http://localhost:59992/',
      readyState: 'loading',
      implementation: [Object],
      ownerDocument: null,
      _contentType: 'text/html',
      doctype: [Object],
      documentElement: [HTMLHtmlElement],
      _templateDocCache: null,
      _nodeIterators: null,
      _nid: 1,
      _nextnid: 35,
      _nodes: [Array],
      byId: [Object: null prototype] {},
      modclock: 1,
      _scripting_enabled: true,
      defaultView: [Object]
    }
  },
  KeyEventsPlugin {
    _doc: <ref *1> {
      parentNode: null,
      _previousSibling: [Circular *1],
      _nextSibling: [Circular *1],
      _index: undefined,
      _childNodes: null,
      _firstChild: [Object],
      nodeType: 9,
      isHTML: true,
      _address: 'http://localhost:59992/',
      readyState: 'loading',
      implementation: [Object],
      ownerDocument: null,
      _contentType: 'text/html',
      doctype: [Object],
      documentElement: [HTMLHtmlElement],
      _templateDocCache: null,
      _nodeIterators: null,
      _nid: 1,
      _nextnid: 35,
      _nodes: [Array],
      byId: [Object: null prototype] {},
      modclock: 1,
      _scripting_enabled: true,
      defaultView: [Object]
    }
  },
  SilentEventPlugin { modifier: 'silent' },
  PreventEventPlugin { modifier: 'prevent' },
  SelfEventPlugin { modifier: 'self' },
  StopEventPlugin { modifier: 'stop' },
  ZoneEventPlugin { modifier: 'init' },
  CaptureEventPlugin { modifier: 'capture' },
  BindEventPlugin { modifier: '$' },
  ServerEventManagerPlugin {
    doc: <ref *1> {
      parentNode: null,
      _previousSibling: [Circular *1],
      _nextSibling: [Circular *1],
      _index: undefined,
      _childNodes: null,
      _firstChild: [Object],
      nodeType: 9,
      isHTML: true,
      _address: 'http://localhost:59992/',
      readyState: 'loading',
      implementation: [Object],
      ownerDocument: null,
      _contentType: 'text/html',
      doctype: [Object],
      documentElement: [HTMLHtmlElement],
      _templateDocCache: null,
      _nodeIterators: null,
      _nid: 1,
      _nextnid: 35,
      _nodes: [Array],
      byId: [Object: null prototype] {},
      modclock: 1,
      _scripting_enabled: true,
      defaultView: [Object]
    }
  }
]

!!!!!!!!!!!!!!!

!!!!!!!!!!!!!!!

[
  SilentEventPlugin { modifier: 'silent' },
  PreventEventPlugin { modifier: 'prevent' },
  SelfEventPlugin { modifier: 'self' },
  StopEventPlugin { modifier: 'stop' },
  ZoneEventPlugin { modifier: 'init' },
  CaptureEventPlugin { modifier: 'capture' },
  BindEventPlugin { modifier: '$' }
]
!!!!!!!!!!!!!!!

The second time seems ok but indeed the first time SilentEventPlugin is not the first item of the plugins array.

Reproduction

https://github.com/VictorienTardif/taiga-ui-ssr

[FEATURE] Add a module

πŸš€ Feature request

Adding providers manually is scary. Would be nice if there was a module with them added already so people could import it.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.