Angular Factory dependency issue - angularjs

I have 2 factories: ApiService and LocationService.
In ApiService i'd like to return the endpoint from an $http call that LocationService will use.
But it seems when the controller calls LocationService, it doesn't wait for the response from ApiService. Here is some snippet of code, in ApiService when I finally get it working I will cache it so I won't need to make a server call each time to get the endpoint:
services.factory("ApiService", ["$location", "$http", function ($location, $http) {
return {
getEndpointUrl: function () {
var endpoint;
$http({
method: 'GET',
url: '/site/apiendpoint'
}).then(function successCallback(response) {
endpoint = response.data;
console.log(endpoint);
return endpoint;
}, function errorCallback(response) {
console.error('Error retrieving API endpoint');
});
}
}
}]);
Here is the location service, it consumes ApiService:
services.factory("LocationService", ["$resource", "ApiService", function ($resource, apiService) {
var baseUri = apiService.getEndpointUrl();
return $resource(baseUri + '/location', {}, {
usStates: { method: 'GET', url: baseUri + '/location/us/states' }
});
}]);
When my controller tries to call LocationService.usStates the baseUri is undefined. What am I doing wrong here?

The reason is because your getEndpointUrl function is asynchronous, and it has no return value.
Since your LocationService uses $resource and depends on on the baseUri, I would suggest bootstrapping that data along with the initial page load and making it a constant like:
angular.module('yourModule').constant('baseUrl', window.baseUrl);
Then your service would inject it to create your resource:
services.factory("LocationService", ["$resource", "ApiService", "baseUrl", function ($resource, apiService, baseUrl) {
return $resource(baseUrl + '/location', {}, {
usStates: { method: 'GET', url: baseUrl + '/location/us/states' }
});
}]);

In ApiService, you're not actually returning a value from getEndpointUrl(). How about you return a promise from ApiService, and then consume that in LocationService in a synchronous fashion?
services.factory("ApiService", ["$location", "$http", function($location, $http) {
return {
getEndpointUrl: function() {
var endpoint;
return $http({
method: 'GET',
url: '/site/apiendpoint'
});
}
}
}]);
services.factory("LocationService", ["$resource", "ApiService", function($resource, apiService) {
return {
getLocations: function() {
return apiService.getEndpointUrl().then(function successCallback(response) {
var baseUri = response.data;
return $resource(baseUri + '/location', {}, {
usStates: { method: 'GET', url: baseUri + '/location/us/states' }
});
}, function errorCallback(response) {
console.error('Error retrieving API endpoint');
});
}
};
}]);
And then in your controller:
LocationService.getLocations().then(function(data) {
$scope.statesResult = data.result.states;
});

Related

Ionic. Using $http giving error Cannot read property 'protocol' of undefined

This question is related to another one.
Before I did added $ionicPlatform, my service working just fine, but now there is something wrong with $http.
Here is example of injectables:
(function () {
"use strict";
angular.module('service', ['ionic'])
.service('BBNService', ["$http", "$localStorage", "$ionicPlatform",
function ($http, $localStorage, $ionicPlatform) {
And using of $http and $ionicPlatform
this.tips = function () {
var url;
$ionicPlatform.ready(function () {
if (window.Connection) {
if (navigator.connection.type == Connection.CELL_4G || navigator.connection.type == Connection.WIFI) {
if (this.getDayId = 0)//If Sunday - retrieve updated tips
url = this.host + "/tips/";
else
url = "data/tips.json";//If not - use saved data
}
}
});
var request = $http({
method: "GET",
url: url
}).then(
function mySucces(response) {
return response.data;
},
function myError(response) {
return response.data;
});
return request;
};
You need to send back the promise, doing a return response.data is not gonna work.
var deferred = $q.defer();
var request = $http({
method: "GET",
url: url
}).then(
function mySucces(response) {
deferred.resolve(response.data);
},
function myError(response) {
deferred.reject(response.data);
});
return deferred.promise;
And at the place where you consume this service:
BBNService.tips().then(
function(data) { //success call back with data },
function(data) { //error call back with data }
);
Please let me know if you need more explanation on using $q; always happy to give more details.

Return deferred.promise 2 services back

I have 3 services.
HttpSender - It controls the $http request
app.service("HttpSender", ["$http", "$q", function ($http, $q) {
this.send = function (path, method, params) {
var deferred = $q.defer();
$http({
url: path,
method: method,
params: params
}).then(function successCallback(response) {
deferred.resolve(response.data);
}, function errorCallback(response) {
deferred.reject(response);
});
return deferred.promise;
};
this.sendRequestWithFile = function (path, method, params) {
//todo check if needed
};
}]);
Api - controls all the api/ access token processes
service("API", ["HttpSender", "$q", 'WindowOpen', function(HttpSender, $q, WindowOpen){
var self = this;
var API = {};
API.requestTypes = {
GetMethod: "GET",
PostMethod: "POST",
DeleteMethod: "DELETE",
PutMethod: "PUT"
};
API.sendRequest = function (path, method, parameters, isCheckAccessToken)
{
path = ServersConfig.getApiServerUrl() + path;
parameters.access_token = getAccessToken();
HttpSender.send(path, method, parameters);
};
return API;
}]);
Api - Endpoint which activate the api request
app.factory('SelectedEndpoint', ['API',
function (API) {
var getPath = function (campaign) {
return "/campaigns/" + campaign.id + '/content/selected';
};
return {
get: function (campaign) {
API.sendRequest(getPath(campaign), API.requestTypes.GetMethod, {}, true).then(function (content) {
});
}
};
}]);
How can return the deferred.promise to the endpoint function so the then will get the answer? The following process only works if i add then also in the api factory the then return it to the endpoint
How can return the deferred.promise to the endpoint function so the then will get the answer?
By properly returning promises on each step.
1. HttpSender service. Do not use deferred here, just return promise directly:
app.service("HttpSender", ["$http", "$q", function($http, $q) {
this.send = function(path, method, params) {
return $http({
url: path,
method: method,
params: params
}).then(function successCallback(response) {
return response.data;
});
};
}]);
2. Api service. Make sure you return previous promise with return HttpSender.send(path, method, parameters);:
service("API", ["HttpSender", "$q", 'WindowOpen', function(HttpSender, $q, WindowOpen) {
var self = this;
var API = {};
API.requestTypes = {
GetMethod: "GET",
PostMethod: "POST",
DeleteMethod: "DELETE",
PutMethod: "PUT"
};
API.sendRequest = function(path, method, parameters, isCheckAccessToken) {
path = ServersConfig.getApiServerUrl() + path;
parameters.access_token = getAccessToken();
return HttpSender.send(path, method, parameters); // note return promise
};
return API;
}]);

Cannot set property from angularjs service

I am trying to set value in html page from angularjs controller.
I am getting value from web api in service but I have issue that I am always getting error:
TypeError: Cannot set property 'messageFromServer' of undefined
But I can't figure what am I doing wrong here. What am I missing?
On the html part I have:
<div ng-app="myApp" ng-controller="AngularController">
<p>{{messageFromServer}}</p>
</div>
In the controller I have:
var app = angular.module('myApp', []);
app.controller('AngularController', ['$scope', 'messageService', function ($scope, messageService) {
$scope.messageFromServer = "When I set it here it works!"
messageService.getMessage();
}]);
app.service('messageService', ['$http', function ($http) {
this.getMessage = function ($scope) {
return $http({
method: "GET",
url: "api/GetMessage",
headers: { 'Content-Type': 'application/json' }
}).success(function (data) {
$scope.messageFromServer = data;
console.log(data);
}).error(function (data) {
console.log(data);
})
};
}]);
Basically the problem is, you missed to $scope object to the service getMessage method. But this is not a good approach to go with. As service is singleton object, it shouldn't manipulate scope directly by passing $scope to it. Rather than make it as generic as possible and do return data from there.
Instead return promise/data from a service and then assign data to the scope from the controller .then function.
app.service('messageService', ['$http', function ($http) {
this.getMessage = function () {
return $http({
method: "GET",
url: "api/GetMessage",
headers: { 'Content-Type': 'application/json' }
}).then(function (response) {
//you could have do some data validation here
//on the basis of that data could be returned to the consumer method
//consumer method will have access only to the data of the request
//other information about request is hidden to consumer method like headers, status, etc.
console.log(response.data);
return response.data;
}, function (error) {
return error;
})
};
}]);
Controller
app.controller('AngularController', ['$scope', 'messageService',
function ($scope, messageService) {
$scope.messageFromServer = "When I set it here it works!"
messageService.getMessage().then(function(data){
$scope.messageFromServer = data;
});
}
]);
Don't use $scope in your service, just return the promise from $http.
var app = angular.module('myApp', []);
app.service('messageService', ['$http', function ($http) {
this.getMessage = function () {
return $http({
method: "GET",
url: "api/GetMessage",
headers: { 'Content-Type': 'application/json' }
});
};
}]);
app.controller('AngularController', ['$scope', 'messageService', function ($scope, messageService) {
messageService.getMessage().then(function(data) {
$scope.messageFromServer = data;
});
}]);
In this example you can unwrap the promise in your controller, or even better you can use the router to resolve the promise and have it injected into your controller.
app.config(function($routeProvider) {
$routeProvider.when('/',{
controller: 'AngularController',
templateUrl: 'views/view.html',
resolve: {
message: function(messageService) {
return messageService.getMessage();
}
}
});
});
Then in your AngularController, you'll have an unwrapped promise:
app.controller('AngularController', ['$scope', 'message', function ($scope, message) {
$scope.messageFromServer = message;
}]);

Return interdependent async promises in $routeProvider resolve

Consider the code:
var myApp = angular.module('myApp', []);
The routes:
myApp.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/', {
templateUrl: 'app.html',
controller:myAppController,
resolve:{
resolveData:function(Resolver){
return Resolver();
}
}
});
});
Resolve:
myApp.factory('Resolver', ['$http', function($http){
return function(){
return $http({url: '/someurl',method: "GET"}).then(function(data) {
// dependent call 1
$http({url: '/someotherurl',method: "GET" }).then(function(data) {
});
// dependent call 2
$http({url: '/someanotherurl',method: "GET" }).then(function(data) {
});
});
}
}]);
Above I have nested 2 calls inside one as they are dependent on the data returned by the parent call.
What I want to do: return the Resolver when all of them have completed and not just the parent call.
I cannot use $q.all() because 2 of the calls are dependent of the first call.
In short, myAppController must be loaded only after all the 3 calls have completed.
You should be using chaining promise and $q service to solve your problem .Just use the below sample code it should work
myApp.factory('Resolver', ['$http','$q', function ($http,$q) {
return function () {
var deferred = $q.defer();
$http({ url: '/someurl', method: "GET" }).then(function (data) {
return $http({ url: '/someurl', method: "GET" })
}).then(function (data) {
return $http({ url: '/someanotherurl', method: "GET" })
}).then(function (data) {
deferred.resolve(data);
});
return deferred.promise;
}
}]);
This works for me:
resolve : {
message: function($q, $route, Restangular) {
var msgId = $route.current.params.msgId;
var deferred = $q.defer();
Restangular.one('message', msgId).get().then(function(message) {
Restangular.one('file', message.audioFile.id).get().then(function (blob) {
message.blob = blob;
deferred.resolve(message);
});
});
return deferred.promise;
}
}

How can I pass back a promise from a service in AngularJS?

I have some code in my controller that was directly calling $http to get data.
Now I would like to move this into a service. Here is what I have so far:
My service:
angular.module('adminApp', [])
.factory('TestAccount', function ($http) {
var TestAccount = {};
TestAccount.get = function (applicationId, callback) {
$http({
method: 'GET',
url: '/api/TestAccounts/GetSelect',
params: { applicationId: applicationId }
}).success(function (result) {
callback(result);
});
};
return TestAccount;
});
Inside the controller:
TestAccount.get(3, function (data) {
$scope.testAccounts = data;
})
How can I change this so rather than passing the result of success back it
passes back a promise that I can check to see if it succeeded or failed?
Make your service to return a promise and expose it to service clients. Change your service like so:
angular.module('adminApp', [])
.factory('TestAccount', function ($http) {
var TestAccount = {};
TestAccount.get = function (applicationId) {
return $http({
method: 'GET',
url: '/api/TestAccounts/GetSelect',
params: { applicationId: applicationId }
});
};
return TestAccount;
});
so in a controller you can do:
TestAccount.get(3).then(function(result) {
$scope.testAccounts = result.data;
}, function (result) {
//error callback here...
});

Resources