1

I want to manually subscribe to an event emitted by a directive, which, by design, should be available to multiple components of my application. At the moment the structure looks like this:

AppComponent
  Draggable.Directive (uses an attribute of a DOM element to control the behaviour)

  (and then, via routing)
  Parent1 Component
     Child1 Component
     Child2 Component

app.module looks like this:

@NgModule({
  imports:      [ BrowserModule, HttpModule, JsonpModule, RouterModule.forRoot(appRoutes) ],
  declarations: [ AppComponent, FooComponent, BarComponent, ParentComponent, DraggableDirective ],
  bootstrap:    [ AppComponent ]
})

Later in the development, another Parent component will be listening to draggable directive and implement its own logic.

None of the children component knows (or should care) about Draggable Directive doing anything to it. The parent component should. So, in parent component:

import { Component, OnInit, ViewChild } from '@angular/core';
import { DraggableDirective } from './draggable.directive';
import { FooComponent } from './foo.component';
import { BarComponent } from './bar.component';

@Component({
  selector: 'parent-view',
  templateUrl: './parent.component.html',
  providers: [DraggableDirective],
  moduleId: module.id
})

export class ParentComponent implements OnInit {
  @ViewChild('foo') fooC:FooComponent;
  @ViewChild('bar') barC:BarComponent;

  constructor(private draggable:DraggableDirective){
    draggable.droppedOn.subscribe(event => {
      console.log('listening', event);
    })
  }

  ngOnInit(): void {
   // updated
   // child view components
   this.fooC.fooInit();
  }

And here is the directive, using Subject and not EventEmitter, as recommended elsewhere:

import { Directive, ElementRef, Renderer, HostListener, AfterViewInit } from '@angular/core';
import {Subject} from 'rxjs/Rx';

@Directive({
    selector: '[draggable], [data-draggable]'
})

export class DraggableDirective implements AfterViewInit {

    public droppedOn = new Subject();

    //... at some point this method is envoked
    couldDrop():void {

        if ( this.dElem ) {
            let _attr = this.dElem.dataset.indexed;
            console.log('emitting', _attr);
            this.droppedOn.next(_attr);

        }

    }
}

I get console logging "emitting" with the correct values. I never get "listening" from the parent component in the console. What am I doing wrong here?

martin
  • 93,354
  • 25
  • 191
  • 226
pop
  • 3,464
  • 3
  • 26
  • 43

1 Answers1

2

The directive you create is not a service so it doesn't go to @Component's providers array but into declarations instead. See for example https://angular.io/docs/ts/latest/guide/attribute-directives.html (you can add it to NgModule as well)

Also ParentComponent can't get an instance of a directive used somewhere in your template to its constructor. That's what ViewChildren is for. See for example: https://angular.io/docs/ts/latest/api/core/index/QueryList-class.html

So in ParentComponent you subscribed to a different instance of DraggableDirective than your template is using.

martin
  • 93,354
  • 25
  • 191
  • 226
  • Ok, so far so good. Should I then subscribe to the Subject() at a child component and forward it to the parent component? – pop Dec 06 '16 at 15:00
  • @pop No, you use `@ViewChildren` annotation and `QueryList` which is Observable itself in the parent component to get updated list of all `DraggableDirective` directives. There you can access `droppedOn` property of each of them an subscribe to it. – martin Dec 06 '16 at 15:03
  • See http://stackoverflow.com/questions/32693061/angular-2-typescript-get-hold-of-an-element-in-the-template/35209681#35209681 – martin Dec 06 '16 at 15:04
  • the problem I have, I guess, is that I use ViewChild variable to initialize the child component and cannot reuse it for observing changes with QueryList... What next? See upated parent component NgOnInit – pop Dec 06 '16 at 15:35