如何将角度材料日期选择器中的日期作为字符串绑定到模型?

时间:2020-06-11 10:08:57

标签: angular angular-material

我们使用标准的日期选择器组件,该组件来自Angular材料(第9.1.2版),如下所示:

<mat-form-field>
    <mat-label i18n>Date of birth</mat-label>
    <input matInput [matDatepicker]="picker" formControlName="dateOfBirth" />
    <mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
    <mat-datepicker #picker></mat-datepicker>
</mat-form-field>

日期采用ISO格式,例如1979-12-02。绑定到表单并显示后,就像在整个表单上调用getRawValue一样,可以将其取回。但是,这会以javascript Date的形式返回日期,然后将其转换为字符串并以“完整” ISO格式发送至后端,例如1979-12-02TXX:00:00.000Z,这会破坏联系人/ API。

如果我们使用MatMomentDateModule而不是MatNativeDateModule,则会得到一个js日期(而不是javascript Date),但这对格式化没有帮助。

是否可以将控件的原始值绑定为字符串而不是日期?最好不要将组件包装在ControlValueAccessor中?也许是自定义的DateAdapter

6 个答案:

答案 0 :(得分:1)

是的,您应该实现自定义DateAdapter以使用短ISO日期字符串。
基本上,您只需要创建扩展DateAdapter的类并实现以下方法:

  abstract getYear(date: D): number;

  abstract getMonth(date: D): number;

  abstract getDate(date: D): number;

  abstract getDayOfWeek(date: D): number;

  abstract getMonthNames(style: 'long' | 'short' | 'narrow'): string[];

  abstract getDateNames(): string[];

  abstract getDayOfWeekNames(style: 'long' | 'short' | 'narrow'): string[];

  abstract getYearName(date: D): string;

  abstract getFirstDayOfWeek(): number;

  abstract getNumDaysInMonth(date: D): number;

  abstract clone(date: D): D;

  abstract createDate(year: number, month: number, date: number): D;

  abstract today(): D;

  abstract parse(value: any, parseFormat: any): D | null;

  abstract format(date: D, displayFormat: any): string;

  abstract addCalendarYears(date: D, years: number): D;

  abstract addCalendarMonths(date: D, months: number): D;

  abstract addCalendarDays(date: D, days: number): D;

  abstract toIso8601(date: D): string;

  abstract isDateInstance(obj: any): boolean;

  abstract isValid(date: D): boolean;

  abstract invalid(): D;

Angular团队提供了两个很好的例子:MomentDateAdapterNativeDateAdapter
实施适配器后,您需要将其添加到模块或组件中,如下所示:

  providers: [
    {provide: DateAdapter, useClass: YourISODateAdapter, deps: [...]}
  ],

答案 1 :(得分:1)

我已经分析了 MatDatepickerInputBase 源代码,目前没有选项可以配置您希望在相关 FormControl 中具有哪种类型或格式的值。 因此,基于覆盖类方法的 this idea,我已将此代码放在 app.module 中,并以我想要的格式 YYYY-MM-DD 获取日期作为字符串值。当用户手动输入日期或在日历组件中选择日期以及日期是否有效时,字符串将传递给控制。我也使用自己的 DateAdapter 重写类,但它与此问题无关,因为 DateAdapter 仅通过重写 parse() 和 format() 方法来格式化日期以在 MatDatepickerInput 控件中显示。

    const customFormatDate = (date: Date) => formatDate(date, 'yyyy-MM-dd', 'en');
    
    MatDatepickerInput.prototype._registerModel = function(model: any): void {
      this._model = model;
      this._valueChangesSubscription.unsubscribe();
    
      if (this._pendingValue) {
        this._assignValue(this._pendingValue);
      }
    
      this._valueChangesSubscription = this._model.selectionChanged.subscribe(event => {
        if (this._shouldHandleChangeEvent(event)) {
          const value = this._getValueFromModel(event.selection);
          this._lastValueValid = this._isValidValue(value);
          // this._cvaOnChange(value);
          this._cvaOnChange(customFormatDate(value));
          this._onTouched();
          this._formatValue(value);
          this.dateInput.emit(new MatDatepickerInputEvent(this, this._elementRef.nativeElement));
          this.dateChange.emit(new MatDatepickerInputEvent(this, this._elementRef.nativeElement));
        }
      });
    };


    MatDatepickerInput.prototype._onInput =  function(value: string) {
      const lastValueWasValid = this._lastValueValid;
      let date = this._dateAdapter.parse(value, this._dateFormats.parse.dateInput);
      this._lastValueValid = this._isValidValue(date);
      date = this._dateAdapter.getValidDateOrNull(date);
    
      if (!this._dateAdapter.sameDate(date, this.value)) {
        this._assignValue(date);
    
        //this._cvaOnChange(date);
        this._cvaOnChange(customFormatDate(date));
    
        this.dateInput.emit(new MatDatepickerInputEvent(this, this._elementRef.nativeElement));
      } else {
        // Call the CVA change handler for invalid values
        // since this is what marks the control as dirty.
        if ((value === '') || value && !this.value) {
          this._cvaOnChange(value);
        }
    
        if (lastValueWasValid !== this._lastValueValid) {
          this._validatorOnChange();
        }
      }
    };

答案 2 :(得分:0)

在将对象发送到api之前,先将其字符串化,如下所示:-

const req = JSON.stringify(reqObj, (key: any, value: any) => {
          if (JSON.parse(JSON.stringify(moment(value))) === value) {
            return moment(value).format("yyyy/MM/DD");
          } else {
            return value;
          }
        });

并将请求发送到api。这会将您的requet对象中的任何时刻日期转换为所需的格式。

答案 3 :(得分:0)

此处日期对象的类型已更改,并且使用补丁值方法已更新了formcontrol值,因此当您从后端api获得响应时,它将自动将UI中的数据绑定。使用moment.js,您可以找到[Using Moment.js] 1

HTML:
      <form [formGroup]="frmStepOne" (ngSubmit)="onSubmit()">
        <div>
                <mat-form-field>
                  <mat-label>Choose a date</mat-label>
                  <input matInput [matDatepicker]="picker" formControlName="dateValue" (dateChange)="date($event)">
                  <mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
                  <mat-datepicker #picker></mat-datepicker>
                </mat-form-field>
            </div>
           <div>
             <button type="submit">Submit</button>
           </div>
         </form>
        TS:
        convertDate:String;
        constructor(private _formBuilder: FormBuilder) {   
             this.frmStepOne = this._formBuilder.group({
              dateValue:['']
            });
        date(e) {
               this.convertDate = new Date(e.target.value).toISOString().substring(0, 10);
              this.frmStepOne.get('dateValue').patchValue(this.convertDate, {
                onlyself: true
              })
            }
     onSubmit(){
          console.log(this.frmStepOne.value.dateValue)
          console.log(typeof(this.convertDate));
        }

答案 4 :(得分:0)

您可以创建HttpInterceptor,然后遍历请求正文以将所有datetime属性转换为所需的格式

import { Injectable } from '@angular/core';
import {
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpInterceptor
} from '@angular/common/http';
import { Observable } from 'rxjs';
import { DatePipe } from '@angular/common';
import { isArray } from 'util';
@Injectable()
export class HttpDatetimeInterceptor implements HttpInterceptor {
  constructor(private datePipe: DatePipe) { }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    this.convertDatetimeToStringFormate(request.body);
    return next.handle(request);
  }

  convertDatetimeToStringFormate(body) {
    for (let [key] of Object.entries(body)) {

      if (isArray(body[key])) {
        for (let prop of body[key]) {
          this.convertDatetimeToStringFormate(prop);
        }
      }

      // Check if property is date
      if (typeof body[key].getMonth === 'function') {
        body[key] = this.datePipe.transform(body[key], 'yyyy/MM/dd');
      }
    }
  }
}

答案 5 :(得分:0)

使用带有moment的土星日期选择器来解析日期。抱歉,现在没有时间摘录。

      dateChanged(event: MatDatepickerInputEvent<DateRange>) {
        if (event.value) {
          const { begin, end } = event.value;
          this.store.dispatch(
            setDateRangeFilterAction({
              begin: moment(begin).tz("UTC").toISOString(),
              end: moment(end)
                .add(1, "days")
                .toISOString()
            })
          );
        } else {
          this.store.dispatch(setDateRangeFilterAction({ begin: null, end: null }));
        }
      }


<mat-form-field>
    <input
      matInput
      class="date-input"
      placeholder="Select a date range"
      name="range-picker"
      [satDatepicker]="picker"
      [value]="creationTimeFilter$ | async"
      (dateChange)="dateChanged($event)"
    />
    <sat-datepicker-toggle matSuffix [for]="picker">
      <mat-icon matDatepickerToggleIcon>keyboard_arrow_down</mat-icon>
    </sat-datepicker-toggle>
    <sat-datepicker #picker [rangeMode]="true"></sat-datepicker>
  </mat-form-field>