Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NgbDatePicker - How to bind longDate string to [ngModel]?

Tags:

I am trying to bind a date formatted as a "longDate" string to the [ngModel] input value in ngbDatePicker. For example, when the user picks a date, I'd like to display "January 15, 2017" instead of "2017-01-15".

I understand that [ngModel] binds to an object of type NgbDateStruct only, and it seems to me that when I pass an object of type NgbDateStruct (let's call it selectedStartDate as shown in code below) to [ngModel] then NgbDateParserFormatter.format(selectedStartDate) gets called behind the scenes to display the date as "yyyy-MM-dd". So how can I bind a longDate format (i.e. January, 15, 2017) to [ngModel]? I've thought about overriding the format() method in NgbDateParserFormatter to display the date as I want it, but I am not sure how/if it would get called when I pass an NgbDateStruct object to bind to [ngModel].

In addition it would be nice to keep the NgbDateStruct parse/format methods as they come because I am passing Date data as "yyyy-MM-dd" strings to an API and these come in handy.. I am using ngbootstrap alpha.18. Any help appreciated thanks!

<div class="form-group">From:
    <div class="input-group">
        <input class="form-control"
               name="dp1" 
               [ngModel]="selectedStartDate"
               (ngModelChange)="selectStartDate($event)" 
               ngbDatepicker 
               [dayTemplate]="customDay" 
               [markDisabled]="isDisabled" 
               #d1="ngbDatepicker" />
        <div class="input-group-addon" (click)="d1.toggle()">
            <img src="img/calendar-icon.svg" style="width: 1.2rem; height:    1rem; cursor: pointer;" />
        </div>
    </div>
</div>

UPDATE: While the solution below works, for some reason I am unable to set a default date value. For example, from the component where my date picker resides, I am implementing OnInit and form within ngOnInit() I am setting my "selectedStartDate" binding field to a date of type NgbDateStruct. Then while in debug mode, I am able to see my selectedStartDate field get populated, and eventually MyNgbDateParserFormatter.format() gets called to format the date into a "longDate" string - however the date parameter within the format() method is null and an error is of course thrown... I can't figure out why it's getting there as null. Afterwards when I select a date then the "selectedDate" is displayed in "longDate" format as expected.

The next issue I have noticed is that now every time I select a date, the method selectStartDate() isn't getting fired.

Here is my module (I am providing this in a 'shared module' because that's where my component using the ngbdatepicker is declared)

    @NgModule({
    imports: [
        CommonModule,
        FormsModule,
        NgbModule,
        ChartsModule
    ],
    exports: [
        CommonModule,
        FormsModule,
        NgbModule,
        CrgbFilterComponent,
        DateFilterComponent,
        BarChartComponent,
        LineChartComponent,
        ChartsModule
    ],
    declarations: [
        CrgbFilterComponent,
        DateFilterComponent,
        BarChartComponent,
        LineChartComponent
    ],
    providers: [
        {
            provide: NgbDateParserFormatter,
            useFactory: () => { return new CustomNgbDateParserFormatter("longDate") }
        },
        DateFilterService,
        BarChartService,
        TableService,
        HelperMethodsService
    ]
})
export class SharedModule { }

Here is my component (the parts that matter):

    export class DateFilterComponent implements OnInit {

 selectedStartDate: NgbDateStruct;
 selectedEndDate: NgbDateStruct;
 @Output() startDateChanged: EventEmitter<string>;
 @Output() endDateChanged: EventEmitter<string>;

    constructor(private dateFilterService: DateFilterService) {
        this.startDateChanged = new EventEmitter<string>();
        this.endDateChanged = new EventEmitter<string>();
    }

 ngOnInit(): void {
        this.selectStartDate(this.dateFilterService.setDefaultStartDate());
        this.selectEndDate(this.dateFilterService.setDefaultEndDate());
    }

 selectStartDate(date: NgbDateStruct) {
        if (date != null) {
            this.selectedStartDate = date;
            let dateString = this.dateFilterService.toServerString(date);;
            this.startDateChanged.emit(dateString);
        }
    }

 selectEndDate(date: NgbDateStruct) {
        if (date != null) {
            this.selectedEndDate = date;
            let dateString = this.dateFilterService.toServerString(date);
            this.endDateChanged.emit(dateString);
        }
    }

Here is my date filter service:

    export class DateFilterService {

    constructor(private parserFormatter: NgbDateParserFormatter) { }

    setDefaultStartDate(): NgbDateStruct {
        // removing for simplicity, returning a NgbDateStruct object correctly.
    }

    setDefaultEndDate(): NgbDateStruct {
        // removing for simplicity, returning a NgbDateStruct object correctly.
    }

    toNgbDateStruct(date: string): NgbDateStruct {
        return this.parserFormatter.parse(date);
    }

    tolongDateString(date: NgbDateStruct): string {
        return this.parserFormatter.format(date);
    }

    toServerString(date: NgbDateStruct): string {
        return this.parserFormatter.formatForServer(date);
    }
}

Thank you for any help in advance, thanks.

like image 497
Jon S. Avatar asked Jan 15 '17 07:01

Jon S.


1 Answers

I think you are on the right track with overriding NgbDateParserFormatter. You want to create your own ParserFormatter implementation with the date format of choice as the one output by the .format() function. Similarly you override the .parse() function to take in your date format of choice and convert it to NgbDateStruct. If you want to have another format for the server then I suggest you create a function for that as well. I have create an example plunker here:

plnkr

As shown the first step is to extend the NgbDateParserFormatter and overwrite the two functions mentioned:

import { NgbDateParserFormatter, NgbDateStruct } from '@ng-bootstrap/ng-bootstrap';
import { DatePipe } from '@angular/common';

export class MyNgbDateParserFormatter extends NgbDateParserFormatter {
    datePipe = new DatePipe('en-US');
    constructor(
        private dateFormatString: string) {
        super();
    }
    format(date: NgbDateStruct): string {
        if (date === null) {
            return '';
        }
        try {
            return this.datePipe.transform(new Date(date.year, date.month - 1, date.day), this.dateFormatString);
        } catch (e) {
            return '';
        }
    }
    formatForServer(date: NgbDateStruct): string {
        if (date === null) {
            return '';
        }
        try {
            return this.datePipe.transform(new Date(date.year, date.month - 1, date.day), 'y-MM-dd');
        } catch (e) {
            return '';
        }
    }
    parse(value: string): NgbDateStruct {
        let returnVal: NgbDateStruct;
        if (!value) {
            returnVal = null;
        } else {
            try {
                let dateParts = this.datePipe.transform(value, 'M-d-y').split('-');
                returnVal = { year: parseInt(dateParts[2]), month: parseInt(dateParts[0]), day: parseInt(dateParts[1]) };
            } catch (e) {
                returnVal = null;
            }
        }
        return returnVal;
    }
}

In your AppModule then you need to provide this new implementation:

@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    ReactiveFormsModule,
    JsonpModule,
    NgbModule.forRoot()
  ], 
  declarations: [
    App,
    NgbdDatepickerPopup
  ]
  bootstrap: [ App ],
  providers: [
    {provide: NgbDateParserFormatter, useFactory: () => new MyNgbDateParserFormatter('longDate')}
  ]
}) 
export class AppModule {}

Your ngbDatepickers will then use this version of the parse() and format() functions. You can also call your formatForServer() function when needed:

import {Component} from '@angular/core';
import { NgbDateParserFormatter } from '@ng-bootstrap/ng-bootstrap';
@Component({
  selector: 'ngbd-datepicker-popup',
  templateUrl: 'src/datepicker-popup.html'
})
export class NgbdDatepickerPopup {
  model;
  constructor(
    private ngbDateParserFormatter: NgbDateParserFormatter
  ) {}
  getServerDate(dateStruct) {
    return this.ngbDateParserFormatter.formatForServer(dateStruct);
  }
}
like image 70
dmungin Avatar answered Sep 21 '22 10:09

dmungin