In the Angular documentation for services, I came across this code:
angular.
module('myServiceModule', []).
controller('MyController', ['$scope','notify', function ($scope, notify) {
$scope.callNotify = function(msg) {
notify(msg);
};
}]).
factory('notify', ['$window', function(win) {
var msgs = [];
return function(msg) {
msgs.push(msg);
if (msgs.length == 3) {
win.alert(msgs.join("\n"));
msgs = [];
}
};
}]);
My question is, why not do it much simpler and just define the function notify inside the $scope.callNotify function?
If services are just functions defined elsewhere, aren't there much simpler ways of accomplishing the same thing?
Just think about resuing that code in another controller; you will not be able to do it.
But if you place it in a service, it can be injected and then reused everywhere.
The primary reason is the idea is that there should not be any business logic present in the controller. It should just act as the glue between your scope and the model.
Another reasons are code reusability, single responsibility principle, better testability, the list is endless.
Overall it is a good programming practice to break your app in smaller pieces which are easily testable on their own, which improves overall maintainability and testability of your app.
Related
So, over the last month I've been diving pretty hard into AngularJS and my controllers got extremely big. I decided to learn about factories and services so I could separate some of my logic...which leads me to my question.
I see a lot of examples online where people are using ajax calls to the server from their controllers. Is this a good practice? The more I study AngularJS and compare it to what I already know about software design, I see the controller's job pass data into the view. Much like a controller in MVC, where business logic shouldn't exist. Is that a safe assumption?
Also -- If I were to move my ajax calls over to a factory or service, would the functionality be the same? Something like below?
Service
app.service('orderService', ['$http', function ($http) {
var orderdata = {};
// Gets orders from WebAPI
this.getRecentOrders = function () {
$http.get('/api/orders').success(function (data) {
this.orderdata = data;
});
return this.orderdata;
};
}]);
Controller
$scope.recentOrders = orderService.getRecentOrders();
edit
I had to modify the code in the controller slightly from the answer to get it to work correctly:
orderService.getRecentOrders().then(function(result) {
$scope.recentOrders = result.data;
});
Services should return an object with methods. Also, the method you invoke should return the promise as it's an async call, so the return this.orderdata will always be undefined (or at least the first time):
app.service('orderService', ['$http', function ($http) {
return {
getRecentOrders: function () {
return $http.get('/api/orders');
}
};
}]);
No with this, you can chain to that promise in order to get the data in your controller:
orderService.getRecentOrders().then(function(data) {
$scope.recentOrders = data;
});
Now as per the question itself, basically, controllers should be "dumb", what does this mean? it should not have any heavy logic within. It should only be the layer that gets data from the services, and use them to show them in the $scope. Everything related to heavy logic (data processing and such) should be apart from the controller. That's why angular provides you with Filters, Services and Directives.
I have a sidebar that contains a feed from various social medias, along with a service in AngularJS that queries my API for the data. Below is the controller, along with the service (in that order). Why isn't it being executed on page load, and how should I rewrite my code to make it load the data when the page is rendered to the client?
angular.module('HomeCtrl', ['MediaServ']).controller('HomeController', [
'$scope',
'MediaServ',
'$rootScope',
function($scope, $rootScope, MediaServ){
if ($rootScope){
$scope = $rootScope;
}
function handleRes(response){
console.log('hello!');
}
angular.element(document).ready(function () {
$scope.SocialMedia = function(){
MediaServ.feed()
.then(handleRes, handleRes);
}
});
}]);
angular.module('MediaServ', []).service('MediaServ', [
'$http',
function($http){
this.feed = function(){
return $http.get('/api/social/feed');
}
}]);
You can only use things (be it services, factories, filters, etc) from another module if you have first injected that module into your current one. As the code above is written your modules don't know about each other, and so you can't inject MediaServ into HomeController.
To fix it, inject the MediaServ module into the HomeCtrl module like this:
angular.module('HomeCtrl', ['MediaServ'])...
I will also suggest not shortening names (minifiers should shorten things, developers should not) and not using the same name for services and apps. The last one in particular can cause a lot of confusion in a large project. Personally I prefer to name modules things like "media.services" and services "MediaService" but that is personal taste, just keep a clear naming convention so that you always know what is what.
You shouldn't need to wrap your code in angular.element(document).ready(function () { });
The controller will execute on page load automatically, provided it is reference in the page.
So, UncleDave and Erik Honns' responses both led me to realize what was wrong (on top of having a typo in handleRes). Here is my new code:
angular.module('HomeCtrl', ['MediaServ']).controller('HomeController', [
'$scope',
'$rootScope',
'MediaServ',
function($scope, $rootScope, MediaServ){
if ($rootScope){
$scope = $rootScope;
}
function handleRes(response){
if (response.data.tweets){
$scope.SocialMedia = response.data.tweets;
}
}
MediaServ.feed()
.then(handleRes, handleRes);
}]);
It is now working. Thank you everybody for your help. I won't pick a best answer, and instead will let others vote. Feel free to flag this question to be deleted, as the errors were more on my side (kind of being lazy about reading through my code, but I thought that this was one of those Angular things you just kind of have to learn).
Thanks everyone for your help!
So, this isn't the typical question of HOW to do it. I know it can be done with a service or a factory but I was wondering if someone could share what the advantages/disadvantages would be of just creating a basic service, injecting it into each controller, and then extending the service with functions from each controller. Something similar to the following example..
app.service('HelperService', function() {
return {};
});
app.controller('Controller1', function($scope, HelperService) {
$scope.somefunc = function() {
//do stuff here
};
HelperService.somefunc = $scope.somefunc;
});
app.controller('Controller2', function($scope, HelperService) {
HelperService.somefunc();
});
This works, and works well. I feel a bit stupid for asking this but it just seems like I'm missing something here as to why this isn't used or recommended?
It may work, but its a bad idea.
Controller2 HelperService.somefunc() won't exist until Controller1 has been instantiated. So theres an implicit dependency of Controller2 on Controller1
the code on HelperService isn't in one place where it can be understood together
if you are doing some sort of data manipulation in that function, it really should be operating on data encapsulated by the HelperService.
The service is a singleton and it will be instantiated once calling new on the function itself -- the function you pass in is essentially a constructor. This will create the empty object you are returning for use everywhere, but if you want to return an object in such a way it makes more sense to use .factory, but this is not a big deal.
At any rate, you can consider your code to conceptually do this:
var HelperService = function () {}
var helperService = new HelperService;
function Controller1() {
helperService.someFunc = function () {}
}
function Controller2() {
helperService.someFunc();
}
I would consider this a dangerous thing to do for a couple of reasons:
Controller1 must be instantiated before Controller2 or else somefunc won't be available to Controller2. Ideally the controllers would have no knowledge of each other.
You are coupling Controller/ViewModel (since you're using scope) with service level logic, but these should be decoupled. HelperService shouldn't know about the controllers either. Instead, you should be injecting a service that has an API that the controllers expect to use. This doesn't always have to be HelperService, it just has to look like HelperService to the controllers and its API shouldn't change.
Without knowing specifics about what you're trying to do, it's hard to advise. In general you may rethink what you want to do, but you can extend functionality of services with other services. Consider services to be in their own layer.
The code below represents a situation where the same code pattern repeats in every controller which handles data from the server. After a long research and irc talk at #angularjs I still cannot figure how to abstract that code, inline comments explain the situations:
myApp.controller("TodoCtrl", function($scope, Restangular,
CalendarService, $filter){
var all_todos = [];
$scope.todos = [];
Restangular.all("vtodo/").getList().then(function(data){
all_todos = data;
$scope.todos = $filter("calendaractive")(all_todos);
});
//I can see myself repeating this line in every
//controller dealing with data which somehow relates
//and is possibly filtered by CalendarService:
$scope.activeData = CalendarService.activeData;
//also this line, which triggers refiltering when
//CalendarService is repeating within other controllers
$scope.$watch("activeData", function(){
$scope.todos = $filter("calendaractive")(all_todos);
}, true);
});
//example. another controller, different data, same relation with calendar?
myApp.controller("DiaryCtrl", function($scope, Restangular,
CalendarService, $filter){
//this all_object and object seems repetitive,
//isn't there another way to do it? so I can keep it DRY?
var all_todos = [];
$scope.todos = [];
Restangular.all("diary/").getList().then(function(data){
all_diaries = data;
$scope.diaries = $filter("calendaractive")(all_diaries);
});
$scope.activeData = CalendarService.activeData;
$scope.$watch("activeData", function(){
$scope.todos = $filter("calendaractive")(all_diaries);
}, true);
});
DRY should be followed purposefully, not zealously. Your code is fine, the controllers are doing what they are supposed to be doing: connecting different pieces of the app. That said, you can easily combine repeated code in a factory method that returns a function reference.
For example,
myApp.factory('calendarScopeDecorator', function(CalendarService, Restangular, $filter) {
return function($scope, section) {
$scope.todos = [];
$scope.activeData = CalendarService.activeData;
Restangular.all(section+"/").getList().then(function(data){
$scope.all_data = data;
$scope.filtered_data = $filter("calendaractive")(data);
});
$scope.$watch("activeData", function(){
$scope.todos = $filter("calendaractive")($scope.all_data);
}, true);
}
});
And then incorporate this into your controller:
myApp.controller("DiaryCtrl", function($scope, calendarScopeDecorator){
calendarScopeDecorator($scope, 'diary');
});
I wouldn't do this kind of thing with a watcher and local reference like in this controller. Instead, I would use $on() / $emit() to establish a pub-sub pattern from the service out to the controllers that care about its updates. This is an under-used pattern IMO that provides a more "DRY" mechanism. It's also extremely efficient - often more so than a watcher, because it doesn't need to run a digest to know something has changed. In network-based services you almost always know this for certain, and you don't need to go from knowing it for certain to implying it in other locations. This would let you avoid the cost of Angular's deep inspection of objects:
$rootScope.$on('calendarDiariesUpdated', function() {
// Update your $scope.todos here.
}, true);
In your service:
// When you have a situation where you know the data has been updated:
$rootScope.$emit('calendarDiariesUpdated');
Note that emit/on are more efficient than using broadcast, which will go through all nested scopes. You can also pass data from the service to listening controllers this way.
This is a really important technique that does a few things:
You no longer need to take a local reference to activeData, since you aren't actually using it (it's DRY).
This is more efficient in most/many cases than a watcher. Angular doesn't need to work out that you need to be told of an update - you know you do. This is also kind of a DRY principle - why use a framework tool to do something you don't actually need? It's an extra step to put the data somewhere and then wait for Angular to digest it and say "whoah, you need to know about this."
You may even be able to reduce your injections. There's no need to take CalendarService because that service can pass a reference to the array right in its notification. That's nice because you don't need to refactor this later if you change the storage model within the service (one of the things DRY advocates also advocate is abstracting these things).
You do need to take $rootScope so you can register the watcher, but there's nothing in pub-sub concepts that violate DRY. It's very common and accepted (and most important: it performs very well). This isn't the same thing as a raw global variable, which is what scares people off from using $rootScope in the first place (and often rightly so).
If you want to be "Super DRY" you can re-factor the call to $filter into a single method that does the filtering, and call it both from your REST promise resolution and from the calendar-update notification. That actually adds a few lines of code... but doesn't REPEAT any. :) That's probably a good idea in principle since that particular line is something you're likely to maintain (it takes that static "calendaractive" parameter...)
It looks like the code in the 2 controllers is identical except for the path to which the API call is made: "vtodo/" or "diary/".
One way to achieve something closer to DRY-ness is to pass the API path as an option to the controller as an attribute. So, assuming we call the controller ApiController, this can be used as
<div ng-controller="ApiController" api-controller-path="vtodo/">
<!-- Todo template -->
</div>
and
<div ng-controller="ApiController" api-controller-path="diary/">
<!-- Diary template -->
</div>
Which is then accessible in the controller by the injected $attrs parameter:
myApp.controller("ApiController", function($scope, $attrs, Restangular, CalendarService, $filter) {
// "vtodo/" or "diary/"
var apiPath = $attrs.apiControllerPath;
As a caution I would beware of over-architecting, not everything needs to be factored out, and there is an argument that you are just following a design pattern rather than copy+pasting code. However, I have used the above method of passing options to a controller for a similar situation myself.
If you are worried about making multiple calls to the same resource CalendarService, I'd recommend finding a way to cache the result, such as defining a variable in that service or using Angular's $cacheFactory.
Otherwise, I don't see anything wrong with your patterns.
Is there an established convention regarding declaration of controllers? (Or any form of module-level configuration).
I have observed two different approaches in use:
var shoppingCartModule = angular.module('ShoppingCart',[])
shoppingCartModule.controller('CheckoutCtrl', function($scope) { ... });
vs
angular.module('ShoppingCart').controller('CheckoutCtrl', function($scope) { ... });
Are there any benefits between the two approaches? Also, is there a preferred (or emerging) convention?
I'm specifically interested in benefits for non-trivial apps with many modules, where declarations of controllers and modules may span many files.
Personally I do the following (reasons after):
angular.module('myApp', []);
angular.module('myApp').controller('myController', ['$dependency', 'anotherDependency',
function($dependency, anotherDependency) {
...
}
]);
Reasons:
I try and avoid the global scope
Redundantly declaring dependencies with string equivalents allows you to safely minify your code
It's consistent, clean and the whole story is there. Eg. with app.something you don't know what app is, with `angular.module('myApp').something' it's pretty obvious what that is.
Edit: Just remembered a cool video I saw on this very topic a while ago - http://www.egghead.io/video/tTihyXaz4Bo. If you haven't checked out John's site, I highly recommend it. I was so impressed with his videos I donated, and you should too!
Personally, I find it be a little bit cleaner this way:
angular.
module('myApp').
controller('myController', ['$dependency', 'anotherDependency', myAppController]);
function myAppController($dependency, anotherDependency) {
...
}
Or, even more better:
var Controllers = {};
Controllers .someController = function myAppController($dependency, anotherDependency) {
...
}
angular.
module('myApp').
controller('myController', ['$dependency', 'anotherDependency', Controllers .someController]);