蚂蚁金服体验团队为社区贡献了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文件导入到浏览器中了。