ng-bootstrap / ng-bootstrap

Angular powered Bootstrap
https://ng-bootstrap.github.io
MIT License
8.2k stars 1.55k forks source link

How to use moment.js dates for the directives? #3218

Open meness opened 5 years ago

meness commented 5 years ago

Hi,

I expect to use different date object for the directives while defined a custom provider. Sometimes, when I select a date from future the input receives an empty string/null. Moreover, the dates defined for directives do not work and default (+10-10 years) displays. By the way, NgbDateStruct date objects work well for directives but I expect the custom date object to work properly.

<div class="input-group">
   <input ngbDatepicker #startAtDatePicker="ngbDatepicker" [maxDate]="maxDateForStartsAtDate"
   readonly [startDate]="minDateForStartsAtDate" [minDate]="minDateForStartsAtDate"
   class="form-control" placeholder="yyyy-mm-dd" formControlName="startsAtDate">
   <div class="input-group-append">
      <span class="input-group-text" (click)="startAtDatePicker.toggle()">
      <i class="la la-calendar"></i>
      </span>
   </div>
</div>
import { Injectable } from "@angular/core";
import { NgbDateAdapter, NgbDateStruct } from "@ng-bootstrap/ng-bootstrap";
import * as moment from "moment";

@Injectable()
export class MomentNgbDateAdapterService extends NgbDateAdapter<moment.Moment> {
  fromModel(value: moment.Moment): NgbDateStruct {
    return {
      day: value.get("day"),
      month: value.get("month"),
      year: value.get("year")
    };
  }

  toModel(date: NgbDateStruct): moment.Moment {
    if (date === null) {
      throw new Error("date cannot be null");
    }

    return moment({ year: date.year, month: date.month, day: date.day });
  }

  constructor() {
    super();
  }
}
// public properties
minDateForStartsAtDate = moment().utc();
maxDateForStartsAtDate = moment()
    .utc()
    .add(1, "month");

// inside formBuilder
startsAtDate: this.formBuilder.control(this.minDateForStartsAtDate, [])
@NgModule({
  imports: [
    NgbDatepickerModule
  ],
  exports: [
    NgbDatepickerModule
  ],
  providers: [
    { provide: NgbDateAdapter, useClass: MomentNgbDateAdapterService }
  ]
})
export class BootstrapModule {}
meness commented 5 years ago

Bad formatting in the adapter causes the null exceptions but now fixed with the below implementation.

import { Injectable } from "@angular/core";
import { NgbDateAdapter, NgbDateStruct } from "@ng-bootstrap/ng-bootstrap";
import * as moment from "moment";
import * as _ from "lodash";

@Injectable()
export class MomentNgbDateAdapterService extends NgbDateAdapter<moment.Moment> {
  constructor() {
    super();
  }

  fromModel(value: moment.Moment): NgbDateStruct {
    return value && moment.isMoment(value)
      ? {
          day: value.date(),
          month: value.month() + 1,
          year: value.year()
        }
      : null;
  }

  toModel(date: NgbDateStruct): moment.Moment {
    return date &&
      _.isInteger(date.year) &&
      _.isInteger(date.month) &&
      _.isInteger(date.day)
      ? moment({
          year: date.year,
          month: date.month - 1,
          date: date.day,
          hour: 12
        })
      : null;
  }
}

But still, I cannot use MomentJS for directives.

danieldiazastudillo commented 5 years ago

I wouldn't use this approach. First of all you are importing the entire Lodash library in order to use one method (isInteger), you can cherry-pick methods in Lodash or, even better, use native Number.isInteger() function as described in MDN.

Moment it's great but it's not tree-shakeable and besides that when bundling with Angular CLI it contains locales wich is very annoying. I suggest using Luxon or, as i do, use date-fns v2 wich is fully tree shakeable and covers almost the entirety Moment's API surface.

fbasso commented 5 years ago

@meness What's going on with the directives ? You told that the DateAdpater is now working, fine, but in a directive, you can just use the moment objects, right ? An example of what you want to achieve would be welcome !

benouat commented 5 years ago

An example of what you want to achieve would be welcome !

Good point! We would really appreciate a reproducible scenario inside a Stackblitz to simply understand what you want to achieve.