I am refactoring some angularjs code to make the controller leaner and move some logic into a service.
Old Code This works OK.
In Controller...
var firmSearchRequest = {
type : "ByDate",
startDate : $scope.startDate,
endDate : $scope.endDate
};
firmService.getFirms(firmSearchRequest).then(function(response) {
firmService.appendFirmList(response.data);
$scope.firmList = firmService.getFirmList();
});
In Service...
var firmList = [];
this.getFirms = function(firmSearchRequest) {
return httpService.putForResponse('firms/search', firmSearchRequest);
};
this.appendFirmList = function(newfirmList){
firmList = firmList.concat(newfirmList);
}
this.getFirmList = function() {
return firmList;
};
Refactored Code Does not work as expected
In Controller...
var firmSearchRequest = {
type : "ByDate",
startDate : $scope.startDate,
endDate : $scope.endDate
};
$scope.firmList = firmService.appendFirmListByDate(firmSearchRequest);
In Service...
var firmList = [];
this.appendFirmListByDate = function(firmSearchRequest){
this.getFirms(firmSearchRequest).then(function(response) {
firmList = firmList.concat(response.data);
});
return firmList;
};
this.getFirms = function(firmSearchRequest) {
return httpService.putForResponse('firms/search', firmSearchRequest);
};
Unexpected Behavior
For the refactored code, when I click the button that executes the code above, I receive no console error but the $scope.firmList is empty. When I click the button a second time for a second execution, $scope.firmList is correctly populated.
Why is the first execution not working correctly? What am I doing wrong?
In your controller
$scope.firmList = firmService.appendFirmListByDate(firmSearchRequest);
here the function firmService.appendFirmListByDate() is a simple function and will run synchronously so the value will be returned immediately so the returned value in this case is empty array named firmList
so the question arises why you get the correct list when you click the button for second time.
when you clicked the button for second time then the value inside the array var firmList = [] was inserted because of the promise that ran for the first time and it was
this.getFirms(firmSearchRequest).then(function(response) {
firmList = firmList.concat(response.data);
});
when you clicked the button second time then the function still ran synchronously and you got the value that was populated in first step.
Note- so every time you are getting a value that was populated by the promise in the last step.
Important point
you can't refactor your code in this way
making a thin controller doesn't mean removing the promise out of it. It means the the business logic should not be there. so your promises should be inside service which should return a promise to the controller and data manipulation etc. should done inside the service
Returning a promise from service
this.appendFirmListByDate = function(firmSearchRequest){
return new Promise(function(resolve,reject){
//if firmList contains data then just return it
if(firmList.length!==0){
resolve(firmList);
}else{
this.getFirms(firmSearchRequest).then(function(response) {
firmList = firmList.concat(response.data);
resolve(firmList);
}).catch(function(error){
reject(error);
});
}
});
};
aren't you attempting to return firmList before (and technically regardless of whether ) the promise is completed?
You should put the return inside the promise, or maybe return the promise.
You can use callaback function
Service
this.appendFirmListByDate = function(firmSearchRequest, fct){
this.getFirms(firmSearchRequest).then(function(response) {
fct(firmList.concat(response.data);)
});
};
Controller
firmService.appendFirmListByDate(firmSearchRequest, function(result){
$scope.firmList = result;
});
Related
I have a CZML datasource declared:
public geometryDataPromise: Cesium.CzmlDataSource;
I am loading it with an endpoint above when a component is loaded:
if(!this.store.geometryDataPromise) {
this.store.geometryDataPromise = Cesium.CzmlDataSource.load(environment.apiBaseURL + `/protectedareas/geometry/all`);
}
all the rendered objects are displayed to terrain but trying to follow the instructions by doing:
this.store.geometryDataPromise.show = false;
the objects are not being hidden
The problem here is that Cesium.CzmlDataSource.load does not return a Cesium.CzmlDataSource. It returns a Promise to asynchronously go get a CzmlDataSource, and that's not the same thing at all. Your code is trying to show or hide the promise, that's not a thing that gets displayed.
var dataSourcePromise = Cesium.CzmlDataSource.load( ... );
var dataSource = null;
dataSourcePromise.then(function(d) { dataSource = d; });
Note that after the above code runs, dataSource will be null for some time while the browser waits for the server's response to finish downloading. Once the callback function fires, the dataSource is ready.
function onClick() {
if (dataSource !== null) {
dataSource.show = !dataSource.show;
}
}
You can wire up a click handler for a toggle button like this. But the toggle won't do anything until after the dataSource is downloaded and ready.
First I have to take the result of the Cesium.CzmlDataSource.load promise
Cesium.when(Cesium.CzmlDataSource.load(environment.apiBaseURL + `/protectedareas/geometry/all`), result => {
this.sources = result;
this.viewer.dataSources.add(this.sources);
});
and then just change it's fiend show when the visibility changed
this.store.sourceVisibility.subscribe(visibility=>this.sources.show=visibility);
I have the following problem: I want to get data from a specific node from firebase during runtime. It should display "stats" of a player that was selected before. Now I could use on() to get all the data in the beginning, but I want to save data transfers by only downloading the data of on player if I need to, so I tried to use once like this:
var firebaseRef = firebase.database().ref();
function getScoresOfPlayer(player) {
console.log(player);
var selectedPlayerScores = [];
firebaseRef.once('value').then(function(snap) {
snap.child('scores').child('thierschi').forEach(function(child) {
selectedPlayerScores.push([child.key, child.val()]);
});
});
return selectedPlayerScores;
}
The problem is that it retruns the array before the data was loaded into it. Also I checked the docs and didn't find a better solution.
Thanks in advance!
This is because the getScoresOfPlayer function returns selectedPlayerScores before the promise returned by the once() method resolves.
You should include the return within the then(), as follows:
var firebaseRef = firebase.database().ref();
function getScoresOfPlayer(player) {
console.log(player);
var selectedPlayerScores = [];
return firebaseRef.once('value') //return here as well
.then(function(snap) {
snap.child('scores').child(player).forEach(function(child) { //I guess it should be child(player) and not child('thierschi') here
selectedPlayerScores.push([child.key, child.val()]);
});
return selectedPlayerScores;
});
}
which means that you have to call your function as follows, since it is going to be asynchronous and to return a promise:
getScoresOfPlayer('xyz')
.then(function(selectedPlayerScores) {
....
})
Specifically, given a list of data, I want to loop over that list and do a fetch for each element of that data before I combine it all afterward. The thing is, as written, the code iterates through the entire list immediately, starting all the operations at once. Then, even though the fetch operations are still running, the then call I have after all that runs, before the data could've been processed.
I read something about putting all the Promises in an array, then passing that array to a Promise.all() call, followed by a then that will have access to all that processed data as intended, but I'm not sure how exactly to go about doing it in this case, since I have nested Promises in this for loop.
for(var i in repoData) {
var repoName = repoData[i].name;
var repoUrl = repoData[i].url;
(function(name, url) {
Promise.all([fetch(`https://api.github.com/repos/${username}/${repoData[i].name}/commits`),
fetch(`https://api.github.com/repos/${username}/${repoData[i].name}/pulls`)])
.then(function(results) {
Promise.all([results[0].json(), results[1].json()])
.then(function(json) {
//console.log(json[0]);
var commits = json[0];
var pulls = json[1];
var repo = {};
repo.name = name;
repo.url = url;
repo.commitCount = commits.length;
repo.pullRequestCount = pulls.length;
console.log(repo);
user.repositories.push(repo);
});
});
})(repoName, repoUrl);
}
}).then(function() {
var payload = new Object();
payload.user = user;
//console.log(payload);
//console.log(repoData[0]);
res.send(payload);
});
Generally when you need to run asynchronous operations for all of the items in an array, the answer is to use Promise.all(arr.map(...)) and this case appears to be no exception.
Also remember that you need to return values in your then callbacks in order to pass values on to the next then (or to the Promise.all aggregating everything).
When faced with a complex situation, it helps to break it down into smaller pieces. In this case, you can isolate the code to query data for a single repo into its own function. Once you've done that, the code to query data for all of them boils down to:
Promise.all(repoData.map(function (repoItem) {
return getDataForRepo(username, repoItem);
}))
Please try the following:
// function to query details for a single repo
function getDataForRepo(username, repoInfo) {
return Promise
.all([
fetch(`https://api.github.com/repos/${username}/${repoInfo.name}/commits`),
fetch(`https://api.github.com/repos/${username}/${repoInfo.name}/pulls`)
])
.then(function (results) {
return Promise.all([results[0].json(), results[1].json()])
})
.then(function (json) {
var commits = json[0];
var pulls = json[1];
var repo = {
name: repoInfo.name,
url: repoInfo.url,
commitCount: commits.length,
pullRequestCount: pulls.length
};
console.log(repo);
return repo;
});
}
Promise.all(repoData.map(function (repoItem) {
return getDataForRepo(username, repoItem);
})).then(function (retrievedRepoData) {
console.log(retrievedRepoData);
var payload = new Object();
payload.user = user;
//console.log(payload);
//console.log(repoData[0]);
res.send(payload);
});
I have a SharePoint App that is built with AngularJS.
I have a controller that is calling a function in a service and it is not returning a value. I am pretty new at angularJS so I am a bit lost.
My Service:
App.factory('uploadAppService', function () {
return {
currentUserPic: function (myProfileProp) {
GetUserProfileInfo(myProfileProp).done(function (data) {
//The large thumbnail pic.
var picUrl = data.d.PictureUrl;
var largePicUrl = picUrl.replace('MThumb', 'LThumb');
console.log(largePicUrl) //the log here is correct. I want to return the largePicUrl back to my controller.
return largePicUrl;
});
}
My Controller call, I want to populate .imageUrl with the url from the service:
$scope.imageUrl = uploadAppService.currentUserPic("PictureUrl");
Thank you in advance.
To me, your currentUserPicfunction doesn't seem to return a value. GetUserProfileInfo indeed returns your largePicUrl, but this value is not used anywhere (if I correctly understand your code).
Shouldn't you use return GetUserProfileInfo(myProfileProp).done(...); ?
Edit: But as RaviH pointed, if the call is asynchronous, you'll still have to handle it in your controller.
I don't see implementation of your GetUserProfileInfo service, but i suppose it's a Deffered object.
So after code
$scope.imageUrl = uploadAppService.currentUserPic("PictureUrl");
finished working - you don't have anything in you variable $scope.imageUrl because your factory function does not return anything.
So, at first you need to modify your factory:
App.factory('uploadAppService', function () {
return {
currentUserPic: function (myProfileProp) {
return GetUserProfileInfo(myProfileProp).done(function (data) {
// ^
// Here - #ababashka's edit
//The large thumbnail pic.
var picUrl = data.d.PictureUrl;
var largePicUrl = picUrl.replace('MThumb', 'LThumb');
console.log(largePicUrl) //the log here is correct. I want to return the largePicUrl back to my controller.
return largePicUrl;
});
}
Return Deffered Object, so after it finished working, you could save your image URL by getting it in the response.
After you need to write next code:
uploadAppService.currentUserPic("PictureUrl").done(
function (response) {
$scope.imageUrl = response;
}
);
to store your URL in $scope's variable.
I am sure I am missing something obvious but I can't seem to make heads or tails of this problem. I have a web page that is being driven by javascript. The bindings are being provided by Knockout.js, the data is coming down from the server using Breeze.js, I am using modules tied together with Require.js. My goal is to load the html, load the info from Breeze.js, and then apply the bindings to show the data to the user. All of these things appear to be happening correctly, just not in the correct order which is leading to weird binding errors. Now on to the code.
I have a function that gets called after the page loads
function applyViewModel() {
var vm = viewModel();
vm.activate()
.then(
applyBindings(vm)
);
}
This should call activate, wait for activate to finish, then apply bindings....but it appears to be calling activate, not waiting for it to finish and then runs applybindings.
activate -
function activate() {
logger.log('Frames Admin View Activated', null, 'frames', false);
return datacontext.getAllManufacturers(manufacturers)
.then(function () {
manufacturer(manufacturers()[0]);
}).then(function () {
datacontext.getModelsWithSizes(modelsWithSizes, manufacturers()[0].manufacturerID())
.then(datacontext.getTypes(types));
});
}
datacontext.getAllManufacturers -
var getAllManufacturers = function (manufacturerObservable) {
var query = entityQuery.from('Manufacturers')
.orderBy('name');
return manager.executeQuery(query)
.then(querySucceeded)
.fail(queryFailed);
function querySucceeded(data) {
if (manufacturerObservable) {
manufacturerObservable(data.results);
}
log('Retrieved [All Manufacturer] from remote data source',
data, true);
}
};
datacontext.getModelsWithSizes -
var getModelsWithSizes = function (modelsObservable, manufacturerId) {
var query = entityQuery.from('Models').where('manufactuerID', '==', manufacturerId)
.orderBy('name');
return manager.executeQuery(query)
.then(querySucceeded)
.fail(queryFailed);
function querySucceeded(data) {
if (modelsObservable) {
for (var i = 0; i < data.results.length; i++) {
datacontext.getSizes(data.results[i].sizes, data.results[i].modelID());
// add new size function
data.results[i].addNewSize = function () {
var newValue = createNewSize(this.modelID());
this.sizes.valueHasMutated();
return newValue;
};
}
modelsObservable(data.results);
}
log('Retrieved [Models With Sizes] from remote data source',
data, false);
}
};
Any help on why this promise isn't working would be appreciated, as would any process to figure it out so I can help myself the next time I run into this.
A common mistake when working with promises is instead of specifying a callback, you specify the value returned from a callback:
function applyViewModel() {
var vm = viewModel();
vm.activate()
.then( applyBindings(vm) );
}
Note that when the callback returns a regular truthy value (number, object, string), this should cause an exception. However, if the callback doesn't return anything or it returns a function, this can be tricky to locate.
To correct code should look like this:
function applyViewModel() {
var vm = viewModel();
vm.activate()
.then(function() {
applyBindings(vm);
});
}