我有一种情况,Angular认为它需要重新创建组件,而不是只使用旧组件,这会导致组件在我的界面中拖动到另一个位置后“刷新”。我有一棵树,我从中生成组件。以下是我的树的简化版本:
{
"left": {
"type": "FirstComponent"
},
"right": {
"left": {
"type": "SecondComponent"
},
"right": {
"type": "ThirdComponent"
}
}
}
将组件拖动到另一个位置后:
{
"left": {
"left": {
"type": "FirstComponent"
},
"right": {
"type": "ThirdComponent"
}
},
"right": {
"type": "SecondComponent"
}
}
我在DockComponent中动态插入组件,如下所示:
private panelRef: ComponentRef<any>;
ngOnChanges() {
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.panel.type);
this.dynamicInsert.clear();
this.panelRef = this.dynamicInsert.createComponent(componentFactory);
}
每当我更改树时,树都会重新生成,因此组件会从头开始构建。我试图在dockTree中保存this.panelRef.instance,但是this.panelRef.instance是readonly,所以我不能用它做任何事情。我尝试保存this.panelRef.instance并将this.panelRef.instance的所有属性设置为已保存实例的属性。这让一切都搞砸了。
有没有办法在Angular中动态插入现有组件?
[编辑]
根据Ilia的回答,我试图在我的树中保存ViewRefs。
export class DockTreeContainer extends DockTreeNode {
private orientation: Orientation;
private left: DockTreeNode;
private right: DockTreeNode;
}
export class DockTreePanel extends DockTreeNode {
type: Type<DockablePanel>;
ref?: ViewRef;
}
树存在DockTreeNodes,它可以是DockTreeContainer,持有两个DockTreeNodes,也可以是DockTreePanel,保持面板的类型。我添加了ViewRef,在DockComponent中设置它:
ngOnInit() {
if (this.panel.ref) {
this.insertExistingComponent();
} else {
this.createNewComponent();
}
}
private createNewComponent() {
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.panel.type);
this.panelRef = this.dynamicInsert.createComponent(componentFactory);
this.initializePanel(this.panelRef.instance);
this.panelRef.instance['panel'] = this.panel;
this.panel.ref = this.dynamicInsert.get(0);
}
private insertExistingComponent() {
this.dynamicInsert.clear();
this.dynamicInsert.insert(this.panel.ref);
}
不幸的是,这不起作用,因为我的组件是使用树这样递归创建的:
<div class="dock-orientation {{getOrientation()}}">
<div *ngIf="isPanel(dockTree.getLeft()); then leftDock else leftContainer"></div>
<div *ngIf="isPanel(dockTree.getRight()); then rightDock else rightContainer"></div>
</div>
<ng-template #leftDock>
<ct-dock class="dock-node dock" [panel]="leftAsPanel()"></ct-dock>
</ng-template>
<ng-template #rightDock>
<ct-dock class="dock-node dock" [panel]="rightAsPanel()"></ct-dock>
</ng-template>
<ng-template #leftContainer>
<ct-dock-container class="dock-node dock-container" [dockTree]="leftAsContainer()"></ct-dock-container>
</ng-template>
<ng-template #rightContainer>
<ct-dock-container class="dock-node dock-container" [dockTree]="rightAsContainer()"></ct-dock-container>
</ng-template>
移动其中一个面板可以将其放在树中完全不同的位置,这会导致Angular的变化检测被触发。到调用DockComponent中的ngOnInit时,我尝试恢复的已保存引用已被破坏。
还有办法解决这个问题,或者我是否应该尝试将所有递归添加到一个组件中,同时希望我的引用不会被销毁?
[EDIT2]
所以,我到了可以在一段时间内实际分离组件的部分,所以我可以稍后再重新安装它们。但是,所有面板都已拆卸,但并非所有面板都在初始化,导致某些面板在移动其他面板时会消失。
// getTreeChange is an Observable that gets triggered after each change in the dockTree, which detaches all the panels in the application.
ngOnInit(): void {
// Detach our panel on changes in the dockTree
this.dockTreeSub = this.dockerService.getTreeChange().subscribe((value) => {
if (value !== '') {
if (this.dynamicInsert.length > 0) {
this.detachPanel();
}
}
});
}
ngAfterViewInit(): void {
if (this.panel.ref) {
this.insertExistingComponent();
} else {
this.createNewComponent();
}
}
/**
* Creates a new component and inserts it into the Dock
*/
private createNewComponent() {
console.log('No panel ref found, creating new component');
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.panel.type);
this.panelRef = this.dynamicInsert.createComponent(componentFactory);
this.initializePanel(this.panelRef.instance);
this.panelRef.instance['panel'] = this.panel;
}
/**
* Takes the old ViewRef from memory and inserts that into the Dock
*/
private insertExistingComponent() {
if (this.panel.ref.destroyed) {
throw new Error('Found destroyed panel');
}
console.log('Found intact panel, inserting');
this.dynamicInsert.clear();
this.dynamicInsert.insert(this.panel.ref);
}
/**
* Detach the panel from this Dock and save it in the DockTree
*/
private detachPanel() {
console.log('Detaching!');
this.panel.ref = this.dynamicInsert.get(0);
this.dynamicInsert.detach(0);
}
[EDIT3]
终于有了这个工作。我正在使用AfterViewChecked挂钩来查看我们是否已经初始化了一个面板。如果没有,我们重新使用旧的。 AfterViewChecked给出了更改检测的问题,因此您需要调用detectChanges()!接受Ilia的答案,因为它帮助我弄清楚了。
ngOnInit(): void {
// Detach our panel on changes in the dockTree
this.dockTreeSub = this.dockerService.getTreeChange().subscribe((value) => {
if (value !== '') {
if (this.dynamicInsert.length > 0) {
this.detachPanel();
}
}
});
if (!this.panel.ref && this.dynamicInsert.length <= 0) {
this.createNewComponent();
}
}
/**
* Check if the panel needs to be re-inserted after moving panels
*/
ngAfterViewChecked(): void {
if (this.panel.ref && this.dynamicInsert.length <= 0) {
this.insertExistingComponent();
}
this.changeDetector.detectChanges();
}
答案 0 :(得分:2)
@Component({
template: 'dynamic <input>',
})
export class DynamicComponent {}
@Component({
selector: 'hello',
template: `
<ng-container #firstContainer></ng-container>
<ng-container #secondContainer></ng-container>
<button (click)="swap()">swap</button>`,
})
export class HelloComponent {
@ViewChild('firstContainer', {read: ViewContainerRef}) firstContainer: ViewContainerRef;
@ViewChild('secondContainer', {read: ViewContainerRef}) secondContainer: ViewContainerRef;
factory;
constructor(resolver: ComponentFactoryResolver) {
this.factory = resolver.resolveComponentFactory(DynamicComponent);
}
ngOnInit() {
this.firstContainer.createComponent(this.factory);
this.secondContainer.createComponent(this.factory);
}
swap() {
const first = this.firstContainer.get(0);
const second = this.secondContainer.get(0);
this.firstContainer.detach(0);
this.secondContainer.detach(0);
this.firstContainer.insert(second);
this.secondContainer.insert(first);
}
}