首先仿照antdv的组件库description的写法,我们设计的组件的用法
<div style="width: 50%">
<app-description [nzBordered]="true">
<app-description-item [nzTitle]="title8" > value8</app-description-item>
<app-description-item nzTitle="title1" > <span style="color: red">value1</span></app-description-item>
<app-description-item nzTitle="title2" > value2</app-description-item>
<app-description-item nzTitle="title3" > value3</app-description-item>
<app-description-item nzTitle="title4" > value4</app-description-item>
<app-description-item nzTitle="title5" > value5</app-description-item>
<app-description-item nzTitle="title6" > value6</app-description-item>
<app-description-item nzTitle="title7" > value7</app-description-item>
</app-description>
<ng-template #title8>
<span style="color: red">title8</span>
</ng-template>
</div>
在这个组件中,最为困难就是ng-template如何将作用域绑定到组件中;
关键点知识讲解:
ng-content: 可以将父组件中所包含的所有子组件,都插入table组件中ng-container所在的位置,跟vue中的slot很像;
ng-container: 可以作为一个组件的模板,跟vue里面的template组件很像;
ng-template: 该东西,是整个组件中最为麻烦的一个东西,直接使用它,会没有任何效果,必须要和TemplateRef和ngTemplateOutlet一起使用,才有有效果,主要是作为模板并引入作用域,具体原理可以看一下官方文档(www.angular.cn/api)
TemplateRef: 主要是用来获取ng-template组件的引用;
ngTemplateOutlet: 将ng-template的内容在html页面展示出来,并绑定变量;
QueryList: 获取description组件中所有的内容指引;
ContentChildren: 内容映射的接口,针对多个子元素采用
ContentChild: 内容映射的接口,针对单个子元素采用
DescriptionItemComponent
先对DescriptionItemComponent组件进行分析: 该组件的作用就是为了运输数据,并且引入内容,该组件本身是不会有任何操作和逻辑,就是一个运输工;
description-item.component.html:
<ng-template>
<ng-content></ng-content>
</ng-template>
table-column.component.ts:
import {
Component,
TemplateRef,
Input,
ContentChild,
AfterContentInit,
ViewChild,
ChangeDetectionStrategy,
ViewEncapsulation
} from '@angular/core';
@Component({
selector: 'app-description-item',
templateUrl: './description-item.component.html',
styleUrls: ['./description-item.component.scss']
})
export class DescriptionItemComponent implements AfterContentInit{
@ViewChild(TemplateRef, { static: true }) // static-True表示在运行更改检测之前解析查询结果,false用于在更改检测后解析。默认为false。
content: TemplateRef<any>;// 获取ng-template组件的一个本地变量
@Input() nzSpan = 1;
@Input() nzTitle: string | TemplateRef<any>;
constructor() {
}
ngAfterContentInit(): void {
// console.log(this.content);
}
}
DescriptionComponent
<div class="descriptions-view">
<table>
<tbody>
<ng-container >
<tr class="descriptions-row" *ngFor="let row of itemMatrix; let i = index">
<ng-container *ngFor="let item of row; let isLast = last">
<!-- Horizontal & NOT Bordered -->
<ng-container *ngIf="!nzBordered">
<td class="descriptions-item" [colSpan]="item.span">
<div class="descriptions-item-container">
<span class="descriptions-item-label" [class.descriptions-item-no-colon]="!nzColon">
<ng-container *ngIf="item.titleTemplate">
{{ item.title|translate }}
</ng-container>
<ng-template *ngIf="!item.titleTemplate" [ngTemplateOutlet]="item.title"></ng-template>
</span>
<span class="descriptions-item-content">
<ng-template [ngTemplateOutlet]="item.content"></ng-template>
</span>
</div>
</td>
</ng-container>
<!-- Horizontal & Bordered -->
<ng-container *ngIf="nzBordered">
<td class="descriptions-item-label" >
<ng-container *ngIf="item.titleTemplate">
{{ item.title|translate }}
</ng-container>
<ng-template *ngIf="!item.titleTemplate" [ngTemplateOutlet]="item.title"></ng-template>
</td>
<td class="descriptions-item-content" [colSpan]="item.span * 2 - 1">
<ng-template [ngTemplateOutlet]="item.content"></ng-template>
</td>
</ng-container>
</ng-container>
</tr>
</ng-container>
</tbody>
</table>
</div>
description.component.ts:
import { Component, Input, AfterContentInit, QueryList, TemplateRef, ContentChildren } from '@angular/core';
import { merge, Subject } from 'rxjs';
import {DescriptionItemComponent} from './description-item/description-item.component';
import {TranslateService} from '@ngx-translate/core';
@Component({
selector: 'app-description',
// tslint:disable-next-line: no-host-metadata-property 给引用组件套上class
host: {
class: 'descriptions',
'[class.descriptions-bordered]': 'nzBordered'
},
templateUrl: './description.component.html',
styleUrls: ['./description.component.scss'],
})
export class DescriptionComponent implements AfterContentInit {
@Input() nzBordered: boolean;
@Input() nzColumn: number|object = defaultColumnMap;
@Input() nzTitle: string|TemplateRef<any>;
@Input() nzExtra: string|TemplateRef<any>;
@Input() nzColon = true;
itemMatrix = [];
@Input() realColumn = 3;
@ContentChildren(DescriptionItemComponent)
items: QueryList<DescriptionItemComponent>;
constructor(public translate: TranslateService) {
// for test
window['angualr'] = this;
}
ngAfterContentInit(): void {
this.prepareMatrix();
}
/**
* Prepare the render matrix according to description items' spans.
*/
prepareMatrix(): void {
console.log('prepareMatrix', this.items);
if (!this.items) {
return;
}
let currentRow = [];
let width = 0;
const column = this.realColumn;
const items = this.items.toArray();
const length = items.length;
const matrix = [];
const flushRow = () => {
currentRow.forEach(e => {
e.titleTemplate = typeof e.title === 'string' ;
});
matrix.push(currentRow);
currentRow = [];
width = 0;
};
for (let i = 0; i < length; i++) {
const item = items[i];
const { nzTitle: title, content, nzSpan: span } = item;
width += span;
// If the last item make the row's length exceeds `nzColumn`, the last
// item should take all the space left. This logic is implemented in the template.
// Warn user about that.
if (width >= column) {
if (width > column) {
console.warn(`"nzColumn" is ${column} but we have row length ${width}`);
}
currentRow.push({ title, content, span: column - (width - span) });
flushRow();
}
else if (i === length - 1) {
currentRow.push({ title, content, span: column - (width - span) });
flushRow();
}
else {
currentRow.push({ title, content, span });
}
}
this.itemMatrix = matrix;
console.log('prepareMatrix', this.itemMatrix);
}
}
const defaultColumnMap = {
xxl: 3,
xl: 3,
lg: 3,
md: 3,
sm: 2,
xs: 1
};
最后实现的效果:
最后表格的样式自己加吧