Как получить доступ к компоненту в директиве Angular2

Я делаю несколько тестов с 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

UPDATE

С Beta 16 нет официального способа добиться такого же поведения. Существует неофициальное обходное решение здесь: https://github.com/angular/angular/issues/8277#issuecomment-216206046


Спасибо @Eric Martinez, ваши указатели были важны для того, чтобы меня в правильном направлении!

Итак, принимая подход Эрика, мне удалось сделать следующее:

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

}

Ответ 2

Забудьте о Службе, там более простая форма этого

Вариант 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() {
  }
}  

См? Было намного проще, чем моя идея использовать сервисы (ужасная, но "работающая" идея).

Таким образом, это намного проще и чище. Здесь plnkr, чтобы вы могли проверить его.

(Это не то, что вам нужно: '()

UPDATE

Вариант 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, "ссылка" не будет существовать в конструкторе)
@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, чтобы выполнить ваши тесты.

Ответ 3

Мне удалось получить доступ к основному компоненту директивы, запросив инжектор для него.

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

Ответ 4

Кажется, что наиболее удобным и чистым способом является использование псевдонима поставщика:

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

где Parent - отдельный класс, который работает как OpaqueToken и абстрактный класс того же типа.

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

Каждый компонент, к которому обращается дочерняя директива, должен обеспечивать себя.

Это описано в документации: ссылка

Ответ 5

Это решение было связано с комментариями одного из других ответов, но оно было скрыто в конце довольно длинного обсуждения Я добавлю его здесь.

Импортируйте ViewContainerRef и введите его в свою директиву.

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

Затем вы можете получить доступ к следующему частному/неподдерживаемому пути свойств для извлечения экземпляра компонента, который связан с элементом, который был украшен директивой.

this.viewContainerRef._data.componentView.component