螞蟻金服體驗團隊爲社區貢獻了Ant.Design這樣優秀的UI框架,官方主要以React實現,同時社區也涌現了很以React、Vue等框架爲基礎的實現,惟獨缺乏Angular的實現。那麼開始嘗試造輪子,把Ant.Design移植到Angular上來。本文從零開始,把踩過的坑都記錄下來。css
本文不是教程,主要記錄學習的過程,若有錯誤或者更好的實現,煩請慷慨賜教。html
本文目前用到的主要技術框架:node
Angularjs 4git
Typescript 2.1.0github
Systemjs 0.19.0npm
下文所說起的Angular均以最新版本爲準json
QuickStart這個項目種子包含了Angular、Typescript,轉譯工具是tsc,構建工具是Systemjs。bootstrap
項目以官方文檔裏面推薦的QuickStart開始,先從GitHub上Clone下來:windows
git clone https://github.com/angular/quickstart.git quickstart cd quickstart npm install npm start
項目已經能夠正常運行了,可是由於Clone下來的版本有許多不須要的東西,咱們先按照文檔裏的操做刪除原有的git版本庫:瀏覽器
rm -rf .git # OS/X (bash) rd .git /S/Q # windows
而後刪除項目裏的沒必要要文件(non-essential files )
彷佛刪除了測試文件
OS/X (bash)
xargs rm -rf < non-essential-files.osx.txt rm src/app/*.spec*.ts rm non-essential-files.osx.txt
Windows
for /f %i in (non-essential-files.txt) do del %i /F /S /Q rd .git /s /q rd e2e /s /q
這裏能夠把項目備份一份,以便後面的項目繼續使用。
接着修改項目名字和相應的package信息,咱們改爲button相關的。
以後初始化咱們本身的git版本:
git init
因爲是寫模塊,因此咱們在項目根目錄下新建一個example的文件夾,將src下的文件通通移到example上,同時須要修改package上的腳本信息,將全部指向src文件的命令通通指向example,而後運行命令:
npm start
文件正常編譯並啓動。
Angular中UI組件通常以特性模塊的方式出現,
咱們在src文件下新建幾個文件:
- index.ts //用於編寫Button Module - button.ts //用於編寫Button Component - button.spec.ts //用於編寫Button 測試用例(karam + jasmine),以後完善 - button.html //用於編寫模板 - style/button.css //用於編寫樣式
import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { AsButton } from './button' @NgModule({ imports: [ CommonModule ], exports: [ AsButton], declarations: [ AsButton ] }) export class AsButtonModule {}
這裏將模塊命名爲AsButtonModule,從button文件引入AsButton組件並在Module文件聲明和導出。
核心和通用模塊做用和用法參考官方文檔。
import { Component, Input, Output, EventEmitter, SimpleChange, ViewChild, ElementRef } from '@angular/core' import * as classNames from 'classnames' export type ButtonType = 'primary' | 'ghost' | 'dashed' | 'danger'; export type ButtonShape = 'circle' | 'circle-outline'; export type ButtonSize = 'small' | 'large'; @Component({ moduleId: module.id, selector: 'as-button', templateUrl: 'button.html', styleUrls: ['style/button.css'] }) export class AsButton { private classes: any; private _loading: boolean; private clicked: boolean; private oldClicked: boolean; timeout: any; delayTimeout: any; // 接口聲明 @Input() type: string @Input() htmlType: string @Input() icon: string @Input() shape: ButtonShape @Input() prefixCls: string @Input() size: ButtonSize @Input() loading: boolean @Input() ghost: boolean @Output() onClick = new EventEmitter<Event>(); @Output() onMouseUp = new EventEmitter<Event>(); @ViewChild('AsButton') button: ElementRef; constructor(){ this.prefixCls = "as-btn"; this.clicked = false; this.ghost = false; this._loading = false; } // 初始化class樣式 ngOnInit(){ this.updateClass() } // loading狀態更新 ngOnChange(changes: {[propKey: string]: SimpleChange}){ const currentLoading = this.loading const loading = changes["loading"]; if (currentLoading) { clearTimeout(this.delayTimeout); } if (loading){ this.delayTimeout = setTimeout(() => { this._loading = !!loading }) } else { this._loading = !!loading } } ngDoCheck() { // 檢測若是this.clicked的狀態改變,則觸發class更新 if (this.clicked !== this.oldClicked) { this.updateClass() this.oldClicked = this.clicked } } /** * 綁定點擊事件 */ handleClick = (e: Event) => { this.clicked = true; clearTimeout(this.timeout); // 防止點擊過快,延遲500毫秒 this.timeout = setTimeout(() => this.clicked = false, 500); // 若是父級組件綁定了點擊事件,則執行 const onClick = this.onClick; if (onClick) { onClick.emit(e) } } handleMouseUp = (e: Event) => { this.button.nativeElement.blur(); if (this.onMouseUp) { this.onMouseUp.emit(e) } } // 更新Class的方法 updateClass = () =>{ const { type, htmlType, icon, shape, prefixCls, size, ghost } = this const sizeCls = ({ large: 'lg', small: 'sm', })[size] || ''; this.classes = classNames(prefixCls, { [`${prefixCls}-${type}`]: !!type, [`${prefixCls}-${shape}`]: !!shape, [`${prefixCls}-${sizeCls}`]: !!sizeCls, // [`${prefixCls}-icon-only`]: !children && icon, [`${prefixCls}-loading`]: !!this._loading, [`${prefixCls}-clicked`]: !!this.clicked, [`${prefixCls}-background-ghost`]: !!ghost, }) } }
由於是移植的關係,組件的API和代碼實現基本參考原版的Ant.Design。
<button #AsButton [class]="classes" [type]="htmlType || 'button'" (click)="handleClick($event)" (mouseup)="handleMouseUp()"> <span> <ng-content></ng-content> </span> </button>
參考Ant.Design的Button樣式
咱們須要看看組件效果是否達到了預期。
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { AppComponent } from './app.component'; import { AsButtonModule } from '../../src/index'; @NgModule({ imports: [ BrowserModule, AsButtonModule ], declarations: [ AppComponent ], bootstrap: [ AppComponent ] }) export class AppModule { }
import { Component } from '@angular/core'; @Component({ selector: 'my-app', templateUrl: './app.component.html', }) export class AppComponent { loading = false; handleOK(){ console.log("click") }; }
這裏的html文件原版是沒有的,咱們來新建一個:
<div> <as-button type="primary" >Primary</as-button> <as-button>Default</as-button> <as-button type="dashed">Dashed</as-button> <as-button type="danger" (onClick)="handleOK($event)">Danger</as-button> </div>
若是此時運行了npm start,命令行應該提示沒法找到src下面的文件,報404。
這裏使用了Systemjs和Browsersync來同步項目,Systemjs沒有正取識別咱們的引用文件,咱們找到項目根目錄下的ts-config.json文件進行修改,添加一行:
{ "server": { "baseDir": "examples", "routes": { "/node_modules": "node_modules", + "/src": "src" } } }
這樣,Systemjs就能正確的將src下面編譯好的js文件導入到瀏覽器中了。