Angular 4中Mat Input货币小数样式及失焦小数上标实现方案
嘿,我来帮你搞定这个Angular 4里Mat Input的货币格式问题!首先得说,Material Chip确实不适合这个场景——它是用来展示标签/选择项的,和输入框的编辑逻辑完全不搭,咱们换个思路来实现你要的效果:输入时显示常规数字,失焦后小数部分用上标呈现。
推荐方案:自定义货币输入组件
因为Mat Input本质是原生input的封装,没法直接渲染富文本(比如<sup>上标标签),所以咱们做一个“视觉切换”的组件:失焦时显示格式化后的文本,聚焦时切换回可编辑的输入框,同时保证表单值的正确性。
1. 组件模板(currency-input.component.html)
<div class="currency-input-container" (click)="focusInput()"> <!-- 失焦时展示的格式化文本 --> <span *ngIf="!isFocused" class="display-text"> {{ integerPart }}<sup>{{ decimalPart }}</sup> </span> <!-- 聚焦时显示的Mat Input --> <mat-form-field *ngIf="isFocused" class="input-field"> <input matInput [(ngModel)]="value" (focus)="onInputFocus()" (blur)="onInputBlur()"> </mat-form-field> </div>
2. 组件逻辑(currency-input.component.ts)
import { Component, Input, Output, EventEmitter, OnInit, OnChanges } from '@angular/core'; @Component({ selector: 'app-currency-input', templateUrl: './currency-input.component.html', styleUrls: ['./currency-input.component.scss'] }) export class CurrencyInputComponent implements OnInit, OnChanges { @Input() value: string = ''; @Output() valueChange = new EventEmitter<string>(); isFocused: boolean = false; integerPart: string = '0'; decimalPart: string = '00'; private readonly decimalPlaces: number = 2; ngOnInit() { this.formatDisplayValue(); } ngOnChanges() { // 外部值变化时同步更新显示 this.formatDisplayValue(); } onInputFocus() { this.isFocused = true; } onInputBlur() { this.isFocused = false; // 输入完成后同步值到父组件,并重新格式化显示 this.valueChange.emit(this.value); this.formatDisplayValue(); } focusInput() { if (!this.isFocused) { // 点击显示文本时切换到输入框并自动聚焦 this.isFocused = true; setTimeout(() => { const inputEl = document.querySelector('.input-field input') as HTMLInputElement; inputEl?.focus(); }, 0); } } private formatDisplayValue() { if (!this.value) { this.integerPart = '0'; this.decimalPart = '0'.repeat(this.decimalPlaces); return; } const numValue = parseFloat(this.value); if (isNaN(numValue)) { this.integerPart = '0'; this.decimalPart = '0'.repeat(this.decimalPlaces); return; } // 拆分整数和小数部分 const fixedValue = numValue.toFixed(this.decimalPlaces); const [integer, decimal] = fixedValue.split('.'); this.integerPart = integer; this.decimalPart = decimal; } }
3. 组件样式(currency-input.component.scss)
.currency-input-container { display: inline-block; cursor: text; font-size: 16px; min-width: 120px; .display-text { padding: 8px 12px; display: inline-block; sup { font-size: 0.7em; vertical-align: super; margin-left: 2px; } } .input-field { margin: 0; width: 100%; mat-form-field { width: 100%; font-size: 16px; } } }
如何使用这个组件
在父组件的模板里直接用双向绑定就行,和普通Mat Input的用法几乎一样:
<app-currency-input [(ngModel)]="productPrice"></app-currency-input>
备选方案:指令+CSS视觉模拟(适合简单场景)
如果不想写完整组件,也可以用指令配合CSS来模拟效果,但这个方案只是视觉层面的,需要额外处理文本同步,不如组件方案可靠:
- 先写一个监听焦点的指令:
import { Directive, ElementRef, HostListener, Renderer2 } from '@angular/core'; @Directive({ selector: '[appCurrencySuperscript]' }) export class CurrencySuperscriptDirective { private decimalSpan: HTMLElement; constructor(private el: ElementRef, private renderer: Renderer2) { // 创建用来显示小数上标的span this.decimalSpan = this.renderer.createElement('span'); this.renderer.addClass(this.decimalSpan, 'decimal-sup'); this.renderer.appendChild(this.el.nativeElement.parentElement, this.decimalSpan); } @HostListener('focus') onFocus() { this.renderer.addClass(this.el.nativeElement.parentElement, 'focused'); } @HostListener('blur') onBlur() { this.renderer.removeClass(this.el.nativeElement.parentElement, 'focused'); this.updateDecimalText(); } @HostListener('input') onInput() { this.updateDecimalText(); } private updateDecimalText() { const value = this.el.nativeElement.value; if (!value) { this.decimalSpan.textContent = '00'; return; } const numValue = parseFloat(value); if (isNaN(numValue)) { this.decimalSpan.textContent = '00'; return; } const decimalPart = numValue.toFixed(2).split('.')[1]; this.decimalSpan.textContent = decimalPart; } }
- 模板和CSS:
<div class="currency-wrapper"> <input matInput appCurrencySuperscript [(ngModel)]="price"> </div>
.currency-wrapper { position: relative; display: inline-block; input { padding-right: 30px; transition: color 0.2s; } .decimal-sup { position: absolute; right: 10px; top: 4px; font-size: 0.7em; opacity: 0; transition: opacity 0.2s; } &:not(.focused) input { color: transparent; } &:not(.focused) .decimal-sup { opacity: 1; } }
为啥Material Chip没用?
Chip组件的定位是展示可选择/可移除的标签项,它没有输入框的编辑能力,也没法和表单值做双向绑定,所以完全不适合你要的“输入时编辑、失焦时格式化”的交互场景,咱们还是得围绕表单输入控件来定制才行。
内容的提问来源于stack exchange,提问作者dhana
相关产品推荐
相关产品推荐

