Trabalhamos com eventos em Angular

Era uma vez, escrevi um artigo sobre como trabalhar com o EventManager no Angular . Nele, falei sobre como você pode salvar a sintaxe usual para assinaturas de eventos, evitando disparos desnecessários de verificação de alterações em eventos frequentes e sensíveis.


No entanto, o método que descrevi é complicado e difícil de entender. É hora de reescrever a filtragem nos decoradores.



Breve replay


Para quem não leu e não quer ler o artigo anterior, um resumo do problema:


  1. Angular permite inscrever declarativamente eventos no template ( (eventName)) e através de decoradores ( @HostListener(‘eventName’)).
  2. Com uma estratégia de validação de alterações, o OnPushAngular executará uma validação se ocorrer um evento no qual assinamos dessa maneira.
  3. Eventos como scroll, mousemove, dragdesencadeou muitas vezes. Na prática, você só precisa responder a alguns deles (por exemplo, quando o usuário rolou o contêiner até o final - carregar novos elementos).
  4. Angular manipula o processamento de eventos EventManagerusando os EventManagerPlugins fornecidos .
  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