ng-bootstrap: ngb-datepicker initial value with angular reactive form group is not getting set

Viewed 1196
  1. I am working with ngb-datepicker which is working fine if no initial values or predefined values are set but when trying to use it formControlName or with [(ngModel)] with an existing predefined value the predefined or initial value is not setting on the redenied view. Imagine this as a scenario of editing a form or record with prefilled values. Other formControls with text and numbers are working as intended.
  2. I am using NgbStruct Model but still not working.
  3. I tried and debugged the code the value are getting assigned to the form control in a patchValue method and in the format of NgbStruct but not seen in the rendered view
  4. I tried to implement similar scenario in example provided in stack blitz by ng-bootstrap it is working fine there

Package.json file

       {
          "name": "angular",
          "version": "0.0.1-SNAPSHOT",
          "description": "Description for angular",
          "private": true,
          "license": "UNLICENSED",
          "cacheDirectories": [
            "node_modules"
          ],
          "dependencies": {
            "@angular/common": "10.0.0",
            "@angular/compiler": "10.0.0",
            "@angular/core": "10.0.0",
            "@angular/forms": "10.0.0",
            "@angular/localize": "10.0.0",
            "@angular/platform-browser": "10.0.0",
            "@angular/platform-browser-dynamic": "10.0.0",
            "@angular/router": "10.0.0",
            "@fortawesome/angular-fontawesome": "0.6.1",
            "@fortawesome/fontawesome-svg-core": "1.2.29",
            "@fortawesome/free-solid-svg-icons": "5.13.1",
            "@ng-bootstrap/ng-bootstrap": "^9.0.2",
            "@ngx-translate/core": "12.1.2",
            "@ngx-translate/http-loader": "5.0.0",
            "bootstrap": "4.5.0",
            "bootstrap-icons": "^1.4.0",
            "moment": "2.27.0",
            "ng-diff-match-patch": "^3.0.1",
            "ng-jhipster": "0.15.0",
            "ngx-cookie-service": "3.0.4",
            "ngx-infinite-scroll": "9.0.0",
            "ngx-webstorage": "5.0.0",
            "rxjs": "6.5.5",
            "swagger-ui-dist": "3.25.1",
            "tslib": "2.0.0",
            "zone.js": "0.10.3"
          },
          "devDependencies": {
            "@angular/cli": "10.0.0",
            "@angular/compiler-cli": "10.0.0",
            "@ngtools/webpack": "10.0.0",
            "@openapitools/openapi-generator-cli": "1.0.13-4.3.1",
            "@types/jest": "26.0.3",
            "@types/node": "13.13.4",
            "@typescript-eslint/eslint-plugin": "2.30.0",
            "@typescript-eslint/eslint-plugin-tslint": "2.30.0",
            "@typescript-eslint/parser": "2.30.0",
            "autoprefixer": "9.8.4",
            "browser-sync": "^2.26.1",
            "browser-sync-webpack-plugin": "2.2.2",
            "codelyzer": "5.2.2",
            "copy-webpack-plugin": "6.0.2",
            "css-loader": "3.6.0",
            "eslint": "6.8.0",
            "eslint-config-jhipster": "0.0.1",
            "eslint-config-prettier": "6.11.0",
            "eslint-loader": "4.0.2",
            "file-loader": "6.0.0",
            "friendly-errors-webpack-plugin": "1.7.0",
            "generator-jhipster": "6.10.5",
            "generator-jhipster-entity-audit": "3.3.0",
            "html-loader": "1.1.0",
            "html-webpack-plugin": "4.3.0",
            "husky": "4.2.5",
            "jest": "26.1.0",
            "jest-date-mock": "1.0.8",
            "jest-junit": "11.0.1",
            "jest-preset-angular": "8.2.1",
            "jest-sonar-reporter": "2.0.0",
            "lint-staged": "8.2.1",
            "mini-css-extract-plugin": "0.9.0",
            "moment-locales-webpack-plugin": "1.2.0",
            "optimize-css-assets-webpack-plugin": "5.0.3",
            "postcss-loader": "3.0.0",
            "prettier": "2.1.2",
            "rimraf": "3.0.2",
            "sass": "1.26.9",
            "sass-loader": "8.0.2",
            "simple-progress-webpack-plugin": "1.1.2",
            "style-loader": "1.2.1",
            "terser-webpack-plugin": "3.0.6",
            "thread-loader": "2.1.3",
            "to-string-loader": "1.1.6",
            "ts-loader": "7.0.5",
            "tslint": "6.1.2",
            "typescript": "3.9.5",
            "webpack": "4.43.0",
            "webpack-bundle-analyzer": "3.8.0",
            "webpack-cli": "3.3.12",
            "webpack-dev-server": "3.11.0",
            "webpack-merge": "4.2.2",
            "webpack-notifier": "1.8.0",
            "workbox-webpack-plugin": "5.1.3",
            "write-file-webpack-plugin": "4.5.1"
          },
          "engines": {
            "node": ">=12.16.1"
          }
        }

app.module.ts

    import { NgModule } from '@angular/core';
    import { RouterModule } from '@angular/router';

    import { CommonModule } from '@angular/common';
    import { FormsModule, ReactiveFormsModule } from '@angular/forms';
    import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
    import { NgJhipsterModule } from 'ng-jhipster';
    import { InfiniteScrollModule } from 'ngx-infinite-scroll';
    import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';

    import * as moment from 'moment';
    import { NgbDateAdapter, NgbDatepickerConfig } from '@ng-bootstrap/ng-bootstrap';
    import { NgbDateMomentAdapter } from './datepicker-adapter';
    
    import { CeramicAccountingSoftwareSharedModule } from 'app/shared/shared.module';
    import { DemoComponent } from './demo-component.component';
    
    @NgModule({
      imports: [FormsModule, CommonModule, NgbModule, NgJhipsterModule, InfiniteScrollModule,  
                  `enter code here`FontAwesomeModule, ReactiveFormsModule],
      providers: [{ provide: NgbDateAdapter, useClass: NgbDateMomentAdapter }],
      declarations: [
        DemoComponent ,
      ],
    })
    export class AppModule {
      constructor(dpConfig: NgbDatepickerConfig) {
        dpConfig.minDate = { year: moment().year() - 100, month: 1, day: 1 };
      }
    }

demo.component.ts

    import { Component, OnInit } from '@angular/core';
    import { FormBuilder, Validators } from '@angular/forms';
    import { NgbDateAdapter, NgbDateStruct } from '@ng-bootstrap/ng-bootstrap';
            
    import { NgbDateMomentAdapter } from './datepicker-adapter';
    import * as moment from 'moment';
    import { Moment } from 'moment';
        
    @Component({
       selector: 'demo',
       templateUrl: './demo.component.html',
       providers: [{ provide: NgbDateAdapter, useClass: NgbDateMomentAdapter }],
    })
        
    export class PurchaseTransactionUpdateComponent implements OnInit {
        editForm = this.fb.group({
            date: ['', Validators.required],
        });
        constructor(
            private fb: FormBuilder,
            private dateAdaptor: NgbDateAdapter<Moment>
        ) {}
        ngOnInit(): void {
            this.editForm.patchValue({
                date: this.dateAdaptor.fromModel(moment('2021-03-20T18:30:00.000+0000')),
            });
        }
        save(): void{
            console.log("Form Value", this.editForm.value);
        }
    }

demo.component.html

    <form name="editForm" role="form" class="mt-4" (ngSubmit)="save()" 
        [formGroup]="editForm">
    <input class="form-control" placeholder="yyyy-mm-dd" type="text" name="dp"
        formControlName="date" ngbDatepicker #d="ngbDatepicker" />
        <div class="input-group-append">
                <button class="btn" (click)="d.toggle()" type="button">
                <i class="bi bi-calendar"></i>
            </button>
        </div>
        <button type="submit" id="save-entity" [disabled]="editForm.invalid || 
            isSaving" class="btn btn-dark">Save</button>
    </form>

datepicker-adapter.ts

    import { Injectable } from '@angular/core';
    import { NgbDateAdapter, NgbDateStruct } from '@ng-bootstrap/ng-bootstrap';
    import { Moment } from 'moment';
    import * as moment from 'moment';
    
    @Injectable()
    export class NgbDateMomentAdapter extends NgbDateAdapter<Moment> {
      fromModel(date: Moment): NgbDateStruct {
        if (date && moment.isMoment(date) && date.isValid()) {
          return { year: date.year(), month: date.month() + 1, day: date.date() };
        }
        // ! can be removed after https://github.com/ng-bootstrap/ng-bootstrap/issues/1544 is resolved
        return null!;
      }
    
      toModel(date: NgbDateStruct): Moment {
        // ! after null can be removed after https://github.com/ng-bootstrap/ng-bootstrap/issues/1544 is resolved
        return date ? moment(date.year + '-' + date.month + '-' + date.day, 'YYYY-MM-DD') : null!;
      }
    }
2 Answers

Actually there is no issue in the code this issue was being faced due to custom NgbDateAdapter which was provided in the core.module.ts which was imported in app.module.ts which was interrupting the default "fromModel" method of NgbDateAdapter with custom method. Actually I was unaware of this was being done as I was using #JHIPSTER form my project and this was done by jhipster datePickerUtility

I solved my issue by removing { provide: NgbDateAdapter, useClass: NgbDateNativeAdapter } from app.module.ts

and use blow code in html

    <input class="form-control" (focus)="datepicker.open()"  
    formControlName="'datepicker'" ngbDatepicker #datepicker="ngbDatepicker">

and use blow code in .ts file

constructor(private ngbFormatter: NgbDateParserFormatter) { }

ngOnInit(): void {
   this.form.get('datepicker').patchValue(this.ngbFormatter.parse(this.date));
}
Related