Reputation: 708
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.
}
}
Upvotes: 17
Views: 28453
Reputation: 572
Not the most convenient, but reliable method. Directive:
@Input() component: any;
Component:
[component]="this"
Upvotes: 0
Reputation: 3902
I was able to get access to a directive's host component by asking the injector for it.
@Directive({
selector: '[with-foo]'
})
export class WithFooDirective implements OnInit {
constructor(private myComponent: MyComponent) { }
ngOnInit() {
console.debug(this.myComponent.foo()) // > bar
}
}
@Component({
selector: 'my-component',
template: '<div></div>'
})
export class MyComponent {
public foo() { return 'bar' }
}
...
<my-component with-foo></my-component>
Upvotes: 4
Reputation: 13576
This solution was linked to in the comments of one of the other answers but it was hidden at the end of quite a long discussion so I will add it here.
Import ViewContainerRef and inject it into your directive.
import { ViewContainerRef } from '@angular/core';
...
constructor(private viewContainerRef: ViewContainerRef) {}
You can then access the following private/unsupported property path to retrieve the component instance which is associated with the element that has been decorated with the directive.
this.viewContainerRef._data.componentView.component
Upvotes: 0
Reputation: 15270
It seems that most convenient and clean way is to use provider alias:
//ParentComponent declaration
providers: [{ provide: Parent, useExisting: forwardRef(() => ParentComponent) }]
where Parent
is separate class that works as OpaqueToken
and abstract class at the same type.
//directive
constructor(@Optional() @Host() parent:Parent) {}
Each component that is accessed from child directive should provide itself.
This is described in documentation: link
Upvotes: 2
Reputation: 708
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!
}
}
Upvotes: 10
Reputation: 31787
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 :
<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>
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() {
}
}
@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.
Upvotes: 5