简体   繁体   中英

How to communicate from the child component to parent attribute directive in Angular 7?

My goal is to let a custom directive listen for a DOM event inside its scope; and handle that event. In this case, that DOMEvent will happen in a third-party component I do not control.

In Angular, I have the following template defined in a component.

This template has a directive (called my-directive ) that is hooked into the keypress event for the ckeditor component.

I don't own the code for ckeditor; and am attempting to get away with not having to change any code so that this can be a reusable hook.

somecomponent.ts

<!-- lots of irrelevant template code snipped -->
<my-directive>
  <ckeditor [editor]="Editor" (keypress)="pressed($event)"></ckeditor>
</my-directive>
<!-- lots of irrelevant template code snipped -->

my.directive.ts has the following defined:

import { Directive, OnInit } from "@angular/core";

@Directive({
    selector: 'my-directive',
  })
  export class MyDirective implements OnInit {
    public pressed(e: Event) : void {
        console.table(e);
    }

    constructor(
    ) { }
    ngOnInit() : void {
    }
}

And the CKEditor comes from the angular-ckeditor package; with the following imports where it's used:

import { CKEditorModule } from '@ckeditor/ckeditor5-angular'; import * as ClassicEditor from '@ckeditor/ckeditor5-build-classic';

When I attempt to call the pressed function from the ckeditor tag; it complains that pressed is not a function:

exception-handler.ts:41 TypeError: _co.pressed is not a function
    at Object.eval [as handleEvent] (SomeComponent.html:60)
    at handleEvent (core.js:21673)
    at callWithDebugContext (core.js:22767)
    at Object.debugHandleEvent [as handleEvent] (core.js:22470)
    at dispatchEvent (core.js:19122)
    at core.js:19569
    at HTMLUnknownElement.<anonymous> (platform-browser.js:993)
    at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:423)
    at Object.onInvokeTask (core.js:16147)
    at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:422) "/myurl/1"

Perhaps the issue is that Angular is attempting to call pressed() on ckeditor ?

So I tried to use a template reference variable:

<!-- lots of irrelevant template code snipped -->
<my-directive #md>
  <ckeditor [editor]="Editor" (keypress)="md.pressed($event)"></ckeditor>
</my-directive>
<!-- lots of irrelevant template code snipped -->

And so I got this error:

TypeError: jit_nodeValue_4(...).pressed is not a function
    at Object.eval [as handleEvent] (SomeComponent.html:60)
    at handleEvent (core.js:21673)
    at callWithDebugContext (core.js:22767)
    at Object.debugHandleEvent [as handleEvent] (core.js:22470)
    at dispatchEvent (core.js:19122)
    at core.js:19569
    at HTMLUnknownElement.<anonymous> (platform-browser.js:993)
    at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:423)
    at Object.onInvokeTask (core.js:16147)
    at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:422) "/myurl/1"

Bottom line, my question is this:

How do I allow a directive to respond to standard DOM events raised in a child component?

I do not want to modify child components; and in some cases I can't (it's not my code). I am hoping this directive can be added to templates for other components and those components don't have to be modified internally to allow for the new behavior this directive implements.

After more investigation; it appears that listening to a standard DOM event by attaching to the child component's template declaration is overkill, and I can achieve what I want by doing the following:

  1. Add a @HostListener to MyDirective :
import { Directive, OnInit } from "@angular/core";

@Directive({
    selector: 'my-directive',
  })
  export class MyDirective implements OnInit {
    public pressed(e: Event) : void {

    }
    @HostListener('keypress') onKeyPress(e: Event) {
        console.table(e);
    }
    constructor(
    ) { }
    ngOnInit() : void {
    }
}
  1. Modify the template to remove the declaration and simply keep the my-directive declaration wrapped around the child:
<!-- lots of irrelevant template code snipped -->
<my-directive>
  <ckeditor [editor]="Editor"></ckeditor>
</my-directive>
<!-- lots of irrelevant template code snipped -->

Now if any keypress event happens within the my-directive tag (whether it or its children), it'll raise an event that @HostListener will respond to.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM