48

I have an element in HTML template of an Angular 2 app. I add a directive to it:

<div myCustomDirective>HELLO</div>

I want that whenever I hover over the div the text inside the div should be changed, but it needs to be done from Directive (mouseover) event.

How to emit an event from a Directive and capture it inside a parent element?

Alexander Abakumov
  • 13,617
  • 16
  • 88
  • 129
raju
  • 6,448
  • 24
  • 80
  • 163
  • Why don't you listen in the component itself for mouseover? What is the parent element? Is it the element that contains above HTML in the template? – Günter Zöchbauer Jun 22 '16 at 12:25
  • 1
    Yes, that
    is the parent container. Also I just just want to learn if there is any way to propogate data from Directive -> Parent.
    – raju Jun 22 '16 at 14:47

4 Answers4

101

If myCustomDirective has an output @Output() someEvent:EventEmitter = new EventEmitter(); then you can use

<div myCustomDirective (someEvent)="callSomethingOnParent($event)">HELLO</div>
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • how can i use thus in a tag? – Ajmal Sha Jul 13 '17 at 08:47
  • 3
    You can't. To communicate with components added by the router, use a shared service (ideally with observable to push new events). – Günter Zöchbauer Jul 13 '17 at 08:54
  • How to subscribe to the base directive EventEmitter emit in the inherited directive? I try various approaches, but no luck so far.. – Alexander Sep 21 '22 at 16:56
  • If you know the type, you should be able to inject it into the directive's class and access it there to subscribe. If you don't know the type then you are probably out of luck. There is a very old issue about that, but I think it was closed not too long ago without a solution. – Günter Zöchbauer Nov 01 '22 at 15:06
35

I'd like to add to @GünterZöchbauer's answer that if you're trying to emit an event from a structural directive and using an asterisk (*) syntax when applying the directive, it won't work. Angular 5.2.6 still doesn't support @Output binding for structural directives if used with the * syntax (see GitHub issue).

You have to transform it to de-sugarized form (see here), i.e.:

<ng-template [customDirective]="foo" (customDirectiveEvent)="handler($event)">
  <div class="name">{{hero.name}}</div>
</ng-template>

instead of:

<div *customDirective="foo" (customDirectiveEvent)="handler($event)" class="name">{{hero.name}}</div>
Alexander Abakumov
  • 13,617
  • 16
  • 88
  • 129
6

You can also use the use the same name for the directive and the @Output:

@Directive({
  selector: '[myCustomMouseover]'
})
export class MyCustomMouseoverDirective {
  @Output()
  public myCustomMouseover = new EventEmitter<void>();

  @HostListener('mouseover', ['$event'])
  public onMouseover(event: MouseEvent): void {
    if (/* only trigger in certain conditions */) {
       this.myCustomMouseover.emit();
    }
  }
}

And you can use in any element just like:

<div (myCustomMouseover)="handler()">
  ...
</div>
José Antonio Postigo
  • 2,674
  • 24
  • 17
  • This is a bad idea. since it's confusing and masks the fact that a template directive is actually applied to an element when reading a template. Instead, it looks like a simple Angular event. – Alexander Abakumov Mar 23 '22 at 22:23
  • 2
    It looks like an elegant solution for me though. So I suppose it depends on personal preferences. Of course, if you name it like dblClick - it might be confusing. But if you name it really like in example above "myCustomMouseover" - it definetely wouldn't be confused with the built-in events. And html got easier to read (no question why there are two attributes instead of one custom). Eventually, whenever one get confused, in WebStorm and VS Code (and also most of other IDEs) it's easy to Cmd+click on the attribute name to go to its definition and see what's behind it. – Maxím G. Aug 17 '22 at 20:15
0

This is my solution with Angular 13. I plan to create a pagination component so ignore the name.

Directive:

import {Directive, EventEmitter, Input, OnInit, Output, TemplateRef, ViewContainerRef} from '@angular/core';

@Directive({
  selector: '[appPaginate]'
})
export class PaginateDirective implements OnInit {
  @Output() newItemEvent: EventEmitter<string> = new EventEmitter<string>()
  constructor(  private templateRef: TemplateRef<any>,
                private viewContainer: ViewContainerRef) { }

  ngOnInit() {

  }

  @Input() set appPaginate(condition: any) {
    if (condition) {
      this.viewContainer.createEmbeddedView(this.templateRef);
      this.newItemEvent.emit('Directive working')
    }
  }
}

Component.html:

<ng-template [appPaginate]="condition" (newItemEvent)="update($event)">
  <p>{{valueFromDirective}}</p>
</ng-template>

Component.ts

import {Component, Input, OnInit} from '@angular/core';
import {Item} from "./item";

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit{
  title = 'tutorial';
  condition = true;
 valueFromDirective = this.title;
  ngOnInit() {
  }

  update($event: any) {
    this.valueFromDirective = $event;

  }
}

enter image description here

Explain

Building on what @Alexander and @Zochbauer's discussion. With <ng-template>, you can define template content that is only being rendered by Angular when you, whether directly or indirectly, specifically instruct it to do so, allowing you to have full control over how and when the content is displayed. Thus when your condition is met you will be required to use this line to display the emitted value onto the html:

this.viewContainer.createEmbeddedView(this.templateRef);

N.B. This is only to help those who think event emitter doesn't work on Angular 7+.

L. Theodore Obonye
  • 1,221
  • 1
  • 6
  • 9