Angualr手动实现description组件

321 阅读2分钟

首先仿照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
};

最后实现的效果:

1621564985(1).jpg

最后表格的样式自己加吧