3

I want to call http request inside a observable which makes select operation from database. I made two services, DbService and BackendService.

BackendService makes http post requests and returns response data. In my design BackendService should subscribe to DbService for getting url, after that make http post request then return response data.

BackendService can take url from DbService and try to make http request but couldn't. response data is (Json format)

{"_isScalar":false,"source":{"_isScalar":false},"operator":{}}

I don't understand what happening here. My services and AppComponent file is below.

There is BackendService

import { Injectable } from "@angular/core";
import { getString, setString } from "application-settings";
import { Headers, Http, Response, RequestOptions } from "@angular/http";
import { Observable } from "rxjs/Observable";
import 'rxjs/add/observable/of';
import "rxjs/add/operator/do";
import "rxjs/add/operator/map";
import "rxjs/add/observable/throw";
import "rxjs/add/operator/catch";
import { DbService } from "./db.service";

@Injectable()
export class BackendService {
    static BaseUrl= "http://blabla.com"

    constructor(public http: Http, private db: DbService) {
    }

        sendPost(key: string, requestObj: Object):Observable<any>{
        console.log("sendPost: ");
        return new Observable(obs=> {
            let obs1 = this.db.getActionUrl(key);
            obs1.subscribe(value => {
                let url = BackendService.BaseUrl + value;

                console.log("key: ", key);
                console.log("url: ", url);
                var h = BackendService.getHeaders();
                obs.next(this.http.post(
                    url,
                    JSON.stringify(requestObj),
                    { headers: h }
                ).map((res: Response) => res.json()));
                // .catch((error: any) => Observable.throw(error.json())));
                obs.complete();
            }
            , error => {
                console.error("send post error: "+ error);
                obs.error(error);
            }
        );
        });
    }

    static getHeaders() {
        let headers = new Headers();
        headers.append("Content-Type", "application/json");
        headers.append("SESSION-ID", this.sessionId);
        // headers.append("Authorization", BackendService.appUserHeader);
        return headers;
    }
}

There is DbService

import { Injectable } from "@angular/core";
import { Observable } from "rxjs/Observable";
import 'rxjs/add/observable/of';
import "rxjs/add/operator/do";
import "rxjs/add/operator/map";
import "rxjs/add/observable/throw";
import "rxjs/add/operator/catch";
import 'rxjs/add/operator/toPromise';

var Sqlite = require("nativescript-sqlite");

@Injectable()
export class DbService {
    private tableActions = "actions";

    private columnActionName = "name";
    private columnActionUrl = "url";


    private database: any;
    constructor() {
        console.log("DbService Constructor");
        (new Sqlite("my_app.db")).then(db => {
            db.execSQL("CREATE TABLE IF NOT EXISTS " + this.tableActions + " (" + this.columnActionName + " TEXT PRIMARY KEY, " + this.columnActionUrl +" TEXT)").then(id => {
                this.database = db;
                console.log("DB SERVICE READY");
            }, error => {
                console.log("CREATE TABLE ERROR", error);
            });
        }, error => {
            console.log("OPEN DB ERROR", error);
        });
    }


    public getActionUrl(key: string):Observable<any>{
    return new Observable(observer => { 
        if (key === "getApiMap") {
            observer.next("/getApiMap");
            observer.complete();
            return;
        }
        console.log("getActionUrl :" + key);
        this.database.all("SELECT * FROM " + this.tableActions).then(
            rows => {
                console.log(rows);
                observer.next(rows[0][this.columnActionUrl]);
                observer.complete();
            }, error => {
                console.log("SELECT ERROR: getActionUrl: ", error);
                observer.error(error);
            })
    });
    }
}

And there is my AppComponent which makes http requests...

//some imports

export class AppComponent {
    public resp: Observable<ModelMainGetApiMapRes>;
    public constructor(private bs: BackendService, private db: DbService) {
let req = new ModelMainGetApiMapReq()
    bs.sendPost("getApiMap", req, false).subscribe(
        (res: ModelMainGetApiMapRes) => {
            console.log("getapimap response received!");
            console.log(JSON.stringify(res));
            console.log("apimap version:" + res.api_version);

        },
        err => {
             console.error("error!", err);
        }
    );
 }

//some functions
}

the console output of app.component is

CONSOLE LOG file:///app/shared/backend.service.js:61:20: sendPost:
CONSOLE LOG file:///app/shared/backend.service.js:66:28: key:  getApiMap
CONSOLE LOG file:///app/shared/backend.service.js:67:28: url:  http://blabla.com/getApiMap
CONSOLE LOG file:///app/app.component.js:55:36: getapimap response received!
CONSOLE LOG file:///app/app.component.js:56:36: {"_isScalar":false,"source":{"_isScalar":false},"operator":{}}
CONSOLE LOG file:///app/tns_modules/tns-core-modules/profiling/profiling.js:10:16: ANGULAR BOOTSTRAP DONE. 7805.849
CONSOLE ERROR file:///app/tns_modules/@angular/core/bundles/core.umd.js:1486:24: ERROR Error: Uncaught (in promise): TypeError: undefined is not an object (evaluating 'res.api_version')
AsyncTask
  • 419
  • 1
  • 7
  • 24
  • 1
    The problem is in the question. You can't and shouldn't do that. Use operators to combine observables (not sure which one will be suitable in your case, possibly switchMap), then subscribe to resulting observable. Since you're dealing with things that cannot fully benefit from observables, it may be reasonable to switch to promises (async..await will be much smoother than observable soup). – Estus Flask Mar 22 '18 at 19:33
  • This answer contains solution https://stackoverflow.com/a/62385358/5535003 – Zaslam Jun 15 '20 at 09:40

1 Answers1

8

With your actual code in BackendService.ts:

return new Observable(obs=> {
    let obs1 = this.db.getActionUrl(key);
    obs1.subscribe(value => {
        let url = BackendService.BaseUrl + value;

        console.log("key: ", key);
        console.log("url: ", url);
        var h = BackendService.getHeaders();
        obs.next(this.http.post(
            url,
            JSON.stringify(requestObj),
            { headers: h }
        ).map((res: Response) => res.json()));
         obs.complete();
        ...
    });
 });

you emitted the http observable

this.http.post(
                    url,
                    JSON.stringify(requestObj),
                    { headers: h }
                ).map((res: Response) => res.json())

that's why you got: {"_isScalar":false,"source":{"_isScalar":false},"operator":{}} when subscribing on it, it's an observable.

The simplest solution with your code, you can emit the data after subscribing the second observable, something like:

return new Observable(obs=> {
    let obs1 = this.db.getActionUrl(key);
    obs1.subscribe(value => {
        let url = BackendService.BaseUrl + value;
        console.log("key: ", key);
        console.log("url: ", url);
        var h = BackendService.getHeaders();
        this.http.post(
            url,
            JSON.stringify(requestObj),
            { headers: h }
        ).map((res: Response) => res.json())
         .subscribe(data => obs.next(data));
    });
});

But the better solution is by using switchMap operator: (or any other xxxxMap operator)

import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/map';
...
sendPost(key: string, requestObj: Object):Observable<any>{
    return this.db.getActionUrl(key)
            .map( value => BackendService.BaseUrl + value)
            .switchMap(url => this.http.post(
                url,
                JSON.stringify(requestObj),
                { headers: h }
            )
            .map((res: Response) => res.json()))
}
Fetrarij
  • 7,176
  • 3
  • 27
  • 35
  • Thank you for reply but both of your suggestions didn't work... When try to use switch map, I've got this error in terminal CONSOLE ERROR file:///app/tns_modules/@angular/core/bundles/core.umd.js:1486:24: ERROR Error: Uncaught (in promise): TypeError: undefined is not a function (near '....switchMap(function (url) { re...')" and when use other code (with obs.emit...) it says emit is not found for Subscriber..., I tried to modify your suggestion like .subscribe(data => {obs.next(data); obs.complete()}); but it didn't work, waits on runtime... – AsyncTask Mar 25 '18 at 22:59
  • @AsyncTask Mhh May be you need to import swtchMap from `import 'rxjs/add/operator/switchMap';` Check this plunker it's the same: https://embed.plnkr.co/rl85Y2fP2Pb2LZjXyNdk/ – Fetrarij Mar 26 '18 at 06:32
  • You right! It worked after adding switchMap import. Well, do you have any idea about other way (with emit method) ? – AsyncTask Mar 27 '18 at 17:25
  • @AsyncTask it should work now, there are a confusion, should use obs.next() instead of obs.emit() – Fetrarij Mar 28 '18 at 05:37