I'm trying to built a service for loading json files. What am I doing wrong?
The Service
app.service("jsonService", function ($http, $q)
{
var deferred = $q.defer();
$http.get('./assets/json/home.json').then(function (data)
{
deferred.resolve(data);
});
this.getHomeItems = function ()
{
return deferred.promise;
}
})
My Controller
app.controller('homeController', function ($scope, jsonService) {
var promise = jsonService.getHomeItems();
promise.then(function (data)
{
$scope.home_items = data;
console.log($scope.home_items);
});
});
Console Error: $scope is not defined
You are missing the dependency injection.
Your service should be:
app.service("jsonService", ["$http", "$q", function ($http, $q)
{
var deferred = $q.defer();
$http.get("./assets/json/home.json").then(function (data)
{
deferred.resolve(data);
});
this.getHomeItems = function ()
{
return deferred.promise;
}
}]);
And your Controller:
app.controller("homeController", ["$scope", "jsonService", function ($scope, jsonService)
{
var promise = jsonService.getHomeItems();
promise.then(function (data)
{
$scope.home_items = data;
console.log($scope.home_items);
});
}]);
Without looking at the HTML, which you did not provide, I reckon you may not have injected the $scope into your controller constructor:
app.controller('homeController', ['$scope', function ($scope, jsonService) {
...
}]);
Theoretically, AngularJS should be able to infer the dependency from the variable name, but according to the official documentation there are circumstances where this does not work and the practice of not explicitly injecting dependencies is discouraged.
So you may want to try explicit injection (as shown above).
See the examples on the official docs here:
https://docs.angularjs.org/guide/controller
and here:
https://docs.angularjs.org/guide/di
You are have a common anti-pattern where you are unwrapping the promise returned by $http and then re-wrapping the data in a promise. This is unnecessary, just return the promise returned by $http.
this.getHomeItems = function () {
return $http.get("./assets/json/home.json");
}
Related
Actually, this question can be simplified to "Return an $http or Restangular call result in a promise". My goal is to have a promise object that is resolved after an $http call completes:
var deferredCall= $q.defer();
Then this is resolved from the call:
$http.get (url)
.then(function(result){ deferredCall.resolve('Success' + result);},
function(){ deferredCall.resolve('Failure' + error););
Then I have a promise object that will be resolved when the call is complete (either succeeding or failing), this is what I want:
deferredCall.promise.then(function (value) {
return //something to pass to controller;
});
The goal is that the controller is instantiated whether the resolve succeds or fails. My problem? Resolve can only take a promise, so: `deferredCall.promise. How do I resolve this promise with the call above withing the Resolve / through a service? Are the results of a service method a promise?
Like if I make a service whose method makes the $http call and then returns deferredCall?
This is how we resolve data in our project:
Angular $routeProvider:
$routeProvider
.when('/something/something', {
templateUrl: 'somewhere/some-details.html',
controller : SomeController,
resolve : {
someItem : function (SomeService) {
return SomeService.getSomethingAll();
}
}
})
Controller:
var SomeController = function ($scope, someItem) {};
Data Service:
.service('SomeService', function (SomeUtils, $http) {
return {
getSomethingAll : function () {
return SomeUtils.promiseHttpResult($http.get("api/something/all"));
}
}
})
Utils Service:
.service("SomeUtils", function($q) {
return {
promiseHttpResult: function (httpPromise) {
var deferred = $q.defer();
httpPromise.success(function (data) {
deferred.resolve(data);
}).error(function () {
deferred.reject(arguments);
});
return deferred.promise;
}
}
})
Nice and simple. No skills required :) (DD)
I'm trying to call the getStuff function from my controller, but I get an error in the console saying that "undefined is not a function". I'm trying to return JSON from the GET and then store it in a $scope variable.
app.factory('UserInfo', function($http) {
var user = [];
return{
getStuff: function(){
user.push($http.get('api/users'));
return user;
},
testPost: function(){
return $http.post('api/users');
}
};
});
The factory is hooked up to the controller as follows
.controller('TwitterController', function($scope, $q, $interval, UserInfo) {
and here's the $scope function I'm using to call the factory function
$scope.datapls = function() {
UserInfo.getStuff().success(function(response){
console.log(response);
$scope.loaduser.push(response);
});
}
Thanks! I appreciate the help.
You're error refers to the .success() function - it doesn't exist.
It looks like you're trying to use promises. If that's the case, then you need to return the promise itself from your service.
Something like this (not tested, but an idea). You want to use the $q in your service, not your contorller.
The examples in the $q on AngularJS docs section are great.
So by doing this, your controller doesn't have to wait around for the data. As soon as it's resolved
app.service('UserInfo', function($http, $q) {
this.getStuff = function(){
var deferred = $q.defer();
$http.get('api/users').success(function(data, status) {
deferred.resolve(data);
}).error(function(data, status) {
deferred.reject(data);
});
return deferred.promise;
}
}
);
And in your controller you can do this:
UserInfo.getStuff().then(function(dataFromService){
// dataFromService is used in here..
$scope.loaduser.push(dataFromService);
}, function(error) {
// the error will come in via here
});
According to the docs, $http in itself returns a promise, you can change your factory function in order to achieve what you trying to do:
app.factory('UserInfo', function($http) {
return{
getStuff: function(){
return $http.get('api/users'));
},
testPost: function(){
return $http.post('api/users');
}
};
});
and in the controller:
$scope.datapls = function() {
UserInfo.getStuff().then(function(response){
console.log(response);
$scope.loaduser.push(response);
});
}
I find myself doing things like this a lot in my Angular services:
getStats: function() {
var deferred = $q.defer();
$http.get('/stats').success(function(stats) {
deferred.resolve(stats);
});
return deferred.promise;
}
Note that the promise I'm returning resolves to the returned data. It's not a situation where I can use a .success callback.
Is there a simpler way to do this without using $q?
That should work, because $http.get returns promise object,
getStats: function() {
return $http.get('/stats');
}
in your code you use it like this:
someService.getStats().success(successFN);
Create an API service:
app.factory('apiService', ['$http', function($http) {
'use strict';
return {
getStats: function (deviceId) {
return $http.get('/stats');
}
}
}
Then inject the apiCall in your controller and use the getStats method.
app.controller("MyController", ['$scope', 'apiService', '$routeParams', function($scope, apiService, $routeParams) {
'use strict';
angular.extend($scope, {
info: {},
deviceId: $routeParams.deviceId,
loadInfo: function () {
apiService
.getStats($scope.deviceId)
.succes(function (res, statusCode) {
$scope.info = res;
})
.error(function () {
alert('Oh NOS!');
})
}
});
$scope.loadInfo();
}]);
In my angular module I wrote a generic http handler for all my ajax requests.'
I was expecting that I could use the service across controllers, but my problem is the promise seems to be global.
Once ControllerOne uses the mapi_loader service, when I load AnotherController (by ng-click="go('/$route_to_load_another_controller')"), AnotherController is loaded a promise that has already returned from ControllerOne even though the URL they fetch are totally different.
So I guess my question is how do I write a service I could use across controllers? Do I really need to write a separate service for each controller where their only difference in code is the URL passed for $http.jsonp?
angular.module('myAppControllers',[])
.service('mapi_loader', ['$http', function($http) {
var promise;
var myService = {
fetch: function(url) {
if ( !promise ) {
promise = $http.jsonp(url)
.then(function (response) {
return response.data.nodes;
});
}
return promise;
}
};
return myService;
}])
.controller('ControllerOne', ['$scope', 'mapi_loader', function ($scope, mapi_loader) {
mapi_loader
.fetch("http://host.com/mapi_data_for_controller_one?callback=JSON_CALLBACK")
.then(function(data) {
$scope.useme = data;
});
}])
.controller('AnotherController', ['$scope', 'mapi_loader', function ($scope, mapi_loader) {
mapi_loader
.fetch("http://host.com/mapi_data_for_another_controller?callback=JSON_CALLBACK")
.then(function(data) {
$scope.useme = data;
});
}])
;
try something like this
angular.module('myAppControllers',[])
.service('mapi_loader', function($http) {
var alreadyLoading = {};
return {
fetch: function(url) {
if ( url in alreadyLoading ) {
return alreadyLoading[url];
}
return alreadyLoading[url] = $http.jsonp(url)
.then(function (response) {
delete alreadyLoading[url];
return response.data.nodes;
});
}
};
})
.controller('ControllerOne', function ($scope, mapi_loader) {
...
})
.controller('AnotherController', function ($scope, mapi_loader) {
...
});
I'm trying to make a single $http request to get one of my JSON files and use the data across all my controllers.
I saw on egghead.io how to share data across multiple controllers, and I've also read this StackOverflow question: "Sharing a variable between controllers in angular.js".
However, the answers there don't use the $http module. When using $http, the controllers don't have the data to work on, and by the time the response is received it's already too late.
I then found the method $q.defer and this question on StackOverflow: "AngularJS share asynchronous service data between controllers"
The solution posted there works fine, BUT it has two issues:
Each controller will trigger the $http request to obtain the same data already used in another controller; and,
If I try to manipulate the data received I have a then function.
Below you can see my code:
controllers.js
'use strict';
/* Controllers */
function appInstallerListCtrl($scope, Data) {
$scope.apps = Data;
}
function appInstallerDetailCtrl($scope, $routeParams, Data) {
$scope.appId = $routeParams.appId;
$scope.apps = Data;
console.log($scope.apps); // <-- then function
console.log(Data); // <-- then function with $vv data returned but I can't access it
for (var i in $scope.apps) // <--- no way, baby!
console.log(i);
}
app.js
var app = angular.module('appInstaller', []);
app.factory('Data', function($http, $q) {
var defer = $q.defer();
$http.get('apps.json').then(function(result) {
defer.resolve(result.data.versions.version);
});
return defer.promise;
});
app.config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/app', {templateUrl: 'partials/app-list.html', controller: appInstallerListCtrl}).
when('/app/:appId', {templateUrl: 'partials/app-detail.html', controller: appInstallerDetailCtrl}).
otherwise({redirectTo: '/app'});
}]);
What I'd like to have is that when launching the app, the $http request will be performed and the response will be used throughout the app across all controllers.
Thanks
I like to store my data in the service, and return a promise to the controllers, because usually you need to deal with any errors there.
app.factory('Data', function($http, $q) {
var data = [],
lastRequestFailed = true,
promise;
return {
getApps: function() {
if(!promise || lastRequestFailed) {
// $http returns a promise, so we don't need to create one with $q
promise = $http.get('apps.json')
.then(function(res) {
lastRequestFailed = false;
data = res.data;
return data;
}, function(res) {
return $q.reject(res);
});
}
return promise;
}
}
});
.controller('appInstallerListCtrl', ['$scope','Data',
function($scope, Data) {
Data.getApps()
.then(function(data) {
$scope.data = data;
}, function(res) {
if(res.status === 500) {
// server error, alert user somehow
} else {
// probably deal with these errors differently
}
});
}]);
Any callbacks that are registered after a promise has been resolved/rejected will be resolved/rejected immediately with the same result/failure_reason. Once resolved/rejected, a promise can't change (its state). So the first controller to call getApps() will create the promise. Any other controllers that call getApps() will immediately get the promise returned instead.
Since you are using a promise, to access the data returned by promise use the callback syntax
function appInstallerDetailCtrl($scope, $routeParams, Data) {
$scope.appId = $routeParams.appId;
Data.then(function(returnedData) {
$scope.apps=returnedData;
console.log($scope.apps);
for (var i in $scope.apps)
console.log(i)
});
}
Make sure this
defer.resolve(result.data.versions.version);
resolve returns array, for the above code to work. Or else see what is there in data and ajust the controller code.
I found the way not sure weather it is a best approach to do it or not.
In HTML
<body ng-app="myApp">
<div ng-controller="ctrl">{{user.title}}</div>
<hr>
<div ng-controller="ctrl2">{{user.title}}</div>
</body>
In Javascript
var app = angular.module('myApp', []);
app.controller('ctrl', function($scope, $http, userService) {
userService.getUser().then(function(user) {
$scope.user = user;
});
});
app.controller('ctrl2', function($scope, $http, userService) {
userService.getUser().then(function(user) {
$scope.user = user;
});
});
app.factory('userService', function($http, $q) {
var promise;
var deferred = $q.defer();
return {
getUser: function() {
if(!promise){
promise = $http({
method: "GET",
url: "https://jsonplaceholder.typicode.com/posts/1"
}).success(function(res) {
data = res.data;
deferred.resolve(res);
})
.error(function(err, status) {
deferred.reject(err)
});
return deferred.promise;
}
return deferred.promise;
}
}
});
This will exactly make only 1 HTTP request.
My issue was that I didn't want to wait for resolve before loading another controller because it would show a "lag" between controllers if the network is slow. My working solution is passing a promise between controllers via ui-router's params and the data from promise can be loaded asynchronously in the second controller as such:
app.route.js - setting the available params to be passed to SearchController, which shows the search results
.state('search', {
url: '/search',
templateUrl: baseDir + 'search/templates/index.html',
controller: 'SearchController',
params: {
searchPromise: null
}
})
landing.controller.js - controller where the user adds search input and submits
let promise = SearchService.search(form);
$state.go('search', {
searchPromise: promise
});
search.service.js - a service that returns a promise from the user input
function search(params) {
return new Promise(function (resolve, reject) {
$timeout(function() {
resolve([]) // mimic a slow query but illustrates a point
}, 3000)
})
}
search.controller.js - where search controller
let promise = $state.params.searchPromise;
promise.then(r => {
console.log('search result',r);
})