Angular 自定义渲染模板

1,185 阅读3分钟

Angular 自定义渲染模板

前言

在来到 ThoughtWorks 之前,我从未使用过 Angular,但是最近的项目都是用的 Angular,只能撸起袖子学 Angular 了。那么怎么学效率才是最高的呢? 我觉得将一些新的知识和以往的知识进行对比是比较好的方法,比如,我熟悉 React,那么我会把 Angular 中的概念与之对比,具体的做法是将同样的功能分别使用ReactAngular进行实现。

需求

给定数组 [{name:'小王',age:10},{name:'老王',age: 12}],渲染出如下 UI, 需要支持自定义渲染模板

自定义渲染模板 React 版

const List = (data = [], renderer = defaultRenderer) => {
  return (
    <div>
      <ul>
        {renderer(data) || render(data)}
      </ul>
    </div>
  )
}

function customRenderer(data = []) {//自定义渲染函数
  return data.map((item) => {
    return <li>
      <h1>age: {item.age}</h1>
      <h1>name: {item.name}</h1>
      <hr />
    </li>
  })
}

function defaultRenderer(data = []) {//默认渲染函数
  return data.map((item) => {
    return <li>
      <span>age: {item.age} </span>
      <span>name: {item.name}</span>
      <hr />
    </li>
  })
}

List([{name:'小王',age:10},{name:'老王',age: 12}],customRenderer)

自定义渲染模板 Angular 版

  • list.component.html
<ul>
  <li *ngFor="let item of data">
    <ng-template
      [ngTemplateOutlet]="customRenderer || defaultRenderer"
      [ngTemplateOutletContext]="{ $implicit: item }"
    ></ng-template>
  </li>

  <ng-template #defaultRenderer let-data>
    <span>age: {{ data.age }}</span>
    <span>name: {{ data.name }}</span>
  </ng-template>
</ul>
  • list.component.ts
import { Component, Input, TemplateRef } from '@Angular/core';

interface Payload {
  age: number;
  name: string;
}

@Component({
  selector: 'app-list',
  templateUrl: './list.component.html',
  styleUrls: ['./list.component.less']
})
export class ListComponent {
  @Input()
  data: Payload[] = [
    {
      age: 10,
      name: '小王',
    },
    {
      age: 12,
      name: '老王',
    }
  ];

  @Input() customRenderer: TemplateRef<{ $implicit: Payload; }>;
}

  • 这样使用
// 使用默认模板渲染:
<app-list></app-list>

// 使用自定义模板渲染:
<app-list [customRenderer]="customRenderer"></app-list>

<ng-template #customRenderer let-data>
  <h1>name:{{ data.name }}</h1>
  <h1>age:{{ data.age }}</h1>
</ng-template>

尼玛, 对比 react 看起来复杂多了,但别方,其实本质是一样的,只不过是 Angular 的 dsl 定制了一些特殊语法。

以 react 的视角去理解

为啥同样的需求,react实现起来我们会觉得更简单呢?因为 react 是用函数去描述组件,而 Angular 使用定制的模板语法去描述,大家都会写函数,所以不会觉得有啥负担,但是 Angular需要你去单独学习他的模板语法,所以增加了理解难度。既然函数有利于理解,那我们也可以将 Angular的模板语法转换成 函数来理解

  • 举个栗子
<ng-template #customRenderer let-data>
  <h1>name:{{ data.name }}</h1>
  <h1>age:{{ data.age }}</h1>
</ng-template>

ng-template 标签可以理解为声明了一个函数。(类比react的渲染函数)

#customRenderer#开头即为函数名,那这里的函数名就是customRenderer

function  customRenderer(){}

let-data声明函数的形参的名称,,那这里的函数形参的名称就是 data

function  customRenderer(data) { }

let-xxx 相当于 function customRenderer(xxx) { }

ng-template 标签内的内容可以理解为函数体

function  customRenderer(){ 
  return ( // 真实的jsx 需要一个根元素包裹,这里只是伪代码
      <h1>name:{ data.name }</h1>
      <h1>age:{ data.age }</h1>
  )
}

最后还有个特殊的语法[ngTemplateOutletContext]="{ $implicit: item }"implicit这名字看着有点吓人,好像很难理解似的,不方,往下看,其实很简单

我们之前定义了函数的形参 data

function  customRenderer(data) { }

思考以下代码

<li *ngFor="let item of data">
    <ng-template
      [ngTemplateOutletContext]="{ $implicit: item }"
    ></ng-template>
  </li>

[ngTemplateOutletContext]="{ $implicit: item }"这段代码主要是定义函数的实参

因为我们使用了*ngFor="let item of data",所以{ $implicit: item }表示将item当做实参传递给渲染函数 function customRenderer(data) { }, 这里可以理解为函数调用

customRenderer(item)

所以$implicit:xxx含义就是声明 xxx实参

敲黑板总结下Angular的模板语法

  • 模板中的 ng-template标签表示声明一个渲染函数
  • 模板中的 #表示函数名
  • 模板中的 let-xxx表示函数形参名称
  • 模板中的 $implicit表示函数实参名称
  • 模板中的 ng-template标签内部内容表示函数体