如何让你的Angular项目更"懒"更"动"

336 阅读2分钟

最近我在搞项目的时候,碰到了一个叫做ant-design的tabs组件。它的需求是要我懒懒地加载组件,还要动态地渲染。你说这不是要求颇高吗?

用ant-design-angular的tabs来做懒加载,轻轻松松

@Component({
  selector: 'nz-demo-tabs-lazy',
  template: `
    <nz-tabset>
      <nz-tab nzTitle="Tab Eagerly 1">
        <nz-demo-tab-content-eagerly></nz-demo-tab-content-eagerly>
      </nz-tab>
      <nz-tab nzTitle="Tab Eagerly 2">
        <nz-demo-tab-content-eagerly></nz-demo-tab-content-eagerly>
      </nz-tab>
      <nz-tab nzTitle="Tab Lazy 1">
        <ng-template nz-tab>
          <nz-demo-tab-content-lazy></nz-demo-tab-content-lazy>
        </ng-template>
      </nz-tab>
      <nz-tab nzTitle="Tab Lazy 2">
        <ng-template nz-tab>
          <nz-demo-tab-content-lazy></nz-demo-tab-content-lazy>
        </ng-template>
      </nz-tab>
    </nz-tabset>
  `
})

看,只需加上<ng-template nz-tab></ng-template>标签,并将需要懒加载的组件放在里面即可。

Angular动态渲染,简单粗暴

  1. 首先,来一个锚点组件:

@Component({
  selector: 'my-anchor',
  template: '<ng-template></ng-template>'
})
export class TbAnchorComponent {
  constructor(public viewContainerRef: ViewContainerRef) { }
}

  1. 然后,把这个锚点组件放在需要动态渲染的地方:

<my-anchor #Anchor></my-anchor>
  1. 接着,挂载组件

// 通过ViewChildren获取这个组件
@ViewChildren("Anchor") Anchor!: QueryList<TbAnchorComponent>;

// 挂载
 this.componentsMappings.children.forEach((compInfo, i) => {
    this.createComponent(compInfo, this.Anchor.toArray()[i])
 });

实现两者的结合

  1. 模板
<nz-tabset>
  <ng-container *ngFor="let item of componentsMappings?.children">
    <nz-tab [nzTitle]="item.mapping_name">
      <ng-template nz-tab>
        <my-anchor #Anchor></my-anchor>
      </ng-template>
    </nz-tab>
  </ng-container>
</nz-tabset>
  1. ts
  ngAfterViewInit(): void {
    this.componentsMappings.children.forEach((compInfo, i) => {
      this.createComponent(compInfo, this.Anchor.toArray()[i])
    });
  }
  
  private createComponent(compInfo: ISmdComponentMappings, Anchor: TbAnchorComponent) {
    const viewContainerRef = Anchor.viewContainerRef;
    if (viewContainerRef.length <= 0) {
      const componentFactory = this.componentFactoryResolver.resolveComponentFactory(SmdColumnComponent);
      const componentRef = viewContainerRef.createComponent(componentFactory);
      const component = componentRef.instance;
      component.componentsMappings = compInfo;
    }
  }

大家看看上面的实现方式是否有问题。其实是有问题的,没有实现懒加载+动态渲染的目标。因为在生命周期钩子ngAfterViewInit的forEach中已经全部将组件动态挂载到Anchor上了。需要实现点击tab的时候在挂载组件。所以我们可以改造这个实现方式,监听tab的点击事件,然后在相对应的tab里获取到这个Anchor。然后动态挂载这个组件。

第一步 改造模版

首先删掉<ng-template nz-tab></ng-template>标签。然后给<nz-tabset (nzSelectedIndexChange)="selectChange($event)"> 添加点击监听。

<nz-tabset (nzSelectedIndexChange)="selectChange($event)">
  <ng-container *ngFor="let item of componentsMappings?.children">
    <nz-tab [nzTitle]="item.mapping_name">
        <my-anchor #Anchor></my-anchor>
    </nz-tab>
  </ng-container>
</nz-tabset>

第二步 监听点击切换的index

  selectChange($event) {
    this.componentsMappings.children.forEach((compInfo, i) => {
      if (i == $event) {
        this.createComponent(compInfo, this.Anchor.toArray()[$event])
      }
    });
  }
  
  // 创建组件的方法
    private createComponent(compInfo: ISmdComponentMappings, Anchor: TbAnchorComponent) {
    const viewContainerRef = Anchor.viewContainerRef;
    if (viewContainerRef.length <= 0) {
      const componentFactory = this.componentFactoryResolver.resolveComponentFactory(SmdColumnComponent);
      const componentRef = viewContainerRef.createComponent(componentFactory);
      const component = componentRef.instance;
      component.componentsMappings = compInfo;
    }
  }
  
  // 在页面初始化的时候手动给index为0的Anchor创建组件。
    ngAfterViewInit(): void {
        this.selectChange(0);
    }

这样,你就像把钥匙插进锁眼,轻轻一转,项目就慢悠悠地懒加载和动态渲染了。不信?试试看!如有其他实现方式,欢迎讨论。