raju
raju

Reputation: 6946

Emit event from Directive to parent element

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?

Upvotes: 53

Views: 69464

Answers (4)

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>

Upvotes: 10

L. Theodore Obonye
L. Theodore Obonye

Reputation: 1340

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+.

Upvotes: 0

Alexander Abakumov
Alexander Abakumov

Reputation: 14579

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>

Upvotes: 35

G&#252;nter Z&#246;chbauer
G&#252;nter Z&#246;chbauer

Reputation: 658067

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

<div myCustomDirective (someEvent)="callSomethingOnParent($event)">HELLO</div>

Upvotes: 105

Related Questions