有没有办法在 Angular 中动态插入现有组件?

Scu*_*Kay 4 angular

我遇到一种情况,Angular 认为它需要重新创建组件,而不仅仅是使用旧组件,这会导致组件在将组件拖到界面中的另一个位置后“刷新”。我有一棵树,我可以从中生成组件。以下是我的树的简化版本:

{
  "left": {
    "type": "FirstComponent"
  },
  "right": {
    "left": {
      "type": "SecondComponent"
    },
    "right": {
      "type": "ThirdComponent"
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

将组件拖动到另一个位置后:

{
  "left": {
    "left": {
      "type": "FirstComponent"
    },
    "right": {
      "type": "ThirdComponent"
    }
  },
  "right": {
    "type": "SecondComponent"
  }
}
Run Code Online (Sandbox Code Playgroud)

我将组件动态插入到我的 DockComponent 中,如下所示:

private panelRef: ComponentRef<any>;

ngOnChanges() {
  const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.panel.type);
  this.dynamicInsert.clear();
  this.panelRef = this.dynamicInsert.createComponent(componentFactory);
}
Run Code Online (Sandbox Code Playgroud)

每当我更改树时,树都会重新生成,因此组件是从头开始构建的。我尝试将 this.panelRef.instance 保存在我的 dockTree 中,但 this.panelRef.instance 是只读的,所以我无法用它做任何事情。我尝试保存 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;
 }
Run Code Online (Sandbox Code Playgroud)

该树存在 DockTreeNode,它可以是包含两个 DockTreeNode 的 DockTreeContainer,也可以是包含面板类型的 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);
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,这不起作用,因为我的组件是使用树递归创建的,如下所示:

<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>
Run Code Online (Sandbox Code Playgroud)

移动其中一个面板可以将其放置在树中完全不同的位置,这会导致触发 Angular 的更改检测。当 DockComponent 中的 ngOnInit 被调用时,我试图恢复的保存的引用已经被破坏了。

是否还有办法解决这个问题,或者我应该尝试将所有递归添加到一个组件中,同时希望我的引用不会被破坏?

[编辑2]

因此,我到达了可以在一定时间内实际分离组件的部分,以便稍后可以重新连接它们。然而,所有面板都被分离,但并非所有面板都在初始化,导致一些面板在移动其他面板时消失。

// 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);
}
Run Code Online (Sandbox Code Playgroud)

[编辑3]

终于让这个工作了。我正在使用 AfterViewChecked 挂钩来查看我们是否已经初始化了面板。如果没有,我们就重新使用旧的。AfterViewChecked 在更改检测方面出现问题,因此您需要调用 detectorChanges()!接受伊利亚的回答,因为它帮助我弄清楚了。

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();
  }
Run Code Online (Sandbox Code Playgroud)

Ili*_*olk 5

@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);
  }
}
Run Code Online (Sandbox Code Playgroud)