1
votes

This might be one of the more frustrating issues I've coped with in a while. Dates and -- in particular -- NgbDatepicker are a bit of a bear to deal with in Angular, in general.

I'm implementing NgbDatepicker against reactive forms in Angular 8, and the gist of my problem is that I can set the initial value of my date control, but that initial value doesn't show-up (visually) on the form. I can log my form to the console and see that the value is getting set, but the form field itself doesn't update. If I select a date from the picker itself then my selection reflects in the form field. I had thought it would be as simple as setting the [startDate] property of the ngbDatepicker input, but apparently no. I've tried a lot of different iterations on this; here's where things stand, code-wise:

My HTML:

<input #licenseExpiration="ngbDatepicker"
    class="form-control"
    fromControlName="licenseExpiration"
    [startDate]="startDate"
    ngbDatepicker />
<button class="btn btn-info zero-spacing pt-1 pr-1 pl-1"
    (click)="licenseExpiration.toggle()"
    type="button">

The relevant bits of my component:

@Component({
  selector: 'app-cert',
  templateUrl: './cert.component.html',
  providers: [
    { provide: NgbDateParserFormatter, useClass: CustomDateParserFormatter }
  ]
})
export class CertComponent implements OnInit {
  certForm: CertForm = new CertForm();

  constructor(
    public readonly formatter: CustomDateParserFormatter) { }

  ngOnInit() {
    this.certForm.setModel(...modelObjectFromOtherService...);
  }

  get startDate() {
    return { year: 2020, month: 6, day: 13 };
  }
}

The above HTML resides in a formGroup that is spec'd by that CertForm type. That form contains the control for the date and looks (in part) like this:

export class CertForm extends FormGroup {
  public dateParser: CustomDateParserFormatter;

  constructor(
    cert: ICertModel = new CertModel()) {
    super({
      ..
      licenseExpiration: new FormControl("", Validators.required),
      ..
    });
  }

  setModel(cert: ICertModel) {
    this.patchValue({
      licenseExpiration
    });
  }

  getModel(): ICertModel {
    const cert = new CertModel();
    ...
    cert.licenseExpiration = this.get("licenseExpiration").value;
    ...
    return cert;
  }
}

From there I do have the custom parser/formatter set-up and it looks just like the one that is spec'd on the NGB examples pages:

@Injectable({
  providedIn: "root"
})
export class CustomDateParserFormatter extends NgbDateParserFormatter {

  readonly DELIMITER = '/';

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

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

And, again, this just won't seem to let me initiate that date field with a visible start-up value. As you can see, at this point I'm just literally hard-coding a return value for that start date. And that date does get properly set -- when you open the picker, that's the date it lands on, but it doesn't actually show-up in the display field when you land on the page. That's just blank until you actually select a value.

I have attempted so many different combinations of ideas and nothing is working. It's incredibly frustrating. I see that variations/fragments of this question do exist already, but I can't find examples of the question being asked with quite this combination of technologies. Most people seem to be relying on templated forms, not reactive forms, when using NgbDatepicker.

1

1 Answers

2
votes

startDatae property on the date picker does not set the initial value. You should set your formControl's value to be the value you want.

This is an example without the parser. I use a custom parser/formatter at work and it does work though (can't post that code here).

Working stackblitz (note this is on latest Angular and NgBootstrap) https://stackblitz.com/edit/angular-zpeul4

App Component

  formGroup = new FormGroup({});

  ngOnInit() {
    console.log('on init');
    this.formGroup.addControl("test", new FormControl({day: 20, month:4, year:1969})) //nice
    this.formGroup.valueChanges.subscribe(val => {
      console.log(val);
    })
    console.log(this.formGroup.value);
  }

Template

 <div class="container" [formGroup]="formGroup">
    
  <input #licenseExpiration="ngbDatepicker"
    class="form-control"
    [formControlName]="'test'"
    ngbDatepicker />

  <button class=""
  (click)="licenseExpiration.toggle()"
  type="button">open</button>

</div>  

If you are still having issues with your parser, make sure you have both an NgbDateParserFormatter and NgbDateAdapter and that your initial value you are setting it to can get converted properly by the adapter (if its not an Ngb Date Object)