Search code examples
angularangular-componentsng-bootstrapangular-providers

Provider has no access to class variables after being initialized in Angular


I have created the following project https://stackblitz.com/edit/angular-agzqbf You can see in the console the variable x is undefined from the beginning, even after initializing the variable in ngOnInit

enter image description here

The function that prints out the value of x is format() which is a function inherited from the parent class, and it is called every time a date is selected in the input field with calendar. But it is always undefined even after changing its value with a button calling changeValue()

Is this the normal behavior of providers? or how can I make sure the format() function has access to the variable class?

app.component.html

<form class="form-inline">
  <div class="form-group">
    <div class="input-group">
      <input class="form-control" placeholder="yyyy-mm-dd"
             (focus)="d.open()" name="dp" [(ngModel)]="model" ngbDatepicker #d="ngbDatepicker">
      <div class="input-group-append">
        <button class="btn btn-outline-secondary calendar" (click)="d.toggle()" type="button"></button>
      </div>
    </div>
  </div>
</form>


<button (click)="changeValue()"> Change X value</button>

app.component.ts

import { Component, OnInit } from '@angular/core';
import { NgbDateParserFormatter, NgbDateStruct } from "@ng-bootstrap/ng-bootstrap";

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: []
})
export class AppComponent extends NgbDateParserFormatter implements OnInit {
  title = 'test-project';

  x: number;
  model;

  constructor() {
    super();
  }

  ngOnInit() {
    this.x = 9;
  }

  changeValue() {
    this.x = 5;
    console.log('x changed', this.x);
  }

  private padNumber(value: number) {
    if (this.isNumber(value)) {
        return `0${value}`.slice(-2);
    } else {
        return "";
    }
  }

  private isNumber(value: any): boolean {
    return !isNaN(this.toInteger(value));
  }

  private toInteger(value: any): number {
    return parseInt(`${value}`, 10);
  }

  parse(value: string): NgbDateStruct {
    if (value) {
      const dateParts = value.trim().split('/');
      if (dateParts.length === 1 && this.isNumber(dateParts[0])) {
        return {year: this.toInteger(dateParts[0]), month: null, day: null};
      } else if (dateParts.length === 2 && this.isNumber(dateParts[0]) && this.isNumber(dateParts[1])) {
        return {year: this.toInteger(dateParts[1]), month: this.toInteger(dateParts[0]), day: null};
      } else if (dateParts.length === 3 && this.isNumber(dateParts[0]) && this.isNumber(dateParts[1]) && this.isNumber(dateParts[2])) {
        return {year: this.toInteger(dateParts[2]), month: this.toInteger(dateParts[1]), day: this.toInteger(dateParts[0])};
      }
    }   
    return null;
  }

  format(date: NgbDateStruct): string {
    console.log('x value', this.x);
    let stringDate: string = ""; 
    if(date) {
      stringDate += this.isNumber(date.year) ? date.year + "/" : "";
      stringDate += this.isNumber(date.month) ? this.padNumber(date.month) + "/" : "";
      stringDate += this.isNumber(date.day) ? this.padNumber(date.day) : "";
    } 
    return stringDate;
  }
}

PS: both parse() and format() functions are inherited from the parent class to set the correct format in the input field after selecting a date


Solution

  • lets look at NgModule description

    providers: [{ provide: NgbDateParserFormatter, useClass: AppComponent}] 
    

    this string means that when something tries to inject NgbDateParserFormatter angular will create AppComponent and passes it instead. To achieve what you are trying to achieve add providers section to app component with useExisting: AppComponent like this:

    @Component({
    ...
    providers: [{ provide: NgbDateParserFormatter, useExisting: AppComponent }]
    })
    export class AppComponent{
    ...