Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to access the Component on a Angular2 Directive?

I'm doing some tests with Angular 2 and I have a directive (layout-item) that can be applied to all my components.

Inside that directive I want to be able to read some metadata defined on the component but for that I need to access the component's reference.

I have tried the following approach but I was unable to get what I need. Does any one has a suggestion?

@Component({...})
@View({...})
@MyAnnotation({...})
export class MyComponentA {...}


// Somewhere in a template
<myComponentA layout-item="my config 1"></myComponentA>
<myComponentB layout-item="my config 2"></myComponentA>

// ----------------------

@ng.Directive({
    selector: "[layout-item]",
    properties: [
        "strOptions: layout-item"
    ],
    host: {

    }
})

export class LayoutItem {

    // What works
    constructor(@Optional() @Ancestor({self: true}) private component: MyComponent1) {

 // with the constructor defined like this, component is defined with myComponent1 instance.
Reflector.getMetadata("MyAnnotation", component.constructor); // > metadata is here!
    }

// What I needed
    constructor(@Optional() @Ancestor({self: true}) private component: any) {

 // This will crash the app. If instead of any I specify some other type, the app will not crash but component will be null.
 // This directive can be applied to any component, so specifying a type is not a solution. 
    }
}
like image 838
jpsfs Avatar asked Jul 31 '15 14:07

jpsfs


People also ask

What are the different types of directives in Angular 2?

By using this we can easily manipulate our Dom layout. In Angular 2, there are three types of directives those are component directive, attribute directive and structural directive What is Component directive?

How to create @angular custom attribute directive?

Angular custom attribute is created to change appearance and behavior of HTML element. Find the steps to create custom attribute directive. 1. Create a class decorated with @Directive () . 2. Assign the attribute directive name using selector metadata of @Directive () decorator enclosed with bracket [] .

How do I create a component in Angular 2?

Ideally, you will use @angular/cli to generate your component: Otherwise, you may need to create child.component.css and child.component.html files and manually add it to app.module.ts: import { ChildComponent } from './child.component'; ...

What is the difference between hidden and structural directives in angular?

This is because hidden retains the DOM element but hides it from the user, whereas structural directives like *ngIf destroy the elements. *ngFor and [ngSwitch] are also common structural directives and you can relate them to the common programming flow tasks. We had a quick look on directives and types of directives in Angular 2.


2 Answers

UPDATE:

Since Beta 16 there is no official way to get the same behavior. There is an unofficial workaround here: https://github.com/angular/angular/issues/8277#issuecomment-216206046


Thanks @Eric Martinez, your pointers were crucial in getting me in the right direction!

So, taking Eric's approach, I have managed to do the following:

HTML

<my-component layout-item="my first component config"></my-component>

<my-second-component layout-item="my second component config"></my-second-component>

<my-third-component layout-item="my third component config"></my-third-component>

Three different components, all of the share the same layout-item attribute.

Directive

@Directive({
  selector : '[layout-item]'
})
export class MyDirective {
  constructor(private _element: ElementRef, private _viewManager: AppViewManager) {
    let hostComponent = this._viewManager.getComponent(this._element);
    // on hostComponent we have our component! (my-component, my-second-component, my-third-component, ... and so on!
  }

}
like image 124
jpsfs Avatar answered Sep 20 '22 18:09

jpsfs


Forget about the Service, there's a simpler form of doing this

Option 1 (Not what you need, but it may be useful for other users)

HTML

<my-component layout-item="my first component config"></my-component>

<my-second-component layout-item="my second component config"></my-second-component>

<my-third-component layout-item="my third component config"></my-third-component>

Three different components, all of the share the same layout-item property.

Directive

@Directive({
  selector : '[layout-item]',
  properties: ['myParentConfig: my-parent-config'] // See the components for this property
})
export class MyDirective {
  constructor() {

  }

  onInit() {
    console.log(this.myParentConfig);
  }
}

Pretty straightforward, not much to explain here

Component

@Component({
  selector : 'my-component',
  properties : ['myConfig: layout-item']
})
@View({
  template : `<div [my-parent-config]="myConfig" layout-item="my config"></div>`,
  directives : [MyDirective]
})
export class MyComponent {
  constructor() {
  }
}

I'm pretty sure that you understand this, but for the sake of a good answer I will explain what it does

properties : ['myConfig: layout-item']`

This line assigns the layout-item property to the internal myConfig property.

Component's template

template : `<div [my-parent-config]="myConfig" layout-item="my config"></div>`,

We are creating a my-parent-config property for the directive and we assign the parent's config to it.

As simple as that! So now we can add more components with (pretty much) the same code

Second component

@Component({
  selector : 'my-second-component',
  properties : ['myConfig: layout-item']
})
@View({
  template : `<div [my-parent-config]="myConfig" layout-item="my config"></div>`,
  directives : [MyDirective]
})
export class MySecondComponent {
  constructor() {
  }
}  

See? Was much easier than my idea of using services (awful but 'working' idea).

With this way it is much simpler and cleaner. Here's the plnkr so you can test it.

(It wasn't what you need :'( )

UPDATE

Option 2

For what I understood of your updated question is that you need a reference to the component, so what I came up with is pretty similar to my original answer

What I did :

  • First I made the components to hold a reference to themselves
<my-cmp-a #pa [ref]="pa" layout-item="my first component config"></my-cmp-a>
<my-cmp-b #pb [ref]="pb" layout-item="my first component config"></my-cmp-b>
<my-cmp-c #pc [ref]="pc" layout-item="my first component config"></my-cmp-c>
  • Then I passed each reference to the LayoutItem directive (which was injected in each component, not at top-level)
@Component({
  selector : 'my-cmp-a',
  properties : ['ref: ref']
})
@View({
  template : '<div [parent-reference]="ref" layout-item=""></div>',
  directives : [LayoutItem]
})
@YourCustomAnnotation({})
export class MyCmpA {
  constructor() {

  }
}
  • Finally in the directive you can have access to the component's constructor (from your updated question I guess that's all you need to get its metadata) (You must use it inside onInit, "reference" won't exist in constructor)
@Directive({
  selector : '[layout-item]',
  properties : ['reference: parent-reference']
})
export class LayoutItem {
  constructor() {
  }

  onInit() {
    console.log(this.reference.constructor);
    Reflector.getMetadata("YourCustomAnnotation", this.reference.constructor);
  }
}

Use this plnkr to do your tests.

like image 39
Eric Martinez Avatar answered Sep 22 '22 18:09

Eric Martinez