How to listen to children elements events in a directive?
Asked Answered
G

3

14

Since there is no template, what's the best way to listen to child elements event in a directive? Is it possible with HostListener? If not, is there any other way?

There's also this similar question: How to listen for child event from parent directive in Angular2, but the suggested approach does not solve my problem, as my directive and child element aren't in the same template (directive is on host).

Cheers!

Edit #1

This is how I'm currently doing it (there has to be a better way):

First injecting ElementRef into my directive:

constructor(private elView: ElementRef) {}

Then binding with jQuery (or plain JS):

$(this.elView.nativeElement)
.on('drag', '#childId', this.slide.bind(this))
Griqua answered 17/11, 2016 at 15:11 Comment(1)
I am not sure, but this may be helpfulBrawley
T
7

If the event you want to listen to is a native DOM event that bubbles, then you can just use @HostListener()

@HostListener('click', ['$event'])
handleClick(event) {
  // handle event
}

If they are outputs of child components, you can query them and subscribe to their outputs

@ContentChildren(ChildComponent) children:QueryList<ChildComponent>;

ngAfterContentInit() {
  this.children.toArray().forEach((item) => {
    item.someOutput.subscribe(...);
  });
}

or you can use the approach used in the answer you linked to in your question.

Truda answered 21/11, 2016 at 15:13 Comment(7)
Thanks for your answer. HostListener is not bulletproof enough, as many events will not propagate due to stopPropagation. I was hoping for something that would allow me to bind directly to a child element.Digitoxin
That's what @ContentChildren() does or as also suggested in the other answer, a shared service.Durr
Those two techniques would not work, as I'm not necessarily listen to events that have been initiated by components. I'm also (mostly) looking to listen to DOM events that were initiated by "normal" elements.Digitoxin
You can use @ContentChildren(ChildComponent, {read: ElementRef}) children:QueryList<ChildComponent>; and add event listeners like explained in https://mcmap.net/q/828832/-angular2-catch-subscribe-to-click-event-in-dynamically-added-htmlDurr
Ah that's cool! But correct me if I'm wrong, ContentChildren only returns direct children. The events I want to listen to don't necessarily come from direct children. And I can't use querySelector for web workers.Digitoxin
If you want to utilize web workers, direct DOM access (querySelector) will cause issues. ContentChildren by default queries for children and descendants. You can pass {descendants: false} to only get direct children.Durr
That's the thing though, I need to listen events on children that might be descendants and I also need it to be web worker safe.Digitoxin
I
1

You might consider to make your components communicate with each other via the service-class.

// service class
class Service {
  private someEventSource = new Subject();
  public someEvent = this.someEventSource.asObservable();

  public invokeAnEvent (data: string) {
    this.someEventSource.next(data);
  }
}

// parentComponent class
Service.someEvent.subscribe(data => {
  console.log(data);
});

// childComponent class
Service.invokeAnEvent('some data to pass');

From Angular documentation:
https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service

Irving answered 21/11, 2016 at 15:30 Comment(1)
I think this would be overkill to listen to DOM events.Digitoxin
C
0

my solution to this problem is to add in the root component ngAfterContentChecked and there to select all my relevant elements and add the event. example:

  ngAfterContentChecked() {
    //console.log('MASTER ngAfterContentChecked')
    let rgx = /^[0-9,\.]+$/;
    let preventNonNumber = e => {
      //let v = (e.target as HTMLInputElement).value
      if (
        // Allow: Delete, Backspace, Tab, Escape, Enter
        [46, 8, 9, 27, 13].indexOf(e.keyCode) !== -1 || 
        (e.keyCode === 65 && e.ctrlKey === true) || // Allow: Ctrl+A
        (e.keyCode === 67 && e.ctrlKey === true) || // Allow: Ctrl+C
        (e.keyCode === 86 && e.ctrlKey === true) || // Allow: Ctrl+V
        (e.keyCode === 88 && e.ctrlKey === true) || // Allow: Ctrl+X
        (e.keyCode === 65 && e.metaKey === true) || // Cmd+A (Mac)
        (e.keyCode === 67 && e.metaKey === true) || // Cmd+C (Mac)
        (e.keyCode === 86 && e.metaKey === true) || // Cmd+V (Mac)
        (e.keyCode === 88 && e.metaKey === true) || // Cmd+X (Mac)
        (e.keyCode >= 35 && e.keyCode <= 39) // Home, End, Left, Right
      ) {
        return;  // let it happen, don't do anything
      }

      if (rgx.test(e['key']) == false) {
        e.preventDefault();
      }
    };
    let inputs = document.querySelectorAll('label-input[numberOnly] input, cbx-label-input[numberOnly] input')
    //console.log(inputs)
    inputs.forEach(i => (i as HTMLInputElement).onkeydown = preventNonNumber)
  }

NOTICE that the ngAfterContentChecked gets fired everytime you touch an input, so the selector runs everytime again, and also setting of the function, but since its a static function that your just override there isn't so much of a performance problem.

Courtly answered 2/4, 2019 at 15:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.