Angular2 +& NgbDatePicker:适用于Date ngModel的ControlValueAccessor

时间:2017-10-11 06:41:41

标签: angular date ng-bootstrap

正在为ng-bootstrap开发一些自定义包装器。这个问题的想法是创建一个以Date作为其[ngModel]的组件。

我注意到我的包装器在我正在处理的应用程序中实现它的一个错误,它需要同一个对象的多个组件,但在同一页面上有不同的引用。似乎如果我创建了两个应引用相同对象的变量,它们实际上彼此不同步。例如:

this.date: Date = new Date();
this.copy: Date = date;
....
<my-component [ngModel]="date"></my-component>
<my-component [ngModel]="copy"></my-component>
<-- Components don't stay in sync - date and copy point at different Dates -->

我理解这是因为我的ControlValueAccessor实现有问题。不知何故,我覆盖了引用以指向新的Date对象而不是修改给定的对象。

我认为这是由我的捡拾器上的56号线引起的?但也可能与ControlValueAccessor有关,我没有正确地理解/理解。看起来像调整我的Date对象以匹配包装的NgbDatePicker的NgbDateStruct通过DoCheck生命周期钩子有点hacky(虽然我不认为它导致问题 - 更喜欢一个聪明的RxJS解决方案,但还没有想到一个。)

Plunker link

我的应用代码:

@Component({
selector: 'my-app',
template: `
  <h1>
    {{title}}
  </h1>
  <hr>
  <h2>Day Picker</h2>
  <app-day-picker [(ngModel)]="dayPickerDay"></app-day-picker>
  Day: {{ dayPickerDay | date:mediumDate }}
  <br>
  <button (click)="setToYesterday()">Change to yesterday</button>
  <button (click)="setToTomorrow()">Change to tomorrow</button>
  <hr>
  <h2>Day Picker for copy (should stay in sync... doesn't.</h2>
  <app-day-picker [(ngModel)]="dayPickerCopy"></app-day-picker>
  Day: {{ dayPickerCopy | date:mediumDate }}
  <br>`
})
export class App {
  title = 'My time components';
  dayPickerDay: Date;
  dayPickerCopy: Date;

  ngOnInit(){
    this.dayPickerDay = new Date(Date.now());
    this.dayPickerCopy = this.dayPickerDay;
  }

  setToTomorrow(){
    this.dayPickerDay = new Date(Date.now() + 24*1000*60*60);
  }

  setToYesterday(){
    this.dayPickerDay = new Date(Date.now() - 24*1000*60*60);
  }
}

我的包装代码:

const noop = () => {};

export const DAY_PICKER_CONTROL_VALUE_ACCESSOR: any = {
  provide: NG_VALUE_ACCESSOR,
  useExisting: forwardRef(() => DayPickerComponent),
  multi: true
};

@Component({
  selector: 'app-day-picker',
  template: `
<form class="form-inline">
  <div class="form-group">
    <div class="input-group">
      <input class="form-control" placeholder="Select a Date..."
       name="dp" [(ngModel)]="dayObject" ngbDatepicker #d="ngbDatepicker" disabled="">
      <button class="input-group-addon" (click)="d.toggle()" type="button">
        <i class="fa fa-calendar" style="width: 1.2rem; height: 1rem; cursor: pointer;"></i>
      </button>
    </div>
  </div>
</form>
  `,
  providers: [
    DAY_PICKER_CONTROL_VALUE_ACCESSOR
  ]
})
export class DayPickerComponent implements ControlValueAccessor, DoCheck {
  private innerValue: Date = new Date(Date.now());
  private dayObject: {year: number, month: number, day: number};

  private onTouchedCallback: () => void = noop;
  private onChangedCallback: (_: any) => void = noop;

  get value(): Date {
    return this.innerValue;
  }

  set value(v: Date){
    if(v.getTime() !== this.innerValue.getTime()){
      this.innerValue = v;
      let years = this.innerValue.getFullYear();
      let months = this.innerValue.getMonth() + 1;
      let days = this.innerValue.getDate();
      this.dayObject = {year: years, month: months, day: days};
      this.onChangedCallback(v);
    }
  }

  writeValue(value: Date){
    if(value === null) {
      value = new Date(Date.now());
    }
    if(value.getTime() !== this.innerValue.getTime()){
      this.innerValue = value;
      let years = this.innerValue.getFullYear();
      let months = this.innerValue.getMonth() + 1;
      let days = this.innerValue.getDate();
      this.dayObject = {year: years, month: months, day: days};
    }
  }

  registerOnChange(fn: (_: any) => void): void{
    this.onChangedCallback = fn;
  }

  registerOnTouched(fn: any){
    this.onTouchedCallback = fn;
  }

  ngDoCheck() {
    if (this.dayObject) {
      this.value.setFullYear(this.dayObject.year, this.dayObject.month - 1, this.dayObject.day);
    }
  }
}

1 个答案:

答案 0 :(得分:0)

问题出在以下代码中:

writeValue(value: Date){
    if(value === null) {
        value = new Date(Date.now());
    }
    if(value.getTime() !== this.innerValue.getTime()){
        this.innerValue = value; //<-- Right here
        let years = this.innerValue.getFullYear();
        let months = this.innerValue.getMonth() + 1;
        let days = this.innerValue.getDate();
        this.dayObject = {year: years, month: months, day: days};
    }
}

您正在将this.innervalue分配给通过引用传递的确切对象!因此,当您执行if(v.getTime() !== this.innerValue.getTime())时,它将始终相等,因为它是完全相同的Date对象。要纠正所有问题,只需执行以下操作:

this.innerValue = new Date(value);

创建副本。