简体   繁体   English

Angular2 +和NgbDatePicker:日期ngModel的正确ControlValueAccessor

[英]Angular2+ & NgbDatePicker: proper ControlValueAccessor for Date ngModel

Was working on some custom wrappers to the ng-bootstrap . 正在为ng-bootstrap创建一些自定义包装。 The idea of the one in question is to create a component that takes in a Date as its [ngModel]. 有问题的一个想法是创建一个将Date作为其[ngModel]的组件。

I noticed a bug with my wrapper implementing it in an app I'm working on that required multiple components of the same object but with different references on the same page. 我注意到包装程序在我正在处理的应用程序中实现该程序时存在一个错误,该错误需要同一对象的多个组件,但同一页面上的引用不同。 Seems that if I create 2 variables that should reference the same object, they actually get out of sync with each other. 看来,如果我创建了两个应该引用同一对象的变量,则它们实际上会彼此不同步。 Eg: 例如:

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 -->

I understand that this is because my implementation of the ControlValueAccessor is faulty. 我知道这是因为我对ControlValueAccessor的实现是错误的。 Somehow I'm overwriting the reference to point to a new Date object instead of modifying the given object. 我以某种方式覆盖了引用以指向一个新的Date对象,而不是修改给定的对象。

I believe this is caused by line 56 on my plunker? 我相信这是由我的插栓上的第56行引起的吗? But may also have something to do with ControlValueAccessor that I'm not doing/understanding correctly. 但可能与我未正确/正确理解的ControlValueAccessor有关。 Seems too like adjusting my Date object to match the NgbDateStruct of the wrapped NgbDatePicker via of the DoCheck lifecycle hook is a bit hacky (though I don't think it's causing the issue - would much prefer a clever RxJS solution, but haven't been able to think of one yet.) 似乎也像通过DoCheck生命周期挂钩来调整我的Date对象以匹配包装的NgbDatePicker的NgbDateStruct一样有点棘手(尽管我不认为这是引起问题的原因-非常希望有一个聪明的RxJS解决方案,但是还没有还能想到一个。)

Plunker link 柱塞链接

my app code: 我的应用程式码:

@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);
  }
}

My wrapper's code: 我的包装器代码:

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);
    }
  }
}

The problem lies in this code: 问题出在以下代码中:

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};
    }
}

You are assigning this.innervalue to the exact object that is being passed in by reference! 您正在将this.innervalue分配给通过引用传递的确切对象! Therefore when you do if(v.getTime() !== this.innerValue.getTime()) it will always be equal since it is the exact same Date object. 因此,当您执行if(v.getTime() !== this.innerValue.getTime()) ,它将始终相等,因为它是完全相同的Date对象。 To rectify all you need to do is: 要纠正所有问题,请执行以下操作:

this.innerValue = new Date(value);

to create a copy. 创建副本。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM