我创建了一个Jasmine规范文件来测试指令。我基本上是在尝试确保在应用指令时以格式化的方式设置在组件中设置的值。我不确定是否需要在测试中显式调用指令的registerOnChange以使格式起作用。 registerOnChange是一个回调函数。我不确定是否必须调用registerOnChange或Onchange方法。 目前,我无法更改输入格式。我期望112500返回以逗号分隔的输出112500。有人可以指出我在做什么错。
规格
@Component({
template: `<form> <input id="itemDefault" type="number" name="title" numberFormat="number:.0-0"></form>`,
host: { '(blur)': 'blurred($event.target.value)' }
})
class NumberFormatComponent {
public decimalPlaces = 2;
private _item = '112500';
get item() {
return this._item;
}
set item(value) {
this._item = value;
}
}
describe('Directive: ShortNumberFormat', () => {
let component: NumberFormatComponent;
let fixture: ComponentFixture<NumberFormatComponent>;
let elDefault: DebugElement;
//let elDefaultDecimalPlaces: HTMLElement;
let numberFormat : DebugElement;
let translate: TranslateService;
let des:any;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
TranslateModule.forRoot({
loader: { provide: TranslateLoader, useClass: TranslateFakeLoader }
})
],
declarations: [NumberFormatComponent, NumberFormatDirective],
providers: [{ provide: TranslateService, useClass: TranslateService }]
});
translate = TestBed.get(TranslateService);
translate.setDefaultLang('en-US');
translate.use('en-US');
fixture = TestBed.createComponent(NumberFormatComponent);
component = fixture.componentInstance;
component.item;
component.decimalPlaces;
fixture.detectChanges();
});
fit('should toggle menu', () => {
TestBed.compileComponents().then(() => {
// const fixture = TestBed.createComponent(NumberFormatComponent);
const elDefault = fixture.debugElement.query(By.directive(NumberFormatDirective));
fixture.detectChanges();
//expect(directiveEl.nativeElement.getAttribute('numberFormat')).toEqual('number:.0-0');
console.log(elDefault.nativeElement.getAttribute('numberFormat'));
const directiveInstance = elDefault.injector.get(NumberFormatDirective);
directiveInstance.format = elDefault.nativeElement.getAttribute('numberFormat');
directiveInstance.inputType = elDefault.nativeElement.getAttribute('type');
directiveInstance.onChange(component.item);
console.log(component.item);
});
});
numberformat指令
import {
Input, Directive, forwardRef, ExistingProvider, Renderer, ElementRef, PipeTransform,
Inject, LOCALE_ID
} from '@angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';
import { DecimalPipe, PercentPipe } from '@angular/common';
import { IntPercentPipe } from '../pipes/int-percent.pipe';
import { NumberPercentPipe } from '../pipes/number-percent.pipe';
const CUSTOM_VALUE_ACCESSOR: ExistingProvider = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NumberFormatDirective), multi: true };
const _NUMBER_FORMAT_REGEXP = /^(\d+)?\.((\d+)(\-(\d+))?)?$/;
const _NUMBER_MATCH_REGEXP = /-?\d+(\.\d+)?/;
function precision(a: number) {
if (!isFinite(a)) return 0;
let e = 1, p = 0;
while (Math.round(a * e) / e !== a) { e *= 10; p++; }
return p;
}
function round_number(num: number, decimals: number) {
return Math.round(num * Math.pow(10, decimals)) / Math.pow(10, decimals);
}
@Directive({
selector: 'input[numberFormat]',
host: { '(blur)': 'blurred($event.target.value)' },
providers: [CUSTOM_VALUE_ACCESSOR]
})
export class NumberFormatDirective implements ControlValueAccessor {
@Input('numberFormat') format: string;
@Input('type') inputType: string;
private _parsedFormat: {
pipe: {
instance: PipeTransform,
map?: (x: number) => number
},
numbers: {
minimumIntegerDigits: number,
minimumFractionDigits: number,
maximumFractionDigits: number
},
raw: string;
};
constructor(private _renderer: Renderer, private _elementRef: ElementRef, @Inject(LOCALE_ID) private locale: string) { }
onChange = (_: any) => { };
onTouched = () => { };
blurred(value: any) {
this.onChange(value);
this.onTouched();
}
formatToString(value: number): string {
if (!this._parsedFormat) this.parseFormat();
const pipe = this._parsedFormat.pipe;
return pipe.instance.transform(value, this._parsedFormat.raw);
}
parseToNumber(value: string): number {
value = value.replace(/,| /g, ''); // strip out commas and whitespaces
const match = _NUMBER_MATCH_REGEXP.exec(value);
if (!match) {
this._renderer.setElementProperty(this._elementRef.nativeElement, 'value', '');
return undefined;
}
let number = parseFloat(match[0]);
if (isNaN(number)) {
this.writeValue(null);
return null;
}
if (this._parsedFormat.numbers.maximumFractionDigits !== undefined) {
number = round_number(number, this._parsedFormat.numbers.maximumFractionDigits);
}
const pipe = this._parsedFormat.pipe;
if (pipe.map) number = pipe.map(number);
this.writeValue(number);
return number;
}
writeValue(value: number): void {
if (this.inputType === 'number') {
this._renderer.setElementProperty(this._elementRef.nativeElement, 'value', value);
} else {
const text = this.formatToString(value);
this._renderer.setElementProperty(this._elementRef.nativeElement, 'value', text);
}
}
registerOnChange(fn: (_: number) => void): void {
this.onChange = (value) => { fn(value === '' ? null : this.parseToNumber(value)); };
}
registerOnTouched(fn: () => void): void { this.onTouched = fn; }
private parseFormat() {
if (!this.format) throw new Error(`format string not set`);
const split = this.format.split(':');
const getPipe = (input: string): {
instance: PipeTransform,
map?: (x: number) => number
} => {
if (input === 'number') return { instance: new DecimalPipe(this.locale) };
if (input === 'numberPercent') return { instance: new NumberPercentPipe(this.locale), map: x => parseFloat((x / 100).toFixed(precision(x) + 2)) };
if (input === 'percent') return { instance: new PercentPipe(this.locale), map: x => parseFloat((x / 100).toFixed(precision(x) + 2)) };
if (input === 'intPercent') return { instance: new IntPercentPipe(this.locale) };
throw new Error(`unknown formatter pipe: ${input}`);
};
const digits = split[1];
let minInt = 1, minFraction = 0, maxFraction = 3;
if (!!digits) {
const parts = _NUMBER_FORMAT_REGEXP.exec(digits);
if (!(parts)) {
throw new Error(`${digits} is not a valid digit info for number pipes`);
}
if (!!parts[1]) { // min integer digits
minInt = parseInt(parts[1]);
}
if (!!parts[3]) { // min fraction digits
minFraction = parseInt(parts[3]);
}
if (!!parts[5]) { // max fraction digits
maxFraction = parseInt(parts[5]);
}
}
this._parsedFormat = {
pipe: getPipe(split[0]), numbers: {
minimumIntegerDigits: minInt,
minimumFractionDigits: minFraction,
maximumFractionDigits: maxFraction,
}, raw: digits
};
}
}