0

I have a very simple component that makes a nice little dashboard card and I can reuse it for multiple pieces of data. When I use test data, my condition works great, as it sends the data immediately and picks the correct pipe. However, on the real dashboard it calls for the data from an API. The data is returned but since this is no longer in the ngOnInit, my condition doesn't fire. Would anyone know how I might pick the right condition after getting data back from my service?

Component:

import { Component, OnInit } from '@angular/core';
import {SecondsToMinutesPipe} from "../seconds-to-minutes.pipe";
import { DecimalPipe } from "@angular/common";

@Component({
  selector: 'app-dash-stat',
  inputs: [
    'icon', 'color', 'amount', 'description', 'time'
  ],
  templateUrl: './dash-stat.component.html',
  styleUrls: ['./dash-stat.component.css'],
})
export class DashStatComponent implements OnInit {

  private loading: boolean = true;
  private icon: string;
  private color: string = '#fff';
  private amount: any = 0;
  private description: string;
  private time: boolean = false;

  constructor(private timePipe: SecondsToMinutesPipe, private numberPipe: DecimalPipe) { }

  ngOnInit() {
      this.loading = false; //remove this and figure out 

      if(this.time){
        this.amount = this.timePipe.transform(this.amount);
      }
      else
      {
        this.amount = this.numberPipe.transform(this.amount, '1.0-0');
      }
  }

}

And I call it from my dashboard like this:

<div class="col-sm">
    <app-dash-stat icon="fa-tint" color="#eb1c2d" description="Litres Flown" amount="{{dashStats.volume}}"></app-dash-stat>
  </div>

  <div class="col-sm">
    <app-dash-stat icon="fa-clock-o" color="#fd6b00" description="Average Loading Time" amount="{{dashStats.filltime}}" time="true"></app-dash-stat>
  </div>
enfrost
  • 165
  • 1
  • 14
  • Seems like a perfect use case for this pipe: https://angular.io/api/common/AsyncPipe – Iskandar Reza Oct 19 '17 at 23:46
  • Ooo, this does look perfect! I will give it a try – enfrost Oct 20 '17 at 17:02
  • Ah it didn't quite work as the async part is in the parent component, I'm sure there was some way i could pass an observable or promise to the child but the on changes worked first try. Nice to know about new filters tho! – enfrost Oct 20 '17 at 18:08

3 Answers3

2

If you want to pass data from one component to the other and this data kind of changes, you wanna create an injectable data service you can subscribe to that can pass the value around. This is a copy of my data service, to give you an idea:

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Rx';
import { ReplaySubject } from 'rxjs/ReplaySubject';

@Injectable()
export class DataService {
    // for single non-async string data
    stringData: string; 


    // for complex async data; use setData/getData
    private dataItem: ReplaySubject<any> = new ReplaySubject<any>();

    public setData(data: any) {
        this.dataItem.next(data);
    };

    public getData(): Observable<any> {
        return this.dataItem.asObservable();
    };
}

In your app.module.ts you import it and add it the @NgModule providers:

import { DataService } from '../app/Services/data.service';

 @NgModule({
...
     providers: [
         DataService
     ]
...

Then in both your components you import it:

import { DataService } from '../Services/data.service';

And you use it in the components like so:

Source component:

export class sourceComponent {
    myVariable: any;
    constructor(
        public dataService: DataService){

    // do something...
    }

    SomeFunction(): void {
    // do something...   
        this.dataService.setData(this.myVariable);
    }
}

Target component:

import { Subscription } from "rxjs/Subscription";

// @component and stuff...
export class targetComponent {
    myValue: any;
    subscription: Subscription;

    constructor(
        public dataService: DataService) {     


        this.subscription = this.dataService.getData()
            .subscribe(
            data => {

                this.myValue = data;
                // do stuff maybe

                },
            error => {
                console.log(error);
            });
    }
}

Then the data should update every time something happens. So that you don't get memory leaks, be sure to unsubscribe in the target component like so:

ngOnDestroy() {
    // unsubscribe to ensure no memory leaks
    this.subscription.unsubscribe();
}
Iskandar Reza
  • 953
  • 1
  • 7
  • 16
0

First, if you think <app-dash-stat icon="fa-tint" color="#eb1c2d" description="Litres Flown" amount="{{dashStats.volume}}"></app-dash-stat> this syntax would pass data to the <app-dash-stat> component that is wrong. The code is syntactically wrong. You cannot simply call attributes like this. You should enclose them with [ ]. <app-dash-stat icon="fa-tint" [color]="#eb1c2d" [description]="Litres Flown" [amount]="dashStats.volume"> and you cannot mix property binding and interpolation. And in your class the variables that need data to be injected from the parent component, you should use the @Input() annotation.

@Input() private loading: boolean = true;
@Input() private icon: string;
@Input() private color: string = '#fff';
@Input() private amount: any = 0;
@Input() private description: string;
@Input() private time: boolean = false;
Nadun Liyanage
  • 463
  • 3
  • 10
  • thank you for pointing out better syntax. The way i had it actually was working, but I didn't know if it was best practice or not :) – enfrost Oct 20 '17 at 17:01
  • strangely I looked it up and you can do either inputs:[] or @input interchangeably but for whatever reason I couldn't use the [attribute] syntax. – enfrost Oct 20 '17 at 18:06
  • please elaborate your exact problem. @Input() cannot be eliminated for input attributes. What did you mean by "you can do either inputs:[] or @input"? they are 2 different things you are referring to. – Nadun Liyanage Oct 20 '17 at 18:20
  • This thread explains how the two are interchangeable: https://stackoverflow.com/questions/33648586/what-is-the-difference-between-input-and-inputs-in-angular2-components – enfrost Oct 20 '17 at 18:59
0

I was thinking maybe emitting events would work, but ngOnChanges ended up doing the trick.

ngOnChanges(changes: SimpleChanges){

    this.loading = false; //remove this and figure out

    if(this.time){
      this.amount = this.timePipe.transform(changes.amount.currentValue);
    }
    else
    {
      this.amount = this.numberPipe.transform(changes.amount.currentValue, '1.0-0');
    }

  }
enfrost
  • 165
  • 1
  • 14