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.
Related
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) {
....
})
I have started recently on Ionic and it requires a good grasp of AngularJS, which has some things I do not understand yet.
Basically, I have a search field, where I bind the property formdata.searchText and trigger the search function each time the value is changed. The callback is a collection of artists that fill my list.
<input type="search" ng-model="formdata.searchText" placeholder="Search"
ng-change="search()">
In my controller, this property is defined and on change, the method search gets called. Search reaches out to the factory for an API call.
.controller('SearchController', function($scope, SpotifyFactory) {
$scope.formdata = [{
searchText: ""
}];
$scope.search = function(){
$scope.searchResults = SpotifyFactory.searchArtist($scope.formdata.searchText);
};
})
This works well so far. The call is being made to the Spotify API and it returns the results of found artists based on my searchterm.
My data:
When I console.log the $scope.searchResults in my controller, it contains my desired values. But I believe that happens because console.log is being executed after a the promise is done, which makes it possible for the data to be displayed. If I console.log the object in my controller, and say for example
console.log($scope.searchResults.artists);
It returns an undefined, while Im still able to access the other properties.
Below is how I make the API call in the factory.
angular.module('starter.services', [])
.factory('SpotifyFactory', function($http){
var foundArtists = function($searchTerm) {
var baseUrl = "https://api.spotify.com/v1/search?query=" + $searchTerm + "&type=artist&offset=0&limit=20";
var searchResults = [];
searchResults.$promise = $http.get(baseUrl).then(function(response){
angular.copy(response.data, searchResults);
return searchResults;
});
return searchResults;
}
return {
searchArtist : foundArtists
}
}
)
My question is, how can I ensure that the data gets returned, after the call of the API is done.
Use a promise
.factory('SpotifyFactory', function($http){
var foundArtists = function ($searchTerm) { return new Promise(function(resolve, reject) {
var baseUrl = "https://api.spotify.com/v1/search?query=" + $searchTerm + "&type=artist&offset=0&limit=20";
var searchResults = [];
searchResults.$promise = $http.get(baseUrl).then(function(response){
angular.copy(response.data, searchResults);
resolve(searchResults);
});
}});
return {
searchArtist : foundArtists
}
}
Then call it from where you want
SpotifyFactory.searchArtist('madonna').then((data) {
$log.info('Data retrieved!', data);
// do things you want to do after results are found
}).catch( ... error function);
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;
});
I added this filter to my angular app to remove certain strings from loaded data:
.filter('cleanteam', function () {
return function (input) {
return input.replace('AFC', '').replace('FC', '');
}
});
<h2 class="secondary-title">{{teamDetails.name | cleanteam }}</h2>
You can see the error here:
http://alexanderlloyd.info/epl/#/teams/61
my controller looks a bit like this:
.controller('teamController', function($scope, $routeParams, footballdataAPIservice) {
$scope.id = $routeParams.id;
$scope.team = [];
$scope.teamDetails = [];
//$scope.pageClass = '';
$scope.$on('$viewContentLoaded', function(){
$scope.loadedClass = 'page-team';
});
footballdataAPIservice.getTeam($scope.id).success(function (response) {
$scope.team = response;
});
footballdataAPIservice.getTeamDetails($scope.id).success(function (response) {
$scope.teamDetails = response;
});
})
Any reason why this might happen? Is it because teamDetails.name is not declared within an ng-repeat loop?
By looking at your code it seems that you didn't handle the case of undefined, while your teamDetails.name can be undefined undefined until it fetch data from service.
Because when you trying to fetch data form service through ajax, your input variable is undefined, when filter code tries to apply .replace method on undefined object, it will never work ( .replace() only works on string)
Checking if your teamDetails.name object is defined or not is good
idea, because filter runs on every digest cycle.
Filter
.filter('cleanteam', function () {
return function (input) {
return angular.isDefined(input) && input != null ? //better error handling
input.replace('AFC', '').replace('FC', ''):'';
}
});
Hope this could help you, Thanks.
Looks to me like the filter is trying to execute before your async call has finished.
Try setting teamDetails to null when you initialize the controller and use an ng-if to prevent the DOM elements from loading before your data arrives:
$scope.id = $routeParams.id;
$scope.team = [];
$scope.teamDetails = null;
<h2 class="secondary-title" ng-if="teamDetails">{{teamDetails.name | cleanteam }}</h2>
This will ensure that the filter won't execute before the async call has populated the teamDetails object.
More on ng-if: https://docs.angularjs.org/api/ng/directive/ngIf
I am currently working on a REST + AngularJS application.
I have a little problem concerning promises on resource save action.
My Factory:
App.factory('Course', function($resource) {
var course = $resource('/AppServer/admin/courses/:courseId', {}, {});
course.findAll = function() {
return course.query();
};
course.findById = function(id) {
return course.get({
courseId : id
});
};
course.saveCourse = function(course) {
return course.$save();
}
return course;
});
My Controller:
App.controller('CourseEditController', function($scope, $routeParams, $location, Course, FlashMessage) {
// load course into edit form
$scope.course = Course.findById($routeParams.courseId);
// save edited course and print flash message
$scope.saveCourse = function() {
var savedCourse = Course.saveCourse($scope.course);
savedCourse.$then(function(httpResponse) {
FlashMessage.set("Die Ă„nderungen am Kurs <i>" + savedCourse.title + "</i> wurden erfolgreich gespeichert.");
$location.path("/kurse/verwalten");
});
}
});
Now the problem is, that I get the following exception:
TypeError: Cannot call method '$then' of undefined
The strange thing is that If I add the same then-callback to one of the finders (e.g. findById) everything works fine. But the return value of "return course.$save()" is undefined, compared to the return value of "return course.get({courseId:id});" which is "Object object".
What I want is to set the FlashMessage when the save action was fully executed and not before that.
Any ideas on this? The response from my REST service is correct. It returns the saved object.
Greets
Marc
There is two slightly different API's, one for working with a resource instance and - in lack of better words - more generic version. The main difference beeing the use of $-prefixed methods (get vs $get)
The $-prefixed methods in ngResource/resource.js. proxies the call and returns the promise directly.
AFAIK before the resource gets instanciated, you can only access resources with the normal get.
var promise = Resource.get().$promise;
promise.then(function(res) { console.log("success: ", res); });
promise.catch(function(res) { console.log("error: ", res); });
With instanciated resource the $-prefixed methods are available:
var res = new Resource({foo: "bar"});
res.$save()
.then(function(res) { console.log("authenticated") })
.catch(function(req) { console.log("error saving obj"); })
.finally(function() { console.log("always called") });
If you look at angular documentation on resource it mentions
It is important to realize that invoking a $resource object method
immediately returns an empty reference (object or array depending on
isArray). Once the data is returned from the server the existing
reference is populated with the actual data.
This may very well means that your call to $save would return empty reference. Also then is not available on Resource api before Angular 1.2 as resources are not promise based.
You should change your saveCourse method call to accept a function parameter for success and do the necessary action there.
This is for Angularjs 1.0.8
In my service I have the following:
angular.module('dataProvider', []).
factory('dataProvider', ['$resource','$q',function($resource,$q) {
//....
var Student = $resource('/app/student/:studentid',
{studentid:'#id'}
);
return {
newStudent:function(student){
var deferred = $q.defer();
var s = new Student({name:student.name,age:parseInt(student.age)});
s.$save(null,function(student){
deferred.resolve(student);
});
return deferred.promise;
}
};
}]);
In my controller:
$scope.createStudent=function(){
dataProvider.newStudent($scope.newStudent).then(
function(data){
$scope.students.push(data);
});
};
I added a method in controller to enable a resource have a promise when it executes a CRUD operation.
The method is the following:
function doCrudOpWithPromise(resourceInstance, crudOpName){
var def=$q.defer()
resourceInstance['$'+crudOpName](function(res){def.resolve(res)}, function(err){def.reject(err)})
return def.promise
}
An invocation example is:
var t=new MyResource()
doCrudOpWithPromise(t,'save').then(...)
This is a late responde but you can have callabck on $save...
var savedCourse = Course.saveCourse($scope.course);
savedCourse.$save(function(savedCourse, putResponseHeaders) {
FlashMessage.set("Die Ă„nderungen am Kurs <i>" + savedCourse.title + "</i> wurden erfolgreich gespeichert.");
$location.path("/kurse/verwalten");
});