如何从指令访问主机组件?
How to access host component from directive?
假设我有以下标记:
<my-comp myDirective></my-comp>
有什么方法可以从指令访问组件实例?
更具体地说,我希望能够从 MyDirective
访问 MyComponent
的属性和方法,理想情况下 无需向上面的 HTML 添加任何内容.
注射即可
class MyDirective {
constructor(private host:MyComponent) {}
一个严重的限制是,你需要提前知道组件的类型。
另见 https://github.com/angular/angular/issues/8277
当您事先不知道类型时,它还提供了一些解决方法。
您的指令可以是可应用于您的任何组件的通用指令。所以,在那种情况下,在构造函数中注入组件是不可能的,所以这里有另一种方法可以做到这一点
在构造函数
中注入ViewContainerRef
constructor(private _viewContainerRef: ViewContainerRef) { }
然后使用
获取它
let hostComponent = this._viewContainerRef["_data"].componentView.component;
这是从 github 期中摘录的,效果很好。缺点是需要事先了解组件,但在您的情况下,您无论如何都需要知道您正在使用的方法。
import { Host, Self, Optional } from '@angular/core';
export class ExampleDirective {
constructor(
@Host() @Self() @Optional() public hostCheckboxComponent : MdlCheckboxComponent,
@Host() @Self() @Optional() public hostSliderComponent : MdlSliderComponent
) {
if(this.hostCheckboxComponent) {
console.log("host is a checkbox");
} else if(this.hostSliderComponent) {
console.log("host is a slider");
}
}
信用:
https://github.com/angular/angular/issues/8277#issuecomment-323678013
constructor(private vcRef: ViewContainerRef){
let parentComponent=(<any>this.vcRef)._view.context;
}
如果您想在自定义组件上使用属性指令,您可以让这些组件从抽象 class 和 'forwardRef' 抽象 class 类型扩展到您的组件类型。这样你就可以在摘要 class 上创建 angular 的 DI select(在你的指令中)。
摘要class:
export abstract class MyReference {
// can be empty if you only want to use it as a reference for DI
}
自定义组件:
@Component({
// ...
providers: [
{provide: MyReference, useExisting: forwardRef(() => MyCustomComponent)}
],
})
export class MyCustomComponent extends MyReference implements OnInit {
// ...
}
指令:
@Directive({
selector: '[appMyDirective]'
})
export class CustomDirective{
constructor(private host:MyReference) {
console.log(this.host);
// no accessing private properties of viewContainerRef to see here... :-)
}
}
这样您就可以在任何扩展您的抽象的组件上使用该指令 class。
这当然只适用于您自己的组件。
我喜欢这个,它适用于 Angular 9 和 11。
nativeElement 是一个DOM
对象,我可以动态设置当前组件的
nativeElement 上自定义 __component
字段的实例;
在Directve
中访问nativeElement.__component
;
导出class FromItemComponentBase {
构造函数(私有主机元素:ElementRef){
hostElement.nativeElement.__component=这个;
}
}
@Component({
selector: 'input-error',
templateUrl: 'component.html'
})
export class FromItemErrorComponent extends FromItemComponentBase {
constructor(private hostElement: ElementRef) {
super(hostElement);
}
}
@Component({
selector: 'input-password',
templateUrl: 'component.html'
})
export class FromItemPasswordComponent extends FromItemComponentBase {
constructor(private hostElement: ElementRef) {
super(hostElement);
}
}
@Directive({selector: 'input-error,input-password,input-text'})
export class FormInputDirective {
component:FromItemComponentBase;
constructor(private hostElement: ElementRef) {
this.component=hostElement.nativeElement.__component;
}
}
您可以使用 ViewContainerRef 访问宿主组件。
constructor(private el: ViewContainerRef) {}
ngOnInit() {
const _component = this.el && this.el.injector && this.el.injector.get(MyComponent);
}
使指令通用的一种可能的解决方法是将组件的模板引用作为@Input 传递给指令。这增加了一些额外的 html 但它比我尝试过的许多其他 hacks 效果更好。
@Directive({selector: '[myDirective]'})
export class MyDirective implements OnInit {
@Input() componentRef: any;
@Input() propName: string;
ngOnInit(){
if (this.componentRef != null) {
// Access component properties
this.componentRef[this.propName];
}
}
}
视图中的用法:
<!-- Pass component ref and the property name from component as inputs -->
<app-component #appComponentRef myDirective [componentRef]="appComponentRef" [propName]="'somePropInComponent'" .... >
也适用于其他指令。使用 @Directive
装饰器的 exportAs 属性 来获取对指令实例的引用。
<form #myForm="ngForm" myDirective [componentRef]="myForm" [propName]="'ngSubmit'" ....>
注意:这是 hacky,可能在 Angular 的未来版本中不起作用。
在 Angular 10 中,我能够像这样访问主机组件:
与@Sunil Garg 的解决方案类似,在指令的 ctor 中注入 ViewContainerRef
:
constructor(_viewContainerRef: ViewContainerRef)
像这样获取主机组件:
let hostComponent = (<any>_viewContainerRef)._lContainer[0][8];
在构造函数中有 viewContainerRef: ViewContainerRef 并有以下代码
(this.viewContainerRef as any)._hostLView[8] 完美运行。但它看起来很老套,因此最好将组件引用作为输入参数传递给指令,如下所述。
<my-comp myDirective [hostComponent]="hostComponent"></my-comp>
在myCompComponent.ts,
hostComponent = this;
在myDirective.ts,
@Input() hostComponent: Component;
我从这里尝试了两种解决方案:
Michiel Windey 的(使用 abstract class
作为将使用指令的组件的接口)和 Anthony 的(使用 @Host() @Self() @Optional()
)。
两者都适用于 Angular 11.
两者都不是 hacky,并且可能是面向未来的,这与使用未记录的私有字段的 hacky 解决方案不同……
第二种的优点是无需更改现有组件即可访问它们。但是您可能需要处理很多检查和特殊情况,具体取决于真正注入的组件。
第一种有要求修改组件的不便,但是你可以在接口(抽象class)中定义你将要在指令中使用的所有字段和方法,从而您可以使用单个参数访问它们,而无需检查注入的组件类型。
所以您的选择实际上取决于您的用例。
对于 Angular 12, 为我指出了正确的解决方案方向。我知道原则上这不是解决此问题的好方法,但由于跨多个模块的关注点分离,我的用例需要能够访问组件实例而不知道它在写入时是什么。
长话短说:
class MyDirective {
constructor(private vcRef: ViewContainerRef) {}
private getHostComponent(): any {
return this.vcRef._lContainer[0][8];
}
}
您可以访问 ViewContainerRef
的 _lContainer
属性、which represents the state associated with the container. This LContainer
has an entry at index 0 (internally the HOST
constant) which is an LView
if the container is on a component node。
LView
又在位置 8 处有一个条目(内部 the CONTEXT
constant) which is a reference to the component instance if the component this is attached to is a non-root component element(例如 <app-*
)。
这意味着您可以通过访问 lContainer[HOST][CONTEXT]
.
来访问主机上下文组件
复制粘贴的长答案及解释:
class MyDirective {
constructor(private vcRef: ViewContainerRef) {}
private getHostElementFromViewContainerRef(): unknown | null {
// TL;DR of the below method:
// return this.vcRef._lContainer[0][8];
// Inspired by
const vcRef = this.vcRef as any; // We're accessing private properties so we cast to any to avoid awkward TS validation issues
// We fetch the component associated with the element this directive is attached to by navigating via the ViewContainerRef.
// The VCRef contains a reference to the LContainer, which represents the state associated with the container:
// https://github.com/angular/angular/blob/12.2.x/packages/core/src/render3/interfaces/container.ts#L65
const lContainer = vcRef._lContainer;
if (!lContainer) {
return null;
}
// LView has all its elements defined as array elements, with keys hardcoded to numeric constants:
// https://github.com/angular/angular/blob/12.2.x/packages/core/src/render3/interfaces/view.ts#L26-L57
// We care about two of them:
const HOST = 0; // https://github.com/angular/angular/blob/12.2.x/packages/core/src/render3/interfaces/view.ts#L29
const CONTEXT = 8; // https://github.com/angular/angular/blob/12.2.x/packages/core/src/render3/interfaces/view.ts#L37
// LContainer is an array, with the element at the HOST position being an LView if the container is on a Component Node.
// This means that this may not work if this directive is declared on a native HTML element.
// Note that LContainer uses the same indexes as LView, so it's the same HOST constant as declared in the LView interfaces file.
// https://github.com/angular/angular/blob/12.2.x/packages/core/src/render3/interfaces/container.ts#L66-L72
const lView = lContainer[HOST];
if (!lView) {
return null;
}
// For a non-root component, the context is the component instance.
// So if this directive is correctly attached to an Angular Component (e.g. `<app-*`),
// this array entry will contain the instance of that component.
// https://github.com/angular/angular/blob/12.2.x/packages/core/src/render3/interfaces/view.ts#L173-L180
const contextElement = lView[CONTEXT];
return contextElement || null;
}
}
假设我有以下标记:
<my-comp myDirective></my-comp>
有什么方法可以从指令访问组件实例?
更具体地说,我希望能够从 MyDirective
访问 MyComponent
的属性和方法,理想情况下 无需向上面的 HTML 添加任何内容.
注射即可
class MyDirective {
constructor(private host:MyComponent) {}
一个严重的限制是,你需要提前知道组件的类型。
另见 https://github.com/angular/angular/issues/8277
当您事先不知道类型时,它还提供了一些解决方法。
您的指令可以是可应用于您的任何组件的通用指令。所以,在那种情况下,在构造函数中注入组件是不可能的,所以这里有另一种方法可以做到这一点
在构造函数
中注入ViewContainerRef
constructor(private _viewContainerRef: ViewContainerRef) { }
然后使用
获取它let hostComponent = this._viewContainerRef["_data"].componentView.component;
这是从 github 期中摘录的,效果很好。缺点是需要事先了解组件,但在您的情况下,您无论如何都需要知道您正在使用的方法。
import { Host, Self, Optional } from '@angular/core';
export class ExampleDirective {
constructor(
@Host() @Self() @Optional() public hostCheckboxComponent : MdlCheckboxComponent,
@Host() @Self() @Optional() public hostSliderComponent : MdlSliderComponent
) {
if(this.hostCheckboxComponent) {
console.log("host is a checkbox");
} else if(this.hostSliderComponent) {
console.log("host is a slider");
}
}
信用: https://github.com/angular/angular/issues/8277#issuecomment-323678013
constructor(private vcRef: ViewContainerRef){
let parentComponent=(<any>this.vcRef)._view.context;
}
如果您想在自定义组件上使用属性指令,您可以让这些组件从抽象 class 和 'forwardRef' 抽象 class 类型扩展到您的组件类型。这样你就可以在摘要 class 上创建 angular 的 DI select(在你的指令中)。
摘要class:
export abstract class MyReference {
// can be empty if you only want to use it as a reference for DI
}
自定义组件:
@Component({
// ...
providers: [
{provide: MyReference, useExisting: forwardRef(() => MyCustomComponent)}
],
})
export class MyCustomComponent extends MyReference implements OnInit {
// ...
}
指令:
@Directive({
selector: '[appMyDirective]'
})
export class CustomDirective{
constructor(private host:MyReference) {
console.log(this.host);
// no accessing private properties of viewContainerRef to see here... :-)
}
}
这样您就可以在任何扩展您的抽象的组件上使用该指令 class。
这当然只适用于您自己的组件。
我喜欢这个,它适用于 Angular 9 和 11。
nativeElement 是一个DOM
对象,我可以动态设置当前组件的
nativeElement 上自定义
__component
字段的实例;在
Directve
中访问nativeElement.__component
;导出class FromItemComponentBase { 构造函数(私有主机元素:ElementRef){ hostElement.nativeElement.__component=这个; } }
@Component({
selector: 'input-error',
templateUrl: 'component.html'
})
export class FromItemErrorComponent extends FromItemComponentBase {
constructor(private hostElement: ElementRef) {
super(hostElement);
}
}
@Component({
selector: 'input-password',
templateUrl: 'component.html'
})
export class FromItemPasswordComponent extends FromItemComponentBase {
constructor(private hostElement: ElementRef) {
super(hostElement);
}
}
@Directive({selector: 'input-error,input-password,input-text'})
export class FormInputDirective {
component:FromItemComponentBase;
constructor(private hostElement: ElementRef) {
this.component=hostElement.nativeElement.__component;
}
}
您可以使用 ViewContainerRef 访问宿主组件。
constructor(private el: ViewContainerRef) {}
ngOnInit() {
const _component = this.el && this.el.injector && this.el.injector.get(MyComponent);
}
使指令通用的一种可能的解决方法是将组件的模板引用作为@Input 传递给指令。这增加了一些额外的 html 但它比我尝试过的许多其他 hacks 效果更好。
@Directive({selector: '[myDirective]'})
export class MyDirective implements OnInit {
@Input() componentRef: any;
@Input() propName: string;
ngOnInit(){
if (this.componentRef != null) {
// Access component properties
this.componentRef[this.propName];
}
}
}
视图中的用法:
<!-- Pass component ref and the property name from component as inputs -->
<app-component #appComponentRef myDirective [componentRef]="appComponentRef" [propName]="'somePropInComponent'" .... >
也适用于其他指令。使用 @Directive
装饰器的 exportAs 属性 来获取对指令实例的引用。
<form #myForm="ngForm" myDirective [componentRef]="myForm" [propName]="'ngSubmit'" ....>
注意:这是 hacky,可能在 Angular 的未来版本中不起作用。 在 Angular 10 中,我能够像这样访问主机组件:
与@Sunil Garg 的解决方案类似,在指令的 ctor 中注入 ViewContainerRef
:
constructor(_viewContainerRef: ViewContainerRef)
像这样获取主机组件:
let hostComponent = (<any>_viewContainerRef)._lContainer[0][8];
在构造函数中有 viewContainerRef: ViewContainerRef 并有以下代码 (this.viewContainerRef as any)._hostLView[8] 完美运行。但它看起来很老套,因此最好将组件引用作为输入参数传递给指令,如下所述。
<my-comp myDirective [hostComponent]="hostComponent"></my-comp>
在myCompComponent.ts,
hostComponent = this;
在myDirective.ts,
@Input() hostComponent: Component;
我从这里尝试了两种解决方案:
Michiel Windey 的(使用 abstract class
作为将使用指令的组件的接口)和 Anthony 的(使用 @Host() @Self() @Optional()
)。
两者都适用于 Angular 11.
两者都不是 hacky,并且可能是面向未来的,这与使用未记录的私有字段的 hacky 解决方案不同……
第二种的优点是无需更改现有组件即可访问它们。但是您可能需要处理很多检查和特殊情况,具体取决于真正注入的组件。
第一种有要求修改组件的不便,但是你可以在接口(抽象class)中定义你将要在指令中使用的所有字段和方法,从而您可以使用单个参数访问它们,而无需检查注入的组件类型。
所以您的选择实际上取决于您的用例。
对于 Angular 12,
长话短说:
class MyDirective {
constructor(private vcRef: ViewContainerRef) {}
private getHostComponent(): any {
return this.vcRef._lContainer[0][8];
}
}
您可以访问 ViewContainerRef
的 _lContainer
属性、which represents the state associated with the container. This LContainer
has an entry at index 0 (internally the HOST
constant) which is an LView
if the container is on a component node。
LView
又在位置 8 处有一个条目(内部 the CONTEXT
constant) which is a reference to the component instance if the component this is attached to is a non-root component element(例如 <app-*
)。
这意味着您可以通过访问 lContainer[HOST][CONTEXT]
.
复制粘贴的长答案及解释:
class MyDirective {
constructor(private vcRef: ViewContainerRef) {}
private getHostElementFromViewContainerRef(): unknown | null {
// TL;DR of the below method:
// return this.vcRef._lContainer[0][8];
// Inspired by
const vcRef = this.vcRef as any; // We're accessing private properties so we cast to any to avoid awkward TS validation issues
// We fetch the component associated with the element this directive is attached to by navigating via the ViewContainerRef.
// The VCRef contains a reference to the LContainer, which represents the state associated with the container:
// https://github.com/angular/angular/blob/12.2.x/packages/core/src/render3/interfaces/container.ts#L65
const lContainer = vcRef._lContainer;
if (!lContainer) {
return null;
}
// LView has all its elements defined as array elements, with keys hardcoded to numeric constants:
// https://github.com/angular/angular/blob/12.2.x/packages/core/src/render3/interfaces/view.ts#L26-L57
// We care about two of them:
const HOST = 0; // https://github.com/angular/angular/blob/12.2.x/packages/core/src/render3/interfaces/view.ts#L29
const CONTEXT = 8; // https://github.com/angular/angular/blob/12.2.x/packages/core/src/render3/interfaces/view.ts#L37
// LContainer is an array, with the element at the HOST position being an LView if the container is on a Component Node.
// This means that this may not work if this directive is declared on a native HTML element.
// Note that LContainer uses the same indexes as LView, so it's the same HOST constant as declared in the LView interfaces file.
// https://github.com/angular/angular/blob/12.2.x/packages/core/src/render3/interfaces/container.ts#L66-L72
const lView = lContainer[HOST];
if (!lView) {
return null;
}
// For a non-root component, the context is the component instance.
// So if this directive is correctly attached to an Angular Component (e.g. `<app-*`),
// this array entry will contain the instance of that component.
// https://github.com/angular/angular/blob/12.2.x/packages/core/src/render3/interfaces/view.ts#L173-L180
const contextElement = lView[CONTEXT];
return contextElement || null;
}
}