Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 5 model binding not working for ng-bootstrap date picker popup

I'm attempting to make my own component out of ng-bootstrap's date picker popup (I don't want all that markup every time I use the date picker).

The date picker works fine own its own, but the value isn't binding back to the page where the component is being used.

Here's my date picker - copied from ng-bootstrap's example page:

<div class="input-group">
  <input class="form-control" placeholder="yyyy-mm-dd" name="dp"  [(ngModel)]="selectedDate" ngbDatepicker #d="ngbDatepicker">
  <div class="input-group-append">
    <button class="btn btn-outline-secondary" (click)="d.toggle()" type="button">
      <img src="img/calendar-icon.svg" style="width: 1.2rem; height: 1rem; cursor: pointer;"/>
    </button>
  </div>
</div>

<hr/>
<pre>Model: {{ selectedDate | json }}</pre>

My component:

import { Component, OnInit } from '@angular/core';
import {NgbDateStruct, NgbDateParserFormatter} from '@ng-bootstrap/ng-bootstrap';

@Component({
  selector: 'reo-datepicker-popup',
  templateUrl: './datepicker-popup.component.html',
})
export class DatepickerPopupComponent implements OnInit {

  selectedDate; any;

  constructor() {}

  ngOnInit() {
  }

}

The component is used like this:

<reo-datepicker-popup  [(ngModel)]="selectedDatePopup"  ngDefaultControl name="datePicker"></reo-datepicker-popup>

There are other controls on my page where [(ngModel)] is working as expected. I can set a default value for selectedDatePopup in the parent page - and that value will propagate down to the date picker, but doesn't make it back up to the parent when changed.

I have tried @Input() instead of [(ngModel)] with the same result. I have also verified that NgbModule is imported in every module (ngbModule.forRoot() in the app module), and FormsModule is always imported before NgbModule.

Angular version 5.2.3
ng-bootstrap version 1.02

Any help is appreciated.

Plunker

like image 202
Jeff Beck Avatar asked Dec 31 '25 13:12

Jeff Beck


1 Answers

You need to implement ControlValueAccessor on your DatePickerPopupComponent. This will allow angular forms (template or reactive) to model bind with your component. Your plnkr doesn't work anymore. I created an example here on StackBlitz. I had to add some additional code to translate the date for the ngbDatePicker. There may be a better way to do that. This should be enough to get you in the right direction.

This is what the component code should look like:

import { Component, OnInit, forwardRef, Input } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR, } from '@angular/forms';

export const DATEPICKER_VALUE_ACCESSOR =  {
  provide: NG_VALUE_ACCESSOR,
  useExisting: forwardRef(() => DatePickerComponent),
  multi: true
};

@Component({
  selector: 'app-date-picker',
  templateUrl: './date-picker.component.html',
  providers: [DATEPICKER_VALUE_ACCESSOR]
})
export class DatePickerComponent implements ControlValueAccessor {

  selectedDate: any;
  disabled = false;

  // Function to call when the date changes.
  onChange = (date?: Date) => {};

  // Function to call when the date picker is touched
  onTouched = () => {};

  writeValue(value: Date) {
    if (!value) return;
    this.selectedDate = {
      year: value.getFullYear(),
      month: value.getMonth(),
      day: value.getDate()
    }
  }

  registerOnChange(fn: (date: Date) => void): void {
    this.onChange = fn;
  }

  // Allows Angular to register a function to call when the input has been touched.
  // Save the function as a property to call later here.
  registerOnTouched(fn: () => void): void {
    this.onTouched = fn;
  }

  // Allows Angular to disable the input.
  setDisabledState(isDisabled: boolean): void {
    this.disabled = isDisabled;
  }

  // Write change back to parent
  onDateChange(value: Date) {
    this.onChange(value);
  }

  // Write change back to parent
  onDateSelect(value: any) {
    this.onChange(new Date(value.year, value.month - 1, value.day));
  }

}

Markup:

<form class="form-inline">
  <div class="form-group">
    <div class="input-group">
      <input class="form-control" 
             placeholder="yyyy-mm-dd"
             name="dp" 
             [(ngModel)]="selectedDate"
             ngbDatepicker #d="ngbDatepicker"
             (change)="onDateChange($event.target.value)"
             (dateSelect)="onDateSelect($event)">

      <div class="input-group-append">
        <button class="btn btn-outline-secondary" (click)="d.toggle()" type="button">
          [[]]
        </button>
      </div>
    </div>
  </div>
</form>

<hr/>
<pre>Model on date picker: {{ selectedDate | json }}</pre>

Implementation on a component:

import { Component, Input } from '@angular/core';

@Component({
  selector: 'hello',
  template: `<h1>Hello {{name}}!</h1>
  <div>
    This is the date picker: <app-date-picker [(ngModel)]="selectedDate"></app-date-picker>
  </div>
  <div>
    Model on parent: {{ selectedDate | date:'short' }}
  </div>
  `,
  styles: [`h1 { font-family: Lato; }`]
})
export class HelloComponent  {
  @Input() name: string;
  public selectedDate: Date;
  constructor() {
    this.selectedDate = new Date(2018, 1, 1);
  }
}
like image 67
Joel Richman Avatar answered Jan 03 '26 12:01

Joel Richman



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!