使用Angular可重用Component思路实现一个自带图标(icon)的input控件

350 阅读2分钟

参考链接:Angular ng-content and Content Projection: A Complete Guide - How To Use ng-content To Improve Component API Design

实现的mockup如下:

初次尝试:假设我们已经创建好了一个自定义Component,其selector为fa-input, 首先看看如何使用:

@Component({
  selector: 'app-root',
  template: `
    <h1>FA Input</h1>
    <fa-input icon="envelope" (value)="onNewValue($event)"></fa-input>
    
    <fa-input icon="lock" (value)="onNewValue($event)"></fa-input>
  `
})
export class AppComponent {
  onNewValue(val) {
    console.log(val);
  }
}

自定义控件fa-input的实现代码:

@Component({
  selector: 'fa-input',
  template: `
    <i class="fa" [ngClass]="classes"></i>
    <input (focus)="inputFocus = true" (blur)="inputFocus = false"
           (keyup)="value.emit(input.value)" #input>
  `,
styleUrls: ['./fa-input.component.css']
})
export class FaInputComponent {
  @Input() icon: string;
  @Output() value = new EventEmitter<string>();
  inputFocus = false;

  get classes() {
    const cssClasses = {
      fa: true
    };
    cssClasses['fa-' + this.icon] = true;
    return cssClasses;
  }

  @HostBinding('class.focus')
  get focus() {
    console.log(this.inputFocus);
    return this.inputFocus;
  }
}

css:

:host {
  border: 1px solid grey;
}

input {
  border: none;
  outline: none;
}

:host(.focus) {
  border: 1px solid blue;
}

(1) icon属性决定待显示的图标外观。该自定义控件的使用者需要传入。
(2) the component has a custom output event named value, that emits new values whenever the value of the text input changes. 一个自定义的名为value的事件发生器,当text input发生变化时,将新的值通知控件使用者。
(3) 检测原生html input字段的focus和blur事件,设置对应的标志位inputFocus. 再将inputFocus通过@HostBinding绑定到host元素的focus CSS样式上。

目前这个设计有一些问题:

(1) 没有考虑到HTML原生的input字段的某些属性,比如:

<input type="email"  autocomplete="off" placeholder='Email'>

为了解决这个问题,需要把这些属性逐一手动迁移到自开发Component中去,但这样一来,自开发组件的template会膨胀不少:


@Component({
  selector: 'fa-input',
  template: `
    <i class="fa" [ngClass]="classes"></i>
    <input (focus)="inputFocus = true" (blur)="inputFocus = false"
           (keyup)="value.emit(input.value)" #input 
           [placeholder]="placeholder"
           [type]="type",
           [autocomplete]="autocomplete">
  `
})
export class FaInputComponent {
...
    @Input() placeholder:string;
    @Input() type:string;
    @Input() autocomplete:string;
...
}

这种设计的问题在于,消费者使用自定义控件时,完全不知道自定义控件背后包含一个元素的input字段,这样,消费者即使想使用原生input字段的某些属性,也缺乏一种优雅的机制传入到自定义Component中。

先看改进版本的自定义Component如何消费:

@Component({
  selector: 'app-root',
  template: `
    <h1>FA Input</h1>
    <fa-input icon="envelope">   
      <input type="email" placeholder="Email">
    </fa-input>  
`})
export class AppComponent {
  
}

一个明显的区别,就是原生input控件的出现。

新版使用ng-content的实现:

@Component({
  selector: 'fa-input',
  template: `
    <i class="fa" [ngClass]="classes"></i>
    <ng-content></ng-content>
  `,
  styleUrls: ['./fa-input.component.css']
})
export class FaInputComponent {

  @Input() icon: string;

  get classes() {
    const cssClasses = {
      fa: true
    };
    cssClasses['fa-' + this.icon] = true;
    return cssClasses;
  }
}

更多Jerry的原创文章,尽在:“汪子熙”: