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

时间:2018-03-13 15:44:28

标签: angular

我有一种情况,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();
  }

1 个答案:

答案 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);
  }
}