angularjhipsterng-bootstrapngb-datepicker

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


  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!;
      }
    }

Solution

  • There was no issue with the code. The problem was caused by the custom NgbDateAdapter which was provided in the file core.module.ts, imported through the file app.module.ts. It was interrupting the default fromModel method of the NgbDateAdapter using a custom method.

    I was unaware of it as I was using JHipster for my project, and this was caused by its datePickerUtility.