Angular Nested Promise - javascript

I’m really struggling to write a complex function in Angular that depends on promises. This is my first time writing a promise and I'm still not sure I fully understand how to do what I want to do with my code.
I have a variable var query = searchQuery.getQuery() in a controller ProfileNavCtrl. Then in my searchQuery service, getQuery fetches the value of localStorage.getItem('searchQuery') and checks if it’s an empty string or null. If it’s not empty or null, it simply returns the value to the controller. The value should be an array of slugs like ['foo','foo-bar','foo-bar-baz'].
If it is null or empty, it executes an $http.get call to fetch a JSON object and parse it. This is where things break down for me. I need getQuery() to return the value from $http.get (if the initial value of query is null) so that the controller variable query is assigned that value. As it is now, query (in the controller) is always set to null or undefined.
The $http.get call also calls setQuery() so that the query is persisted and future calls are avoided.
Here is my controller:
app.controller('ProfileNavCtrl', ['$scope', '$http', '$location', '$q', 'searchQuery',
function($scope, $http, $location, $q, searchQuery){
var query = searchQuery.getQuery;
// do something with query
And here is my service:
app.service('searchQuery', ['$http', '$timeout', '$q', function($http, $timeout, $q){
var query = [];
this.getQuery = new Promise(function(){
var query = localStorage.getItem('searchQuery');
if(query == "" || query == [""] || query == null){
var slugArray = [];
var query = $http.get('/companies.json')
.then(function(resp) {
if(resp && resp.data) {
for(var i in resp.data) {
var result = resp.data[i];
if(resp.data[i].name){
slugArray.push(resp.data[i].name.toLowerCase().split(' ').join('-'));
}
}
setQuery(slugArray);
} else {
resetQuery();
}
}, function(err) {
resetQuery();
}).then(function(resp){
return resp;
})
return query;
} else {
return query;
};
}).then(function(success){
return success;
});
UPDATE: 2nd Attempt
Here is my controller code:
var getQuery = searchQuery.getQuery();
getQuery.then(function(query){
query = searchQuery.getQuery();
// Check if user is on main site or portal
if(location.pathname.split('/')[3] == null){
var currentProfile = location.pathname.split('/')[1];
} else {
var currentProfile = location.pathname.split('/')[3];
};
// Get the next/prev query element (if any)
console.log('6: ');
console.log(query);
var prev = query.slice(query.indexOf(currentProfile)-1)[0];
var next = query.slice(query.indexOf(currentProfile)+1)[0];
// Check if next/prev is undefined and if so, set to first/last element in query array
if(prev){
var prevProfile = prev;
} else {
var prevProfile = query.pop();
};
if(next){
var nextProfile = next;
} else {
var nextProfile = query[0];
};
$scope.goToPrev = function() {
// Check if user is on main site or portal
if(location.pathname.split('/')[3] == null){
var profileUrl = location.origin + '/' + prevProfile;
// window.location = profileUrl;
console.log(profileUrl);
} else {
var profileUrl = location.origin + '/' + location.pathname.split('/').slice(1,3).join('/') + '/' + prevProfile;
// window.location = profileUrl;
console.log(profileUrl);
}
};
$scope.goToNext = function() {
// Check if user is on main site or portal
if(location.pathname.split('/')[3] == null){
var profileUrl = location.origin + '/' + nextProfile;
// window.location = profileUrl;
console.log(profileUrl);
} else {
var profileUrl = location.origin + '/' + location.pathname.split('/').slice(1,3).join('/') + '/' + nextProfile;
// window.location = profileUrl;
console.log(profileUrl);
}
};
});
Here is my updated service:
this.getQuery = function(){
return new Promise(function(){
var query = localStorage.getItem('searchQuery');
if(query == "" || query == [""] || query == null){
var slugArray = [];
return $http.get('/companies.json')
.then(function(resp) {
if(resp && resp.data) {
for(var i in resp.data) {
var result = resp.data[i];
if(resp.data[i].name){
slugArray.push(resp.data[i].name.toLowerCase().split(' ').join('-'));
}
}
setQuery(slugArray);
} else {
resetQuery();
}
return slugArray;
}, function(err) {
resetQuery();
});
} else {
return query;
};
});
};

In Angular promises are provided through the $q service. See the documentation for more detail.
The basic outline to implement $q promise in your service is outlined below, I'll leave the detail on how to save to local storage etc to you:
this.getQuery = function(){
var deferred = $q.defer();
var query = localStorage.getItem('searchQuery');
if(query == "" || query == [""] || query == null){
$http.get('yoururl').then(function(resp) {
// assuming resp is an array, else do your parsing to get array
query = resp;
deferred.resolve(query);
}, function(err) {
query = null;
deferred.reject(err);
});
} else {
deferred.resolve(query);
};
return deferred.promise;
};
You can then use this in your controller like:
var query = null;
searchQuery.getQuery().then(function(result) {
query = result;
}, function(err) {
// Error occured
});

Related

AngularJS filter with a promise

I'm upgrading a filter from a static object to an object retrieved from our database, but I can't seem to get a proper return. This filter takes in an integer which represents a named location, it looks up the location with the key, and returns the name. After troubleshooting, I'm getting close as I can see the object from the database and I can see some of the lookups correctly inside of the then section, but it's not being returned at the end of the filter. Is there a better method on getting this with a filter?
stPartFieldFilters.js
angular.module('app').filter('partLocation', function(stPartMgmtSvc, $q) {
var locs;
function getLocations() {
if(!locs) {
var dfd = $q.defer();
stPartMgmtSvc.getLocations().then(function(res) { locs = res; dfd.resolve(locs); }, function(response) { dfd.reject(response.data.reason) });
return dfd.promise;
}
else {
var dfd = $q.defer();
dfd.resolve(locs);
return dfd.promise;
}
}
function getResults(loc, type) {
var lr = null; // this should be updated once a match is found below
getLocations().then(function(ls) {
if (loc || loc === 0) {
loc = loc.split(',');
if(typeof loc === 'object') {
var res = new Array;
loc.forEach(function(l) {
res.push(ls[l].name)
});
if(!type) { lr = res.toString().replace(',', '<br>'); } // this line provides the correct output; see below (LOCFLTRRES1)
else { lr = res.toString().replace(',', ', '); }
}
else { lr = ls[loc].name; }
}
else { lr = false; };
});
return lr; // return the updated result
}
return function(loc, type) {
return getResults(loc, type); //return the final result for the filter
}
});
inventory.jade
~~~
td.desktop-only {{part.partLoc | partLocation:1}}
~~~
Here is an image of the results of the database object.
Here is an image of the result inside of the filter. This should be set to lr and returned to the main filter function.
Here is the expected result.
Finally, here is the actual result.

Call factory function once

I have this factory which is called multiple times by directives. Since it returns a lot of data the rendering at the end is slow. How can i call it only once or save it in a cashe when its called the second time and n time?
appBMdata.factory('Trends',['$http','Config','$q',
function($http,Config,$q){
function getAllData() {
var source1 = $http.get(Config.api_server + 'bizmonitor/indicators/get/2016');
var source2 = $http.post(Config.api_server + 'trends');
return $q.all([source1, source2]);
};
return {
getAllData : getAllData,
};
}]);
You can save the promise in a var, and return it if it has been already set:
appBMdata.factory('Trends',['$http','Config','$q',
function($http,Config,$q){
var _cacheGetAllData;
function getAllData() {
var source1 = $http.get(Config.api_server + 'bizmonitor/indicators/get/2016');
var source2 = $http.post(Config.api_server + 'trends');
_cacheGetAllData = _cacheGetAllData || $q.all([source1, source2]);
return _cacheGetAllData;
}
return {
getAllData : getAllData,
};
}]);
If you want successive calls to force to update, you can edit it to something like this:
appBMdata.factory('Trends',['$http','Config','$q',
function($http,Config,$q){
var _cacheGetAllData;
function getAllData(ignoreCache) {
var source1 = $http.get(Config.api_server + 'bizmonitor/indicators/get/2016');
var source2 = $http.post(Config.api_server + 'trends');
if (ignoreCache) {_cacheGetAllData = undefined;}
_cacheGetAllData = _cacheGetAllData || $q.all([source1, source2]);
return _cacheGetAllData;
}
return {
getAllData : getAllData,
};
}]);
I'm resolving it in the service and then store data, if it has data, returning data in a promise. If you want to fetch data again just add true as first arguement.
appBMdata.factory('Trends', ['$http', 'Config', '$q', function($http, Config, $q) {
var data;
function getAllData(nocache) {
var deferred = $q.defer();
if (data.length && !nocache) {
deferred.resolve(data);
} else {
var source1 = $http.get(Config.api_server + 'bizmonitor/indicators/get/2016');
var source2 = $http.post(Config.api_server + 'trends');
$q.all([source1, source2])
.then(function (values) {
data = values;
deferred.resolve(data);
})
.catch(function (err) {
deferred.reject(err);
});
}
return deferred.promise;
}
return {
getAllData : getAllData
};
}]);
Yes you can keep the data on $rootScope and return the data from there when its called multiple times.
appBMdata.factory('Trends',['$http','Config','$q','$rootScope'
function($http,Config,$q,$rootScope){
function getAllData() {
var source1 = $http.get(Config.api_server + 'bizmonitor/indicators/get/2016');
var source2 = $http.post(Config.api_server + 'trends');
return $q.all([source1, source2]);
};
if($rootScope.data){ // check if data already present
$rootScope.data=getAllData(); // assign data to rootscope
}
return {
getAllData : $rootScope.data, //return data from rootscope
};
}]);

angular return a promise [duplicate]

This question already has answers here:
Immediately return a resolved promise using AngularJS
(5 answers)
Closed 7 years ago.
I have a problem with a promise. This is the controller for a detail page. I want to get the item details from a web service (or from the local store if they are stored).
app.controller('ProductCtrl', function($scope, productsService) {
//load the product by id from productService
productsService.get("CODE-1111").then(function(result){
$scope.currProduct = result;
$scope.currProductSizes = $scope.currProduct.sizes;
});
}
app.service('productsService', function($http, localStorageService){
var _key = 'myProducts'
var _storedData = []
var self = this;
self.get = function(id){
var i = 0;
return self.getAll().then(function(result) {
//get the item by id
_storedData = result;
for(i=0;i<_storedData.length;i++){
if(_storedData[i].id == id)
break;
}
return _storedData[i];
});
}
self.getAll = function() {
_storedData = localStorageService.get(_key);
if(_storedData != null){
**//How return a promise here? Or how I can handle it**
return _storedData instanceof Array ? _storedData : [_storedData];
}
else{
var url = baseUrl + "api/GetStyles";
return $http({method: 'GET', url: url, headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }})
.then(function(response) {
_storedData = mapToProducts(JSON.parse(response.data));
return _storedData;
},
function(response) {
console.log(response.status);
});
}
};
})
The first time (the items aren't in the local store) all works well because the getAll method return a promise, but the second time it doesn't return a promice so I get
Cannot read property 'then' of undefined
How I can solve this?
You can return a promise like this:
return $q.when(_storedData instanceof Array ? _storedData : [_storedData]);
You need to response a promise in your get and getAll functions, I changed your functions to do that:
app.service('productsService', ['$http', '$q', 'localStorageService', function($http, $q, localStorageService){
var q = $q;
var _key = 'myProducts';
var _storedData = [];
var self = this;
self.get = function(id){
var deferred = q.defer(); //Initialize your own promise
var i = 0;
self.getAll().then(function(result) {
//get the item by id
_storedData = result;
for(i=0;i<_storedData.length;i++){
if(_storedData[i].id == id)
break;
}
return deferred.resolve(_storedData[i]); //This is your response for success
}).catch(function(error) {
deferred.reject(error); //This is your response for failure
});
return deferred.promise; //return the promise
}
self.getAll = function() {
var deferred = q.defer(); //Initialize your own promise
_storedData = localStorageService.get(_key);
if(_storedData != null){
var data = _storedData instanceof Array ? _storedData : [_storedData];
deferred.resolve(data);
} else {
var url = baseUrl + "api/GetStyles";
$http({method: 'GET', url: url, headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }}).then(function(response) {
_storedData = mapToProducts(JSON.parse(response.data));
deferred.resolve(_storedData); //This is your response for success
}, function(error) {
deferred.reject(error); //This is your response for failure
});
}
return deferred.promise;
};
}]);

How to scale controllers with angular

I have some angular app, that is really easy. I've put everything into one controller, but i want to split it into multiple controllers so every controller should do action that belongs to it, not have a lot of different function of different meaning in one controller.
Here is a code:
var videoApp = angular.module('videoApp', ['videoAppFilters', 'ui.unique', 'angularUtils.directives.dirPagination']);
videoApp.controller('VideoListCtrl', function ($scope, $http, $filter) {
$scope.getFilteredResults = function (category, data, callback) {
callback = callback ||$filter('articleFilter');
$scope.videos = callback(category, data);
return $scope.videos;
};
$scope.setPageSize = function (pageSize) {
$scope.pageSize = pageSize;
return $scope.pageSize;
};
$scope.addFavorite = function (data, key) {
localStorage.setItem(key, data);
$scope.getFilteredResults(data, $scope.allData);
return alert(key + " "+ data + " was added to your favorite list.");
};
$scope.addSelectedClass = function (event) {
if($(event.target).hasClass("selected") == true)
{
$(event.target).removeClass("selected");
} else {
$(".selected").removeClass("selected");
$(event.target).addClass("selected");
}
};
$scope.formatDate = function (dateString) {
var date = new Date(parseInt(dateString));
return date.toDateString();
};
$scope.cacheLoad = function (url, allowCache) {
if(allowCache == false || localStorage.getItem(url) && (parseInt(localStorage.getItem(url + 'time')) + 20000) < (new Date().getTime()) || (!localStorage.getItem(url) )) {
$http.get(url).success(function (data) {
$scope.allData = data;
$scope.videos = data;
if(localStorage.getItem('category')) {
$scope.videos = $scope.getFilteredResults(localStorage.getItem('category'), $scope.allData);
} else {
$scope.videos = data;
}
$scope.categories = $filter('categoryFilter')(data);
if(allowCache == true && parseInt(localStorage.getItem(url + 'time')) + 20000 < (new Date().getTime() )) {
localStorage.setItem(url, JSON.stringify(data));
localStorage.setItem(url + 'time', new Date().getTime());
}
});
} else {
$scope.allData = JSON.parse(localStorage.getItem(url));
$scope.videos = JSON.parse(localStorage.getItem(url));
$scope.categories = $filter('categoryFilter')(JSON.parse(localStorage.getItem(url)));
}
};
$scope.pageSize = 12;
$scope.cacheLoad('http://academy.tutoky.com/api/json.php', true);
});
So, how to split this into multiple controllers and how to pass data between them?
You could split things out into Services, for example the following item could be a service in your code, that you then dependency inject into your controller:
Your Cache logic, This is normally something you would want to reuse so it makes sense to be a service.
You might also want to make the following item a filter or directive:
$scope.formatDate - Rather than calling this function everytime you want to format a date, it would be much easier in your html to call {{ date | formatDate }} or <div formatDate>{{ date }}</div>
You could probably strip out the pageSize too but it depends how granular you want to go.

what's a proper way to check for null/empty string in js before including to params?

I am building a querystring and want to exclude keys if vals are empty, what's a proper way?
setQueryString: function () {
var keyword = $('#keyword').val();
//how to exclude it if keyword is empty?
var params = {
"keyword": $.trim(keyword)
};
return params;
}
take into account, that I will have 20+ inputs like keyword..trying to avoid lots of IF statements
If you have multiple params and you don't want lots of if statements:
setQueryString: function () {
var params = {
'param1': $.trim($('#param1').val()),
'param2': $.trim($('#param2').val())
}
for (p in params) {
if (params.p == null || params.p == '') {
delete params.p;
}
}
return params;
}
Don't set it if it's empty is all:
var keyword = $.trim($('#keyword').val());
var params = {};
if(keyword) {
params.keyword = keyword;
}
return params;
(edit)
If you have lots of things to check, consider using either a loop:
var items = {
keyword: $.trim($('#keyword').val())
// etc.
};
var params = {};
for(var x in items) {
if(items.hasOwnProperty(x) && items[x]) {
params[x] = items[x];
}
}
return params;
or a function of some kind, for example:
var params = {};
function check(name) {
var value = $.trim($('#' + name).val());
if(value) {
params[name] = value;
}
}
check('keyword');
// etc.
return params;
As an empty string is a falsy value in JavaScript you can simpley check if val() is true:
setQueryString: function () {
var keyword = $('#keyword').val();
if(keyword){
var params = {
"keyword": $.trim(keyword)
};
return params;
}
}
Try something like:
setQueryString: function () {
var keyword = $.trim($('#keyword').val());
var params = {};
if(keyword !== undefined && keyword !== '') {
params.keyword = keyword;
}
return params;
}
I believe you need extend: http://api.jquery.com/jQuery.extend/

Categories