How to execute functions sequentially in Angular 2/4 Typescript

I am having trouble trying to run functions sequentially in my Angular 2/4 project.

I have a function retrieveData()

that fetches data from a data service and assigns it to an array I declared.

Then I have a function displayData()

that takes the data stored in an array and displays it in a chart.

When I try to run them like:

function(){
  this.retrieveData();
  this.displayData();
}

      

displayData()

the function starts earlier than retrieveData()

, mainly due to the data service in the function retrieveData()

. Therefore, the graph cannot be displayed correctly.

One way I've found that I can run functions sequentially is async.waterfall

from a library async

, but I can't manage to import the library into my project, with the console log saying: Uncaught Error: Unexpected value 'waterfall' imported by the module 'AppModule'. Please add a @NgModule annotation.

I don't want to use Promises and Observables because they require the initial function to have some kind of return value in order to progress to the next functions. I managed to achieve this with a function setTimeOut()

, but I really doubt the robustness and reliability of the approach.

So, any help with use async

in Angular 2/4, or any ways to make the functions wait without any promises returning?

UPDATE

Sorry for the confusion and inconvenience the guys have caused. I posted and requested an overly simplified version. Here's a more complete piece of my code. I am Angular and Typescript noob, and more a noob when it comes to Async programming techniques.

Below is my implementation of the Promises method in retrieveAllData()

. It doesn't give any compile-time or run-time errors. But when functions are still running asynchronously, that is, refreshAllCharts()

everything is still running before retrieveAllData()

. Are there any downsides to my implementation of promises?

import { Component, OnInit, AfterContentInit } from '@angular/core';
import { DataService } from '../data.service';
import {BaseChartDirective} from 'ng2-charts/ng2-charts';
import {IMyDpOptions,IMyDateModel} from 'mydatepicker';

//import {MomentTimezoneModule} from 'angular-moment-timezone';
import * as moment from 'moment-timezone';

// import async from 'async-waterfall';

@Component({
  templateUrl: 'chartjs.component.html'
})
export class ChartJSComponent {

  tempArr = []; //array to store temperature values for the chart
  timeTempArr = []; //array to store timestamps for the chart label

  device = "1CB001"; //a parameter used for the data service method to query the database

  dateSelected; //variable to store the date chosen from the datepicker on the html side of the component

  constructor(private dataService: DataService){
  }

  ngOnInit(){
  }

//function to retrieve temperature values and assign them into "tempArr" array
  retrieveTempDataAssign(){
    var _arr = new Array();

    this.dataService.getData(this.device, this.dateSelected).subscribe(response => {

      console.log("Response: " + JSON.stringify(response));
      for(var item of response){
        _arr.push(item.Temperature);
      }

      this.tempArr = _arr;
      console.log("Array assigned Temp: " + this.tempArr);
    });

    this.retrieveTempTimeDataAssign();

  }

//function to retrieve time values and assign the date and time objects into "timeTempArr" array
  retrieveTempTimeDataAssign(){

    var _arr = new Array();

    this.dataService.getData(this.device, this.dateSelected).subscribe(response => {

      for(var item of response){
        // var value = "'" + item.Date + "'";
        // _arr.push(value);

        var value = item.Date;
        var time = moment.tz(value, "Asia/singapore");
        _arr.push(time);
      }
      this.timeTempArr = _arr;
      console.log("Array assigned Time: " + this.timeTempArr);
    });
  }

//function to refresh the whole of Temperature chart
  refreshTempChart(){
    this.showTempData();
    setTimeout(() => this.showTempLabels(), 500);
  }

//function to assign the "tempArr" array into the dataset for the temperature chart
  showTempData(){
    console.log("To display: " + this.tempArr);
    this.datasetsTemp = [{
      label: "Values",
      data: this.tempArr
    }];
  }

//function to assign the "timeTempArr" array into the labels for the temperature chart
  showTempLabels(){
    console.log("To label: " + this.timeTempArr);
    this.labels = this.timeTempArr;
  }

//date picker format
  private myDatePickerOptions: IMyDpOptions = {
        dateFormat: 'yyyy-mm-dd',    
  };

//change event listener on the datepicker
  onDateChanged(event: IMyDateModel){

    this.dateSelected= event.formatted;
    console.log("Selected Date: " + this.dateSelected);

//**The implementation part**
    this.retrieveAllData().then(()=>{
      this.refreshAllCharts();
    })

  }

//to run all functions to retrieve respective data
  retrieveAllData(){
    return new Promise((resolve, reject) => {
      this.retrieveTempDataAssign(); //assign the retrieved values into the array first

      return true;
    });
  }

//to run all functions to update all the charts
  refreshAllCharts(){
    this.refreshTempChart();
  }

//objects used by the chart to display data
  private datasetsTemp = [
    {
      label: "Values",
      data: []
    }
  ];

  private labels = [];

  private options = {
    scales: {
      xAxes: [{
          display: true,
          type: "time",
          time: {
              unit: "hour",
              tooltipFormat: 'YYYY-MM-DD hh:mm A'
          },
          scaleLabel: {
              display: true,
              labelString: 'Time'
          }
      },],
      yAxes: [{
        ticks: {
          beginAtZero: false
        }
      }]
    }
  };
}

      

+4


source to share


5 answers


You don't need to return any value to navigate to the next functions using a promise. If you want your function signature to be intact (i.e. retrieveData()

, displayData()

take no parameters and return void), consider using promises like this:

private dataStorage: string = null;
private retrieveDataResolver;

  displayData(): void {
    // your display code goes here
    console.log("2. DISPLAYING DATA", this.dataStorage);
  }
  retrieveData(): void {
    // your async retrieval data logic goes here
    console.log("1. GETTING DATA FROM SERVER");
    setTimeout(() => { // <--- Change it - your service data retrieval
      this.dataStorage = '++DATA++';
      this.retrieveDataResolver(); // <--- This must be called as soon as the data are ready to be displayed
    }, 1000);
  }

  retrieveDataPromise(): Promise<any> {
    return new Promise((resolve) => {
      this.retrieveDataResolver = resolve;
      this.retrieveData();
    })
  }
  retrieveAndThenDisplay() {
    this.retrieveDataPromise().then(() => {this.displayData()});
  }

      



I recommend using the promise wrapper as a powerful chaining construct for serializing asynchronous operations

+3


source


Next to your update, you can tie a promise. first get data and update graphe when data is ready. this link can help you understand promises better: https://codecraft.tv/courses/angular/es6-typescript/promises/



// get data
var job1 = new Promise(function(resolve, reject){
    resolve('done1');
});


job1.then(function(data) {
    refreshGraphe();
})

      

+2


source


There is a great site that explains asynchronous concepts in terms of rxjs , I found this very handy since the rxjs docs are written in very complex terminology.

They suggest this as the equivalent of the asynchronous waterfall feature:

var Rx = require('rx');

var async = {
    waterfall: series => {
        return Rx.Observable.defer(() => {
            var acc = series[0]();
            for (var i = 1, len = series.length; i < len; i++) {

                // Pass in func to deal with closure capture
                (function (func) {

                    // Call flatMapLatest on each function
                    acc = acc.flatMapLatest(x => func(x));
                }(series[i]));
            }

            return acc; 
        });
    }
}

      

Then you use fromCallback for the first in the sequence and fromNodeCallback for sequential calls to chain the results across the sequence.

+1


source


Try something like this:

  retrieveData(): Promise<any> {
      return this.dataService.getData(this.device, this.dateSelected)
          .map(response => response.json())
          .toPromise();
  }


  executeSerialFunctions() {
      this.retrieveData()
          .then(response => {
              for(var item of response){
                var value = item.Date;
                var time = moment.tz(value, "Asia/singapore");
                _arr.push(time);
              }
              this.timeTempArr = _arr;
              console.log("Array assigned Time: " + this.timeTempArr);
              return response 
          }).then(response => {
              displayData();
          })
  }

      

The retrieveData function retrieves data via dataService and returns a promise.

You call executeSerialFunctions and retrieveData is called, and when that fetch is complete and the data is returned, it will continue and process the data as you had inside the retrieveData function in your code (with the responses iterator).

Once that is complete, it will return a response and then execute - it efficiently eats the response, but will call displayData after all the data has been processed.

I cannot test this, but I think it should work.

+1


source


Today modern javascript architecture is not synchronized, but asynchronous, "for a better ui experience ...". What I mean is that the HTTP get request has started, during which time you did not wait for the data to be ready, but you continue to execute your program. As soon as your data is ready (xhr request), you will receive a notification and can use it. you need to implement an angular service that fetches data and uses observables.

basically a sample code for your view that calls the service responsible for http requests

    import { Component, OnInit } from '@angular/core';
    import { IntervalObservable } from "rxjs/observable/IntervalObservable";
    // my data model
    import { PositionsModel } from "./positionsmodel";
    // the service responsible to make the http requests
    import { MouvementService } from './mouvement.service';

    @Component({
      selector: 'app-mouvementview',
      template: '<div  *ngIf="data">  
                <div>{{data.x1r'}} </div>
                </div> ',
      styleUrls: ['./mouvementview.component.css']
    })

    export class MouvementviewComponent implements OnInit {
      public data: PositionsModel;
      private display : boolean;

      // Inject mouvementService
      constructor(private mouvementService: MouvementService) {
        this.display = false;
        this.alive = true;
        }

      ngOnInit() {
          this.mouvementService.getPositions()
          .first() // only gets fired once
          .subscribe((data) => {
            // this part of code will execute only when data will be retrieved. 
            this.data = data;
            this.display = true;
            /* Refresh your chart , NOTE : chart refresh will be done itself , if you are using some components with databing */
          });

     // if you need periodic refresh 
     // get our data every subsequent 200 mseconds
        IntervalObservable.create(200)
          .takeWhile(() => this.alive) // only fires when component is alive
          .subscribe(() => {
            this.mouvementService.getPositions()
              .subscribe(data => {
            console.log(data);
            this.data = data;
            /* refresh your chart */ 
              });
          });
     } 
  }

      

here is my Http service:

import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { Observable } from "rxjs";
import 'rxjs/Rx';
import { PositionsModel } from "./positionsmodel";



@Injectable()
export class MouvementService      {

constructor(private http: Http) {}

getPositions(): Observable<PositionsModel> {
    // Make the HTTP request:
    console.log("launching http get request");
    return this.http
      .get('http://192.168.1.170:8080/api/mouvements')
      .map((res: Response) => {
        return res.json();
      });    
    }
}

      

0


source







All Articles