明确需求
业务场景: 在根据某些条件搜索产品时,需要有两个输入框输入单价的范围。当输入有效时,搜索按钮处于可点击状态。
实现思路:使用一个FormGroup包裹两个FormControl分别接收单价的最低值与最高值。
功能点:
- 双边检查:当两个输入框输入均为空时,判定为有效,此时搜索将会显示全量数据。
- 单边检查:当只有一个输入框为空时,判定为无效。并且空值框的边框会变红。
- 区间范围检查:当最低价高于最高价时,判定为无效。
实现
如果只考虑第三点,两个FormControl可以共用一个validator函数判断其是否有效。实现如下
// An easy method
//!!!!!!worng!!!!!!!!!
import { Component, Input, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';
import * as _ from 'lodash';
@Component({
selector: 'app-range',
templateUrl: './range.component.html',
styleUrls: ['./range.component.scss'],
})
export class RangeComponent implements OnInit {
constructor() {
}
@Input()
title: string = '单价';
@Input()
startValue: FormControl;
@Input()
endValue: FormControl;
ngOnInit() {
this.startValue.setValidators(this.validator());
this.endValue.setValidators(this.validator());
}
private validator() {
return () => {
if (this.isValueEmpty(this.startValue.value) && !this.isValueEmpty(this.endValue.value)
&& this.isInvalidValueRange) {
return { outOfRange: true };
}
};
}
isValueEmpty = (value: number) => {
return !_.isNumber(value) && !_.isBoolean(value) && _.isEmpty(value);
}
}
但是这样虽然满足了需求1和3,但是将无法满足需求2。需求2中,当两个输入框均为空,没有框变红。当输入一个值后,当前这个框不变红但是另一个框将感应到这个框的变化而变红。因此在程序中加入Subscriber来传递变化消息。实现如下:
import { Component, Input, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';
import * as _ from 'lodash';
@Component({
selector: 'app-range',
templateUrl: './range.component.html',
styleUrls: ['./range.component.scss'],
})
//The correct method
export class RangeComponent implements OnInit {
constructor() {
}
@Input()
title: string = '单价';
@Input()
startValue: FormControl;
@Input()
endValue: FormControl;
ngOnInit() {
this.startValue.setValidators(this.startValueValidator());
this.endValue.setValidators(this.endValueValidator());
this.initSubscriber();
}
private endValueValidator() {
return () => {
if ((this.isValueEmpty(this.endValue.value) && !this.isValueEmpty(this.startValue.value))
|| this.isInvalidValueRange) {
return { outOfRange: true };
}
};
}
private startValueValidator() {
return () => {
if ((this.isValueEmpty(this.startValue.value) && !this.isValueEmpty(this.endValue.value))
|| this.isInvalidValueRange) {
return { outOfRange: true };
}
};
}
get isInvalidValueRange() {
return !this.isValueEmpty(this.startValue.value) && !this.isValueEmpty(this.endValue.value)
&& this.startValue.value > this.endValue.value;
}
initSubscriber() {
this.startValue.valueChanges
.subscribe(() => this.endValue.updateValueAndValidity({ emitEvent: false }));
this.endValue.valueChanges
.subscribe(() => this.startValue.updateValueAndValidity({ emitEvent: false }));
}
isValueEmpty = (value: number) => {
return !_.isNumber(value) && !_.isBoolean(value) && _.isEmpty(value);
}
}
接下来补充以下html文件以及样式文件:
Range.scss
.input-range {
font-size: 14px;
}
input {
display: inline-block;
width: 100px;
height: 32px;
border-radius: 3px;
border: solid 1px $silver;
margin: 0 8px;
}
.invalid-input-border {
border-color: $cinnabar;
}
Range.html
<div class="input-range">
<span>{{title}}</span>
<input class="input-control form-control"
type="number"
[ngClass]="{'invalid-input-border': startValue.invalid}"
[formControl]="startValue">
<span> - </span>
<input class="input-control form-control"
type="number"
[ngClass]="{'invalid-input-border': endValue.invalid}"
[formControl]="endValue">
</div>