0
votes

I am trying to use the ngbDatepicker in an angular reactive form and would want to be able to display the initial value.

My question in this is very similar to another one with the crucial difference, that I am also using formly, an angular package to automate away form generation. As the answer to the original question is based on generating a new formControl and adding it to the form, which in my case is done by formly, which is why I'm struggling.

The "component" generally functions when you want to enter a new value (because that triggers an event which I use to change the model value), but the initial value isn't displayed properly. While the model always has the value of e.g. "2020-07-05" if inspected with augury, this is never displayed in the input-field. This stays true if I convert the date in the model to an NgbDate ({year, month, day}) in ngOnInit().

Formly calls my custom component (defined below) that contains solely the field and binds the pre-existing formcontrol (including model) and field-configurations to the input field. The component itself doesn't do much, it contains the necessary HTML for the ngbDatepicker and a function that, when a date is chosen, converts it from an NgbDate ({year, month, day}) to a string ("yyyy-mm-dd") and ensures that that is stored in the model instead of the NgbDate. It also opens the datepicker when you click on fa-calendar icon:

//formly-datepicker.component.html

<div class="form-group" [ngClass]="{'d-none': to.hidden}">
    <!-- Heading --> 
    <!-- to = TemplateOptions -->
    <label for="datepicker">
        <strong>
            {{to.label}}
            <span *ngIf="to.required">*</span>
        </strong>
    </label>

    <!-- Input -->
    <div class="input-group">
        <input
        class="form-control" 
        placeholder="yyyy-mm-dd" 
        name="datepicker" 
        [formControl]="formControl" 
        [formlyAttributes]="field" 
        ngbDatepicker 
        #datepickerInput="ngbDatepicker"
        (dateSelect)="inputDateToField($event)"
        >
        <div class="input-group-append">
            <div class="btn btn-outline-light fa fa-calendar" (click)="datepickerInput.toggle()"></div>
        </div>
    </div>
</div>


<ng-template #customDay let-date>
    <div class="datepicker-day btn-light">
        {{ date.day }}
    </div>
</ng-template>



//formly-datepicker.component.ts
import { Component, } from '@angular/core';
import { NgbDate } from '@ng-bootstrap/ng-bootstrap';
import { FieldType } from '@ngx-formly/core';

@Component({
  selector: 'app-formly-datepicker',
  templateUrl: './formly-datepicker.component.html',
  styleUrls: ['./formly-datepicker.component.scss']
})
export class FormlyDatepickerComponent extends FieldType{
  inputDateToField(event: NgbDate){
    this.model.session_date = `${event.year}-${event.month}-${event.day}`;
  }
}

I have tried manipulating the model in ngOnInit() and ngAfterViewInit(), I have tried setting startDate, but nothing seems to help in this case. What am I to do here?

2
Were you able to find a solution for this? Even I'm facing the exact same issue. It works when the custom template is a simple textbox, but not when I update it to an ngbDatepickerAashwath Acharya
@Aashwath Acharya Sadly not. I typically update my questions with a detailed explanation of the solution I found, but this one has eluded me to this day.Philipp Doerner
it was actually working for me, I was passing in the date in an incorrect format instead of YYYY-MM-DD. The difference may lie in the configs of the NgBootstrap and Formly libraries. Have elaborated in my answer.Aashwath Acharya

2 Answers

1
votes

I would urge you to update the configs for both NgBootstrap and Formly (if you haven't already):

  1. Instead of using the inputDateToField method in your component, create these two services (parser and adapter) recommended by NgBootstrap for the NgbDatePicker. The parser will maintain the format you specify in the model and the adapter will display the date in the UI, again as per your customization. E.g. I use the parser to convert the ISO string the backend provides to save it as 'YYYY-MM-DD' in my model and the adapter to display it as 'DD-MM-YYYY'
  2. Wherever you import FormlyModule update the config like so (if it isn't in the AppModule, you may have to use forChild):
FormlyModule.forRoot({types: [{ name: 'date', component: FormlyDatepickerComponent, extends: 'input'}]})
  1. In your FormlyDatepickerComponent's template, add type="text" (optional).
1
votes

you can take two aproach:

1.-when create the form use as defaultValue a NgbDateStruct

//I imagine you has an object "data" with a property "date" 
//in the way yyyy-mm-dd and create the field as
{
      key: 'date',
      type: 'input',
      defaultValue: {
           year:+data.date.substr(0,4),
           month:+data.date.substr(5,2),
           day:+data.date.substr(8,2)
      },
      templateOptions: {
        label: 'First Name (initialized via default value)',
      },
    },

In this case you're always mannage the "date" an an object with properties year,month and day, yes, a FormControl can be store an object, it's not "extrange"

2.- Use a custom Adapter that mannage a string. It's a bit complex. You need create two class:

CustomAdepter

@Injectable()
export class CustomAdapter extends NgbDateAdapter<string> {

  readonly DELIMITER = '-';

  fromModel(value: string | null): NgbDateStruct | null {
    if (value) {
      let date = value.split(this.DELIMITER);
      return {
        day : parseInt(date[2], 10),
        month : parseInt(date[1], 10),
        year : parseInt(date[0], 10)
      };
    }
    return null;
  }

  toModel(date: NgbDateStruct | null): string | null {
    return date ? date.year+ this.DELIMITER+('00'+date.month).slice(-2) + this.DELIMITER + ('00'+date.day).slice(-2)   : null;
  }
}

and CustomDateParserFormatter (makes that when you enter an input create the ngbDateStructurt)

@Injectable()
export class CustomDateParserFormatter extends NgbDateParserFormatter {

  readonly DELIMITER = '-';

  parse(value: string): NgbDateStruct | null {
    if (value) {
      const separator=value.indexOf(".")>=0?".":value.indexOf("-")>=0?"-":value.indexOf("/")>=0?"/":null
      if (separator)
      {
      let date = value.split(separator);
      return {
        day : parseInt(date[2], 10),
        month : parseInt(date[1], 10),
        year : parseInt(date[0], 10)
      };
    }
    }
    return null;
  }

  format(date: NgbDateStruct | null): string {
    return date ? date.year+ this.DELIMITER+('00'+date.month).slice(-2) + this.DELIMITER + ('00'+date.day).slice(-2)   : "";
  }
}

Then in your component you need add as providers in your module (you can also add as providers in your component)

  providers: [
    {provide: NgbDateAdapter, useClass: CustomAdapter},
    {provide: NgbDateParserFormatter, useClass: CustomDateParserFormatter}
  ]

Now you can use and your model or formControl will be a string

a forked stackblitz the example in ng-bootstrap