2

I am in the process of converting a AngularJS app to Angular 7 in TypeScript.

I am having some problems with converting some complex nested promises to Observables.

Here is an example of the code I'm dealing with:

signup.component.js

function SomethingSignupController(somethingApplication) {
    function activate() {
        getApplication();
    } 

    function getApplication() {
        vm.getFromServer = false;
        vm.promises = [];
        SomethingRepo.get().then(function(application) {
            vm.getFromServer = true;
            vm.application = application;
            vm.promises.push(Something.getCompany().then(function(company) {
                vm.company = company;
                if (vm.company.structure === ‘more_25’) {
                    return SomethingRepo.getAllOwners().then(function(owners) {
                        vm.owners = owners;
                        for(var i = 0; i < vm.owners.length; i++) {
                            vm.promises.push(getOwnerFiles(vm.owners[i]));
                        }
                    }
                }
            }
            vm.promises.push(SomethingRepo.getSomethingOne().then(function(somethingOne) {
                vm.somethingOne = somethingOne;
            }
            vm.promises.push(SomethingRepo.getSomethingTwo().then(function(somethingTwo) {
                vm.somethingTwo = somethingTwo;
            }
            vm.promises.push(SomethingRepo.getSomethingThree().then(function(somethingThree) {
                vm.somethingThree = somethingThree;
            }
            /* and a few more like the above */
            $q.all(vm.promises).then(function(){
                postGet();
            }).finally(function() {
                vm.promises = [];
            });
        }
    }

    function postGet() {
        /* does something with the data acquired from SomethingRepo */
    }

    /* when an application is send */
    function send() {
        somethingApplication.promises = [];
        somethingApplication.errors = [];
        if (vm.getFromServer) {
            update();
        } else { 
            create();
        }
    }

    function update() {
        somethingApplication.promises.push(SomethingRepo.update(vm.application).then(angular.noop, function(error) {
            somethingApplication.parseErrors(error, ‘Some error’);
        }));
        patchInfo();
    }

    function create() {

    }

    function patchInfo() {
        somethingApplication.promises.push(SomethingRepo.patchAccount(vm.account).then(angular.noop, function(error) {
            somethingApplication.parseErrors(error, ‘Account error: ‘);
        }
        /* a few more patches */
        $q.all(somethingApplication.promises).then(function() {
            /* display dialog */
        }, angular.noop).finally(function() {
            postGet();
            somethingApplication.promises = [];
            if (somethingApplication.errors.length >= 1) {
                vm.errors = somethingApplication.errors;
            }
        });
    }
}

somethingApplication.service.js

function somethingApplication(SomethingRepo) {
    var promises = [], errors = [];

    var service = {
        promises: promises;
        errors = errors;
        parseErrors: parseErrors;
    };

    return service; 


    function parseErrors(error, base_string) {
        angular.forEach(error.data.erros, function(value_params, key_params) {
            this.errors.push(base_string + ‘ ‘ + key_params.replace(/_/g, ‘ ‘) + ‘ ‘ + value_params);
        }, this);
    }
}

somethingRepo.js

function SomethingRepo(Server) {
    function get() {
        return Server.get(‘/something/application’, null, {noGlobal: true});
    }
}

I have reduced the files, but they consist of more code like this. The point of the controller is to create or update an application for another website. On my website I have a form of fields corresponding to the form on the other website. If you already have filed for an application, but want to update it, the info you already filed are loaded from the other website.

The problem is, in order to create or update an application, a lot of different endpoints requested og posted to.

In AngularJS I store the promises from each request and run them asynchronously in the end. In TypeScript and Angular I want to use Observables and subscribe to the data change.

How do I get started? How do I subscribe to an Observable the requires parameters from another Observable? Any advice how to proceed?

georgeawg
  • 48,608
  • 13
  • 72
  • 95
FrankfromDenmark
  • 170
  • 2
  • 12
  • 4
    `Observable` supports a `ToPromise` method, converting the Observable to a callable Promise. If you want to keep the `Promise.all` approach, you can. Otherwise, you can think about using other approaches suggested by RxJs, like `.concatMap`, `forkJoin` and so on. Take advantage of `async await`, where possible, this will help you to keep track of what it's done in the migration phase. – briosheje Dec 19 '18 at 13:10
  • 1
    "How do I get started? How do I subscribe to an Observable the requires parameters from another Observable? Any advice how to proceed?" If I understand this correctly, you are looking for the [`forkJoin`](https://www.learnrxjs.io/operators/combination/forkjoin.html). You can combine this with `from(promise)` to create an observable which waits for a collection of promises to resolve and then emits a list of the resolved values. – FK82 Dec 19 '18 at 13:18
  • I don't want to keep the `Promise` approach. Do you have a concrete example of how I can `forkJoin` to my specific code? Not that I want you to do the job, but I have hard time apply what I've read about `forkJoin` to my code – FrankfromDenmark Dec 19 '18 at 13:28
  • This may help - https://stackoverflow.com/questions/39319279/convert-promise-to-observable – Tushar Walzade Dec 19 '18 at 13:29
  • take a look at this & update your service accordingly - https://angular.io/guide/http Then you could easily use forkjoin as follows - https://medium.com/@swarnakishore/performing-multiple-http-requests-in-angular-4-5-with-forkjoin-74f3ac166d61 – Tushar Walzade Dec 19 '18 at 13:32
  • @TusharWalzade Thank you, I will give it a go – FrankfromDenmark Dec 19 '18 at 13:34
  • 3
    @FrankfromDenmark forkJoin works **exactly like Promise.all**: you provide an array (or a list) of **Observables** (angular's HttpClient.get returns an `Observable` by default) and its callback fires all the resolved values. FK82 provided the official documentation above. take a look at Example 5, it's extremely close to your use case. – briosheje Dec 19 '18 at 14:06

1 Answers1

2

Here's an example demonstrating how you can easily use observables in your scenario -

Your service would be something like this -

import { Injectable } from '@angular/core';
import { AppConstants } from '../../app.constants';
import { HttpClient } from '@angular/common/http';

@Injectable()
export class ExampleService {
    constructor(private appconstants: AppConstants, private http: HttpClient) { }

    get() {
        return this.http.get(this.appconstants.apiUrl);
    }

    getSomethingOne() {
        return this.http.get(this.appconstants.apiUrl1);
    }

    getSomethingTwo() {
        return this.http.get(this.appconstants.apiUrl2);
    }
}

Then simply use it in your component as follows -

import { Component } from '@angular/core';
import { forkJoin } from 'rxjs';
import { ExampleService } from '../services/example.service';

@Component({
  selector: 'app-example',
  templateUrl: './example.component.html',
  styleUrls: ['./example.component.css']
})
export class ExampleComponent {

    data;
    dataOne;
    dataTwo;

    constructor(private exampleService: ExampleService) { }

    getApplication() {
        const combined = forkJoin([
            this.exampleService.get(),
            this.exampleService.getSomethingOne(),
            this.exampleService.getSomethingTwo()
        ]);
        combined.subscribe(res => {
            this.data = res[0];
            this.dataOne = res[1];
            this.dataTwo = res[2];
        });
    }
}
Tushar Walzade
  • 3,737
  • 4
  • 33
  • 56
  • Two questions: 1) Can i not have my service return an Observable like: `get(): Observable { return this.client.get(this.appconstants.apiUrl).pipe( map((item: any) => { return Object.assign(new Application(), item); }) ); }` 2) How would I go about using a result from the `forkJoin` to create another request? Can i chain it, or should I make a separate request afterwards? – FrankfromDenmark Dec 19 '18 at 13:56
  • 1
    1) This approach is also fine & is a good practice too. 2) You could return an `Observable` from your function - `getApplication()` and then do further actions in its *subscription* – Tushar Walzade Dec 19 '18 at 14:07
  • 1
    Here's a ref. for creating an observable - https://medium.com/@luukgruijs/understanding-creating-and-subscribing-to-observables-in-angular-426dbf0b04a3 – Tushar Walzade Dec 19 '18 at 14:15
  • Thank you very much – FrankfromDenmark Dec 19 '18 at 14:19