Exposing directive controller to parent controller - angularjs

I want to expose some of my directive's functionality through its controller (think a public API for this directive).
return {
restrict: 'E',
scope: {},
controller: function($scope) {
this.method1 = ...;
this.method2 = ...;
},
controllerAs: 'dir',
link: function (scope, element, attrs) { ... }
}
Then in my parent controller or template call dir.method1 to get stuff accomplished inside the directive. Any ideas if this is possible as of Angular 1.3?
I'd like to refrain from event passing or even function passing, I have heard this is possible although I have never seen an implementation of this.

It is possible, but your issue isn't to figure out how to get the API out. It's how to get TO it from the parent. You're creating an isolate scope through your use of the 'scope' option. You're also making an element-type directive, so I'm guessing you're doing something like this:
<my-parent>
<my-child></my-child>
</my-parent>
where <my-parent> is the parent directive, and <my-child> is the directive with the API you want to expose.
The real question is what you're trying to achieve here. There is totally a way to do what you're asking. Just because the scope is isolated doesn't mean you can't get to it. You can just iterate through the parent $scope's $$childHead/etc list to find the child whose API you want to access. Anything you define in the child like this:
$scope.myApiFunction = function() {
};
will be visible here. (Things you put into 'this' will not - use the $scope storage bucket instead.)
That means if you only had ONE child you could do something like this from the parent controller:
$scope.$$childHead.myApiFunction();
Simple. Also, very crude. There are lots of problems here: what if you have many children? What if this child with its API ends up one level down? Etc. It's breaking all kinds of OO patterns and it's going to get messy, fast.
Your question is very abstract - it might be good if you updated it with an exact example. Without that, let me guess at your goal. There are two ways to do something "like this" that are encouraged within Angular:
Services. Whenever you say "API", think Service first. A service is a singleton (automatically) so it's tailor-made for creating APIs. And services can use the Factory pattern to return objects of a type, so THOSE are tailor made for doing things like having a manager service handle, say, a buddy list in an IM client, with API methods for creating, removing, and finding buddies.
Items that add "optional" functionality to their parents when they're defined. Let's say we have three possible types of tooltips: tooltips that have a hover effect, those that have a click effect, and those that are triggered by a "walkthrough" system in some order. For this kind of thing, the easy thing to do is just reverse the API, like this:
Parent Controller:
$scope.tooltipHandler = {
showTooltip: function() {},
hideTooltip: function() {}
};
Child Controller:
$scope.$parent.tooltipHandler = {
showTooltip: function() {
// Do some real work
},
hideTooltip: function() {
// Do some real work
},
}
What happens here is if there's no tooltip defined, when the parent runs its walkthrough, nothing happens. If you add the blue tooltip display module, when the parent runs its walkthrough now, it's going to show blue tooltips.
Make sense?

I arrived here looking for a similar response. So far the best that I can figure is to do what Angular does with ngForm.
In the documentation clearly states
If the name attribute is specified, the form controller is published onto the current scope under this name.
This basically makes the form controller accessible from anywhere.
If you have the following DOM
<div ng-controller="MyCtrl as parentCtrl">
<form name="parentCtrl.frmCtrl">
<my-child-directive>
</form>
</div>
You can use require: 'ngForm' in my-child-directive to get access from an inside directive. If you are in the parent controller you can access it trough the frmCtrl variable.
Not sure if this is best practice. In ngForm the name attribute works well, but I don't even know how to call such an attribute for a custom directive.
Thats why I arrived here, I wanted to know if this is "The Angular way" and what types of convetions are on the subject.
Hope it helps!

Related

Decoupling UI and Controllers in a nested custom directive

What I think I want to do is completely isolate each step of a wizard into a custom element directive.
I think this would allow me to completely encapsulate the detail of each page of the wizard. For example:
<custom-wizard-tag>
<enter-name-page page="1" name-placeholder="name"/>
<enter-address-page page="2" name-placeholder="name" address-placeholder="address" last-page/>
</custom-wizard-tag>
So far, so good. Each of the elements above has its own directive, and each of these specifies a templateUrl and a controller (templateUrl could be supplied as an attribute, of course).
I want each page of the wizard to 'inherit' some behaviour. The UI components would contain the buttons, which would need to query the outer scope, for example to determine whether it is possible to move forward, backward and so on. We would also need to call member functions on the parent scope in order to actually move the wizard forwards and backwards, and to check whether the current page number matches 'ours'.
I'm new to this so bear with me...
I read the documentation on directive, and thought I could use scope: { onNext: '&onNext' } in order to 'inherit' the onNext function from the previous scope (which is assumed to be one which is 'wizard-like'). However, this is not what angular seems to do. It seems to want map the inner scope's onNext via an attribute called on-next, thus breaking encapsulation, because now the UI elements must reference functions in the parent scope - which is exactly what I wanted to avoid.
Am I barking up the wrong tree, or is there an idiomatic way to do this. A day of web searching has not got me far, but I could be using the wrong search terms.
Thanks for your patience.
scope: { onNext: '&onNext' }
won't do any inherintance, you would have to define onNext in the template (the template scope) the same way you do with the page property: <enter-name-page page="1"
If you have a function onNext defined in you customWizardTag directive either in link function or in its controller, you'll have to put it in the controller, because the controller can be passed to the child directive. Then you'll be able to pass the parent directive's controller in the link functions of somethingPage directives.
.directive('parentDirective, function() {
return {
controller: someControllerThatHasOnNext,
}
})
.directive('childDirective', function() {
return {
require: '^^parentDirective',
link: function(scope, element, attrs, theParentDirectivesController){
theParentDirectivesController.onNext();
}
}
})
If this is what you wanted

AngularJS - Sharing data from a parent route controller to all child directives on the page

I have a page that has multiple components and I've created each component as a directive. When the page is first loaded, that's when I grab all the data that should be available on the page. So all of the data exists on the controller for that route, which we'll just call pageCtrl. And then what I've been doing is binding any required data to each directive through the attributes, which of course ends up creating an isolate scope for each of them.
I know there are a few ways to share data, so given this situation, is there a recommended way of doing it? Or has anyone had better success doing it one particular way? While it's working perfectly fine the way I'm doing it, I've run into a few caveats. If I need just even one bit of information from the pageCtrl, I need to add another attribute to the directive. So it ends up creating more code on the directive element itself.
I was thinking about just creating a service that would store all the data, which the pageCtrl could initialize, instead of setting it on itself. Any feedback would be appreciated.
good question :)
First solution is to create in parent controller object and pass this object (via ng-model) to all directives. This object will be passed by reference (not by value) so controller and all directives will have access to the same object.
```
// in controller
$scope.shared_data = {someItems: []};
// in html
<my-directive ng-model=shared_data></my-directive>
Second solution is to create some simple service to store all of those data.
// in this solution you have to inject additional service to directive controller
(extended idea of point 2) creating service/factory that will be responsible by collecting and returning data. This service could be injected into directive and use the same methods to collect data. To avoid making multiple calls to API (REST) it could have some cache for each sensitive method.
Communication via events.... (probably the worsts solution for your example)
The first two ideas are probably the best, I do not know full specification of your product so final solution picking belongs to You:).
My advice is to try/play with all of those methods to really understand what is going on and how and when to use each of them :)
You can directly call your parent controller from child directive controller by using $parent.
App.controller('aCtrl', ['$scope', function ($scope) {
$scope.refresh=function(){
.......... //Updated Data get from DB
};
...........
}]);
App.directive('bDirective',function(){
restrict: 'EC',
replace: true,
scope: {},
controller: function($scope) {
$scope.$parent.refresh();
}
...
});
HTML:
<div ng-controller="aCtrl">
<div class="bDirective"></div> //directive
</div>

Recommended way to handle AngularJS Directive data

I regularly see examples such as <calendar events="a.appointments"></calendar> in which data assigned within a controller is passed in to a directive via an attribute binding.
The code below shows an alternative solution, in which the required data is gathered directly within the directive link function. Using this approach eliminates the need for a separate controller.
diary.html
<calendar></calendar>
calendar.js
angular.module('diary').
directive('calendar', ['AppointmentsService', function(AppointmentsService) {
return {
template: 'calendar.html',
scope: {},
link: function($scope) {
$scope.events = {};
AppointmentsService.getAppointments().then(function(result) {
$scope.events = result;
});
}
};
}]);
Is this a suitable or ultimately flawed approach to take?
If your calendar directive is completely decoupled from everything, it is absolutely fine approach to go for. However, if you need a certain communication between calendar and, let's say, events-tracker directive, you might want to enclose them in a parent controller. Or if you have a collections of calendar directives on a single page (with an option to remove or add new calendar), then passing the data by reference is a way to go as well.
EDIT
e.appointments are probably passed to calendar through scope, not attribute, i.e. calendar having isolated scope with events field.
Having thought about this some more, I've realised the alternative solution does have a flaw and it's a reusability issue.
When providing events through an attribute binding to the directive's isolate scope, the source of the data is totally flexible.
Fetching the appointments directly within the link function of the directive however, creates a specific dependency on AppointmentsService as the data source.

AngularJS: Should I convert directive's linking function to a controller?

I heard it's a good practice to use the controllerAs syntax along with bindToController: true in directives that use an isolate scope. References: one, two
Suppose, I have a directive like this:
angular.module('MyModule').directive('MyDirective', function(User) {
return {
scope: {
name: '='
},
templateUrl: 'my-template.html',
link: function(scope) {
scope.User = User;
scope.doSomething = function() {
// Do something cool
};
}
};
});
<!-- my-template.html -->
<div>
User Id: {{ User.id }}
Name: {{ name }}
<button ng-click="doSomething()">Do it</button>
</div>
As you can see, there is no controller in this directive. But, to be able to leverage controllerAs and bindToController: true I have to have a controller.
Is the best practice to convert the linking function to a controller?
angular.module('MyModule').directive('MyDirective', function(User) {
return {
scope: {
name: '='
},
templateUrl: 'my-template.html',
bindToController: true,
controllerAs: 'myCtrl',
controller: function() {
this.User = User;
this.doSomething = function() {
// Do something cool
};
}
};
});
<!-- my-template.html -->
<div>
User Id: {{ myCtrl.User.id }}
Name: {{ myCtrl.name }}
<button ng-click="myCtrl.doSomething()">Do it</button>
</div>
My understanding is that directive's controller should be used as a mechanism to expose directive's API for a directive-to-directive communication.
Could anyone shed light on what's the best practice these days, having Angular 2.0 in mind?
I consider it best practice to move initialization code and/or exposing API functions inside of a directive's controller, because it serves two purposes:
1. Intialization of $scope
2. Exposing an API for communication between directives
Initialization of Scope
Suppose your directive defines a child scope (or inherits scope). If you initialize scope inside of your link function, then child scopes will not be able to access any scope variables defined here through scope inheritance. This is because the parent link function is always executed after the child link function. For this reason, the proper place for scope initialization is inside of the controller function.
Exposing a Controller API
Child directives can access the parent directive's controller through the 'require' property on the directive definition object. This allows directives to communicate. In order for this to work, the parent controller must be fully defined, so that it can be accessed from the child directive's link function. The best place to implement this is in the definition of the controller function itself. Parent controller functions are always called before child controller functions.
Final Thoughts
It is important to understand that the link function and the controller function serves two very different purposes. The controller function was designed for initialization and directive communication, and the linker function was designed for run-time behavior. Based on the intent of your code, you should be able to decide whether it belongs in the controller, or it belongs in the linker.
Should you move any code that initializes scope from the link function to the controller function?
Yes, that is one of the primary reasons that the controller function exists: to initialize scope, and allow its scope to participate in prototypical scope inheritance.
Should you move $watch handlers from the link function to the controller function?
No. The purpose of the link function is to hookup behavior and potentially manipulate the DOM. In the link function, all directives have been compiled, and all child link functions have already executed. This makes it an ideal place to hookup behavior because it is as close DOM ready as it can be (it is not truly DOM ready until after the Render phase).
I will start with your last sentence. It's all about how you want to write your angular code. If you want to stick with the guideline for writing good code for angular 1.x then don't even bother thinking too much about what is ideal. However, if you want to prepare for the next version of Angular, as well as, the upcoming web technologies, I would suggest that you start adopting the new concepts and adjust them to the way you write your code today. Bare in mind there is no right or wrong in this case.
Speaking about angular 2.0 and ES6, I would like to stress out that the notion of directives will be more in align with the Web Components technology.
In Angular 2.0 (according to the current design) will get rid of the complex way of defining directives; That is no more DDO. Thus I think it would be better if you start thinking in that way. A component will just have a View and a controller.
For example,
#ComponentDirective({
selector:'carousel',
directives:[NgRepeat]
})
export class Carousel{
constructor(panes:Query<CarouselItem>) {
this.items= panes;
}
select(selectedCarouselItem:CarouselItem) { ... }
}
The above code is written in AtScript (a superset of typescript and ES6), but you will be able to express the same thing in ES5, as well. You can see how simpler things will be. There in np such notion like link function or compile etc.
In addition, the view of the above component will be directly bound to the above class; So you can already find a similarity to the controllerAs syntax.
So in essence, I would suggest that you first look at the general idea behind Web Components, and how the future of the Web Developments might be, and then I think you would start writing Angular 1.x code with that in mind.
In summary, try to code in a way that favours the current version of Angular, but if you believe that there are some parts of your code that can embrace some concepts of the next version, then do it. I don't believe it will harm you. Try to keep it simple as the new version of Angular will be simpler.
I would suggest that you read the following posts:
https://www.airpair.com/angularjs/posts/component-based-angularjs-directives
http://eisenbergeffect.bluespire.com/all-about-angular-2-0/
https://www.airpair.com/angularjs/posts/preparing-for-the-future-of-angularjs
http://teropa.info/blog/2014/10/24/how-ive-improved-my-angular-apps-by-banning-ng-controller.html
UPDATE
(at the bottom I added a code/plnkr that shows the approach)
Apart from the article you mentioned: https://www.airpair.com/angularjs/posts/preparing-for-the-future-of-angularjs#3-3-match-controllers-with-directives, which basically not only advocates the pattern you are asking for, but component based front-end in general, I have found: http://joelhooks.com/blog/2014/02/11/lets-make-full-ass-angularjs-directives/ (it advocates Minimal use of the link function and use ui-bootstrap as an example where such a pattern has been used). I cannot agree more with both these articles.
Another thing about Angular2.0: no more $scope in angular2.0 -- https://www.youtube.com/watch?v=gNmWybAyBHI&t=12m14s, so surely if you can get rid of $scope as much as possible, then the transition should be smoother.
I made a small mistake as well:
Still, I prefer to define all functions in controller and just call
them via link's scope. Ideally it is just one call:
scope.init ctrl.init(/*args*/) (where ctrl is
directive's controller).
To some degree it is a matter of taste, but there are some valid reasons to keep the link function as thin as possible:
The logic in link function is not easily testable. Sure, you can compile the directive in your unit tests and test its behaviour, but the link function itself is a black box.
If you have to use controller (let say to inter directive communication), then you end up with two places where to put your code. It is confusing, but if you decide to have the link function thin, then everything that can be put in controller should be put in controller.
You cannot inject additional dependencies directly to the link function (you can still use those injected to the main directive function). There is no such a problem in case of controller's approach. Why it matters:
it keeps better structure of the code, by having the dependencies closer to the context where they are needed
people coming to angular with non-JS backgrounds have still problems how functional closure works in JS
So what has to be put in the link function:
Everything that needs to be run after the element has been inserted into DOM. If $element exposed $on('linked') event than basically this point is not valid.
Grabbing references to controllers require:ed. Again, if it was possible to inject them into the controller directly...
Still, I prefer to define all functions in controller and just call them via link's scope. Ideally it is just one call: scope.init.
Misko Hevery told a couple of times that DDO is far from being perfect and easy to understand and it evolved to what it is right now. I am pretty sure, that if the design decisions were made upfront then there would a single place to put the logic of the directive - as it will be in angular2.0.
Now answering your question if you should convert link function to a controller. It really depends on a number of criteria, but if the code is actively developed then probably it is worth to consider. My experience (and couple of people I talked about it) can be illustrated by this image:
About angular2.0 -- it is going to be a tectonic shift, so from that perspective it should not matter much, but still the controller's approach seems to be closer to the way directives/components are going to be declared in v2.0 via ES6 classes.
And as the last thing: To some degree it is a matter of taste, but there are some valid reasons to keep the CONTROLLER function thin as well (by delegating logic to services).
UPDATE -- PLNKR
PLNKR exemplifying the approach:
html
<input ng-model="data.name"/>
<top-directive>
<my-directive my-config="data">
</my-directive>
</top-directive>
js
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.data = { name : 'Hello, World'};
});
app.controller('MyCtrl', function($scope){
var self = this;
this.init = function(top){
this.topCtrl = top;
this.getTopName = top.getName.bind(top);
this.getConfigName = function(){return this.config.name};
console.log('initilizing', this, $scope, this.getConfigName, this.getTopName());
}
// if you want to $watch you have to inject $scope
// you have access to the controller via name defined
// in contollerAs
$scope.$watch('myCtrl.config', function(){
console.log('config changed', self.getConfigName());
}, true);
});
app.directive('topDirective', function(){
return {
controller : function(){
this.name = "Hello, Top World";
this.getName = function(){return this.name};
}
}
});
app.directive('myDirective', function(){
return {
require: ['myDirective', '^topDirective'],
controller : 'MyCtrl',
bindToController: true,
controllerAs: 'myCtrl',
template : '{{myCtrl.getConfigName() + " --- " + myCtrl.getTopName()}} ',
scope : {
config : "=myConfig",
},
link : function(scope, element, attrs, Ctrls){
Ctrls[0].init(Ctrls[1]);
}
}
});
As per the latest documentation this is still the recommended practice "use controller when you want to expose an API to other directives. Otherwise use link." I would like to hear from other people also and the approach they are using.
sharing the contents from here, (I dont have enough reputations to put it as comments)
“where do I put code, in ‘controller’ or ‘link’?”
Before compilation? – Controller
After compilation? – Link
Couple of things to note:
controller ‘$scope’ and link ‘scope’ are the same thing. The difference is paramaters sent to the controller get there through Dependency Injection (so calling it ‘$scope’ is required), where parameters sent to link are standard order based funcitons. All of the angular examples will use ‘scope’ when in the context, but I usually call it $scope for sanity reasons: http://plnkr.co/edit/lqcoJj?p=preview
the $scope/scope in this example is simply the one passed in from the parent controller.
‘link’ in directives are actually the ‘post-link’ function (see rendering pipeline below). Since pre-link is rarely used, the ‘link’ option is just a shortcut to setting up a ‘post-link’ function.
So, whats a real world example? Well, when I’m deciding, I go by this:
“Am I just doing template and scope things?” – goes into controller
“Am I adding some coolbeans jquery library?” – goes in link
Credit for the answer goes to jasonmore

AngularJS: Using services in directives

This is an angularjs app. I have a service that handles the loading of content (ajax). While the service is getting the content, a number of things throughout the app hide, later showing again (depending on the content returned). They might have the same scope, different scope, whatever. They just need to hide while content is loading, and then show when it's done. Pretty normal stuff.
Right now, I have separate controllers watching a "loading" property of the service and using regular angular directives (ng-show, ng-hide, etc.) to show/hide. But this feels like overkill. I'd prefer to write a custom "loading" directive that injects the loading service and does the watching and showing/hiding.
My question is: Is what I want to do "bad"? The controller way, I end up boilerplating a bunch of code, maybe up to like 5 or 6 times, or even more as the app grows. The custom directive way, I write it once and use an attribute where I need it. Yeah - there's a dependency on that service, but that just doesn't feel like the end of the world that some people have made me start to think I should think it is.
For what it's worth, I feel like I've heard "separation of concerns" so many times I've become paralyzed by it. It leads me to overthink everything because I want to do things the right way, but it sure doesn't feel like I'm being very productive.
If I understood correctly, you have a bunch elements that should hidden when a particular service is loading data, and then be displayed again when the data is loaded, right?
In that case, events might be a good solution:
they can be global to the appliciation (which i think is what you are aksing for).
they allow for avoiding direct coupling between elements (also one of your concerns).
So, in your service, just broadcast events when stuff happens:
$rootScope.$broadcast('loading-data');
axajStuffOrWhatever(function() {
$rootScope.$broadcast('data-loaded');
});
Then, wrap the show/hide behaviour in a directive that will listen to those events.
.directive('hideWhileLoadingData', function() {
return {
link: function(scope, el, attrs) {
scope.$on('loading-data', function() {
el.css('display', 'none');
});
scope.$on('data-ready', function() {
el.css('display', 'block');
});
}
};
});
Use the directive:
<div hide-while-loading-data>something</div>
The advantage of using events here, is that later on, they could be originated by a different service, or by multiple services, and the directive will not be affected by that as long as the events are the same.
For more complex behaviour, you could also parametrize the events and the directive, so different elements will react to different kind of stuff.
I've made an example of this.
In my opinion all scopes which depend on this service should be children of one parent scope. If you have the parent scope responsible for talking with the service then any directive of any scope can access it via $parent on the $scope.

Resources