I am early on in my angularjs development.
I have created a single page app. A couple of the areas call controllers that have code like below embedded in them.
snippet from the route::
.when('/searchDrug/:searchStr', {
templateUrl: SNIPsArray['searchresultsSNIP'],
controller: 'searchDrugCtrl'
})
snippet from the controller::
function ($scope, $http, $routeParams, $location) {
$http.get(url).success(function(data) {
if ( data.drugmaster.length == 1 ){
$location.path('/fetchDrug/'+data.drugmaster[0].drug_Id);
return;
}
$scope.druglist = data.drugmaster;
});
}
Question:
looking on the angularjs site and reading some articles on best practices, it is suggested to move $http from the controller to a service. I am looking for some help on what the advantage is of adding another level and another file for this call?
Maintainability
As your application grows, you will find that there will be more and more need of encapsulating your business logic somewhere. In angular you do this using services.
Encapsulation
Not only do services allow you to reuse code, but also to introduce good principles like abstraction and encapsulation.
Richer domain
What's also important to note is that perhaps you want to introduce some additional modelling and functionality above what you get back from the service. With a service, you can do stuff like do the http call, then wrap the results in a function with some methods on it. This results in a much richer domain model and readable code.
For example, here the search function returns a promise with some domain logic on it to massage the data coming back from the service:
function ($scope, $routeParams, $search) {
$search($scope.drug)
.then(function(result){
$scope.drugList = result.getDrugList({include: ['title', 'description']});
});
}
Testing
It's obviously much better to write tests for a controller that depends on a service and tests for a service on it's own.
Because of code reuse, maintainability and testability. Testing resources is a separate process than testing whole controller.
Related
I inherited some code that is using a global object to to store angular services. These services get attached to the global object in the run function of the angular module. My question is, can this lead to trouble down the road? What sort of trouble does this cause for testing? Passing around services like this seems a lot easier than injecting all of the services in each controller so I see why this is done. What are other arguments for not doing this? Here is some code to illustrate what I am talking about:
// vars
var globalObject =
{
ng: {},
};
// Setup module
var myModule = angular.module("myModule", []);
myModule.config(doStuff);
myModule.run(setUpGlobals);
// Setup app globals
function setUpGlobals(ngRootScope, ngHttp, ngTimeout)
{
globalObject.rootScope = ngRootScope;
// angular services
globalObject.ng.http = ngHttp;
globalObject.ng.Timeout = ngTimeout;
}
setUpGlobals.$inject = ['$rootScope', '$http', '$timeout'];
Modules and DI were introduced in Angular exactly to avoid relying on globals and improve modularity.
This is naive approach that only works if there is a single module and a single application instance. It will fail if there are several modules that can be used separately (including tests). It will produce awful bugs if there is more than one application instance on the page (for example, if Angular is used for non-SPA applications).
A monolith module hurts testability. Even if it is used like that, some options will be unavailable, e.g. injecting spied or stubbed services in $controller(...) - because a controller relies on globals.
setUpGlobals results in eager service instantiation. This may not be a problem for core services but will be a problem for services that don't need to be instantiated right now.
Less important concern is code size in minified application. ng.$rootScope can be minified to a.$rootScope but not any further. Annotated function should mention '$rootScope' string once, but $rootScope variable name can be minified to a. There will be improvements if a service is used more than once inside a function.
There's a lot of reasons why global variables are bad. Some of them won't be applicable in this case, other ones will.
This makes testing nightmaraish. Dependency Injection is great, because it means you can do some atomic tests, by mocking out the services you don't need. In a simple, non-convulted example, imagine a service that makes API calls via http, if you DI it in, your test can mock out the http and just fake a return, letting you test only the bits of code you want, and saving you from having a test that relies on the API, or worse a test suit that uses up your API calls. With the provider in the global scope, that's much more difficult to achieve.
Just one reason, i'm sure there are many others.
Suppose this is my angular controller
app.controller("MyCtrl", function($scope, $modal, $state,) {
});
I am thinking of having one global variable holding most commonly used dependencies like
var all = ['$scope', '$modal', '$state']
and then use all at every place along with some other dependencies if needed
Is there any performace issue having put all dependencies everywhere
Having to inject more code would have a performance hit, but not a major one. I don't recommend defining your dependencies globally like that because dependencies should be very visible. You should know exactly what you're doing with them without having to open another file and check.
If you need to reuse a set of dependencies everywhere, that suggests more that there's probably something wrong with the code. How come different regions of the code base all talk to the same stuff? That suggests duplication of concerns. I don't extend that assertion to just having to inject $scope or $http all the time.
In short, I don't think it's a good idea to manage dependencies like that.
While I don't have deep knowledge of the Angular internals, nor have I bothered to actually measure the performance of what you are asking, I would venture my educated guess as .... no. You're not going to see a performance impact here. The only impact would be on controller instantiation, which only happens once per view. And even then, we're just talking about new-ing up a few objects ... the perf impact ought to be very negligible, and not something I would worry about.
You can't inject using a variable you define within another controller or service. One thing you could do is create a factory and put your dependencies in the $rootScope.
app.factory('root',function($rootScope, $modal, $state){
$rootScope.modal = $modal;
$rootScope.state = $state;
});
You just put $rootScope in all your controllers and you have access to whatever you like. You would only need to inject 'root' in your main controller (if you have one). It seems like fishy architecture to need this shortcut, but that is how I would do it. No perf hit really - non-primitives are reference types.
Within Angular, is it considered a good practice getting the dependencies directly from an $injector instance, rather than as parameters?
I have the problem that my controllers started having a lot of dependencies, so rather than:
myApp.controller(['$scope', 'Dep1', 'Dep2', 'Dep3', function($scope, Dep1, Dep2, Dep3) {
...
}]);
I would do:
myApp.controller(['$scope', '$injector', function($scope, $injector) {
var Dep1 = $injector.get('Dep1');
var Dep2 = $injector.get('Dep2');
var Dep3 = $injector.get('Dep3');
}]);
I find it the same functionality-wise, but with much less clutter on the parameters. I suppose that this will make my components slightly less easy to test though, right?
What do you think?
Based on my opinion and after reading the following posts:
Post 1
Post 2
Angular's documentation on DI (Dependency Injection)
Your second approach in order to minimize the long dependency list is considered as Service Locator Anti Pattern.
See - Service locator AntiPattern
Using the Service Locator Anti Pattern is bad because it will make your life as a maintenance developer worse because you will need to use considerable amounts of brain power to grasp the implications of every change you make. Using the $injector obfuscates the actual dependencies of the resource (in this case controller) and kills maintainability.
In addition, according to angular's documentation the the preferable way is:
Using the inline array annotation (preferred)
If your controller is ending up using so many dependencies maybe you are doing something wrong, Maybe you broke the Single responsibility principle. consider:
Delegate more of the logic to service(s) that are injected in
Separate out into different controllers, so each only has (just about) 1 responsibility
It depends how you write the code and how is easy to you and make you flexible to write code. I always write the controllers in this way hope it will help :}
var appCtrl = function($scope, others){...}
app.controller('appCtrl',['$scope', 'others', appCtrl]);
I have an impression that all data-related requests has to be delegated to services/factories. But reading examples of $http implementation met not once that they are put right inside of a controller, e.g.:
var app = angular.module("MyApp", []);
app.controller("PostsCtrl", function($scope, $http) {
$http.get('data/posts.json').
success(function(data, status, headers, config) {
$scope.posts = data;
}).
error(function(data, status, headers, config) {
// log error
});
});
is that considered as normal practice/pattern or it is just for the sake of an example?
So best practices are dependent on the project-specific context, mostly overall code size of your application but also size of team, length of project/engagement, etc. For a small utility with just a few hundred lines of code, creating a service to encapsulate a single use of $http is over engineering. It makes your project harder, not easier, to read and maintain. However, for a full-size application with many modules, dozens of files, thousands of lines of code, where the same service logic might need to be reused across several controllers, then yes, it makes sense to move your $http code to a service where it can be independently encapsulated, shared, and tested. So no, for "normal" applications (medium or large in size), using $http in a controller is not considered a best practice pattern. However, for an instructive example/demo code snippet, or a very trivial project, it's fine.
What reasons are there to have multiple controllers in an AngularJS application? I've built a few angular apps now and I've never encountered a problem where I thought multiple controllers would make things easier for me.
I'm still a bit of a n00b, have never written a unit test and my code isn't as manageable as it could be so I'm sure it's just ignorance. And I've heard that other people have multiple controllers.
Put another way: how does one know that they should create a new controller?
From what I've seen of it an Angular application should have separate controllers for separate scopes. For instance, almost all applications have user data. You'll want to have this data attached to a user model, inside a user controller:
function UserCtrl ($scope) {
$scope.user = {
name: "bmorrow",
lastLogin: "4/16/2013"
};
}
And the template (our view) will be inside a specific portion of the applications structure. The right side of a navigation bar for example, or on a user details page. We establish where this portion is by assigning it a controller using ng-controller. This creates the scope of said controller and binds the corresponding models to it. The model (our data) is connected to the view (the HTML) via the controller.
Suppose the application has a page for the user's written articles. We can create another controller restricted only to the section of HTML that specifically holds the article content.
function ArticleCtrl ($scope) {
$scope.article = {
title: "Hello World",
body: "Lorem ipsum...."
};
}
In the trivial example above, combining both of the controllers won't do any harm. But once your application begins to grow, logically organizing your controllers/views according to the data it represents will make your code cleaner and easier to understand. Less unneeded complexity will make everything much easier on you. And using one controller for everything is an unneeded complexity.
You can see this illustrated in Basarat's answer as well. You don't necessarily need to use one controller per route, but doing so helps to logically structure the application.
So, to answer your question, you should usually have one controller per category of data. Users, Articles, Fruits, Vegetables, Transactions, and so on.
Read about Angular Controllers and the Model-View-Controller pattern for more information if you haven't already. I hope this helps.
You definitely start to need more controllers when you start to split you application into multiple views.
e.g. When you start to use routes (also called deep linking) you have a template url as well as a controller to go with that template (check out http://docs.angularjs.org/tutorial/step_07) e.g.
angular.module('phonecat', []).
config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/phones', {templateUrl: 'partials/phone-list.html', controller: PhoneListCtrl}).
when('/phones/:phoneId', {templateUrl: 'partials/phone-detail.html', controller: PhoneDetailCtrl}).
otherwise({redirectTo: '/phones'});
}]);
I like to think as controllers as "widgets." One one page of my backend, they can open up the ViewUsers controller (widget), which can open up more UserDetail controllers.
I guess if you're used to OOP, it feels pretty natural to want to keep their scopes separate and encapsulated.