I've came across with problem exposing api of directives in order to its interaction with controller.
There is a simplified jsfiddle to describe my structure.
The problem is that directives has templateUrl property (I replaced it with template in fiddle above), it leads to loading templates async (correct behavior according to this question).
As a result directive's controller is called after main controller therefore directive's api function doWork is undefined (even if you wrap it call with something like $timeout):
.controller('MainCtrl', function($scope, $timeout) {
$scope.api = {};
$scope.init = function() {
$timeout(function() {
$scope.api.doWork();
})
}
$scope.init()
})
There is an approach that comes to my mind - use event in link function of directive and subscribe $scope.api.doWork to that event. But I'm not happy about using events. Also it's not clear how to handle such case if there are some directives nested to each other with similar way of exposing api.
On the other hand it's possible to replace templateUrl with template - but it's also quite bad decision in case of complex layout in template.
Hence, I stuck a bit with the way to resolve such kind of problem. What's the best way to fix it out? Probably there is another technique to expose directive's api (I was inspired by Andrej Kaurin answer in that thread)?
If you are using Angular 1.5+ and are using components you could try $postLink or $onInit function link. Otherwise you just create onDoWork directive scope property and then apply some function from main controller that will be fired when doWork will actually happen(since I think that directive should control when to doWork, if not then maybe you should just create service ?)
Related
I have a custom scope method within a Controller, and when a custom directive loads, I want to run a method inside the defined controller. I've seen a lot of options, but which one could be referenced inside a template via an ng-* call? Otherwise, what are the best options?
Since the controller is instantiated when the directive is loaded, any method called in your controller will be called on page load. In my code it is often something like
angular.module('app')
.controller('controllerName', ctrl);
function ctrl() {
/*--------Initialize--------*/
someMethod()
}
If you are on angular 1.5 already and can use the new component way to build your custom directive, you could use the newly introduced $onInit method instead of polluting the constructor, that should only initalize the object itself.
For details, please see this blogpost: https://toddmotto.com/on-init-require-object-syntax-angular-component/
When working with the $ionicModal in Ionic Framework, I noticed a lot of people instantiate the Modal inside the controller and pass the controller scope to the Modal.
Like so,
$ionicModal.fromTemplateUrl("views/call_options_view.html", function ($ionicModal) {
$scope.menu = $ionicModal;
}, {
scope: $scope,
animation: "slide-in-up"
});
Doing this allows the modal to invoke methods in the controller scope. Is there some way we can give a separate controller to the Modal?
Right now, using the controller scope, isn't there a MVC violation? The controller owns two views. Suppose I want the same modal available on another controller, then I would have to duplicate my functionality for the modal on both the controllers. MVC is supposed improve code reuse. So essentially, I want to re-enforce MVC by giving my modal a separate controller.
One way I thought of fixing this is by putting the modal in the Root Controller. Doing so, will make it accessible from all the child controllers and the functionality for the modal will only be available in the root controller. I still don't like this fix, cause i don't want to clutter my root controller with too much logic.
Any other suggestions?
I stumbled on your question while trying to come up with a solution similar to your concern.
Because I had a problem regarding navigation in my routes, I decided to use $ionicModal to show a view of another state in a modal view. I came up with a solution I crafted there (but I did not implement it for my working context yet) that should work in my case, while I'm not really satisfied with it.
To summarize, all my states are nested under tabs; when I am in the tabs.home state, I want to directly show the tabs.settings.sub state.
However, tabs.settings.sub relies on data populated by its parent state tabs.settings. Hence my problem with giving the scope of my current state (tabs.home) to tabs.settings.sub.
My modal uses a template that will include the template of my view:
<script id="templates/modal.html" type="text/ng-template">
<ion-modal-view>
<ng-include src="templateUrl" ng-controller="controller"></ng-include>
</ion-modal-view>
</script>
I can then reuse the view from the state. Regarding the scope, I used $scope.new(true) to isolate it, and populated it with data required by my modal template:
var subState = $state.get ('tabs.settings.sub');
var subScope = $scope.$new (true); // true: isolate
subScope.title = 'Sub';
subScope.templateUrl = subState.templateUrl;
subScope.controller = function () {
if (subState.controller)
return $controller (subState.controller, {$scope:subScope});
return null;
};
The modal is instantiated using this scope (that's one problem to my opinion: mixing the scope of the modal and the scope of the controller).
The controller has to be a function that returns the appropriate controller.
$ionicModal.fromTemplateUrl ('templates/modal.html', {
scope: subScope
}).then (function (modal) {
modal.show ();
});
The major problem with my solution is to transit data up to the controller of the view to show (in this case SubCtrl). But it is more narrowed to my specific context: my modal is not aware of the chain of inheritance of controllers adn states, because this is handled by UI router.
I don't know if it is possible to access the state associated to a controller (the usual pattern seems to be to use $state.parent, but this cannot be used here, as mentioned by the UI router wiki).
The workaround I use here (this is the part I am not satisfied with) is to federate data through the states:
.state ('tabs.settings', {
data: { status: 'valid' }
}
I have access to it when creating my modal:
subScope.status = subState.data.status;
And I have access to it from the parent controller:
$scope.status = $state.current.data.status;
I am creating reusable UI components with AngularJS directives. I would like to have a controller that contains my business logic with the nested components (directives). I want the directives to be able to manipulate a single property on the controller scope. The directives need to have an isolate scope because I might use the same directive more than once, and each instance needs to be bound to a particular controller scope property.
So far, the only way I can apply changes back to the controller's scope is to call scope.$apply() from the directive. But this breaks when I'm inside of an ng-click callback because of rootScope:inprog (scope operation in progress) errors.
So my question: What is the best way to make my controller aware when a child directive has updated a value on the controller's scope?
I've considered having a function on the controller that the directive could call to make an update, but that seems heavy to me.
Here is my code that breaks on an ng-click callback. Keep in mind that I don't just want to solve the ng-click issue. I want the best overall solution to apply reusable directives to modify a parent scope/model.
html
<div ng-controller="myCtrl">
<my-directive value="val1"></my-directive>
</div>
controller
...
.controller('myCtrl', ['$scope', function ($scope) {
$scope.val1 = 'something';
}});
directive
...
.directive('myDirective', [function () {
return {
link: function(scope) {
scope.buttonClick = function () {
var val = 'new value';
scope.value = val;
scope.$apply();
};
},
scope: {
value: '='
},
template: '<button ng-click="buttonClick()"></button>'
};
}]);
The purpose of two-way data binding in directives is exactly what you're asking about -- to "[allow] directives to modify a parent scope/model."
First, double-check that you have set up two-way data binding correctly on the directive attribute which exposes the variable you want to share between scopes. In the controller, you can use $watch to detect updates if you need to do something when the value changes. In addition, you have the option of adding an event-handler attribute to the directive. This allows the directive to call a function when something happens. Here's an example:
<div ng-controller="myCtrl">
<my-directive value="val1" on-val-change="myFunc"> <!-- Added on-change binding -->
<button ng-click="buttonClick()"></button>
</my-directive>
</div>
I think your question about $scope.apply is a red herring. I'm not sure what problem it was solving for you as you evolved this demo and question, but that's not what it's for, and FWIW your example works for me without it.
You're not supposed to have to worry about this issue ("make controller aware ... that [something] modified a value on a scope"); Angular's data binding takes care of that automatically.
It is a little complicated here because with the directive, there are multiple scopes to worry about. The outer scope belongs to the <div ng-controller=myCtrl>, and that scope has a .val property, and there's an inner scope created by the <my-directive> which also has a .val property, and the buttonClick handler inside myDirective modifies the inner one. But you declared myDirective's scope with value: '=' which sets up bidirectional syncing of that property value between the inner and outer scope.
So it should work automatically, and in the plunker I created from your question code, it does work automatically.
So where does scope.$apply come in? It's explicitly for triggering a digest cycle when Angular doesn't know it needs to. (And if you use it when Angular did know it needed a digest cycle already, you get a nested digest cycle and the "inprog" error you noticed.) Here's the doc link, from which I quote "$apply() is used to execute an expression in angular from outside of the angular framework". You need to use it, for example, when responding to an event handler set up with non-Angular methods -- direct DOM event bindings, jQuery, socket.io, etc. If you're using these mechanisms in an Angular app it's often best to wrap them in a directive or service that handles the Angular-to-non-Angular interface so the rest of your app doesn't have to worry about it.
(scope.$apply is actually a wrapper around scope.$digest that also manages exception handling. This isn't very clear from the docs. I find it easier to understand the name/behavior of $digest, and then consider $apply to be "the friendlier version of $digest that I'm actually supposed to use".)
One final note on $apply; it takes a function callback argument and you're supposed to do the work inside this callback. If you do some work and then call $apply with no arguments afterwards, it works, but at that point it's the same as $digest. So if you did need to use $apply here, it should look more like:
scope.buttonClick = function() {
scope.$apply(function() {
scope.value = newValue;
});
});
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
Hi I'm a beginning Angular developer and I was wondering if the way I've passed data between controllers is bad practice or not.
I'm creating a seatmap widget whereby you click on a seat and it will display data in another part of the App using a different controller.
1) I have a directive that incorporates dynamic templating. Based on the model being passed (from an ng-repeat), it will create an event listener like so:
if(scope.seat.isAvailable) {
element.bind('click', function() {
scope.$emit('clickedSeat', scope.seat);
}
element.append(seatAvailableTemplate);
}
2) In my controller, I have the following listening for the click:
$scope.$on('clickedSeat', function(event, seat) {
seatSelectionService.broadcastSeatData(seat);
}
3) I have a service where I've passed in $rootScope which allows me to broadcast the data to a different controller:
var seatSelectionService = {};
seatSelectionService.broadcastSeatData = function(clickedSeat) {
seatSelectionService.seatData = clickedSeat;
$rootScope.$broadcast('seatSelected');
}
return seatSelectionService;
4) In the other controller, I have a listener which sets the $scope attribute, which renders {{selectedSeat}} in the view:
$scope.$on('seatSelected', function() {
$scope.selectedSeat = seatSelectionService.seatData;
$scope.apply();
}
Of course I have had to pass in seatSelectionService into both controllers for it to work.
Is there a better way to do this? Or is this way of passing data valid practice?
I would say less is more- if you can get rid of code do it- I would suggest you remove this in your controller unless you are doing something you are not showing, and put it in your directive, in step one:
$scope.$on('clickedSeat', function(event, seat) {
seatSelectionService.broadcastSeatData(seat);
}
$broadcast and $emit themselves aren't bad practice, but it does look like you've over complicated your app.
You can add services directly to directives - they can have controllers built into them that depend on the services.
So in your directive, add a controller that uses the seatSelectionService, something like:
seatSelectionService.setSeat( scope.seat )
Which cuts out one of the steps at least - your setSeat method would still want to do a $broadcast when it's done down to anything listening.
Directives can be really simple or really complex - they can simply be a quick way of outputting a repeated template, or a dynamic template with a controller and access to multiple services.
Something else you might want to look at is promises - I used to use $broadcast and $emit a lot more than I needed to, until I learnt how to use promises and defer properly