angular input_Angular组件之 - 懒加载Tab组件

有开发大型应用的同学对Angular路由懒加载一定不陌生。Angular在框架层面支持路由懒加载,用户首次访问应用,没有访问到的应用路由不会立刻加载,提升应用的初始加载速度。

懒加载tab组件,也是同样的概念,用户没有访问到的tab内容不加载。在复杂应用中可以切分不同功能为不同的tab,并且在切换tab时候不造成销毁,从而保留用户对不同tab的操作,提升使用体验。在各个手机app中都能看到,比如知乎,b站,淘宝(tab抽象为底部导航)。

v2-65ddfa08d104d4361d3c3829df9fdbd9_b.jpg
知乎手机端实现懒加载tab - 未访问的tab内容不会被渲染

目前无论是zorro还是material的tab组件都是一次性渲染所有内容(material支持ng-template的懒加载),在大多数情况下并没有问题,但是遇到特别多的tab内容(比如每个tab里都有很多异步请求,很多渲染逻辑),一次性加载可能造成性能问题。

实现

Tab组件结构

可以想象大体的tab组件使用写法应该是:

<app-tab>
    <app-tab-pane>...</app-tab-pane>
    <app-tab-pane>...</app-tab-pane>
</app-tab>

从结构上来说没有什么问题,app-tab包裹整个tab组件,app-tab-pane为各个tab的内容组件。但是这样写,angular会整体接管所有视图的渲染,app-tab-pane里的内容都会直接渲染执行,无法做到懒加载。要控制视图的渲染,需要将app-tab-pane通过结构指令来实现。

<app-tab>
    <div *appTabPane></div>
    <div *appTabPane></div>
</app-tab>

具体懒加载需求

  • 在未切换到某一个pane时,该pane底下的内容不渲染
  • 渲染过的pane不销毁
<div *appTabPane> <!-- <- 视图容器的位置 -->
    <div>I am a template which will not be rendered until createEmbeddedView</div> <!-- <- 模板 -->
    <!-- call ViewContainerRef.insert. the view will insert here. -->
</div>

结构指令内部可以通过两个抽象来控制视图的渲染ViewContainerRef, TemplateRefViewContainerRef视图的容器,TemplateRef模板(即未被渲染的视图)。渲染之后会得到ViewRef视图引用。pane指令在初次渲染时,需要将TemplateRef渲染得到ViewRef,并放到视图容器中,在后续的渲染操作中都复用初次得到的视图引用即可。关于ViewContainerRef, TemplateRef更多的理解可参考https://segmentfault.com/a/1190000008672478

  constructor(
    private template: TemplateRef<TabPaneDirective>,
    private container: ViewContainerRef,
  ) { }

通过注入的方式可以获得ViewContainerRefTemplateRef

  show() {
    // 如果存在视图引用,则复用。不存在则渲染模板,并保留视图引用
    if (this.viewRef) {
      this.attachView();
    } else {
      this.createView();
    }
  }

  hide() {
    this.container.detach();
    this.active = false
  }

  createView() {
    this.viewRef = this.container.createEmbeddedView(this.template);
  }

  attachView() {
    this.container.insert(this.viewRef);
  }

this.templateTemplateRef,是appTabPane内部的模板视图,通过this.container.createEmbeddedView(this.template)可将模板渲染得到视图并挂载的视图容器中。而视图容器的insertdetach方法用于处理视图容器插入或者删除已有的视图。通过保留viewRef引用,我们达到了懒加载并且不销毁的功能。

其他pane组件属性

@Input('appTabPane') name: string;

active: boolean;

name为pane的名称,active为当前pane的显示/隐藏状态。

Tab父组件

<div class="head">
  <div
    class="head-item"
    *ngFor="let pane of panes"
    [class.active]="pane.active"
    (click)="selectPane(pane)"
  >
    {{pane.name}}
  </div>
</div>
<div class="body">
  <ng-content></ng-content>
</div>

通过ng-content直接插入pane的内容,ngFor显示pane名称。最简化的组件设计并且满足组件需求。

测试代码

代码地址: github

<app-tab>
  <app-tab-example-timer *appTabPane="'tab1'"></app-tab-example-timer>
  <app-tab-example-timer *appTabPane="'tab2'">
    <input type="text">
  </app-tab-example-timer>
</app-tab>

运行得到tab1tab2两个pane, 只有当点击到相应的tab之后,timer才会启动,并且你在input里输入的东西也不会随着切换pane而被销毁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值