写在前面的话
蚂蚁金服体验团队为社区贡献了Ant.Design这样优秀的UI框架,官方主要以React实现,同时社区也涌现了很以React、Vue等框架为基础的实现,唯独缺少Angular的实现。那么开始尝试造轮子,把Ant.Design移植到Angular上来。本文从零开始,把踩过的坑都记录下来。
本文不是教程,主要记录学习的过程,如有错误或者更好的实现,烦请慷慨赐教。
本文目前用到的主要技术框架:
Typescript 2.1.0
Systemjs 0.19.0
下文所提及的Angular均以最新版本为准
准备工作
QuickStart这个项目种子包含了Angular、Typescript,转译工具是tsc,构建工具是Systemjs。
项目以官方文档里面推荐的QuickStart开始,先从GitHub上Clone下来:
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
文件正常编译并启动。
第一个模块 Button
Angular中UI组件一般以特性模块的方式出现,
- index.ts //用于编写Button Module - button.ts //用于编写Button Component - button.spec.ts //用于编写Button 测试用例(karam + jasmine),之后完善 - button.html //用于编写模板 - style/button.css //用于编写样式
编写Module
src/index.ts
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文件声明和导出。
核心和通用模块作用和用法参考官方文档。
编写Component文件
src/button.ts
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。
编写HTML模板
src/button.html
<button #AsButton [class]="classes" [type]="htmlType || 'button'" (click)="handleClick($event)" (mouseup)="handleMouseUp()"> <span> <ng-content></ng-content> </span> </button>
编写CSS样式文件
src/style/button.less to .css
参考Ant.Design的Button样式
导入到Example
我们需要看看组件效果是否达到了预期。
修改App Module
example/app/app.module.ts
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 { }
修改App Component
example/app/app.component.ts
import { Component } from '@angular/core'; @Component({ selector: 'my-app',templateUrl: './app.component.html',}) export class AppComponent { loading = false; handleOK(){ console.log("click") }; }
修改App Component Html
这里的html文件原版是没有的,我们来新建一个:
App.component.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文件导入到浏览器中了。
预期中的效果