Angular公共组件开发

2,568 阅读5分钟

小谈一下:

最近在国外的一些社区和网站上经常看到一个技术发展方向Bit,国外预测前端2020年的发展方向是组件公共化,大家可以上传自己开发的组件到bit,也可以直接下载复用别人的组件,并且无论组件是用Vue、Ng还是React开发的,都会被打包成一个js文件,直接在index.html导入即可实现复用,看似很神奇的一个东西,但是大量导入外部组件,一旦出错,维护成本将大大增加,开发者都清楚,修bug比写bug更累心,况且还是格式化过的代码...总之,目前bit上的资源数量和可利用量都很低,可能是大家还没有适应这种开发模式,或者是其他一些原因...下面我就Angular8,谈一下怎么在项目中开发并使用公共组件吧。

重点知识点:

@Input() @Output() @Viewchild() @ContentChild()这些装饰器还熟悉吗?
不熟悉也没关系,只看概念真的能把人绕晕,还是实战让人理解地更快。所用到的也就是通过这些装饰器进行父子组件之间的通讯。记得vue这一块用的也是类似的方法,具体就不展开说了。先说一下这几个装饰器的作用吧。
以上装饰器是Angular的内置装饰器,也就是框架自带,初设的,不用我们再自己封装。

@Input

比如: @Input() dataSource: any;
定义在公共组件里,用来接收所调用者通过属性绑定传来的值,在公共组件OnInit里才能接收到。之后就可以当成变量用,但是是双向的,在公共组件里修改其值之后,调用者那边的值也随之改变,因此最好不要直接修改这个变量,先复制一份,去修改复制过的,保留元数据的clean。

@Output

比如:@Output() readonly sort: EventEmitter;
定义在公共组件里,用来导出在公共组件里触发的事件并导出参数。定义之后需要在构造器里先初始化一下
this.sort = new EventEmitter();
之后在所触发的事件中通过this.sort.emit(data);,传出参数数据。

@ContentChild

比如: @ContentChild(TemplateRef, { static: true }) tableActions: TemplateRef;
定义在公共组件里,用来接收调用者传入的template。这里需要在html里留出一个供template渲染的块
<ng-container *ngTemplateOutlet="tableActions;context: {data: row}">
tableActions是template的Name,context是传出的数据;
调用时
<ng-template #tableActions let-row="data" > </ng-template》
在这里面写入你想在公共组件里#tableActions位置插入的HTML标签。

下面提供一下源码:

公共组件 table.component.ts
import {
    Component,
    OnInit,
    Input,
    Output,
    EventEmitter,
    TemplateRef,
    ContentChild,
    SimpleChanges,
    OnChanges
} from '@angular/core';
import { SelectionModel } from '@angular/cdk/collections';
import { MatDialog } from '@angular/material/dialog';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { MatTableDataSource } from '@angular/material/table';

export interface TableColumns {
    field: string; // 字段
    title: string; // 字段名
    isShow?: boolean; // 是否显示
    isSort?: boolean; // 可否排序
    type?: ColumnType; // 类型
}
export enum ColumnType {
    default, // 默认
    slideToggle // slide滑动条
}

@Component({
    selector: 'table',
    templateUrl: './table.component.html',
    styleUrls: ['./table.component.scss']
})
export class TableComponent implements OnInit, OnChanges {
    selectionRow = new SelectionModel<any>(true, []); // 选中的行
    columnType: typeof ColumnType = ColumnType; // 定义Column类型枚举
    renderedDataSource: any; // 转化后的数据
    @Input() dataSource: any; // 远程数据
    @Input() placeholder: string; // // 默认提示
    @Input() checkable: boolean; // // 是否出现复选框
    @Input() hiddenCheckHead: boolean; // // 是否隐藏复选框title
    @Input() columns: TableColumns[]; // 远程列名
    @Output() readonly checkChange: EventEmitter<any>; // 复选框点击事件传出选中元素
    @Output() readonly sort: EventEmitter<any>; // 复选框点击事件传出选中元素
    @ContentChild(TemplateRef, { static: true }) tableActions: TemplateRef<any>; // 子组件的模板

    constructor(public dialog: MatDialog, public httpClient: HttpClient) {
        this.checkChange = new EventEmitter<any>();
        this.sort = new EventEmitter<any>();
        this.checkable = false;
        this.placeholder = '抱歉!没有搜索到任何结果';
        this.columns = [];
        this.renderedDataSource = new MatTableDataSource([]);
        this.hiddenCheckHead = true;
    }

    /**
     * when data change trigger
     * @param changes changed data
     */
    ngOnChanges(changes: SimpleChanges): any {
        if (changes['dataSource']) {
            this.renderedDataSource = new MatTableDataSource(this.dataSource);
        }
    }
    /**
     * init the columns
     */
    ngOnInit(): any {
        this.columns.forEach((v, i, arr) => {
            if (!v.isShow) {
                arr.splice(i, 1);
                this.columns = arr;
            }
        });
        if (this.tableActions) {
            this.columns.push({
                field: 'actions',
                title: '操作',
                isShow: true,
                isSort: false,
                type: ColumnType.default
            });
        }
        if (this.checkable) {
            this.columns.unshift({
                field: 'check',
                title: '选择',
                isShow: true,
                isSort: false,
                type: ColumnType.default
            });
        }
    }
    /**
     * Whether the number of selected elements matches the total number of rows.
     */
    isAllSelected(): any {
        const numSelected = this.selectionRow.selected.length;
        const numRows = this.renderedDataSource.data.length;
        return numSelected === numRows;
    }
    /**
     * Selects all rows if they are not all selected; otherwise clear selection.
     */
    masterToggle(): any {
        this.isAllSelected()
            ? this.selectionRow.clear()
            : this.renderedDataSource.data.forEach((row: any) => {
                  this.selectionRow.select(row);
              });
    }
    /**
     * click single row and output the event
     * @param row checked row
     */
    checkChanged(row?: any): any {
        this.checkChange.emit(this.selectionRow.selected);
    }
    /**
     * The label for the checkbox on the passed row
     * @param row checked data
     */
    checkboxLabel(row?: any): string {
        if (!row) {
            return `${this.isAllSelected() ? 'select' : 'deselect'} all`;
        } else {
            return `${
                this.selectionRow.isSelected(row) ? 'deselect' : 'select'
            } row ${row.ID + 1}`;
        }
    }

    sorted(data: any): any {
        this.sort.emit(data);
    }
}


table.component.html

<div class="table-container">
  <div fxLayout="column" fxLayoutAlign="start none" fxLayoutGap="20px">

    <mat-table #table class="table" mat-table [dataSource]="renderedDataSource" [class.no-data]="!dataSource.length"
      [attr.data-placeholder]="placeholder" matSort cdkDropListGroup notaddPerfectScrollbar
      [notaddPerfectScrollbarOptions]="{suppressScrollX: true, updateOnRouteChange : true}"
      (matSortChange)="sorted($event)">
      <ng-container *ngFor="let col of columns; let i = index" [matColumnDef]="col.field">
        <ng-container *ngIf="col.isSort">

            <mat-header-cell *matHeaderCellDef mat-sort-header fxflex  >
                <ng-container *ngTemplateOutlet="headerCheckable;context:{col:col}" ></ng-container>
                <ng-container *ngTemplateOutlet="headerUnCheckable;context:{col:col}" ></ng-container>
                
            </mat-header-cell>
    
        </ng-container>
        <ng-container *ngIf="!col.isSort">

            <mat-header-cell *matHeaderCellDef fxflex  >
                <ng-container *ngTemplateOutlet="headerCheckable;context:{col:col}" ></ng-container>
                <ng-container *ngTemplateOutlet="headerUnCheckable;context:{col:col}" ></ng-container>
                
            </mat-header-cell>
    
        </ng-container>
        
        <ng-template #headerCheckable let-col="col">

                <ng-container *ngIf="col.field !== 'check'">
                  {{ col.title }}
                </ng-container>
        </ng-template>
        <ng-template #headerUnCheckable let-col="col">

      
            <ng-container  *ngIf="col.field==='check' && !hiddenCheckHead">
                <ng-container *ngTemplateOutlet="allCheckboxTemp;">
                </ng-container>
              </ng-container>
        </ng-template>
        
        
        <mat-cell *matCellDef="let row" fxflex>
          <ng-container *ngIf="col.field === 'check'">
            <ng-container *ngTemplateOutlet="checkboxTemp;context{data:row}">
            </ng-container>
          </ng-container>
          <ng-container *ngIf="col.field !== 'check'">
            <ng-container *ngIf="col.type===columnType.slideToggle">
              <ng-container *ngTemplateOutlet="slideToggle;context{data: row}"></ng-container>
            </ng-container>
            <ng-container *ngIf="col.type === columnType.default">
              {{ row[col.field] }}
            </ng-container>
            <ng-container *ngIf="col.field ==='actions'" >
              <div [class.rowHover]="row.hover" >
                <ng-container *ngTemplateOutlet="tableActions;context: {data: row,rowHover:rowHover}"></ng-container>
              </div>
            </ng-container>
          </ng-container>
        </mat-cell>
      </ng-container>
      <mat-header-row *matHeaderRowDef="columns | tableColumnKeys;sticky: true;"></mat-header-row>
      <mat-row *matRowDef="let row; columns: columns| tableColumnKeys; " (mouseenter)="row.hover=true" (mouseleave)="row.hover=false"></mat-row>
    </mat-table>
    <ng-template #checkboxTemp let-row="data">
      <mat-checkbox (click)="$event.stopPropagation()" color="primary"
        (change)="$event ? selectionRow.toggle(row) : null;checkChanged(row);" [checked]="selectionRow.isSelected(row)"
        [aria-label]="checkboxLabel(row)">
      </mat-checkbox>
    </ng-template>
    <ng-template #allCheckboxTemp>
      <mat-checkbox (change)="$event ? masterToggle() : null;checkChanged();" [disabled]="hiddenCheckTitle" color="primary"
        [checked]="selectionRow.hasValue() && isAllSelected()"
        [indeterminate]="selectionRow.hasValue() && !isAllSelected()" [aria-label]="checkboxLabel()">
      </mat-checkbox>
    </ng-template>
    <ng-template #slideToggle let-row="data">
      <mat-slide-toggle class="example-margin" color="primary" [checked]="row.status" matTooltip="可用/禁用">
      </mat-slide-toggle>
    </ng-template>
  </div>
</div>

调用时:

call.component.html

<table   
                    [dataSource]="tableDatasource" 
                    [columns]="tableColumns"  
                    [checkable]="checkable"
                    (checkChange)="checkChanged($event)"
                    (sort)="onSorted($event)"
                    >

          <ng-template #tableActions let-row>
                <button mat-icon-button  class=" operate-icon operate-icon-view"  (click)="onViewClick(row)"  >
                  <mat-icon class="s-16" matTooltip="查看">remove_red_eye</mat-icon>
                </button>
          </ng-template>
    </table>

call.component.ts (部分代码)


    tableDatasource: any; // table数据 
    tableColumns: TableColumns[]; // table column
    checkable: boolean;
    
    ----------
    
    checkChanged(data: any){
        console.log(data);
    }
    onSorted(data: any){
        console.log(data)
    }

好了,到这一个公共表格就开发完成了。有问题欢迎共同探讨。