写在前面的话
蚂蚁金服体验团队为社区贡献了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组件一般以特性模块的方式出现,
我们在src文件下新建几个文件:
- 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文件导入到浏览器中了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。