Angular 6 observables - extract data from .subscribe() function and use it elsewhere - javascript

I'm banging my head against the wall with observables. Almost all of the documentation I can find is in the older rxjs syntax.
I have an API call which is an observable. I'm calling it elsewhere and subscribing to it - trying to populate a table with the data from this GET request.
If I simply console.log my getData function, it logs the subscription rather than my data.
I can successfully console.log data within the .subscribe function, but I want to use data outside of .subscribe().
How do I extract data out of the .subscribe() function and use it elsewhere? Or, must all of my logic be contained within the .subscribe() function to use data?
getData2() {
return this.m_dbService.get('api/myApiPath').subscribe(
data => (console.log(data)), //This properly logs my data. How to extract `data` out of here and actually use it?
error => { throw error },
() => console.log("finished")
);
}
workbookInit(args){
var datasource = this.getData2(); // this returns the subscription and doesn't work.
}

just return the HTTP req from getData() and subscribe it inside the workbookInit function.
getData2() {
return this.m_dbService.get('api/myApiPath')
}
workbookInit(args){
this.getData2().subscribe(
data => {
var datasource = data
},
error => { throw error },
() => console.log("finished")
}

What you probably want to do is to populate another Observable with the data so that you can access it elsewhere in your project without the need for calling the API more than once.
To do this, you create what is known as a Subject (in this case a BehaviorSubject) and you can populate that with data when your API call returns a response.
Then, in order to access this data elsewhere, you can create a "get" function to return the Subject (which is itself an Observable) whenever you need the data.
Here is an example:
my-data.service.ts
myData: BehaviorSubject<number> = new BehaviorSubject<number>(0);
callApi() {
this.dbService.get('apiUrl').subscribe(
(data) = > this.myData.next(data) // Assuming data is a 'number'
);
}
getMyData() {
return this.myData.asObservable();
}
Now to use this in a component:
this.myService.getMyData().subscribe(
(data) => { /* Use the value from myData observable freely */ }
);
Or you could rely on the Angular async pipe (which is a very convenient method for dealing with observables in your code).

You should not subscribe to the Observable inside getData2. Return it as is instead, then do the following:
var dataSource;
this.getData2().subscribe(res => dataSource = res);
Please note that the variable dataSource will be set when the request is done (asynchronously), so you can't use it immediately in the same block scope.
If you want to use it immediately, then put your code inside the subscription.

If you have an observable that provides data to populate a table, the best way is not to use subscribe(), but use the observable directly in your html template by using the async pipe. You'll have less to worry about and your code will be much simpler.

Related

Returning a nested observable

Given this method:
public logIn(data:any): Observable<any> {
this.http.get('https://api.myapp.com/csrf-cookie').subscribe(() => {
return this.http.post('https://api.myapp.com/login', data);
});
}
I would like it to return that nested observable, so that my calling code can use it like so:
this.apiService.logIn(credentials).subscribe(() => {
// redirect user to their dashboard
});
without needing to know about the first /csrf-cookie request. Obviously the above doesn't work - but I'm struggling to understand how to make the inner HTTP request wait for the outer one to finish AND be returned by the method.
you should use switchMap see the documentation on switch map
public logIn(data:any): Observable<any> {
return this.http.get('https://api.myapp.com/csrf-cookie').pipe(
switchMap(x => this.http.post('https://api.myapp.com/login', data))
);
}
with rxjs nested subscribes are generally not a good idea. There are many great operators within the library that will get you around it. In this case above where one call depends on another switchMap(...) is the best fit.
Also the code has been modified to return the observable not the subscription

How to wait for a observable method to finish before proceeding in Angular 2

I am new to Angular and I am working on fixing something that is written in Angular 2. In the method we have a call to to observable method after that we are assigning a value to a variable. I need the obersavable method to finish execution before assigning the value. Given below is the code for your reference.
Method1(): void {
this.Service.getData()
.subscribe(
res => {
},
error => this.errorMessage = <any>error);
this.gotData = true;
}
So, As shown above, I need to assign true to gotData variable after the getData Observable method is finished execution. But gotData is getting assigned true even before we actually get the data.
Please help me who to wait until the observable method is finished execution. Thank you !
Sounds like you need to read up on Observables and Subscriptions.
Angular Docs
Alternatively, the subscribe() method can accept callback function definitions in line, for next, error, and complete handlers. For example, the following subscribe() call is the same as the one that specifies the predefined observer:
In your case the callback you have named res will be called after your observable method is finished.
So your code could be:
Method1(): void {
this.Service.getData()
.subscribe(
res => {
this.gotData = true;
},
error => this.errorMessage = <any>error
);
}

Firebase '.get()' does not return any data

I had this query:
this.firestore.collection('name', ref => ref.limit(1)).get().subscribe(x => {
console.log(x);
});
That didn't return any data
Just for a test I changed that to this a and it works:
this.firestore.collection('name', ref => ref.limit(1)).valueChanges().subscribe(x => {
console.log(x);
});
However I want to use .get(), any ideas what is the problem with that?
The problem is that .subscibe method deals with Observable. It's simlar mechanizm like in Promise, but different.(reference)
According to this reference valueChanges():
Returns an Observable of document data. All Snapshot
metadata is stripped. This method provides only the data
While get() takes snapshot using Promise mechanism. So you cannot use subscribe with get.
I hope it will help!

How can I create an Angular factory with a getter and setter method without running into a race condition

I'm creating a factory to take a userId from one page, make a call to a REST API, and return the results on the following view. My initial attempts were largely taken from this answer but - unsurprisingly - I keep getting caught in a situation where the doesn't respond in time and the get() method returns an empty array.
Here's the factory itself
app.factory('GetMessages', function() {
var messages = []
function set(userId) {
Restangular.all('/api/messages/').getList({'_id': userId}).then(function(docs){
messages = docs
})
}
function get() {
return messages;
}
return {
set: set,
get: get
}
});
For what it's worth I'm having no trouble getting the userId into the factory as it's just passed in on a function like this
view:
<a ng-click='passToFactory(message.user.id)' href='/home/inbox/reply'>Reply</a>
controller:
$scope.passToFactory = function(id) {
GetMessages.set(id);
};
and the controller for the following view is just
$scope.messages = GetMessages.get()
The issue I'm having is that after the factory returns the empty set no further changes from the factory are recognized (even though after time elapses it does get the proper response from the API) and $scope.messages remains empty.
I've attempted to move the API call to the get method (this hasn't worked as the get method often does not get the userId in time) and I can't find a way to use a promise to force get() to wait on set() completing.
I'd prefer to keep using Restangular in the eventual solution but this is a small thing that has taken too much time so any fix works.
I'm fairly new to Angular so I'm sure there's something totally obvious but right now I'm just lost. Thanks.
The race condition that you have is that the function inside the .then method is executed asynchronously after the call to the set function. If the get function executes before the $q service fulfills the promise, the get function returns an empty array.
The solution is to save the promise and chain from the promise.
app.factory('GetMessages', function() {
var promise;
function set(userId) {
promise = Restangular.all('/api/messages/').getList({'_id': userId});
}
function get() {
return promise;
}
return {
set: set,
get: get
}
});
In your controller, chain from the promise.
GetMessages.get.then( function (docs) {
$scope.messages = docs;
}) .catch ( function (error) {
//log error
};
For more information on chaining promises, see the AngularJS $q Service API Reference -- chaining promises.
You are breaking the reference to the original messages array when you reassign it.
Try:
Restangular.all('/api/messages/').getList({'_id': userId}).then(function(docs){
messages.concat(docs) ; // keep same array reference
});
Simple example to explain why it isn't working
var arr = [];
var x = arr;
arr = [1,2,3]; // is now a different array reference
console.log(x); // is still empty array. x !== arr now
cherlietfl is right.
The problem is that you break the reference to the messages array since you assign a new array to messages inside your get function. But concat is doing this as well.
Try this:
Restangular.all('/api/messages/').getList({'_id': userId}).then(function(docs){
messages.splice(0, messages.length); // clear the array
messages.push.apply(messages, docs); //add the new content
});
Try assigning you function to the scope. Then call that function in the model. Like so:
// controller
$scope.getMessages = GetMessages.get;
View:
<div ng-repeat="message in getMessages()"></div>
This way when the request call finishes and the digest cycle goes through the watchers again, the get function will be called and you will get your messages.

Why does angular $resource add extra objects ($promise, $resolve...) to my data response?

I return a resource with a URL
$resource("http://foo.com/bar.json").get().
$promise.then(function(data){ $scope.result = data},
function(error){ $scope.msg = "error" } );
Resource returns
["item1"...."item_n",.....,"$promise", "$resolved", "$get", "$save", "$query", "$remove", "$delete"]
Why do I get all those objects in my data set. I'm guessing $promise just returns all this and waits for the server response. But once I have the server response where can I just get my server data without the Promise jargon?
If you look at the angular source here:
https://github.com/angular/angular.js/blob/master/src/ngResource/resource.js#L505
There is a toJSON method on the Resource prototype chain that will accomplish this for you.
For example:
$resource("http://foo.com/bar.json").get(function(res) {
$scope.result = res.toJSON();
});
You need to return wrapped result like {'result': { 'some_key': 'some_val' }} from your backend.
Or just do like described above.
Diary.getSharedWithMe(function(data) {
delete data.$promise;
delete data.$resolved;
_self.sharedDiariesWithMe = data;
}, function(error) {
console.log(error)
});
$resource returns an object or array that will have your data when the call completes. All those functions are there to help you out and $resource is mainly intended for CRUD operations. If you want the data, you have to wait for it to get returned so you might as well use the promise. If you want to strip all of those properties you can use angular.toJson to convert it to json, but angular does that for you when posting it back to a resource or $http call so you shouldn't have to.
$scope.data = $resource("http://foo.com/bar.json").get();
// $scope.data does not have your data yet, it will be
// populated with your data when the AJAX call completes
...
// later in a call from a save button maybe you can just do
// this to post your changes back:
$scope.data.$save();
So in case someone else is stumbling here and didn't understand promises/angularjs here is what is going on. When you use .then() or .get() you get a promise and some helper functions all in the same object. This is awesome because then you don't worry about callbacks being defined and whether data is available because the promise object always has some properties. This object contains your raw data in another object within. So the promise object is nested, you just have to reference the data object within when the data is ready.
Here's what I was doing
$resource("http://foo.com/bar.json").get().
$promise.then(function(data){ $scope.result = data},
//data is actually a promise object.
function(error){ $scope.msg = "error" } );
promise object
Note the data is actually under another object called "data". So in your success callback to get just the data you should do in this case: data.data
To automatically remove them from every request, you can add an interceptor:
angular.module('app').config(config);
config.$inject = ['$httpProvider'];
function config($httpProvider) {
$httpProvider.interceptors.push(interceptor);
}
interceptor.$inject = [];
function interceptor() {
return {
request: (config) => {
if (config.data) {
delete config.data.$promise;
delete config.data.$resolved;
}
return config;
}
};
}

Categories