Angular UI組件編寫學習記錄 - Button

寫在前面的話

螞蟻金服體驗團隊爲社區貢獻了Ant.Design這樣優秀的UI框架,官方主要以React實現,同時社區也涌現了很以React、Vue等框架爲基礎的實現,惟獨缺乏Angular的實現。那麼開始嘗試造輪子,把Ant.Design移植到Angular上來。本文從零開始,把踩過的坑都記錄下來。css

本文不是教程,主要記錄學習的過程,若有錯誤或者更好的實現,煩請慷慨賜教。html

本文目前用到的主要技術框架:node

下文所說起的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

文件正常編譯並啓動。

第一個模塊 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文件導入到瀏覽器中了。

預期中的效果

圖片描述

本文涉及代碼:https://github.com/ng-compone...

相關文章
相關標籤/搜索