Consider for example a currentUser directive.
I could let the controller use a service to get the data about the current user, provider it to the directive and let the directive render some "hello {user.name}" template.
Alternatively I could have the directive have a dependency on some currentUserService and in the directive's controller ask for currentUserService.getCurrentUser.
Is one of the two significantly better then the other for any reason?
I tend to go with the first option but not sure if the using the second one would not have a benefit of having all the current-user-logic less spread around...
Thanks
As long as you're requesting the data from a service, I believe having a dependency toward it in a directive is fine.
The main aspect of the controller is having access to $scope, not much more.
There are two scenarios and it really depends on the purpose of your directive:
the directive is only used to display user-data (in a complex way)
the directive displays data and manipulates it (according to user-input)
SCENARIO 1
Since the only purpose of the directive it to render the data somehow, the directive should not be responsible for retrieving the data.
So you decouple the logic how to access the data and how to display the data. This way you can also use the directive for users other than the currently logged in user.
If there should be some special things visible, if the user is logged in, the directive should use ng-if or ng-show for that (and maybe a parameter to disable that view-part).
SCENARIO 2
In this case the purpose of the directive is to provide a gui for some business-logic (service functionality). Therefore the service should be injected into the directive.
Remark:
PERFORMACE
If you get the data via method-call from your service, this method will only be called once in every digest-cycle if you load the data and inject it into the directive-controller. Otherwise it may be called once for each occurance of the directive.
INTEGRITY
Remember, that if your service-method requests data via http and you are using the directive for example 3 times in a view and the directive calls the service-method itself, this will result in 3 identical requests which may have non-identical results (i.e. someone other changes the data while the requests are processed).
It is always better to use service to reside business logic.You should use service to get data and inject that service to directive.Do not use controller to communicate between directive.Service is meant for that purpose, initiate once.
Related
My angular skills are not great and I have encountered a problem which has stalled my progress.
Initially I had a Main controller and 5 directives. The directives and the main controller were happily working together, by passing data to/from the main controller through scope. Until the 5th directive required the data from all the other directives to be computed. I tried passing the data from all the directives to the main controller and then passed it to the 5th directive. but unfortunately due to the asynchronous nature of both data and js it did not work i.e. it is not being updated in some parts of the code and i kept getting null. I tried putting a $watch even that did not work.
Upon recommendation of a colleague, I introduced a service to transfer data between the directives and controllers. So no I have a Main controller, few directives and a service.
The service loads data from the DB for each directive through multiple queries. and Each directive performs computations on their set of data. but still the same problem of data not being available / undefined at different points in code exists. I don't know what else to do. If anyone can suggest a solution that will be a massive help.
Thanks in Advance
Pardon me if this sounds stupid but I have been using AngularJS for a while now and everywhere I have seen people telling me to wrap my logic in a directive(or service ?) instead of my controller and keep only the bindings in my controller. Apart from the reusability aspect of a directive is there any other reason ?
Until now I haven't actually understood why this is the case. Doesn't writing a directive come with a lot of overhead ? I haven't faced any kind of problems writing logic in my controller and it is EASY. What ARE the downfalls of this approach ?
The controller is the right place to do all and everything that is related to the scope. It is the place where you write all the
$scope.$watch(...)
and define all the $scope functions that you need to access from your views ( like event handlers ). Generally, the event handler is a plan function which will in turn call a function a service.
$scope.onLoginButtonClick = function(){
AuthenticationService.login($scope.username,
$scope.password);
};
On very rare occasions you can add a promise success handler in there.
DONT: Write business logic in controllers
There was a very specific reason why the earlier example was like that. It showed you a $scope function that was in turn calling a function in a service. The controller is not responsible for the login mechanism or how login happens. If you write this code in a service, you are decoupling the service from the controller which means anywhere else that you want to use the same service, all that you need to do is, inject and fire away the function.
Rules for the Controller going forward:
Controllers should hold zero logic
Controllers should bind references to Models only (and call methods returned from promises)
Controllers only bring logic together
Controller drives Model changes, and View changes. Keyword; drives, not creates/persists, it triggers them!
Delegate updating of logic inside Factories, don't resolve data inside a Controller, only update the Controller's value with updated Factory logic, this avoids repeated code across Controllers as well as Factory tests made easier
Keep things simple, I prefer XXXXCtrl and XXXXFactory, I know exactly what the two do, we don't need fancy names for things
Keep method/prop names consistent across shared methods, such as this.something = MyFactory.something; otherwise it becomes confusing
Factories hold the Model, change, get, update, and persist the Model changes
Think about the Factory as an Object that you need to persist, rather than persisting inside a Controller
Talk to other Factories inside your Factory, keep them out the Controller (things like success/error handling)
Try to avoid injecting $scope into Controllers, generally there are better ways to do what you need, such as avoiding $scope.$watch()
There two good reasons for me for keeping logic out of a controller:
Reusability
If your application has multiple controllers and each do pretty much the same thing with some differences then keeping logic in the controller means you will be repeating the code you write. It's better if you Don't Repeat Yourself. By putting that logic into a service you can inject the same code into multiple controllers. Each service (really a Factory) is created as a new instance of itself each time it is injected into a controller. By pushing logic into a service you can modularise your code, which keeps it easier to maintain and test (see below)
Testing
Good code is tested. Not just by people but by the unit tests you write. Unit tests give you as a developer assurance that your code does what you expect it too. They also help you design your code well.
If your controller has 20 different methods each with their own logic, then testing (and your code) is turning into spaghetti.
It's easier to write unit tests that are narrow i.e. they test one thing at a time. And fortunately it's also good (for the reasons outlined above) to break your code up into encapsulated pieces i.e. they do one thing and can do it in isolation. So unit tests (especially if you write your tests first) force you into thinking about how to break up your code into maintainable pieces, which leaves your application in a good state if you want to make changes in the future (you run the unit tests and can see where things break).
Example
Form application:
You have a form application serving multiple forms. You have a controller for each form. When the user submits the form the data is sent via a proxy to a CRM that stores the information in a database.
If a customer already exists in the CRM you don't want to create duplicates (yes the CRM should handle data cleansing but you want to avoid that where possible). So once the user submits their form data something needs to implement logic that goes something like:
search for the user in the CRM via an API endpoint
if the user exists get the user ID and pass it with the form data to another endpoint
if they don't exist hit another endpoint and create a new user, get the user ID and send it and the form data to associate it with the user
NB: Arguably all of the above should be done by a back-end service but for the sake of example let's go with it.
Your application has multiple forms. If you hardcode the same logic in each controller for each form (yes you should have a controller per form i.e. one per view) then you are repeating yourself multiple times. And writing tests that need to check the controller can do the basics (post data, manage changes to the view) but also test all of that of that logic for each controller.
Or instead write that logic once, put it in a service, write one test for it and inject it wherever you like.
References
Look at the Angular documentation, and look at the patterns that Angular implements and why these are good to follow (Design Patterns - the big ones being modular, dependency injection, factory and singleton).
The biggest problem with controllers is that you don't define the html it works on.
When you use...
<div ng-controller="myController"></div>
... then you have to inject your html in your controller, which is basicly old fashioned jQuery thinking.
When you use...
<div ng-controller="myController">... some html ...</div>
... your directive and your html it works on are defined in different places. Also not what you want.
Using directives forces you to put your piece of html and the code that it needs in the same place. Because the directive also has it's own scope, there will not be any interference with other variables elsewhere in your code. If you need variables from elsewhere you also have to explicitly inject them, which is also a good thing.
The word I use for why this is a good thing is 'atomic' but I'm not sure if this is the right word. Meaning: all the things that should work together are in one file. With templateUrl this isn't exactly true anymore, still the template is defined in the directive.
So in my controllers there is no code that does anything with the dom. Just the bare minimum like some page/view counting code, or connecting API data to the scope, or doing something with $routeParam data. All other code is put in either Services/Factories (business logic) or Directives (dom logic).
BTW: it is possible to define a controller for your directive, but is normally only used for 'inter-directive communication' (so they can share state), but you only use this with directives that always work together (like a tab directive that is repeated inside a tabs directive).
The main reason why you don't write logic in controllers is all $scopes in the controller get garbage collected with $destroy() on route changes. In the ngView directive when a $routeChangeSuccess broadcast is received, there is a function that only keeps $scope for the currently active view, all other $scopes are destroyed.
So for example, if you have a shopping cart app and your business logic is the controller using $scopes, the user will lose the product and all form data already entered on the order page, if they use the back button, etc.
I have a question about the best way to structure the flow of an app using Angular 1.3. I'm trying to incorporate the 'Component-based Directive' approach as per guys like Matias Niemela (https://www.airpair.com/angularjs/workshops/component-based-directives-angularjs). He says in a video to still use controllers for routing rather than going completely controller-free, but to use directives to create reusable components that have all their dependencies injected.
I'm a bit new to Angular but have used Java mvc frameworks before where a controller written in Java first finishes getting the data needed on the page to be displayed, before some html is constructed and served up. I'm wondering if I'm coming a bit unstuck due to the asynchronous nature of the javascript calls used in Angular, maybe assuming things happen in an order they can't be guaranteed to....
I want to try to make the directives involved have anything they rely on injected (likely via attr's) for increased test-ability etc.
I had envisaged something like the following, for a simple customer listing, with clicking on a single customer in that list going through to a customer details page:
1) a route is defined with a target html page and a controller.
2) The controller gets the list of customers (ideally via a service), which is then available to the html page via Controller As (so Controller MyController As myCont, means the list should be available via something like myCont.myCustomers
3) A Directive is defined to display a customer row's details (name/address, plus a clickable link that passes the customer number as a parameter to a details page)
4) The html page has an ng-repeat to display all of myCustomer in myCustomers, using the Directive. So, I would want to pass the single customer details into the directive via Attr's (for my dependency injection) and then get them displayed for each customer...
(Alternatively, of course, the directive could take in all the customers and display all of them)
So, my question is really whether this is the correct approach to take, or am I missing something about the way these apps need to flow? I've tried unsuccessfully to get it working (posting this from home and my attempt is at work... I'll post the attempt if ppl think it valuable, but really my first question is about whether my overall approach is inherently flawed or not)
And then, secondly, to get the data for each customer passed to the directive, I'm assuming I need to pass the fields in in the html page that has the directive in it, passing something like {{ myCustomer }}... is this correct? (can I assume the Controller will have finished getting all the data before the ng-Repeat tries to cycle through all my customers and send their data to the directive, or not, in which case should I be passing the data via some other mechanism?)
I can't seem to find any examples on the web that tie together Component based Directives, with routing via a Controller, passing data from that controller to the directive, so any help is greatly appreciated!
Thanks!!
I've been reading that Services are great for sharing data. But I am having a hard time making all data be in sync...
For example, let's say you have
an ItemService with an array of items.
a ItemListController, which shows a list of items ($scope.items = ItemService.items)
a AddItemController, a form to add items to the array (using the service).
When I add a new item, it doesn't automatically shows in the list controller. My workaround so far is to use cache:false in the router so that it refreshes every time I look at it...
I can't use $scope.$apply() because the adding happens on one controller while I want to see it on another...
You need to use $rootScope.$broadcast('item.added'); in the controller that initiates changes (AddItemController), after it successfully updates the array in the service.
Then in the ItemListController you use
$rootScope.$on('item.added', function(event){
$scope.items = ItemService.items;
});
Check this question
#Sarpdoruk There is two-way data binding between views and controllers, but not between controllers and services. By using this line $scope.items = ItemService.items; you copy 'items' from service to items in controller but you copy by value, not by reference. You can change $scope.items in your view or controller and they will get updated between the view and controller but these changes will have no effect on the service. Same thing if you update items in the service - controller will not know that something has been changed - so you need to $broadcast or $emit that something has changed from service and listen for the changes in the controller. You can also $broadcast between controllers, directives and even services (but since services have no access to $scope, you have to use $rootScope and some people frown upon that). The bottom line is, if you really have to use a $rootScope in the service because otherwise you will have to overcomplicate parts of your app, then do it and never bother with "best practice evangelist" The first rule of the app is that it has to work, only after it works you should worry about clean code. I hope it makes it clearer now.
Use case
For use in a form, I created a directive that tracks changes in an array. It allows changes to be reverted and deletions and additions to be stored separately. It allows for an array (one to many mapping in the database) to be updated incrementally (rather than requiring the server to either diff, or rewrite the entire list).
Problem?
My question is about the way I expose the functionality to the controller's scope. I currently use an two-way databound attribute on the directive's scope. This works, and it seems reliable (of course you can easily break it by reassigning the scope's value, but intentionally you can break anything).
Code
You can see this plunk to see this in action. It allows methods on the directive's controller to be called from the view and the view's controller. (I am using the directive controller intentionally because that's what I do in my actual code for the directive to directive communication, but this could also just be placed in the linking function.)
Question
Is this way of doing it bad design? Am I completely throwing AngularJS out of the window now and hacking in my own code. Are there any better ways to expose functions from a directive (keep in mind that there'll be multiple of these in a single form).
It's very easy to pass in my-attribute="someFunction()" to have the directive be a consumer of the view controller. I can't find a better way to do the opposite and have the view controller consume from the directive.
Alternative?
I've been thinking about using a service here, in which the service will provide an object that is instanciated in the view, passed to the directive, and have the directive blurp out it's results to that object. Then in turn have the view controller consume the information from that service's object. Would this be a better approach?
There's nothing wrong with your approach. In fact built-in angular directives such as ng-form use this approach to store the controller in the scope (see the name property of ng-form) http://docs.angularjs.org/api/ng.directive:ngForm
For more re-usability though I would put the api methods on the controller and then put the controller itself in the api:
this.getChanges = function () {};
this.resetChanges = function(){};
$scope.api = this;
In directives, the main purpose of the controller is to serve as an api for other directives (if you didn't need an api for other directives you could just do everything in the link function). Doing it this way ensures the api is available both on the scope as well as to any directive that 'requires' the oneToMany directive.