angular自定义组件-UI组件篇-switch组件

前言

前端框架多吗? 多! 前端UI组件库多吗? 更多! 咱们都知道,前端生态圈里提供了各色各样的组件库供咱们选择使用,大多数都能知足开发者的需求,相信你们也都用过不少。可是实际上,据我了解到的,稍微大一些有本身产品的公司都会有一套自定义的UI组件库,知足自身复杂的需求与绚丽的效果。 博主目前所在的公司也有一套本身的产品,PC端所用的前端框架是Angular4. Angular4其实也有它专门定制的前端组件库PrimeNg.就像Vue.jsElement同样. 那么按理在开发中咱们已经有了前端组件库可使用,为何还要花那么多的精力和时间来从新设计UI组件呢?话很少说,先上几张官方提供的UI组件图: css

offSwitch
onSwitch
确实不是我吐槽,官方提供的一些组件样式真的有点奇怪😭...虽然Angular官网 组件样式这一文档中已经说明了能够用 ::ng-deep来进行对组件样式的修改,但修改起来仍是比较麻烦。有那时间,本身都已经撸了一个了... (项目中用的 primengv4.2.2版本的,目前已经迭代到了 v6.1.5,因此如今官网上看到的 inputSwitch组件会比这个好看点) emmm....为了追求用户体验(呸,熟悉 angular4的使用)因此博主决定利用闲暇之余自定义一些UI组件,以知足咱们产品"一些无礼的要求"。

1、肯定组件存放的位置

一个项目中会有各类文件、文件夹,如何存放管理好这些文件真的很重要。不只为本身提供了方便,也为后来的开发者提供方便。 因此咱们在设计公用组件的时候也应该把它们都归结在一块儿。 我习惯在项目中新建一个common文件夹,里面存放一些共用的compoentservice等等。 html

app/common/component
如上图,能够看到 common文件夹下导出的是一个名为 shared的模块。 shared模块的建立过程: (1)打开命令行(使用vscode编辑器的小伙能够直接使用Ctrl+` 快捷键打开终端,而后一路跳转到common文件夹:

cd src\app\common
复制代码

(2) 使用建立模块的指令:前端

ng g m shared
复制代码

其实很好理解:ngangular一向的指令,ggenerate建立的缩写,mmodule模块的缩写,后面接着你的模块名。(后面建立组件也是这个原理) 建立的模块实际上导出的是一个带有@NgModule装饰器的类而已,其中提供了咱们自定义的公有组件component,公有服务service,以及管道pipe等等。vue

2、建立组件

因为咱们要建立的是一个switch公用组件,因此在component文件夹下在建立一个文件夹general-control,以前都是直接堆积在component文件夹下的,近期发现堆得有点多了,因此又单首创建了一个general-control文件夹来存放一些基础的公用组件。 此时你须要打开命令行(使用vscode编辑器的小伙能够直接使用Ctrl+` 快捷键打开终端,而后一路跳转到general-control文件夹:sass

cd src\app\common\component\general-control
复制代码

在此目录下执行指令:bash

ng g c switch
复制代码

上面指令的意思是建立一个名为switch的组件,原理和建立模块时同样。 能够看到如今的general-control文件夹下多出了一些东西: 前端框架

switch文件夹
没错,就是咱们使用指令建立的 switch组件。 指令会自动帮你生成一个文件夹和4个文件。(基于 TypeScript的语法,因此生成的 js文件也就是 ts) 很好理解,对应的 html文件编写 HTML代码css文件编写 CSS代码ts文件编写 js代码,至于 spec.ts文件咱们能够不用管它。 因为我在项目中使用的是 sass,因此将 switch.component.css这个文件的后缀名修改成 scss(使用了less等其它扩展语言的小伙同理),并在 ts中对 css的引用进行修改:
scss
修改ts

使用上面的指令建立的组件是会被自动引用到shared这个模块中的。 shared.module.ts:app

import { SwitchComponent } from './component/general-control/switch/switch.component';//模块中import引入组件

@NgModule({
declarations: [
  SwitchComponent  //模块中声明组件
  ...
  ]
})
复制代码

上面俩步是你在使用ng g c switch指令时自动帮你完成的,但如果你想在其它的模块中使用这个switch组件,还得将其导出,导出的方式是将这个组件添加至shared.module.ts文件的exports中:框架

import { SwitchComponent } from './component/general-control/switch/switch.component';//模块中import引入组件

@NgModule({
  declarations: [
    SwitchComponent  //模块中声明组件
    ...
    ],
  exports: [
    SwitchComponent  //模块中导出组件
    ...
  ]
})
复制代码

完成上面的步骤你就能够安心的来开发本身的组件了。less

3、编写switch组件

一番查找,发现网上也有不少自定义switch组件的文章和源码,多是你们都以为原生的样式很差看吧... 有使用input而后来进行修改样式的,也有用其它标签来自定义的。 博主这里找了一个最简单方案,一个span标签搞定:

// switch.component.html
<span class="weui-switch" [ngClass]="currentClass" [ngStyle]="style" (click)="toggle()">
    
</span>
复制代码

基础css

// switch.comonent.scss
.weui-switch {
    display: inline-block;
    position: relative;
    width: 38px;
    height: 23px;
    border: 1px solid #DFDFDF;
    outline: 0;
    border-radius: 16px;
    box-sizing: border-box;
    background-color: #DFDFDF;
    transition: background-color 0.1s, border 0.1s;
    cursor: pointer;
    &.disabled{
      opacity: 0.6;
      cursor: not-allowed;
    }
  }
  .weui-switch:before {
    content: " ";
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    border-radius: 15px;
    background-color: #FDFDFD;
    transition: transform 0.35s cubic-bezier(0.45, 1, 0.4, 1);
  }
  .weui-switch:after {
    content: " ";
    position: absolute;
    top: 0;
    left: 0;
    width: 56%;
    height: 97%;
    border-radius: 15px;
    background-color: #FFFFFF;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
    transition: transform 0.35s cubic-bezier(0.4, 0.4, 0.25, 1.35);
  }
  .weui-switch-on {
    border-color: #1AAD19;
    background-color: #1AAD19;
  }
  .weui-switch-on:before {
    border-color: #1AAD19;
    background-color: #1AAD19;
  }
  .weui-switch-on:after {
    transform: translateX(77%);
  }
复制代码

效果大概就是这个样子:

自定义switch组件
(录频并转换GIF推荐使用 GifGam) 能够看到,组件的样式设计大多都是使用伪类 :after:before来实现的,而开关的效果是经过点击的时候添 加/移除 classweui-switch-on来实现的。(讲js的时候会讲到)

因为咱们建立的switch组件是须要在多处使用,而且要向外输出一些值,因此在ts中咱们首先要引入一下@Input@Output装饰器和EventEmitter

import { Component, OnInit, Input, Output, EventEmitter, OnChanges } from '@angular/core';
复制代码

而且定义一些基础的变量

@Input() style;//{ 'width': '40px' }//外部组件输入的样式对象
  @Input() isChecked: boolean = false;//开关是否打开
  @Input() disabled: boolean = false;//开关是否被禁用
  @Output() change: EventEmitter<any> = new EventEmitter();
  _isSwitch: boolean = false;
  currentClass = {}
复制代码

此时咱们的ts变成了这样:

import { Component, OnInit, Input, Output, EventEmitter, OnChanges } from '@angular/core';

@Component({
  selector: 'app-switch',
  templateUrl: './switch.component.html',
  styleUrls: ['./switch.component.scss']
})
export class SwitchComponent implements OnInit, OnChanges {
  constructor() { }
  @Input() style;//{ 'width': '40px' }//外部组件输入的样式对象
  @Input() isChecked: boolean = false;//外部组件输入进来的:开关是否打开
  @Input() disabled: boolean = false;//开关是否被禁用
  @Output() change: EventEmitter<any> = new EventEmitter();
  _isSwitch: boolean = false;//switch组件自己的:开关是否打开
  currentClass = {} //class集合
  
  ngOnInit() {//初始化组件的生命周期
    
  }
  ngOnChanges() {//当被绑定的输入属性的值发生变化时调用
    
  }
}
复制代码

3.1 setIsSwitch()方法

组件中定义了俩个“开关是否打开”的变量isChecked_isSwitch 一个是外部组件传递进来的默认值,一个是 switch组件自身的值。 因此在组件进行初始化和发生改变的时候咱们应该让其统一:

ngOnInit() {//初始化组件的生命周期
    this.setIsSwitch();
  }
  ngOnChanges() {//当被绑定的输入属性的值发生变化时调用
    this.setIsSwitch();
  }
  setIsSwitch() {//设置_isSwitch
    this._isSwitch = this.isChecked;
  }
复制代码

3.2 setStyle()方法

因为是自定义的组件,咱们固然是但愿大小也能够自定义,因此我想要的效果是: 在调用组件的时候,输入一个宽度width属性,组件可以自动调节尺寸。 所以我在设计的时候就定义了一个style变量 它是一个对象,能够容许开发者输入任意的样式,格式为{ 'width': '40px' } 同时为了减小输入样式的复杂度,咱们还能够来编写一个方法,让组件可以根据宽度来调节高度:

setStyle() {//设置样式
    if (this.style) {
      if (this.style['width'] && !this.style['height']) {//如果输入了宽度没有输入高度则自动计算
        let width = this.getWidth(this.style['width']);
        this.style['height'] = (width * 0.55) + 'px';
      }
    }
  }
getWidth(widthStr) {//判断用户输入的width带不带px单位
    let reg = /px/;
    let width = reg.test(widthStr) ? widthStr.match(/(\d*)px/)[1] : widthStr //正则获取不带单位的值
    if (!width) width = 0;
    return width;
  }
复制代码

能够看到,上面我编写的setStyle()方法是判断有没有宽度和高度,并将高度设置为0.55 * width(0.55为我找到的最合适的比例)

3.3 setClass()方法

完成了上面的步骤咱们基本就完成了对组件样式的初始化,可是,最重要的一步固然是经过添加/移除一些类来进行组件的交互:

setClass() {//转换switch时切换class
    this.currentClass = {
      'disabled': this.disabled,
      'bg_main bor_main weui-switch-on': this._isSwitch
    }
  }
复制代码

对象currentClass存储的是组件变更的类名,对象的键名为类名,值为一个布尔类型的变量(true / false) 经过布尔类型的变量来判断添加仍是移除这些类名。 第一个类disabled表示的是开关是否被禁用,也就是用户只能查看开关,并不能对其进行操做,它受disabled变量控制。 第二个类为三个类名的合写bg_mainbor_main、和weui-switch-on,他们受_isSwitch变量控制, 也就是开关打开的时候则添加这三个类。 前俩个类名是我在项目中使用的“皮肤类名”,由于客户的须要,咱们产品有几套不一样的主题色,用户能够进行换肤功能来切换主题色,所以就有一些类名须要用来控制主题色。 如橘色主题:

.bg_main {
            background-color: #ff7920!important;
}
.bor_main {
            border-color: #ff7920!important;
}
复制代码

固然,你如果没有主题色的话请忽略这俩个类。

上面的几个方法咱们都须要在组件初始化和变量发生改变的时候调用,因此能够整合到一个函数中:

ngOnInit() {
    this.initComponent();
  }
  ngOnChanges() {
    this.initComponent();
  }
  initComponent() {
    this.setIsSwitch();
    this.setStyle();
    this.setClass();
  }
复制代码

3.4 toggle()方法

光有样式可没用,咱们还须要将组件和用户的行为给结合在一块儿,所以给组件一个click事件来进行交互,并编写toggle()方法:

toggle() {//切换switch
    if (this.disabled) return;//如果禁用时则直接返回
    this._isSwitch = !this._isSwitch;
    this.isChecked = this._isSwitch;
    this.change.emit(this._isSwitch); //向外部传递最新的值
  }
复制代码

整合后的ts文件为这样:

import { Component, OnInit, Input, Output, EventEmitter, OnChanges } from '@angular/core';

@Component({
  selector: 'app-switch',
  templateUrl: './switch.component.html',
  styleUrls: ['./switch.component.scss']
})
export class SwitchComponent implements OnInit, OnChanges {

  constructor() { }
  @Input() onLabel: string = '';//暂无
  @Input() offLabel: string = '';
  @Input() style;//{ 'width': '40px' }//外部组件输入的样式对象
  @Input() isChecked: boolean = false;//开关是否打开
  @Input() disabled: boolean = false;//开关是否被禁用
  @Output() change: EventEmitter<any> = new EventEmitter();
  _isSwitch: boolean = false;
  currentClass = {}

  ngOnInit() {
    this.initComponent();
  }
  ngOnChanges() {
    this.initComponent();
  }
  initComponent() {//初始化并刷新组件
    this.setIsSwitch();
    this.setStyle();
    this.setClass();
  }
  setIsSwitch() {
    this._isSwitch = this.isChecked;
  }
  setStyle() {//设置样式
    if (this.style) {
      if (this.style['width'] && !this.style['height']) {//如果输入了宽度没有输入高度则自动计算
        let width = this.getWidth(this.style['width']);
        this.style['height'] = (width * 0.55) + 'px';
      }
    }
  }
  setClass() {//转换switch时切换class
    this.currentClass = {
      'disabled': this.disabled,
      'bg_main bor_main weui-switch-on': this._isSwitch
    }
  }
  getWidth(widthStr) {//判断用户输入的width带不带px单位
    let reg = /px/;
    let width = reg.test(widthStr) ? widthStr.match(/(\d*)px/)[1] : widthStr //正则获取不带单位的值
    if (!width) width = 0;
    return width;
  }
  toggle() {//切换switch
    if (this.disabled) return;//如果禁用时则直接返回
    this._isSwitch = !this._isSwitch;
    this.isChecked = this._isSwitch;
    this.change.emit(this._isSwitch);
  }
}
复制代码

4、引用switch组件

完成了上面的部分,到了咱们最激动的时候了,看看咱们亲手制做的组件有没有用吧,哈哈。 首先,在使用其它组件的时候,咱们要将其引入进来,因为咱们最开始是将switch组件引入到shared这个模块中,并从这个模块中导出的,因此想要在其它模块中使用 switch组件就得先引入shared模块。

4.1 引入shared模块

本项目中有另外一个模块名为coursemanage,如今我将其做为父组件来引用一下switch组件 首先在模块里引用:

//coursemanage.module.ts
import { NgModule } from '@angular/core';
import { SharedModule } from "./../common/shared.module";
@NgModule({
  imports: [
      SharedModule
  ]
})
export class CourseManageModule { }
复制代码

引入了shared模块就至关因而引入那个那个模块中的全部组件和方法。

4.2 使用switch组件

coursemanage模块中,有其子组件course这个组件,在course中使用switch

<!--course.component.html-->
<app-switch [isChecked]="dataStatus" (change)="changeSwitch($event)"></app-switch>
复制代码
//course.component.ts

dataStatus: boolean = false;
changeSwitch($event) {
  this.dataStatus = $event;
}
复制代码

此时就完成了switch组件的编写和使用。 你也能够给组件设置另外一个属性disabled:

<!--course.component.html-->
<app-switch [isChecked]="dataStatus" [disable]="true" (change)="changeSwitch($event)"></app-switch>
复制代码

后语

上述设计的switch组件应该是UI组件中比较简单的一种UI组件了,还有更多复杂的组件有待咱们的开发,经过本身设计UI组件,emmm....可让咱们更有创造力吧应该说,也促使本身多去看别人的博客与源码,最后再写上一篇总结,我认为这应该是一个正向的激励💪,哈哈,全篇废话不少,不过仍是要感谢小伙的阅读🙂。

相关文章
相关标签/搜索