I'm using AngularJS to build my web application, I've been always using controllers to make HTTP request, which makes things easier and clear for me.
But for a better code structure, and better execution for my application, I wanted to use services instead of controllers to use the web service.
I tried to make :
var app = angular.module('ofcservices', []);
app.factory('news', ['$http', function ($http) {
var news={};
news.getnews= function () {
return $http.get('http://int.footballclub.orange.com/ofc/news?offset=0&limit=5');
};
return news;
}]);
and the code of the controller :
.controller('news', function($scope, ofcservices) {
$scope.news = ofcservices.getnews();
})
Everything seems to be right ?
ofcservices.getnews() is a promise You need manage with the function sucess and error
ofcservices.getnews().
success(function(data) {
$scope.news=data
}).
error(function(data, status, headers, config) {
//show a error
});
As weel change app.factory('news' to app.factory('newsFactory' and call it in controller('news', function($scope, newsFactory) {
You can get more data about promise in the angular documentation
The concept is more or less right, but you should use the callback functions to handle the $http response correctly.
But your controller and service have the same name news, which is BAD :-) and you need to inject the newsService and not the module name.
.controller('newsController', function($scope, newsService) {
newsService.getnews().then(
function(newsData) {
$scope.newsData = newsData
},
function optionalErrorhandler() {});
})
angular
.module('MyApp', [])
.controller('MyController', MyController)
.factory('MyService', MyService);
MyController.$inject = ['$scope','MyService'];
MyService.$inject = ['$http'];
function MyService($http){
var service = {
var myServiceFunction : function(){
$http({
// your http request on success return the data.
}).success(function(data)){
return data;
});
}
};
return service;
}
function MyController($scope, MyService){
MyService.myServiceFunction(); //Call service from the controller.
}
Related
Learning Angular. Working with 1.6.6 right now.
In my initial stab, I put all my $http requests into my controller, so I'm trying to pull that out of the controller and into a factory for more reusable code. I think that I am missing something related to Promise or injection, but the data from my $http call is not being bound to $scope correctly.
I've been following along this guide.
I have a template that's begin loaded from the controller (the template is loading fine):
<form ng-init="getAllRegions()" ng-submit="getRandomTown()">
<h3>Generate Random Town</h3>
<div class="form-group-row">
<label for="selectRegion">Select Region:</label>
<select name="nameEntity"
id="selectRegion"
ng-model="guidEntity"
ng-options="item.guidEntity as item.nameEntity for item in regions">
<option value="" ng-if="!guidEntity"></option>
</select>
</div>
</form>
The ng-init function is being called, and passes through the controller and the service. In chrome inspector I can see that data is pulling through correctly, but I'm new to working with Promises, so I'm losing something along the way. This was all working as expected when the $http call was right in the controller and the data was being bound to $scope in the controller.
The controller:
angular.
module('randomTown.controller', []).
component('randomTownGenerator', {
templateUrl: 'js/components/randomTownGenerator/randomTownGenerator.tpl.html'
}).
controller('RandomTownCtrl',
function($scope, randomTownFactory) {
$scope.data = {};
$scope.getAllRegions = function () {
$scope.regions = randomTownFactory.getAllDBRegions();
}
}
);
The service:
angular.
module('randomTown.service', []).
factory('randomTownFactory', function ($q, $http) {
var service = {};
service.getAllDBRegions = function() {
var deferred = $q.defer();
$http({
method: 'GET',
url: '/all-regions'
}).then(function success(data) {
deferred.resolve(data);
}), function error(response) {
deferred.reject('There was an error');
}
return deferred.promise;
}
return service;
});
There is no need to manufacture a promise with $q.defer. Simply return the $http promise:
app.factory('randomTownFactory', function ($q, $http) {
var service = {};
service.getAllDBRegions = function() {
̶v̶a̶r̶ ̶d̶e̶f̶e̶r̶r̶e̶d̶ ̶=̶ ̶$̶q̶.̶d̶e̶f̶e̶r̶(̶)̶;̶
̲r̲e̲t̲u̲r̲n̲ $http({
method: 'GET',
url: '/all-regions'
}).then(function success( ̶d̶a̶t̶a̶ response) {
̶d̶e̶f̶e̶r̶r̶e̶d̶.̶r̶e̶s̶o̶l̶v̶e̶(̶d̶a̶t̶a̶)̶;̶
return response.data;
}),̶ ̶f̶u̶n̶c̶t̶i̶o̶n̶ ̶e̶r̶r̶o̶r̶(̶r̶e̶s̶p̶o̶n̶s̶e̶)̶ ̶{̶
̶d̶e̶f̶e̶r̶r̶e̶d̶.̶r̶e̶j̶e̶c̶t̶(̶'̶T̶h̶e̶r̶e̶ ̶w̶a̶s̶ ̶a̶n̶ ̶e̶r̶r̶o̶r̶'̶)̶;̶
̶}̶
̶r̶e̶t̶u̶r̶n̶ ̶d̶e̶f̶e̶r̶r̶e̶d̶.̶p̶r̶o̶m̶i̶s̶e̶;̶,
}
return service;
});
Also notice that the success handler does not reveal the data. It reveals a response object of which data is one of the properties.
The controller:
app.controller('RandomTownCtrl',
function($scope, randomTownFactory) {
$scope.getAllRegions = function () {
var promise = randomTownFactory.getAllDBRegions();
promise.then(function(data) {
$scope.regions = data;
}).catch(function(errorResponse) {
console.log("ERROR", errorResponse.status)
});
}
}
);
For more information, see
AngularJS $http service API Reference
You're Missing the Point of Promises
Try this:
$scope.getAllRegions = function () {
randomTownFactory.getAllDBRegions()
.then((regions) => {$scope.regions = regions});
}
Why: because etAllDBRegions returns a promise, so you have to use then property to get the result
All server data accesses in my page are performed by my RequestFactory provider.
(The RequestFactory uses $http to perform the actual server calls.)
My controller scope has a reference to the list of data returned from the RequestFactory.
My question is that since the RequestFactory calls are asynchronous, and the RequestFactory does not (and should not) have access to the controller scope, what is the proper way for the RequestFactory to hand off the data to the controller?
var requestFactory = {};
angular.module("myApp").factory("RequestFactory", function($http) {
requestFactory.fetch = function() {
$http.get(url).
success(function(data) {
controller.setData(data)
}).
error(function(data, status) {
console.info("fetch Failed. Error code: " + status + " Url:" + url);
}).finally(function() {
controller.setSubmitting(false);
});
};
return requestFactory;
});
You should return the promise from you factory. See below snippet.
.factory("RequestFactory", function($http) {
return {
fetch : function() {
return $http.get(url).then(function(result) {
return result.data;
});
}
}
});
In your controller you should use like
.controller('MainCtrl', function($scope, RequestFactory) {
RequestFactory.fetch().then(function(data)
$scope.foo = data;
});
});
You can do the following :
Service
(function(){
function Service($http){
function get(){
//We return a promise
return $http.get('path_to_url');
}
var factory = {
get: get
};
return factory;
}
angular
.module('app')
.factory('Request', Service);
})();
Controller
(function(){
function Controller($scope, $q, Request) {
var defer = $q.defer();
//Call our get method from the Request Factory
Request.get().then(function(response){
//When we get the response, resolve the data
defer.resolve(response.data);
});
//When the data is set, we can resolve the promise
defer.promise.then(function(data){
console.log(data);
});
}
angular
.module('app', [])
.controller('ctrl', Controller);
})();
As you know, $http service return promise, so after that, you can easily combining them.
I'm working on a mobile app using AngularJS as a framework, currently I have a structure similar to this:
app.config(['$routeProvider', function($routeProvider) {
$routeProvider
.when('/', {
templateUrl : 'pages/home.html',
controller : 'homeCtrl'
})
.when('/one', {
templateUrl : 'pages/one.html',
controller : 'oneCtrl'
})
.when('/two', {
templateUrl : 'pages/two.html',
controller : 'twoCtrl'
});
}]);
app.controller('homeCtrl', ['$scope', function($scope) {
}]);
app.controller('oneCtrl', ['$scope', function($scope) {
}]);
app.controller('twoCtrl', ['$scope', function($scope) {
}]);
And then I'm displaying the content with an ng-view:
<div class="ng-view></div>
Things are working well but I need to load data from a JSON file to populate all the content of the app. What I want is to make and an AJAX call only once and then pass the data through all my different controllers. In my first attempt, I thought to create a Service with an $http.get() inside of it and include that in every controller, but it does not work because it makes a different ajax request everytime I inject and use the service. Since I'm new using angular I'm wondering what is the best way or the more "angular way" to achieve this without messing it up.
Edit: I'm adding the code of the service, which is just a simple $http.get request:
app.service('Data', ['$http', function($http) {
this.get = function() {
$http.get('data.json')
.success(function(result) {
return result;
})
}
});
Initialize the promise once, and return a reference to it:
No need to initialize another promise. $http returns one.
Just tack a .then() call on your promise to modify the result
angular.module('app', [])
.service('service', function($http){
this.promise = null;
function makeRequest() {
return $http.get('http://jsonplaceholder.typicode.com/posts/1')
.then(function(resp){
return resp.data;
});
}
this.getPromise = function(update){
if (update || !this.promise) {
this.promise = makeRequest();
}
return this.promise;
}
})
Codepen example
Edit: you may consider using $http cache instead. It can achieve the same results. From the docs:
If multiple identical requests are made using the same cache, which is not yet populated, one request will be made to the server and remaining requests will return the same response.
Try this to get JSON Data from a GET Link:
(function (app) {
'use strict';
app.factory('myService', MyService);
MyService.$inject = ['$q', '$http'];
function MyService($q, $http) {
var data;
var service = {
getData: getData
};
return service;
//////////////////////////////////////
function getData(refresh) {
if (refresh || !data) {
return $http.get('your_source').then(function(data){
this.data = data;
return data;
})
}
else {
var deferrer = $q.defer();
deferrer.resolve(data);
return deferrer.promise;
}
}
}
}(angular.module('app')));
Now you can add this dependency in your controller file and use:
myService.getData().then(function(data){
//use data here
}, function(err){
//Handle error here
});
I have a simple service which grab data from HTTP end point send it back to controller.
I also implemnted caching in the service however, i get this error TypeError: undefined is not a function on this line of code in my controller
myappApi.getItems().then(function(data)
I tried to figure out why i couldn't.
here is the controller code:
.controller('ItemsCtrl',['$scope','myappApi',function($scope, myappApi){
myappApi.getItems().then(function(data){
$scope.items = data;
});
}])
As am using Ioniframework here how i injected my services in the app.js:
angular.module('myApp', ['ionic', 'myApp.controllers', 'myApp.services', 'angular-data.DSCacheFactory'])
and here is the code of my service:
(function() {
'use strict';
angular.module('myApp.services',[]).factory('myappApi', ['$http', '$q', '$ionicLoading', 'DSCacheFactory', myappApi]);
function myappApi($http, $q, $ionicLoading, DSCacheFactory) {
self.itemsCache = DSCacheFactory.get("itemsCache");
//to re-use expired cached data if no internet connection
self.itemsCache.setOptions({
onExpire: function (key, value) {
getItems()
.then(function () {
console.log("items items Cache was automatically refreshed.", new Date());
}, function () {
console.log("Error getting data. Putting expired item back in the cache.", new Date());
self.itemsCache.put(key, value);
});
}
});
function getItems() {
var deferred = $q.defer(),
cacheKey = "items",
itemsData = self.itemsCache.get(cacheKey);
if (itemsData) {
console.log("Found data inside cache", itemsData);
deferred.resolve(itemsData);
} else {
$http.get("services/data.json")
.success(function(data) {
console.log("Received data via HTTP");
self.itemsCache.put(cacheKey, data);
deferred.resolve(data);
})
.error(function() {
console.log("Error while making HTTP call.");
deferred.reject();
});
}
return deferred.promise;
}
return {
getItems: getItems
};
};
})();
Thank you for your time.
Take a look in the angular-cache file CHANGELOG.md :
"- Angular module renamed to angular-cache
- DSCacheFactory renamed to CacheFactory"
You will have to change:
app.js:
instead of 'angular-data.DSCacheFactory' use 'angular-cache'
service.js
instead of 'DSCacheFactory' use 'CacheFactory'
It looks like you've declared the myappApi factory before the myappApi function is actually defined. Try something like:
angular.module('myApp.services',[]).factory('myappApi', ['$http', '$q', '$ionicLoading', 'DSCacheFactory',
function($http, $q, $ionicLoading, DSCacheFactory) {
// myappApi code
}]);
I've ran into problem with ng-controller and 'resolve' functionality:
I have a controller that requires some dependency to be resolved before running, it works fine when I define it via ng-route:
Controller code looks like this:
angular.module('myApp')
.controller('MyController', ['$scope', 'data', function ($scope, data) {
$scope.data = data;
}
]
);
Routing:
...
.when('/someUrl', {
templateUrl : 'some.html',
controller : 'MyController',
resolve : {
data: ['Service', function (Service) {
return Service.getData();
}]
}
})
...
when I go to /someUrl, everything works.
But I need to use this controller in other way(I need both ways in different places):
<div ng-controller="MyController">*some html here*</div>
And, of course, it fails, because 'data' dependency wasn't resolved. Is there any way to inject dependency into controller when I use 'ng-controller' or I should give up and load data inside controller?
In the below, for the route resolve, we're resolving the promise and wrapping the return data in an object with a property. We then duplicate this structure in the wrapper service ('dataService') that we use for the ng-controller form.
The wrapper service also resolves the promise but does so internally, and updates a property on the object we've already returned to be consumed by the controller.
In the controller, you could probably put a watcher on this property if you wanted to delay some additional behaviours until after everything was resolved and the data was available.
Alternatively, I've demonstrated using a controller that 'wraps' another controller; once the promise from Service is resolved, it then passes its own $scope on to the wrapped controller as well as the now-resolved data from Service.
Note that I've used $timeout to provide a 1000ms delay on the promise return, to try and make it a little more clear what's happening and when.
angular.module('myApp', ['ngRoute'])
.config(function($routeProvider) {
$routeProvider
.when('/', {
template: '<h1>{{title}}</h1><p>{{blurb}}</p><div ng-controller="ResolveController">Using ng-controller: <strong>{{data.data}}</strong></div>',
controller: 'HomeController'
})
.when('/byResolve', {
template: '<h1>{{title}}</h1><p>{{blurb}}</p><p>Resolved: <strong>{{data.data}}</strong></p>',
controller: "ResolveController",
resolve: {
dataService: ['Service',
function(Service) {
// Here getData() returns a promise, so we can use .then.
// I'm wrapping the result in an object with property 'data', so we're returning an object
// which can be referenced, rather than a string which would only be by value.
// This mirrors what we return from dataService (which wraps Service), making it interchangeable.
return Service.getData().then(function(result) {
return {
data: result
};
});
}
]
}
})
.when('/byWrapperController', {
template: '<h1>Wrapped: {{title}}</h1><p>{{blurb}}</p><div ng-controller="WrapperController">Resolving and passing to a wrapper controller: <strong>{{data.data ? data.data : "Loading..."}}</strong></div>',
controller: 'WrapperController'
});
})
.controller('HomeController', function($scope) {
$scope.title = "ng-controller";
$scope.blurb = "Click 'By Resolve' above to trigger the next route and resolve.";
})
.controller('ResolveController', ['$scope', 'dataService',
function($scope, dataService) {
$scope.title = "Router and resolve";
$scope.blurb = "Click 'By ng-controller' above to trigger the original route and test ng-controller and the wrapper service, 'dataService'.";
$scope.data = dataService;
}
])
.controller('WrapperController', ['$scope', '$controller', 'Service',
function($scope, $controller, Service) {
$scope.title = "Resolving..."; //this controller could of course not show anything until after the resolve, but demo purposes...
Service.getData().then(function(result) {
$controller('ResolveController', {
$scope: $scope, //passing the same scope on through
dataService: {
data: result
}
});
});
}
])
.service('Service', ['$timeout',
function($timeout) {
return {
getData: function() {
//return a test promise
return $timeout(function() {
return "Data from Service!";
}, 1000);
}
};
}
])
// our wrapper service, that will resolve the promise internally and update a property on an object we can return (by reference)
.service('dataService', function(Service) {
// creating a return object with a data property, matching the structure we return from the router resolve
var _result = {
data: null
};
Service.getData().then(function(result) {
_result.data = result;
return result;
});
return _result;
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.27/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.27/angular-route.min.js"></script>
<div ng-app="myApp">
By ng-controller |
By Resolve |
By Wrapper Controller
<div ng-view />
</div>
Create a new module inside which you have the service to inject like seen below.
var module = angular.module('myservice', []);
module.service('userService', function(Service){
return Service.getData();
});
Inject newly created service module inside your app module
angular.module('myApp')
.controller('MyController', ['$scope', 'myservice', function ($scope, myservice) {
$scope.data = data;
// now you can use new dependent service anywhere here.
}
]
);
You can use the mechanism of the prototype.
.when('/someUrl', {
template : '<div ng-controller="MyController" ng-template="some.html"></div>',
controller: function (data) {
var pr = this;
pr.data = data;
},
controllerAs: 'pr',
resolve : {
data: ['Service', function (Service) {
return Service.getData();
}]
}
})
angular.module('myApp')
.controller('MyController', ['$scope', function ($scope) {
$scope.data = $scope.pr.data; //magic
}
]
);
Now wherever you want to use
'<div ng-controller="MyController"></div>'
you need to ensure that there pr.data in the Scope of the calling controller. As an example uib-modal
var modalInstance = $modal.open({
animation: true,
templateUrl: 'modal.html',
resolve: {
data: ['Service', function (Service) {
return Service.getData();
}]
},
controller: function ($scope, $modalInstance, data) {
var pr = this;
pr.data = data;
pr.ok = function () {
$modalInstance.close();
};
},
controllerAs:'pr',
size:'sm'
});
modal.html
<script type="text/ng-template" id="modal.html">
<div class="modal-body">
<div ng-include="some.html" ng-controller="MyController"></div>
</div>
<div class="modal-footer">
<button class="btn btn-primary pull-right" type="button" ng-click="pr.ok()">{{ 'ok' | capitalize:'first'}}</button>
</div>
</script>
And now you can use $scope.data = $scope.pr.data; in MyController
pr.data is my style. You can rewrite the code without PR.
the basic principle of working with ng-controller described in this video https://egghead.io/lessons/angularjs-the-dot
Presuming that Service.getData() returns a promise, MyController can inject that Service as well. The issue is that you want to delay running the controller until the promise resolves. While the router does this for you, using the controller directly means that you have to build that logic.
angular.module('myApp')
.controller('MyController', ['$scope', 'Service', function ($scope, Service) {
$scope.data = {}; // default values for data
Service.getData().then(function(data){
// data is now resolved... do stuff with it
$scope.data = data;
});
}]
);
Now this works great when using the controller directly, but in your routing example, where you want to delay rendering a page until data is resolved, you are going to end up making two calls to Service.getData(). There are a few ways to work around this issue, like having Service.getData() return the same promise for all caller, or something like this might work to avoid the second call entirely:
angular.module('myApp')
.controller('MyController', ['$scope', '$q', 'Service', function ($scope, $q, Service) {
var dataPromise,
// data might be provided from router as an optional, forth param
maybeData = arguments[3]; // have not tried this before
$scope.data = {}; //default values
// if maybeData is available, convert it to a promise, if not,
// get a promise for fetching the data
dataPromise = !!maybeData?$q.when(maybeData):Service.getData();
dataPromise.then(function(data){
// data is now resolved... do stuff with it
$scope.data = data;
});
}]
);
I was trying to solve the problem using ng-init but came across the following warnings on angularjs.org
The only appropriate use of ngInit is for aliasing special properties
of ngRepeat, as seen in the demo below. Besides this case, you should
use controllers rather than ngInit to initialize values on a scope.
So I started searching for something like ng-resolve and came across the following thread:
https://github.com/angular/angular.js/issues/2092
The above link consists of a demo fiddle that have ng-resolve like functionality. I think ng-resolve can become a feature in the future versions of angular 1.x. For now we can work around with the directive mentioned in the above link.
'data' from route resolve will not be available for injection to a controller activated other than route provider. it will be available only to the view configured in the route provider.
if you want the data to the controller activated directly other than routeprovider activation, you need to put a hack for it.
see if this link helps for it:
http://www.johnpapa.net/route-resolve-and-controller-activate-in-angularjs/
Getting data in "resolve" attribute is the functionality of route (routeProvider) , not the functionality of controller.
Key( is your case : 'data') in resolve attribute is injected as service.
That's why we are able fetch data from that service.
But to use same controller in different place , you have fetch data in controller.
Try this
Service:
(function() {
var myService = function($http) {
var getData = function() {
//return your result
};
return {
getData:getData
};
};
var myApp = angular.module("myApp");
myApp.factory("myService", myService);
}());
Controller:
(function () {
var myApp = angular.module("myApp");
myApp.controller('MyController', [
'$scope', 'myService', function($scope, myService) {
$scope.data = myService.getData();
}
]);
//Routing
.when('/someUrl', {
templateUrl : 'some.html',
controller : 'MyController',
resolve : {
data: $scope.data,
}
})
}());