Angular CDK:如何在 ComponentPortal 中设置输入
Angular CDK: How to set Inputs in a ComponentPortal
我想使用 material CDK 中的新 Portal 在表单的多个部分中注入动态内容。
我有一个复杂的表单结构,目标是拥有一个指定子组件可以(或不能)注入模板的多个位置的表单。
也许 CDK Portal 不是最好的解决方案?
我尝试了一些东西,但我确信这不是做事的方式:
https://stackblitz.com/edit/angular-yuz1kg
我也尝试过 new ComponentPortal(MyPortalComponent)
但是我们如何设置输入呢?通常是 componentRef.component.instance.myInput
您可以创建自定义注入器并将其注入您创建的组件门户。
createInjector(dataToPass): PortalInjector {
const injectorTokens = new WeakMap();
injectorTokens.set(CONTAINER_DATA, dataToPass);
return new PortalInjector(this._injector, injectorTokens);
}
CONTAINER_DATA
是由 -
创建的自定义注入器 (InjectorToken
)
export const CONTAINER_DATA = new InjectionToken<{}>('CONTAINER_DATA');
要使用创建的注入器,请使用 -
let containerPortal = new ComponentPortal(ComponentToPort, null, this.createInjector({
data1,
data2
}));
overlay.attach(containerPortal);
overlay
是 OverlayRef
的实例(即 Portal Outlet)
在ComponentToPort
里面,你需要注入创建的注入器-
@Inject(CONTAINER_DATA) public componentData: any
关于此的更多信息 here。
您可以使用在 ComponentPortal
的 第 3 个参数 上传递的特定注入器向 ComponentPortal
注入数据
修复语法问题:
Can't resolve all parameters for Component: ([object Object], [object Object], ?
这是代码
export const PORTAL_DATA = new InjectionToken<{}>('PortalData');
class ContainerComponent {
constructor(private injector: Injector, private overlay: Overlay) {}
attachPortal() {
const componentPortal = new ComponentPortal(
ComponentToPort,
null,
this.createInjector({id: 'first-data'})
);
this.overlay.create().attach(componentPortal);
}
private createInjector(data): PortalInjector {
const injectorTokens = new WeakMap<any, any>([
[PORTAL_DATA, data],
]);
return new PortalInjector(this.injector, injectorTokens);
}
}
class ComponentToPort {
constructor(@Inject(PORTAL_DATA) public data ) {
console.log(data);
}
}
可以通过这种方式设置组件输入(或绑定到输出作为可观察对象):
portal = new ComponentPortal(MyComponent);
this.portalHost = new DomPortalHost(
this.elementRef.nativeElement,
this.componentFactoryResolver,
this.appRef,
this.injector
);
const componentRef = this.portalHost.attach(this.portal);
componentRef.instance.myInput = data;
componentRef.instance.myOutput.subscribe(...);
componentRef.changeDetectorRef.detectChanges();
这似乎更简单一些,使用 cdkPortalOutlet 和(附加的)发射器
import {Component, ComponentRef, AfterViewInit, TemplateRef, ViewChild, ViewContainerRef, Input, OnInit} from '@angular/core';
import {ComponentPortal, CdkPortalOutletAttachedRef, Portal, TemplatePortal, CdkPortalOutlet} from '@angular/cdk/portal';
/**
* @title Portal overview
*/
@Component({
selector: 'cdk-portal-overview-example',
template: '<ng-template [cdkPortalOutlet]="componentPortal" (attached)=foo($event)></ng-template>',
styleUrls: ['cdk-portal-overview-example.css'],
})
export class CdkPortalOverviewExample implements OnInit {
componentPortal: ComponentPortal<ComponentPortalExample>;
constructor(private _viewContainerRef: ViewContainerRef) {}
ngOnInit() {
this.componentPortal = new ComponentPortal(ComponentPortalExample);
}
foo(ref: CdkPortalOutletAttachedRef) {
ref = ref as ComponentRef<ComponentPortalExample>;
ref.instance.message = 'zap';
}
}
@Component({
selector: 'component-portal-example',
template: 'Hello, this is a component portal {{message}}'
})
export class ComponentPortalExample {
@Input() message: string;
}
版本 angular 9 'DomPortalHost' 后已弃用,已更改为 'DomPortalOutlet'。所以现在它会喜欢:
this.portalHost = new DomPortalOutlet(
this.elementRef.nativeElement,
this.componentFactoryResolver,
this.appRef,
this.injector
);
const componentRef = this.portalHost.attachComponentPortal(this.portal);
componentRef.instance.myInput = data;
除此之外,我觉得最好的解决方案就是绑定(附加的)事件并在那里设置输入:
<ng-template [cdkPortalOutlet]="yourPortal" (attached)="setInputs($event)"> </ng-template>
并在 ts 中设置您的输入:
setInputs(portalOutletRef: CdkPortalOutletAttachedRef) {
portalOutletRef = portalOutletRef as ComponentRef<myComponent>;
portalOutletRef.instance.inputPropertyName = data;
}
如果您使用的是 Angular 10+ 和以下 ,PortalInjector 现已弃用,因此不再使用:
new PortalInjector(this.injector, new WeakMap([[SOME_TOKEN, data]]))
您现在拥有:
Injector.create({
parent: this.injector,
providers: [
{ provide: SOME_TOKEN, useValue: data }
]
})
我想使用 material CDK 中的新 Portal 在表单的多个部分中注入动态内容。
我有一个复杂的表单结构,目标是拥有一个指定子组件可以(或不能)注入模板的多个位置的表单。
也许 CDK Portal 不是最好的解决方案?
我尝试了一些东西,但我确信这不是做事的方式: https://stackblitz.com/edit/angular-yuz1kg
我也尝试过 new ComponentPortal(MyPortalComponent)
但是我们如何设置输入呢?通常是 componentRef.component.instance.myInput
您可以创建自定义注入器并将其注入您创建的组件门户。
createInjector(dataToPass): PortalInjector {
const injectorTokens = new WeakMap();
injectorTokens.set(CONTAINER_DATA, dataToPass);
return new PortalInjector(this._injector, injectorTokens);
}
CONTAINER_DATA
是由 -
InjectorToken
)
export const CONTAINER_DATA = new InjectionToken<{}>('CONTAINER_DATA');
要使用创建的注入器,请使用 -
let containerPortal = new ComponentPortal(ComponentToPort, null, this.createInjector({
data1,
data2
}));
overlay.attach(containerPortal);
overlay
是 OverlayRef
的实例(即 Portal Outlet)
在ComponentToPort
里面,你需要注入创建的注入器-
@Inject(CONTAINER_DATA) public componentData: any
关于此的更多信息 here。
您可以使用在 ComponentPortal
ComponentPortal
注入数据
修复语法问题:
Can't resolve all parameters for Component: ([object Object], [object Object], ?
这是代码
export const PORTAL_DATA = new InjectionToken<{}>('PortalData');
class ContainerComponent {
constructor(private injector: Injector, private overlay: Overlay) {}
attachPortal() {
const componentPortal = new ComponentPortal(
ComponentToPort,
null,
this.createInjector({id: 'first-data'})
);
this.overlay.create().attach(componentPortal);
}
private createInjector(data): PortalInjector {
const injectorTokens = new WeakMap<any, any>([
[PORTAL_DATA, data],
]);
return new PortalInjector(this.injector, injectorTokens);
}
}
class ComponentToPort {
constructor(@Inject(PORTAL_DATA) public data ) {
console.log(data);
}
}
可以通过这种方式设置组件输入(或绑定到输出作为可观察对象):
portal = new ComponentPortal(MyComponent);
this.portalHost = new DomPortalHost(
this.elementRef.nativeElement,
this.componentFactoryResolver,
this.appRef,
this.injector
);
const componentRef = this.portalHost.attach(this.portal);
componentRef.instance.myInput = data;
componentRef.instance.myOutput.subscribe(...);
componentRef.changeDetectorRef.detectChanges();
这似乎更简单一些,使用 cdkPortalOutlet 和(附加的)发射器
import {Component, ComponentRef, AfterViewInit, TemplateRef, ViewChild, ViewContainerRef, Input, OnInit} from '@angular/core';
import {ComponentPortal, CdkPortalOutletAttachedRef, Portal, TemplatePortal, CdkPortalOutlet} from '@angular/cdk/portal';
/**
* @title Portal overview
*/
@Component({
selector: 'cdk-portal-overview-example',
template: '<ng-template [cdkPortalOutlet]="componentPortal" (attached)=foo($event)></ng-template>',
styleUrls: ['cdk-portal-overview-example.css'],
})
export class CdkPortalOverviewExample implements OnInit {
componentPortal: ComponentPortal<ComponentPortalExample>;
constructor(private _viewContainerRef: ViewContainerRef) {}
ngOnInit() {
this.componentPortal = new ComponentPortal(ComponentPortalExample);
}
foo(ref: CdkPortalOutletAttachedRef) {
ref = ref as ComponentRef<ComponentPortalExample>;
ref.instance.message = 'zap';
}
}
@Component({
selector: 'component-portal-example',
template: 'Hello, this is a component portal {{message}}'
})
export class ComponentPortalExample {
@Input() message: string;
}
版本 angular 9 'DomPortalHost' 后已弃用,已更改为 'DomPortalOutlet'。所以现在它会喜欢:
this.portalHost = new DomPortalOutlet(
this.elementRef.nativeElement,
this.componentFactoryResolver,
this.appRef,
this.injector
);
const componentRef = this.portalHost.attachComponentPortal(this.portal);
componentRef.instance.myInput = data;
除此之外,我觉得最好的解决方案就是绑定(附加的)事件并在那里设置输入:
<ng-template [cdkPortalOutlet]="yourPortal" (attached)="setInputs($event)"> </ng-template>
并在 ts 中设置您的输入:
setInputs(portalOutletRef: CdkPortalOutletAttachedRef) {
portalOutletRef = portalOutletRef as ComponentRef<myComponent>;
portalOutletRef.instance.inputPropertyName = data;
}
如果您使用的是 Angular 10+ 和以下
new PortalInjector(this.injector, new WeakMap([[SOME_TOKEN, data]]))
您现在拥有:
Injector.create({
parent: this.injector,
providers: [
{ provide: SOME_TOKEN, useValue: data }
]
})