Wir pumpen Arbeit mit Ereignissen in Angular

Es war einmal ein Artikel über die Arbeit mit EventManager in Angular . Darin habe ich darüber gesprochen, wie Sie die übliche Syntax für Ereignisabonnements speichern und gleichzeitig unnötige Auslöser für die Überprüfung von Änderungen bei häufigen und sensiblen Ereignissen vermeiden können.


Die von mir beschriebene Methode ist jedoch umständlich und schwer zu verstehen. Es ist Zeit, die Filterung für Dekorateure neu zu schreiben.



Kurze Wiederholung


Für diejenigen, die den vorherigen Artikel nicht gelesen haben und nicht lesen möchten, eine Zusammenfassung des Problems:


  1. Angular ermöglicht das deklarative Abonnieren von Ereignissen in der Vorlage ( (eventName)) und über Dekoratoren ( @HostListener(‘eventName’)).
  2. Bei einer Änderungsvalidierungsstrategie führt OnPushAngular eine Validierung durch, wenn ein Ereignis auftritt, das wir auf diese Weise abonniert haben.
  3. Ereignisse wie scroll, mousemove, dragausgelöst sehr oft. In der Praxis müssen Sie nur auf einige von ihnen antworten (z. B. wenn der Benutzer den Container bis zum Ende gescrollt hat - laden Sie neue Elemente).
  4. Angular behandelt die Ereignisverarbeitung EventManagermit den bereitgestellten EventManagerPlugins.
  5. Angular , .

. . .



Angular (keydown.ctrl.enter ).


, EventManager. . Angular , EVENT_MANAGER_PLUGINS . , , . addEventListener , , , , , . .


preventDefault stopPropagation. , , , :


@Injectable()
export class StopEventPlugin {
  supports(event: string): boolean {
    return event.split('.').includes('stop');
  }

  addEventListener(
    element: HTMLElement, 
    event: string, 
    handler: Function
  ): Function {
    const wrapped = (event: Event) => {
      event.stopPropagation();
      handler(event);
    };

    return this.manager.addEventListener(
      element,
      event
        .split('.')
        .filter(v => v !== 'stop')
        .join('.'),
      wrapped,
    );
  }
}

. , :


  1. Angular, .
  2. .
  3. .

, NgZone :


@Injectable()
export class SilentEventPlugin {
  supports(event: string): boolean {
    return event.split('.').includes('silent');
  }

  addEventListener(
    element: HTMLElement,
    event: string,
    handler: Function
  ): Function {
    return this.manager.getZone().runOutsideAngular(() =>
      this.manager.addEventListener(
        element,
        event
          .split('.')
          .filter(v => v !== 'silent')
          .join('.'),
        handler,
      ),
    );
  }
}

, .



, -. /, this.


, , . — - , , . $event @HostListener. :


export function shouldCall<T>(
  predicate: Predicate<T>
): MethodDecorator {
  return (_target, _key, desc: PropertyDescriptor) => {
    const {value} = desc;

    desc.value = function(this: T, ...args: any[]) {
      if (predicate.apply(this, args)) {
        value.apply(this, args);
      }
    };
  };
}

. — - Angular, .


Angular 10 Ivy , markDirty(this). , - NgZone. . , . , NgZone , :


@Injectable()
export class ZoneEventPlugin {
  supports(event: string): boolean {
    return event.split('.').includes('init');
  }

  addEventListener(
    _element: HTMLElement,
    _event: string, 
    handler: Function
  ): Function {
    const zone = this.manager.getZone();
    const subscription = zone.onStable.subscribe(() => {
      subscription.unsubscribe();
      handler(zone);
    });

    return () => {};
  }
}

.init , ( , ). @HostListener(‘prop.init’, [‘$event’]) :


export function shouldCall<T>(
  predicate: Predicate<T>
): MethodDecorator {
  return (_, key, desc: PropertyDescriptor) => {
    const {value} = desc;

    desc.value = function() {
      const zone = arguments[0] as NgZone;

      Object.defineProperty(this, key, {
        value(this: T, ...args: any[]) {
          if (predicate.apply(this, args)) {
            zone.run(() => {
              value.apply(this, args);
            });
          }
        },
      });
    };
  };
}

, . , . Ivy.




, , :
https://stackblitz.com/edit/angular-event-filter-decorator


, , , . , , . async Observable :


<p *ngFor="let item of service.items$ | async">{{item}}</p>

, :


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

@Component({
  selector: 'awesome-component',
  templateUrl: './awesome-component.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AwesomeComponent {
  constructor(@Inject(Service) readonly service: Service) {}

  @HostListener('scroll.silent', ['$event.currentTarget'])
  @HostListener('init.onScroll', ['$event'])
  @shouldCall(scrolledToBottom)
  onScroll() {
    this.service.loadMore();
  }
}

. :
https://stackblitz.com/edit/angular-event-filters-scroll


, . CustomEvent, . .


(1 gzip) open-source- @tinkoff/ng-event-filters. Angular 10 2.0.0, markDirty(this), Angular 4.



npm-


-, open source, ? Angular Open-source Library Starter, . CI, , , CHANGELOG, .

All Articles