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/
According to the Angular docs:
Scope is the glue between application controller and the view. During the template linking phase the directives set up $watch expressions on the scope. The $watch allows the directives to be notified of property changes, which allows the directive to render the updated value to the DOM.
Now my Question is : if my function is not connected to the view, should we use $scope or not?
I assume that you mean if you should do $scope.functionName = function(), even if the function isn't connected to the view.
No you shouldn't, why would you expose a function to the view, which isn't needed to the view? Also you get a better overview which functions is internally when only using function funcName().
You shouldn't use the $scope to declare every function you are using, especially if it's not connected to the view.
However, there are some cases you need to use the $scope in a function not connected to view, for example if you want to emit/receive/broadcast a message on the scope tree or access something on a parent scope (although it's not necessarly a good practice).
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!
I was reading all the answers about communication between controllers and directive, but is seems to me occurred using shared service and inject it to each one of them. When I'm developing a very large scale application, I don't know what my page is going to have in it. I may have 2 controllers need to communicate between them, and I also may have 5 directive and 2 controllers in the same page. I don't know from scratch what is going to be inside my view/page, so I need a better way how to communicate between them. I'm looking for that better way to do it, the right AngularJS way.
http://i60.tinypic.com/2z87q05.png
Above is my view example I'm working on: on the left side I have a directive tree, on the right side I have chart controller and directive grid. I may have more than this, but this is a good example of what I may have. Keep in mind, the view can have X componenets, you don't know from the beginning what will be in it.
Now, lets say each time I select node in tree on the left, I want to be able to tell the other controllers that nodeSelectedChange event happend. I don't want to inject each one of them a service that holds that info, so I thought about something like having a Page Manager Controller, which is the father of the all view. All my controllers/directives inside my page should talk with each other only by the PageManagerController, he is the only thing in page that knows what he has inside of it.
Important here to keep in mind: The tree don't know the page has chart or grid, they don't need to know each other in order to communicate. The page manager knows everything, now I want it to make the magic - Should it has a service? Should each other component has service and service can talk with PageManager service?
Help me to think. Hope I can connect all the dots to create a BETTER way for communication.
I think the sharedService way is good for small app, when you know from start what is going on in your app, but most of the time - You just don't know who and when is going to use your directive, it should be able to talk with everyone.
What I don't like about events:
The events being fired inside the controller, while it should be inside a service.
The listener controller should know the name of the event he is going to listen too. If I change the event name being $emit or $broadcast, I need to go all over the listeners $on("eventName") in all app and change to that unique name.
Directive is like a black box, I don't want to check inside of it each time and find the names of the events he is being broadcasting in order to communicate with it.
I need a way to exposed the events NAMES out of the directive, probably with a service connected to that controller.
There are a couple of good practices:
Use the $scope to communicate between directives and controllers for runtime dependencies (e.g. models you fetch from the server, instantiated classes, etc.). The way to go is to have an isolated scope with attributes mapped to the dependencies:
directive('tree', function(){
return {
scope: {
somevalue : "="
}
}
});
Use it like this:
<tree somevalue="objectFromController">
Use the injected services to communicate with static dependencies (Presentation Models, global sharable state, etc.)
directive('tree', function(treeState){
return {
scope: {
somevalue : "="
},
link: function(scope){
// some logic updating the treeState
treeState.openNodes = ['x', 'y'];
}
}
});
controller('ctrl', function($scope, treeState){
// react to treeState changes
// you can use $scope.$watch for it
// or any other way you like
// see: https://github.com/mr-mig/angular-react-to
});
If you want to have maximum composability, stick to the first pattern:
directive('tree', function(){
return {
scope: {
model : "="
},
link: function(scope){
// some logic updating the treeState, stored as scope.model
scope.model.openNodes = ['x', 'y'];
}
}
});
controller('ctrl', function($scope, treeFactory){
$scope.treeModel = treeFactory.create();
// react to treeState changes
// you can use $scope.$watch for it
// or any other way you like
// see: https://github.com/mr-mig/angular-react-to
});
And compose this stuff using template and binding as a communication bus:
<tree model="treeModel">
Sticking to this pattern you get:
No events
Well-defined directive "interface" (attributes in the isolated scope)
Easy composability
Reactive behavior based on scope change propagation
It really depends on the type of information you want to share from the tree directive to the other sections of the page.
You have a few options in front of you:
Use Scope Events
As someone mentioned above, you could fire an event and listen for events in various controllers and/or services. That said, it can get really ugly, really fast. Tracing events and figuring out what event listeners are active at a given point can give the best engineers a migraine!
Use a Service
Another option would be to use a Service, let's say a PageManagerService. In that case,
Each click of a tree item would set some information on the PageManagerService, saying which page, what objects, and what items it needs to display
Each component that needs to change could
Register a listener to be triggered when the PageManagerService changes
Add a watch on the service and run its code when the PageManagerService changes
The service itself is just going to be shared state, and it would be upto the directives and components on how it wants to consume and respond to changes in the state.
Use UI Router
But the more I think about this, the more it seems like a good use case from something like UI Router. UI Router allows you to define states, and have different parts of the page respond in different ways to state changes. Each section could respond to a state change in its own way, by loading
A different controller
A different template, possibly with different components and widgets
So what you would end up having is a structure with
The Tree directive on the left
A ui-view named, let's say, top
A ui-view named, let's say, bottom
Each item in your tree directive can then be a ui-sref, which is just a fancy way of saying instead of redirecting to a URL, redirect to a state.
You could then define your configurations in your application in a single place, like so:
$stateProvider.state('dashboard', {
views: {
"top": { templateUrl: 'my/dashboard.html', controller: 'DashboardCtrl'}
"bottom": { templateUrl: 'my/dashboard-grid.html', controller: 'DashboardGridCtrl'}
}
})
Similarly, you could have a state definition for each item in your tree directive, and each one just be a link to a different state.
Of course, the state definitions are done in your config section in an AngularJS application, which you would know is before the application starts. What if you needed dynamic states as well?
Well, a few answers / thoughts to lead you down the way for that as well:
The controllers and services would have to be predefined, you would not be dynamically creating HTML content and/or JS controllers
It is possible to expose the $stateProvider as a global variable in the config section, and dynamically call stateProvider.state inside a controller / service, wherever you have new state definitions.
More often than not though, the controllers and HTML remain constant, and we just need to trigger the different states with various parameters. That can easily be done by calling transitionTo function to transition to a defined state with various state parameters.
you can use $on, $emit and $broadcast to communicate among different controllers/scopes .
Please follow one of my earlier post . I have a setup a plunk . You can try out the example.
Angular Js newbie - link in a controller view that triggers another controller action
$on : setup a event handler
$emit : communicate to parent controllers
$broadcast : communicate to child controllers
To know more visit - https://docs.angularjs.org/api/ng/type/$rootScope.Scope
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.