AngularJS: get promise value without internal properties - javascript

Ok, another promise-related question (I see there are already quite a few of them). I'm trying to get promise result from endpoint and use it in ng-repeat, first is the typical response from endpoint:
{"ebbe5704-4ea5-470e-8ab9-102ee8ca457f":"Foo",
"e380a6f7-2f88-46bb-bd54-251719353627":"Bar"
}
here's how I get it from promise:
RequestService.getValues().$promise.then(function(res) {
vm.result = res;
});
and here I render it in HTML:
<p ng-repeat="i in vm.result">{{i}}</p>
The problem is the rendered view contains internal fields of the promise ($promise and $resolved):
Am I doing something wrong? Is there any less ugly way instead of filtering through result keys?
UPD:
RequestService.getValues is a $resource, so can be replaced like this:
$resource("/rest/", null, {
getValues: {
url: "/rest/values/",
method: "GET"
}
}).getValues().$promise.then(function(res) {
vm.result = res;
console.log("RES:", res);
});

The problem here is that you are iterating with ng-repeat over an object (so getting the keys of each property of the object) and not over an array.
AngularJS resources are made to be used with ng-repeat only if the returned resource is an array.
When facing an object ng-repeat uses something like for(var key in obj) whereas when facing an array it uses the indexer to loop as following for(var i=0; i<arr.length; i++). Thus, with an array, it is skipping other properties like $promise and so.
What you should do is implement a custom filter that will skip angular decorators methods away from the ng-repeat iteration:
$scope.skipResourceFn= function(key)
{
// is it an angular decorator (starting with '$' char):
return key.indexOf('$') != 0;
};
Then in your template:
<p ng-repeat="i in vm.result | filter:skipResourceFn">{{i}}</p>

One can use the $http service to get the object:
var config = {
url: "/rest/values/",
method: "GET"
};
$http(config).then(function(response) {
var data = response.data;
vm.result = data;
console.log("RES:", data);
});
The $http service does not append additional properties to an object.

Related

Angular controller not able to read update from service. Any advice?

I have a situation where within my angular service, I have a number of properties. These properties are linked to the controller.
Service:
angular.module('...')
.factory('PollServ', function PollServ($http, ...) {
var service = {
question: '',
votes: [[]]
}
...
// make http request to API
var request = $http({ ...
// once the value is retrieved, update properties
request.then(function (res) {
service.question = res.data.question;
...
}
Controller:
angular.module('...')
.controller('PollCtrl', function PollCtrl(..., PollServ) {
$scope.question = PollServ.question;
$scope.votes = PollServ.votes;
...
Now, although the votes are being updated properly, the question is not. I am not doing anything different, except the fact that votes is an array and question is just a regular string. I think the array may have something to do with being able to dynamically update, but not the simple string.
How can I get it to work, without unnecessary turning the string into an array as well?
You said it yourself - the question is a string and thus will not be updated in your controller/view.
What you could do is turning the question into an object. For example:
In Factory
var service = {
question: {
name: ''
},
votes: [[]]
}
...
service.question.name = res.data.question;
You then need to change the reference in your view to question.name.

trying to convert/map the Objects in my JSON array to my KO observable function

I have an JSON array that I read in, now I want to make each object in the array a KO observable so that it can be mapped to the function
function Person(data)
{
this.name = ko.observable(data.name);
this.age = ko.observable(data.age);
this.link = ko.observable(data.link);
}
function ViewModel()
{
var self = this;
self.Persons = ko.observableArray([]);
var JSONdataFromServer;
$.getJSON('http://127.0.0.1:8080', function(data) {
self.Persons(data);
for(var k in self.Persons) {
k = $.map(k, function(item) { return new Person(item) });
}
});
}
However When I run this code(this is only a portion of it) I get the error "Cannot use 'in' operator to search for '0' in G". Again all I want to do is convert the JSON object in the Persons array to a Person KO observable object.
self.Persons is an observableArray, so if you want to look at its contents, you need to invoke it:
for (var k in self.Persons())
Other things you're doing are perplexing me. To convert the JSON object to an observableArray of Persons, you'd likely do something like this (depending on what the data looks like -- you don't say):
$.getJSON('http://127.0.0.1:8080', function(data) {
var arrayOfPersons = ko.utils.arrayMap(data, function (item) {
return new Person(item);
});
self.Persons(arrayOfPersons);
});
Avoid manually mapping incoming data from the server. Use ko mapping plugin for this. Maintaining your client side viewModel in sync with data from the server is very hard as soon as your viewModel starts growing, better to leverage this to ko mapping, which is also very flexible if you need more control over how the mapping should work.

ember js, how can i use the array in the controllers from the routes

Hi I am new to ember and I want to use deal with some data in a array fetched from my server side. But I do not know how to do that. The below code is in routes.
searchList: null,
model: function() {
// This is so that we don't refresh it all the time
// arguably, this is a hack.
var searchList = this.get('searchList');
if(searchList !== null) {
return searchList;
}
var self = this;
return Ember.$.ajax({
url: 'http://coen268.peterbergstrom.com/timezones.php',
dataType: 'jsonp'
}).then(function(response) {
var cities = []; <-----------------------this is an array of names from server side
if (response && response.length) {
for(var i=0; i<response.length; i++) {
cities.push(Ember.Object.create(response[i]));
}
}
/*
cities.sort(function(a, b) {
return a.id - b.id
})
*/
self.set('searchList', cities);
return cities;
I just want to use the cities var in my controller so that I can do something like sort, add and reorganize the output to final view html.
Many thanks!
If your route is called, say App.CitiesRoute, then you need to declare your CitiesController like this:
App.CitiesController = Ember.ArrayController.extend({});
You can then use sorting with simple Controller properties, or filtering when calling filter() on this (which refers to your ArrayController), as shown on Ember's Guides,
You also have access to all functions and properties of ArrayController, as shown in the Ember API documentation.
You can even specify a specific Controller for a every item of your array by specifying the itemControllerproperty in your ArrayController, like:
App.CitiesController = Ember.ArrayController.extend({
itemController: 'city'
});
// Here you can specify an ObjectController linked to all your items in the cities array
App.CityController = Ember.ObjectController.extend({
cityNameLowercase: function() {
return this.get('cityName').toLowerCase();
}.property('cityName')
});
which will be bound to each city of your array.

How to use angular promise as part of a string

I'm looking for a generic solution regarding handling promises and string creation. Basically a timing issue. This code isn't the actual code, but illustrates my problem and my attempted solutions.
I have a two json objects that I need to combine. Either one or both objects might have values that require some information from an API. This information is used to create a label showing which two objects have been combined.
Object with defined label (no lookup necessary):
var object1 = {
type: "some.type",
distribution: 50,
label: "Male"
}
Object with dynamic label (and psuedo code to get label via service $http request):
var object2 = {
type: "some.type",
distribution: 50,
value: "68"
}
// call service to get the data to populate the label
myService.getDynamicObjectData("68").then(function(response){
// should be "Alaska"
object2.label = response.data.label;
});
Desired combination:
var combinedObj = {
type: "some.type.combined",
distribution: 25,
// ideally label would be "Male > Alaska"
label: object1.label + " > " + object2.label
values: [object1, object2]
}
My problem is that object2.label is not populated until after the combination object has been created, specifically the label string. In the view, I'm seeing "male > undefined". I've managed to get as far as "male > 68" but that doesn't really help. When I'm not combining objects, the label is updated as soon as the promise is resolved and there is no issue getting "Alaska" and "Male" to show up as two unique entries. When I combine and create the string from the two labels, it's happening too fast.
The object1 and object2 are created in a service that deals with reading in data and creating these kinds of objects for internal use, then this combination code is in another service dealing with the nesting of such data; so I can't really use a watcher to update that value.
I've tried setting the label to the promise hoping that will work, but it doesn't:
var promise = myService.getDynamicObjectData("68").then(function(response){
// should be "Alaska"
object2.label = response.data.label;
});
var object2 = {
type: "some.type",
distribution: 50,
value: "68",
label: promise
}
The label is just an object with {then(), catch(), finally()} inside. I can't figure a way to get the actual returned values, even if then() returns the right value.
I've tried to use an array and a filter so that I'm never really creating the string until the last while, which means that since the string isn't "real" then it should work as the model is finally updated (as it does when showing objects separately):
var combined = {
type: "some.type.combined",
distribution: 25,
label: [object1.label, object2.label]
values: [object1, object2]
}
module.filter('labelFilter', function(){
return function(input){
if(angular.isArray(input)){
// but input[1].label is a promise object, how do I get the resolved value?
return input[0].label + " > " + input[1].label
}
return input;
}
});
So, I'm turning to the community to see what I might be able to do here. How to you create a string where part of that string is based on the result of a promise? I think if I use $resource, I'd be able to set label: labelResource, and labelResource would eventually resolve to the actual data I want (even the parent object of the data I want would be helpful). Unfortunately, there is other logic that is too complex for $resource so I can't use it without a bit of a refactor. I'm hoping to be able to set label to something like $q.deferred.result and have it all work out (even if I still need the filter).
Anyway, thanks for looking!
You can do it with promises and $q
https://docs.angularjs.org/api/ng/service/$q go to the bottom of page where you can find $q.all which basically is a solution to your problem, when all promises will be resolved you can then call you function to join the strings (labels)
You can try checking out the method mentioned here to do it inside filters.
I made a simple fiddle demonstrating async filters which you can use as a starting point to this problem -> http://jsfiddle.net/7eqsc/5/
angular.module('myApp', [])
.controller('myCtrl', function ($scope, $timeout) {
})
.filter('test', function ($q, $timeout) {
var cache={};
return function (input) {
if (!cache[input]){
//I used timeout, but anything that needs to happen async can happen here
$timeout(function () {
//do something with the input
cache[input]=input+'!';
}, 500);
//return something in the meanwhile to hold it
return 'loading';
}
else return cache[input];
}
});
Not tested of course, but try something like:
module.filter('labelFilter', function () {
var cache = {};
return function (input) {
var cachedItem = cache[inputHash(input)];
if (!cachedItem) {
if (angular.isArray(input)) {
input[0].label.then(function (text) {
//once the promise is finished, put the correct verison inside the cache
cache[inputHash(input)].label = text + " > " + input[1].label;
})
//meanwhile, return the unmodified object.
return input;
}
}
return cachedItem;
}
//you'll have to identify each input for the cache somehow.. it can be a combination of fields for example
function inputHash(input) {
return input.id; //some unique identifier..
}
});
Just keep in mind calling a promise "label" can be very confusing, you should try and reorganize your code so it's more clear when a prop has a promise inside.
Good luck!

Why is my controller property 'undefined' when I assign it the result of $resource query()?

I have a JSON data structure:
[
{
"title" :"a1",
"id" :"b1",
"name" :"c1"
},
{
"title" :"a2",
"id" :"b2",
"name" :"c2"
}
]
I am accessing is as an external JSON and parsed through a factory method. I want it to assign it to a Javascript variable in my controller.
function Control($scope,data)
{
var e=data.query(); /* getting the external JSON data */
alert(e[0].title);
}
It says that e[0] is undefined. Is there any other way I can assign it to a Javascript variable and then traverse through it? Please help.
Most likely, #Marty is correct. If you are using the query() method from the $resource service, it is asynchronous. This will likely do what you want:
data.query( function( data ) {
var e = data;
alert(e[0].title);
});
Okay, so $resource can be confusing like this... It immediately gives you a reference to the return object, but doesn't update the object until the asynchronous AJAX call returns... so...
If you put your return value from data.query() in a property on $scope, since it's $watched when you bind it in your view, you'll see it update. HOWEVER, if you're just trying to alert it, it will alert the value before it's been updated.. again because of the async aspect of $resource.
Otherwise, you can get the value the way that #MarkRajcok has shown in his answer.
Here is a psuedo-code illustration of ways you can use $resource query();
app.controller('FooCtrl', function($scope, $resource) {
var Bar = $resource('/Bar/:id', {id: '#id'});
// here, we get the result reference and stick it in the scope,
// relying on a digest to update the value on our screen.
$scope.data = Bar.query();
//OR
//here we already have a reference.
var test = Bar.query(function() {
//in here, test has been populated.
$scope.data2 = test;
});
alert(test); //not populated here, yet.
//OR
Bar.query(function(x) {
$scope.data3 = x;
});
});
This is all done so the object(s) returned can have functions pre-instantiated on them like $save(), etc.

Categories