如何访问 Angular2 指令上的组件?

How to access the Component on a Angular2 Directive?

我正在用 Angular 2 做一些测试,我有一个指令(布局项)可以应用于我的所有组件。

在该指令中,我希望能够读取组件上定义的一些元数据,但为此我需要访问组件的引用。

我已经尝试了以下方法,但我无法得到我需要的东西。有人有什么建议吗?

@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. 
    }
}

忘掉服务吧,有一种更简单的方式可以做到这一点

选项 1 (不是你需要的,但可能对其他用户有用)

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>

三个不同的组件,所有组件共享相同 layout-item 属性。

指令

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

  }

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

很简单,这里就不多解释了

组件

@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() {
  }
}

我很确定你明白这一点,但为了一个好的答案,我将解释它的作用

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

此行将 layout-item 属性 分配给内部 myConfig 属性.

组件模板

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

我们正在为指令创建一个 my-parent-config 属性 并将父级的配置分配给它。

就这么简单!所以现在我们可以使用(几乎)相同的代码添加更多组件

第二个分量

@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() {
  }
}  

看到了吗?比我 using services 的想法容易得多(糟糕但 'working' 的想法)。

通过这种方式,它更加简单和干净。这是 plnkr,您可以对其进行测试。

(这不是你需要的 :'( )

更新

选项 2

据我了解,您更新后的问题是您需要对组件的引用,因此我得出的结论与我原来的答案非常相似

我做了什么:

  • 首先,我让组件持有对自身的引用
<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 指令(它被注入到每个组件中,而不是在顶层)
@Component({
  selector : 'my-cmp-a',
  properties : ['ref: ref']
})
@View({
  template : '<div [parent-reference]="ref" layout-item=""></div>',
  directives : [LayoutItem]
})
@YourCustomAnnotation({})
export class MyCmpA {
  constructor() {

  }
}
  • 最后,在指令中,您可以访问组件的构造函数(根据您更新的问题,我想这就是获取其元数据所需的全部内容) (必须在onInit中使用,"reference"不会存在于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);
  }
}

使用此 plnkr 进行测试。

更新:

自 Beta 16 以来,没有正式的方法来获得相同的行为。这里有一个非官方的解决方法:https://github.com/angular/angular/issues/8277#issuecomment-216206046


感谢@Eric Martinez,您的指点对让我朝着正确的方向前进至关重要!

因此,采用 Eric 的方法,我成功地做到了以下几点:

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>

三个不同的组件,都共享相同的 layout-item 属性。

指令

@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!
  }

}

看来最方便干净的方法是使用提供商别名:

//ParentComponent declaration
providers: [{ provide: Parent, useExisting: forwardRef(() => ParentComponent) }]

其中 Parent 是独立的 class,它作为 OpaqueToken 和抽象 class 在同一类型下工作。

//directive
constructor(@Optional() @Host() parent:Parent) {}

从子指令访问的每个组件都应提供自身。

这在文档中有描述:link

此解决方案已链接到其他答案之一的评论中,但它隐藏在相当多的 long discussion 末尾,因此我将其添加到此处。

导入 ViewContainerRef 并将其注入您的指令。

import { ViewContainerRef } from '@angular/core';
...
constructor(private viewContainerRef: ViewContainerRef) {}

然后您可以访问以下 private/unsupported 属性 路径来检索与已使用指令修饰的元素关联的组件实例。

this.viewContainerRef._data.componentView.component

我能够通过向注入器请求访问指令的主机组件。

@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>

不是最方便但可靠的方法。 指令:

@Input() component: any;

组件:

[component]="this"