Angular 自定义渲染模板
前言
在来到 ThoughtWorks 之前,我从未使用过 Angular,但是最近的项目都是用的 Angular,只能撸起袖子学 Angular 了。那么怎么学效率才是最高的呢?
我觉得将一些新的知识和以往的知识进行对比是比较好的方法,比如,我熟悉 React,那么我会把 Angular 中的概念与之对比,具体的做法是将同样的功能分别使用React和Angular进行实现。
需求
给定数组 [{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标签内部内容表示函数体