Angular 2:如何将 JavaScript 日期对象与 NgModel 两种方式绑定一起使用

Posted

技术标签:

【中文标题】Angular 2:如何将 JavaScript 日期对象与 NgModel 两种方式绑定一起使用【英文标题】:Angular 2: How to use JavaScript Date Object with NgModel two way binding 【发布时间】:2016-08-31 12:28:14 【问题描述】:

我正在使用 Angular 2,我有这个代码:

JS,这段代码初始化模板的雇员变量:

handleEmployee(employee : Employee)
        this.employee = employee;
        this.employee.startDate = new Date('2005/01/01');
        console.log(this.employee);
    

模板:

...
<div>
    <label>Start date: </label>
    <input [(ngModel)]="employee.startDate" type="date" name="startDate"/>
  </div>
  <div>
...

其他数据如名字显示正确。但是对于我刚刚得到的日期:

mm/dd/yyyy

在输入元素中,应该是日期。

我该怎么做?

【问题讨论】:

何时调用handleEmployeeemployee.startDate 是否在组件创建时初始化(在构造函数中或使用变量声明)? 【参考方案1】:

更新:

StackBlitz

当我写这个答案时DatePipe不存在,现在你可以这样做

<input [ngModel]="startDate | date:'yyyy-MM-dd'" (ngModelChange)="startDate = $event" type="date" name="startDate"/>

`


旧答案:

PLUNKER

您需要将date object 转换为input type="date" 格式,即yyyy-mm-dd,这就是它的工作原理

模板:

<input [(ngModel)]="humanDate" type="date" name="startDate"/>

组件 (TS):

export class App 
  startDate: any;

  constructor() 
    this.startDate = new Date(2005, 1, 4);
  

  set humanDate(e)
    e = e.split('-');
    let d = new Date(Date.UTC(e[0], e[1]-1, e[2]));
    this.startDate.setFullYear(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate());
  

  get humanDate()
    return this.startDate.toISOString().substring(0, 10);
  

【讨论】:

我必须修改它以使用 parseInt(e[1] -1 ) 作为月份,否则每次我选择日期时都会添加一个月。我猜这是因为月份是零 - 在 Date 构造函数中被索引。 感谢@dafyddPrys。我更新了答案和 PLUNKER,还有另一个由 timeZone 引起的问题,现在它应该可以正常工作了。只需要处理无效的日期。 @A_Singh 我遇到了另一个问题,即 ISOString 中忽略了本地时区偏移量。我必须计算时区偏移量:(new Date()).getTimezoneOffset() * 60000 ;,然后使用该偏移量打印出日期:return (new Date(this.endDate.getTime() - tzOffset)).toISOString().slice(0,10); .... 这样,我也不需要在设置 setFulYear 时添加 1 ti ...在 Angular 4 下为我工作。确保 ngModel 没有“()”-> oneWay 绑定,并确保您使用的是 (ngModelChange)="startDate = $event" 然后它可以工作对我来说,正如 Ankit Singh 所描述的那样。谢谢 更新后的答案完美 - 简洁明了【参考方案2】:

阅读pipes 和ngModel 我的决定:

<input type="date" class="form-control" id="myDate" [ngModel]="myDate | date:'y-MM-dd'" (ngModelChange)="myDate = $event" name="birthday">

【讨论】:

OP 应该阅读您的决定吗? 好的答案将始终解释所做的事情以及为什么以这种方式完成,不仅适用于 OP,而且适用于 SO 的未来访问者。 @bub 也许答案并不完整。 这是我的第一次体验。但是在官方文档的页面中有详细的解释如何使用ngModel以及它是由什么组成的。 您的答案很好,简单高效。效果很好。【参考方案3】:

FormControls(模板驱动和响应式)通过实现ControlValueAccessor 的指令订阅值和写入值。看看相关的方法selectValueAccessor,它在所有必要的指令中都有使用。普通输入控件(例如&lt;input type="text"&gt;)或文本区域由DefaultValueAccessor 处理。另一个例子是CheckboxValueAccessor,它应用于复选框输入控件。

这项工作一点也不复杂。我们只需要为日期输入控件实现一个新的值访问器。DateValueAccessor 是个好名字:

// date-value-accessor.ts

import  Directive, ElementRef, HostListener, Renderer, forwardRef  from '@angular/core';
import  ControlValueAccessor, NG_VALUE_ACCESSOR  from '@angular/forms';

export const DATE_VALUE_ACCESSOR: any = 
  provide: NG_VALUE_ACCESSOR,
  useExisting: forwardRef(() => DateValueAccessor),
  multi: true
;

/**
 * The accessor for writing a value and listening to changes on a date input element
 *
 *  ### Example
 *  `<input type="date" name="myBirthday" ngModel useValueAsDate>`
 */
@Directive(
  selector: '[useValueAsDate]',
  providers: [DATE_VALUE_ACCESSOR]
)
export class DateValueAccessor implements ControlValueAccessor 

  @HostListener('input', ['$event.target.valueAsDate']) onChange = (_: any) =>  ;
  @HostListener('blur', []) onTouched = () =>  ;

  constructor(private _renderer: Renderer, private _elementRef: ElementRef)  

  writeValue(value: Date): void 
    this._renderer.setElementProperty(this._elementRef.nativeElement, 'valueAsDate', value);
  

  registerOnChange(fn: (_: any) => void): void  this.onChange = fn; 
  registerOnTouched(fn: () => void): void  this.onTouched = fn; 

  setDisabledState(isDisabled: boolean): void 
    this._renderer.setElementProperty(this._elementRef.nativeElement, 'disabled', isDisabled);
  

我们将DateValueAccessor附加到多提供者DATE_VALUE_ACCESSOR,以便selectValueAccessor可以找到它。

唯一的问题是,应该使用哪个选择器。我决定选择加入解决方案。 这里 DateValueAccessor 选择属性“useValueAsDate”。

<input type="date" name="myBirthday" ngModel useValueAsDate>

OR

<input type="date" name="myBirthday" [(ngModel)]="myBirthday" useValueAsDate>

OR

<input type="date" formControlName="myBirthday" useValueAsDate>

也可以修复默认实现。 以下选择器将神奇地激活该功能。

// this selector changes the previous behavior silently and might break existing code
selector: 'input[type=date][formControlName],input[type=date][formControl],input[type=date][ngModel]'

但请注意,这可能会破坏依赖旧行为的现有实现。所以我会选择选择加入版本!

这一切都在 NPM 和 Github 上

为了您的方便,我在 Github 上创建了项目 angular-data-value-accessor。 还有一个 NPM 包可用:

npm install --save angular-date-value-accessor

然后通过 NgModule 导入模块:

// app.module.ts

import  DateValueAccessorModule  from 'angular-date-value-accessor';

@NgModule(
  imports: [
    DateValueAccessorModule
  ]
)
export class AppModule  

现在您可以将“useValueAsDate”应用于您的日期输入控件。

演示

当然,这里有一个演示: http://johanneshoppe.github.io/angular-date-value-accessor/

【讨论】:

好东西!这适用于 ng2-Bootstrap 指令吗? [btnRadio] 使用字符串,因此对于数字(枚举)的 ngModel 属性绑定失败。 还没有为 ng2-Bootstrap 测试过这个,但通常你可以通过实现自己的 ControlValueAccessor 来做很多事情【参考方案4】:

我开始尝试实施 Ankit Singh 的解决方案,但在验证和时区方面遇到了一些问题。 (即使在尝试了该答案评论部分的建议之后)

相反,我选择使用 moment.js 来处理使用 ISO8601 格式日期字符串的字符串和日期之间的转换。过去我使用 moment.js 取得了很好的成绩,所以这不是一个艰难的决定。似乎对我来说效果很好,希望其他人觉得这很有用。

对于我的 Angular 2 应用,我运行了 npm install --save moment,然后将 Ankit 的解决方案变成了一个 js Date 对象的包装器:

import * as moment from 'moment';

export class NgDate 

    date: any;

    constructor() 
        this.date = new Date();
    

    set dateInput(e) 
        if (e != "") 
            var momentDate = moment(e, moment.ISO_8601).toDate();
            this.date = momentDate;
        
        else 
            this.date = null;
        
    

    get dateInput() 
        if(this.date == null)
        
            return "";
        

        var stringToReturn = moment(this.date).format().substring(0, 10);
        return stringToReturn;
    

那么对于 html

<input type="date" name="someDate" [(ngModel)]="ngDateModel.dateInput"/>

【讨论】:

【参考方案5】:

用这段代码修复它:

handleEmployee(employee : Employee)
        this.employee = employee;

        let dateString : string = employee.startDate.toString();
        let days : number = parseInt(dateString.substring(8, 10));
        let months : number = parseInt(dateString.substring(5, 7));
        let years : number = parseInt(dateString.substring(0, 5));
        let goodDate : Date = new Date(years + "/" + months + "/" + days);
        goodDate.setDate(goodDate.getDate() + 2);
        this.date = goodDate.toISOString().substring(0, 10);
    

HTML:

<div>
    <label>Start date: </label>
    <input [(ngModel)]="date" type="date" name="startDate"/>
  </div>

【讨论】:

【参考方案6】:

我认为接受的答案缺少将输入日期字符串转换为 Date 对象的函数调用。所以这个:

(ngModelChange)="startDate = $event"

应该是这样的:

(ngModelChange)="startDate = toDate($event)"

我正在使用 Moment.js,这让事情变得更容易:

my.component.ts

...
import * as moment from 'moment';
...
@Component (
  ...
)
export class MyComponent implements OnInit 

  public fromDate: moment.Moment;
  public toDate: moment.Moment;
  
  ngOnInit() 
    this.toDate = moment();
    this.fromDate = moment().subtract(1, 'week');
  

  dateStringToMoment(dateString: string): moment.Moment 
    return moment(dateString);
  

我的组件.html

...
<input type="date" min=" fromDate | date:'yyyy-MM-dd' " name="toDate" [ngModel]="toDate | date:'yyyy-MM-dd'" (ngModelChange)="toDate = dateStringToMoment($event)">
<input type="date" max=" toDate | date:'yyyy-MM-dd' " name="fromDate" [ngModel]="fromDate | date:'yyyy-MM-dd'" (ngModelChange)="fromDate = dateStringToMoment($event)">
...

【讨论】:

感谢您的持续关注【参考方案7】:

创建了一个本地字符串变量

dateField: string;

我将它绑定到我的表单

日期输入

<input type="text" class="form-control" required [(ngModel)]="dateField" />

稍后只需在进行任何 API 调用之前将其分配回 Date 属性

insert() 
    this.myObjet.date = new Date(this.dateField);
    ... call api

update() 
    this.myObjet.date = new Date(this.dateField);
    ... call api

【讨论】:

以上是关于Angular 2:如何将 JavaScript 日期对象与 NgModel 两种方式绑定一起使用的主要内容,如果未能解决你的问题,请参考以下文章

Angular - 如何将 Javascript 导入 Angular 组件

如何将 Angular 指令设置为在 Javascript 中创建的 DOM 元素

如何将此 javascript 和 HTML 代码添加到 Angular 项目中?我可以从 javascript 函数中以角度呈现 html 吗?

Angular 2 将图像编码为 base64

如何将前端 JS 重构为 Angular 2 以与 PHP MVC 后端完美配合? [关闭]

如何在 TypeScript Angular 中使用外部 Javascript