I have two function in a class where
First function returns Observable.
Second function is called from other component
I want call first function in second use the value of first and process it.
Sample code:
#Injectable()
export class SampleService {
service:string;
getService(): Observable<any> {
return this._http.get(`url`, {
headers: this.headers()
}).map(res=>res.json();)
.catch(err=>console.log(err);
}
}
generateToken():string{
const service="";
this.getService().subscribe(res=>{service=res});
//process it
return service;
}
Whenever i call the second function the value of service is return as empty.How to await till the subscribe is over and then process.
You can't return a value that you get from an observable.
You can either use map in the 2nd method as in the first method and then subscribe where you call generateToken
generateToken():string{
return this.getService().map(res=>{return service=res});
}
someMethod() {
this.generateToken.subscribe(res => this.service = res);
}
or assign it to a field in the 2nd property
generateToken():string{
return this.getService().subscribe(res=>{this.service =res});
}
update
someMethod() {
this.generateToken.subscribe(res => {
this.service = res;
// other code here
});
}
This will return asynchronously because the subscribe will not come back until it receives a response:
generateToken():string{
const service="";
this.getService().subscribe(res=>{service=res}); //Async
return service; //Instant return of blank value
}
I would suggest returning the observable itself and subscribing to it where you need it:
generateToken(){
return this.getService().map(res => res.json()); //presuming json payload
}
Then inside your components after requesting sampleService in constructor:
this.sampleService.generateToken().subscribe(data = > { //use data });
Related
I try to get an array from backend using http.get. In response I have: ["Item1","Item2","Item3"].
constructor(private http:Http){
this.http.get('api').subscribe(
data => {this.array = data}
);
}
The code above makes this.array = undefined.
What should I use to get an array?
Please try to console first whether you are getting data or not , also you need to use .map (If you are not using httpClient) before subscribe like this
constructor(private http:Http){
this.http.get('api')
.map(data => {
console.log(data.json());
reuturn data.json();
)
.subscribe(data => {this.array = data});
}
You are accessing values outside subscribe and because get is asynchronous, you are not getting value in it.
constructor(private http:Http){
this.http.get('api').subscribe(
data => {this.array = data}
console.log(this.array); <== here it won't be undefined
);
}
If you want to access array in component you can use getter as:
get Array() {
return this.array;
}
in component you can access this by this.service.Array
Solution2
You can use async pipe.
I have two rest calls I need to make. Rest call 2 depends on rest call 1. And I want to store each result in the service before proceeding. Then I want to return an observable to the component that calls rest call 1 so the component can subscribe in case of issues.
code from service:
login(username: string, password: string): Observable<AuthAccess> {
// rest call 1
return this.getAuthClient()
.flatMap(res => {
this.client = res.body;
// rest call 2
return this.authenticate(username, password)
.flatMap(access => {
this.userAccess = access.body;
return Observable.of(this.userAccess);
});
});
}
I can get this to chain correctly, but the component that is calling this and subscribing to the call will show an undefined response. Any help on this would be greatly appreciated as I cannot find responses on this exact use case.
Live working example.
Use a map operator (and the lettable operatos sintax), instead of chain a new flatMap.
login(username: string, password: string): Observable<any> {
// rest call 1
return this.getAuthClient()
.pipe(flatMap(res => {
this.client = res.body;
// rest call 2
return this.authenticate(username, password)
.pipe(map(access => {
this.userAccess = access.body;
return this.userAccess;
}));
}));
}
So I want to call a method to check if the component should be subscribe or not but i had tried many way and it failed.
let container = (props, onData) => {
Meteor.call("check", (err,res){ res return true and false
if(res){
const sub = Meteor.subscribe("data");
if(sub.ready()){
onData(null, data);
}
}
});
}
but the sub.ready() in call back would always false but if i subscribe outside the call back it work but I don't want to subscribe to every component that check method return false
I've had trouble with something similar. My workaround was to not do any too many tasks from within the callbacks. Please check if something along these lines works:
let container = (props, onData) => {
// a simple flag
var canSubscribe = false;
Meteor.call("check", (err,res){ res return true and false
if(res){
canSubscribe = true;
}
});
//moved this outside of the callback.
if(canSubscribe){
const sub = Meteor.subscribe("data");
if(sub.ready()){
onData(null, data);
}
}
}
I'm new to Angular 2 and Observables so I apologise if my problem is trivial. Anyway I'm trying to test the Angular 2 HTTP Client using RxJS. Although I got it to work I need to add more logic to the service I'm currently working on. Basically I'd like to have a mapping function to convert the object I receive from the web service I'm connected to, to the model object I have in Angular.
This is the code that works:
import { Injectable } from 'angular2/core';
import { Http, Response } from 'angular2/http';
import { Observable } from 'rxjs/Observable';
import { Person } from '../models/person';
#Injectable()
export class PersonsService {
constructor(private http: Http) { }
private personsUrl = 'http://localhost/api/persons';
getPersons(): Observable<Person[]> {
return this.http.get(this.personsUrl)
.map(this.extractData)
.catch(this.handleError);
}
private extractData(res: Response) {
if(res.status < 200 || res.status >= 300) {
throw new Error('Bad response status ' + res.status);
}
let body = res.json();
return body.data || {};
}
private handleError(error: any) {
let errMsg = error.message;
return Observable.throw(errMsg);
}
}
With the above code I have no problems whatsoever. The issue I'm having is that I'd like to map the object I'm getting from the service to the one I have in Angular i.e. Person. What I tried is to call another function from the extractData function that's being used by the .map function.
private extractData(res: Response) {
if(res.status < 200 || res.status >= 300) {
throw new Error('Bad response status ' + res.status);
}
let body = res.json();
// map data function
var data = this.mapData(body.data);
return data || {};
}
private mapData(data: any) {
// code to map data
}
Obviously the code above doesn't work as when this is referenced inside the extractData function, this does not refer to the PersonsService class, but it refers to a MapSubscriber object.
I don't know if it is possible to call an "external" function. It might be a silly thing but I can't find any information regarding this.
Instead of just passing the function reference use arrow functions to retain this
.map((res) => this.extractData(res))
Observable's map function allows you to pass a reference variable as a second argument on how should this actually work inside the higher-order function.
so the solution is
.map(this.extractData,this)
This way while passing the extractData function you are also passing the current class's this execution context to the higher-order function.
It will work.
Observable Doc Reference Link
Basically what i try to do is to hit my API once and save the result inside global variable in my Service, and then share and modify this value in my parent and child component with two helpers functions.
repairs.service.ts
public myItems:any[];
public GetRepairs = ():Observable<any> => {
this.headers = new Headers();
this.headers.set('Authorization', 'Bearer' + ' ' + JSON.parse(window.localStorage.getItem('token')));
return this._http.get(this.actionUrl +'repairs'{headers:this.headers})
.map((res) => {return res.json();
}).map((item) => {
let result:Array<any> = [];
if (item.items) {
item.items.forEach((item) => {
result.push(item);
});
}
this.myItems = result;
return this.myItems;
});
};
public GetItems() {
return this.myItems;
};
public UpdateItems(data:any[]) {
this.myItems = data;
};
And then in my main component i do
repairs.component.ts
export class RepairsComponent implements OnInit {
public myItems:any[];
constructor(private _userService:UserService,
private _RepairsService:RepairsService,
public _GlobalService:GlobalService) {
}
ngOnInit() {
this._userService.userAuthenticate();
this.getAllItems();
}
private getAllItems():void {
this._RepairsService
.GetRepairs()
.subscribe((data) => {
this._RepairsService.UpdateItems(data);
},
error => console.log(error),
() => {
this.myItems = this._RepairsService.GetItems();
});
}
}
This work just fine but when i try to invoke GetItems() in child component i get undefinded. I try to do it inside constructor and ngOnInit with the same result.
child.component.ts
export class ChildComponent {
private items:any[] = [];
constructor(private _RepairsService:RepairsService,
private _Configuration:Configuration) {
this.items = this._RepairsService.GetItems();
// undefinded
}
ngOnInit() {
this.items = this._RepairsService.GetItems();
// undefinded
}
}
From what i can see in the limited amount of code you shared, it would seem you are trying to get the items before the http get call finishes and saves the data. I think a better design pattern would be to make the GetItems() function also an observable or promise, and check if the data is there, if not call the http get call, and once that completes send the data back to the different components that need it.
As #MSwehli mentioned with async code execution you can't rely on the order of code lines. In this code:
ngOnInit() {
this.items = this._RepairsService.GetItems();
// undefinded
}
the async code in GetItems(); is scheduled for later execution into the event queue and then continued with the sync code. The scheduled code will be executed eventually but it's not determined when. It depends on the response of the server in this example.
If you return a Promise you can use .then(...) the chain the execution so that your code is only executed when the async execution is completed.
There are two errors/inconsistencies in your code:
userAuthenticate() call followed with getAllItems() call. These calls are async, user is not yet authenticated by the time getAllItems() is called, getAllItems will fail.
Solution here is to chain calls using rxjs flatMap:
//assuming userAuthenticate returns Observable
userService.userAuthenticate().flatMap(()=>{
return repairsService.GetRepairs();
}).subscribe(..process repairs..);
getAllItems() is called nearly at the same time as GetItems(). In most cases it fails also, because previous http request is not completed when GetItems() is called.
In my opinion early initialization is not necessary here, use service directly:
//ChildComponent
ngOnInit() {
this._RepairsService.GetRepairs().subscribe(..do anything with list of repairs i.e. assign to bindable property..);
}
You could add console.log statements in each part of the code to see the order of events in your app.