Как получить доступ к компоненту в директиве 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