Search code examples
angulartypescriptangular-services

Pass a value to another component using Services when a change in value (dropdown selection) happens in the parent component - Angular


I'm trying to pass a value (year_value) from a parent component to a child component using services, whenever there is a change in the value (dropdown selection). Also I'm using Angular routing (<router-outlet>) for navigation to child component, since I will be adding more charts later which are separate components.

app.component.html

<mat-toolbar class="toolbar2">
        <div>
            <button mat-button (click)="getChartType('bar')" [routerLink]="'/bar-chart'" matTooltip="Bar Chart">
                <img src="assets/images/column.svg">
            </button>
        </div>
        <div>
          <mat-form-field>
            <mat-label>Select year</mat-label>
            <mat-select placeholder="{{initialValue}}" (selectionChange)="selectByYear($event)"
                [(ngModel)]="year_value">
                <mat-option *ngFor="let year of years" [value]="year.value">
                    {{year.viewValue}}
                </mat-option>
            </mat-select>
          </mat-form-field>
        </div>
    </mat-toolbar>

    <mat-divider></mat-divider>

    <div class="container-fluid">
        <div class="col1"></div>
        <div class="col2">
          <div class="col2-row1 charts-area">
            <router-outlet>
              <div class="card" id="container"></div>
            </router-outlet>
          </div>
          <div class="col2-row2"></div>
        </div>
    </div>

<mat-select> is the dropdown and when selecting a value, the selectByYear updates the year property.

app.component.ts

  export class AppComponent implements OnInit {

  constructor(private commonActions: CommonActionsService) {}

  beData = this.commonActions.getData();

  canvas: any;
  ctx: any;
  header: any;
  xValues: string[] = [];
  dataValues: any[] = [];
  yValues: number[] = [];
  year: string = '';
  initialValue: any;
  year_value;
  years: any[] = [];
  bgColors: string[] = [];
  buttons = ['Status', 'Auditor', 'ISOScheme'];
  numbers = ['Audits'];
  chart_yfield: string[] = [];
  chart_xfield: string[] = [];
  chartType;
  activity: any;
  xData: any;
  label: any;
  options: any;
  currentYear: any;

  ngOnInit() {
    this.selectYear();
    this.year_value = 2022;
    this.chart_xfield.push('Status');
    this.chart_yfield.push('Audits');
    const index: number = this.buttons.indexOf('Status');
    if (index !== -1) {
      this.buttons.splice(index, 1);
    }
    this.numbers = []
    this.header = this.chart_xfield[0]
    this.getChartType('bar');
  }

  selectYear() {
    this.currentYear = 2022;
    let earliestYear = this.currentYear - 5;
    while (this.currentYear >= earliestYear) {
      let dateOption = {
        "viewValue": this.currentYear + ' ' + '-' + ' ' + (this.currentYear + 1),
        "value": this.currentYear
      };
      this.years.push(dateOption);
      this.currentYear -= 1;
    }
    this.initialValue = this.years[0].viewValue;
    this.selectByYear(this.years[0]);
  }

  selectByYear(event) {
    this.year = event.value;
    this.getChartType(this.chartType);

    this.commonActions.setYearValue(this.year);
  }

  getChartType(type: string) {
    if (this.chart_yfield.length != 0 && this.header != '') {
      if (!this.year) {
        return;
      }
      this.chartType = type;
      this.isHighChart = true;
      this.dataValues = [];
      this.xValues = [];
      this.yValues = [];
      this.lineData = [];
      this.beData[this.year][this.header].forEach((entry) => {
        this.dataValues.push([entry.name, entry.value]);
      });
      this.beData[this.year][this.header].forEach((entry) => {
        this.xValues.push(entry.name);
      });
      this.beData[this.year][this.header].forEach((entry) => {
        this.yValues.push(entry.value);
      });
      this.beData[this.year][this.header].forEach((entry) => {
        this.bgColors.push(entry.color);
      });
      console.log(this.xValues, this.yValues, this.header, this.year);

    }
  }

}

So inside the selectByYear() I'm setting the year. After the value of year gets updated I'm updating the same in the CommonActionsService also.

common-actions.service.ts

export class CommonActionsService {

  year_value: string;

  constructor() { }

  setYearValue(year: string) {
    this.year_value = year;
  }

  getYearValue() {
    return this.year_value;
  }

}

Now in the bar-chart component, I call the get method. But nothing is displaying in the console.

bar-chart.component.ts

export class BarChartComponent implements OnInit {

  year_value: string;

  constructor(private commonActions: CommonActionsService) {}

  ngOnInit() {
    this.year_value = this.commonActions.getYearValue();
    console.log("Year Value from Bar Chart: ", this.year_value);
  }

}

I want to get the value of this.year inside the selectByYear() in app.component.ts, inside bar-chart.component.ts whenever the year updates.

Or is there any other way other than using services?

I'm adding my full code here - https://stackblitz.com/edit/stackblitz-starters-k2touo?file=src%2Fmain.ts , but I'm not able to compile it online.


Solution

  • Using services is the right way to share information accross component if they're not parent-child.

    Here it is a matter of concurrency: you are calling getYearValue before it has been setted in the other component (both event are being called OnInit, and maybe one component is being initialized before the other).

    My suggestion is to use Subject and Observablein order to create streams so that they'll always be up to date.

    Here there is a simple example of how you could structure it (I couldn't directly use your code since it was not compiling)