如何将Angular Material日期值转换为ISO格式YYYY-MM-DD
需求说明
我需要将MaterialDatePicker选中的日期值转换为和原生input[type=date]一致的ISO格式,目标是让formControl中存储的日期值固定为YYYY-MM-DD格式。想确认Angular Material是否提供了对应的内置属性或方法直接实现该需求。
我查阅了大量相关解答,给出的方案实现逻辑都比较繁琐,因此自己写了一个简易实现,但认为该方案效率不高,代码如下:
现有实现代码
app.component.html
<form [formGroup]="form"> <mat-form-field appearance="fill"> <mat-label>选择日期</mat-label> <input matInput [matDatepicker]="picker" formControlName="date1" readonly /> <mat-hint>{{this.form.get('date1').value}}</mat-hint> <!-- 当前获取到的默认值 --> <mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle> <mat-datepicker #picker></mat-datepicker> </mat-form-field> </form><br> {{this.form.get('date2').value}} <!-- 期望得到的YYYY-MM-DD格式值 -->
app.component.ts
constructor(private fb: FormBuilder) {} form = this.fb.group({ date1: [''], date2: [''] }); ngOnInit() { this.form.get('date1').valueChanges.subscribe(value => this.form.patchValue({ date2: new Date(value).toISOString().split('T')[0] })); }
待解答疑问
- 为什么实现该需求单个输入字段需要使用2个formControl?
- 能否在
valueChanges回调中直接更新当前字段自身的值?我之前尝试该操作时出现RangeError: Maximum call stack size exceeded报错,判断该报错出现符合逻辑,但想确认是否有可行写法。 - 是否存在实现该需求的简便技巧?
回答
你当前用两个FormControl存值的方案属于冗余实现,Angular Material本身提供了日期值的格式化、解析自定义能力,完全不需要额外加控件或者冗余的变更监听。
疑问对应解答
- 不需要两个控件实现需求,你现在的双控件写法是没有用到框架自带能力的绕路方案。
- 可以在
valueChanges回调中更新当前字段自身的值,只要调用patchValue时传入{emitEvent: false}配置,就不会再次触发值变更事件造成无限递归,也就不会出现栈溢出错误。示例写法如下:
this.form.get('date1').valueChanges.subscribe(value => { const formatVal = new Date(value).toISOString().split('T')[0]; this.form.get('date1').patchValue(formatVal, {emitEvent: false}); });
不推荐这种写法:Material DatePicker默认绑定值为Date类型,直接将字符串写回控件会导致后续打开日期选择器时出现解析异常。
最优简便实现方案
直接使用Angular Material提供的MAT_DATE_FORMATS注入令牌自定义日期解析、输出格式即可,支持组件级或全局配置,不需要写值变更订阅,也不需要冗余控件,配置完成后控件值会自动按要求格式化。
组件级配置示例:
import { Component } from '@angular/core'; import { FormBuilder, FormGroup } from '@angular/forms'; import { MAT_DATE_FORMATS, DateAdapter } from '@angular/material/core'; // 自定义ISO日期格式配置 const MY_ISO_DATE_FORMAT = { parse: { dateInput: 'YYYY-MM-DD', }, display: { dateInput: 'YYYY-MM-DD', monthYearLabel: 'YYYY年MM月', dateA11yLabel: 'LL', monthYearA11yLabel: 'YYYY年MM月', }, }; @Component({ selector: 'app-root', templateUrl: './app.component.html', providers: [ { provide: MAT_DATE_FORMATS, useValue: MY_ISO_DATE_FORMAT } ] }) export class AppComponent { form: FormGroup; constructor(private fb: FormBuilder, private dateAdapter: DateAdapter<Date>) { // 可根据需求设置本地化区域,避免时区、显示偏差 this.dateAdapter.setLocale('zh-CN'); this.form = this.fb.group({ date1: [''] // 仅需保留这一个表单控件即可 }); } }
如果你使用
@angular/material-moment-adapter作为日期适配器,配置完成后可直接通过Moment对象的format('YYYY-MM-DD')方法拿到目标字符串,或配合值转换配置直接让表单存储字符串格式值,性能远高于手动订阅转换,也不会出现值不同步问题。
如果不想做注入配置,更轻量的做法是在表单提交时统一用DatePipe对日期字段做格式化转换,不需要在值变更阶段做实时处理,完全没必要增加额外的订阅逻辑。
内容的提问来源于stack exchange,提问作者avishekdr

