Dependency injection in Angular Directive - angularjs

Why is injecting a Controller in a directive done through require but other dependencies through the array annotation?

Require a Controller
If you want to share the same instance of a controller, then you use require.
require ensures the presence of another directive and then includes its controller as a parameter to the link function. So if you have two directives on one element, your directive can require the presence of the other directive and gain access to its controller methods. A common use case for this is to require ngModel.
^require, with the addition of the caret, checks elements above directive in addition to the current element to try to find the other directive. This allows you to create complex components where "sub-components" can communicate with the parent component through its controller to great effect. Examples could include tabs, where each pane can communicate with the overall tabs to handle switching; an accordion set could ensure only one is open at a time; etc.
In either event, you have to use the two directives together for this to work. require is a way of communicating between components.
Courtesy of Josh David Miller
How to require a controller in an angularjs directive
For the array annotation reason take a look at this stuffs
Why is the function in angular's DI inline annotation a array element?

Controllers are never really injected into something else. When you use require, you're just gaining access to other controllers on the parent element or current element. These "other directives" have to exist on their own on the same element, or parent element, hence the name 'require.'
Another way of putting it is, with require you're not asking for something to be passed in, instantiated, or created, you're just saying "I want that to exist on this element... and oh by the way I can access it in the link function since I know it exists."

Related

How to chain nested directives which use isolated scopes in angularjs

I'm trying to chain two nested directives that both use isolated scopes.
<div ng-controller="myController">
<my-dir on-done="done()">
<my-dir2 on-done="done()">
</my-dir2>
</my-dir>
</div>
I would like the second directive (my-dir2) to call the done() function of the first directive (my-dir) which in turn would call the controller one.
Unfortunately I don't know how to make the second directive access the callback of the first directive (so far the second directive is looking inside the high level controller, bypassing the first directive).
I think one could possibly make use of "require" but I can't since the two directives are not related (I want to use my-dir2 inside other directives not only my-dir).
To make it clear : I don't want to use require because it means that there would be a dependency of myDir on myDir2. My point is : I want to be able to reuse myDir2 inside others directives. So I don't want myDir2 to be based on myDir but I do want to inform the upper directive (myDir) when something is done (like in a callback in js).
I have made a plunker : as you can see in the javascript console, my-dir2 is calling directly the done function from the high level controller.
Does anyone has a clean way to deal with that kind of situation ?
Thanks
Update:
to be able write directives that are independent of each other you need to use events:
use $emit('myEvent', 'myData') to fire an event that will be handled by scopes that are upward in the hierarchy.
use $broadcast('myEvent', 'myData') to fire an event that will be handled by scopes that are downward in the hierarchy.
to handle the event that was fired by $emit or $broadcast use $on('myEvent', function(event, data){\\your code})
P.S.: in your case the $emit won't work because both directives scopes are on the same level in the hierarchy so you will need to use $rootScope.$broadcast('myEvent' \*, myData*\); I've updated my plunker to reflect the needed changes http://plnkr.co/edit/eTkO6sk6hpuYPnCjlSKn?p=info
The following will make inner directive dependent on the outer directive:
basically to be able to call a function in the first directive you need to do some changes:
add require = '^myDir' to myDir2
remove the onDone from myDir2 and keep the isolated scope
scope:{}
add controller parameter to link function in myDir2 link:
function(scope,element,attrs,controller)
in myDir1 controller change the definition of the done function
from $scope.done to this.done
call controller.done() in myDir2
here is a plunker with the needed changes http://plnkr.co/edit/eTkO6sk6hpuYPnCjlSKn
I think you can do something like these:
angular.element('my-dir').controller('myDir').done();
give a try!

How to output data from a directive to a controller, and should I?

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.

Partially apply an AngularJS directive

Say I want to create 'desktop-only' and 'mobile-only' directives that hide or show an element based on and env test. There are a dozen ways I could go about it, but a cool one would be to "partially apply" the existing 'ng-show' directive. So 'desktop-only' would be the equivalent of, and actually delegate to, 'ng-show="env.isDesktop"' but without the need to do the env test in some parent controller and put env in my scope.
Another common example would be a tabs plugin. I could write a tab directive to show and hide elements using show and hide jQuery functions, but this would lose a lot of the functionality already in the ngShow directive. Couldn't I have my tab directive extend the ngShow directive in order to get access to all that variable parsing, integration with ngAnimate, etc...
Is this possible?
Thanks
When using isolate scopes you could manually proxy some variables into the scope: $scope.current.device = $rootScope.current.device;
Appart from that, you could use the afforementioned approach to put a device model on $rootScope and have methods and fields, such as 'hasTouch' that did calls to Modernizr and similar.
So yes, this is possible.

require one directive controller in another

These are the ways i tried to require one directive's controller in another one.
1) http://jsfiddle.net/Xarm2/1/
2) http://jsfiddle.net/82UKq/1/
In both the cases you can see in the firebug console, that the required directive's controller is either undefined or an empty object.
1) How can i access 'd1Cntrl' inside the d2 directive.
2)Please let me know one use case that will need me to require an directive controller in another directive.
See this nifty screencast from John Lindquist on directive to directive communication
For example, if you have an input element where you would like to apply two custom validations where one validation should only run if first validation passes (e.g. pattern matching validation should only activate if element has a value).
Few thinks i could like to put it here
1) Directives are not creating their own scope, For ex.
<superhero strength>The Hulk</superhero>
<superhero flight speed strength>Superman</superhero>
<superhero speed>The Flash</superhero>
these three directives will share the same scope, unless we explicitly say scope:true or scope:{} in the superhero definition. If you we are not creating new scope, then the last superhero will be in effect.
2) If we create a new scope at superhero level, that same scope is shared by the sibling directives like strength, flight etc..
3) If we require an directive controller inside another directive, the api that is defined on the directives controller using this keyword will be exposed to the requiring directive. Please note that the methods that are defined in the controller scope $scope wont be visible.
Please feel free to edit if the sentences making is wrong or the points were wrong.

Modify scope within link function in AngularJS

Normally in all examples/source code of AngularJS modifications of scope is done in controllers. In my directive I need to get some information from another directive (or it's scope) and put it into scope (so visible in template of directive). As this information is common for all instances of this directive, using scope binding does not sound good for me.
So the only solution I found is to modify instance scope in linking function:
link: function(scope, element, attr, parentCtrl) {
scope.data = parentCtrl.someData;
}
This solution works. Plnkr example
The question: Is it ok according to AngularJS philosophy/style to modify scope in linking function or there is another solution?
Since you are creating isolate scopes in your directives (in your example plnkr), and you want to allow for parents to be 'somewhere' in the scope hierarchy (according to your comment to #MathewBerg), I believe your only option is to use the linking function to modify the scope.
(I suppose you could define methods on your MainCtrl that only the child directives should call, but enforcing that would be messy and break encapsulation).
So, to echo what #MathewBerg already said, yes, modify the scope in the directive/linking function.
Modifying scope in directives is fine. As for sharing information between directives there are a few methods. One is the way you described, where you access the parents controller and get it's data, another very similar method would be to have
scope.data = scope.$parent.data;
Instead of
scope.data = parentCtrl.someData;
The general way to share stuff between directives though is to use a service. This would allow you to inject the service into each directive and they can share the values. The problem with your initial method (and the one that I've described) is that if you ever move the element around so that the hierarchy of scopes change, your code will break. This is why I would recommend using a service over both. I suggest reading up on the service docs. There's also plenty of videos out there describing how to set them up: http://www.youtube.com/watch?v=1OALSkJGsRw

Resources