I have two directives, one which takes care of a list and one which creates an item in the list. The problem is that the directive which creates an item can be created anywhere in the dom. When I have created an item, how do I tell the other directive to update the list so I can list the new item?
The create-directive has a function called createItem which should call the other list-directives function "updateList". I have a service which both can use but I'd rather not fiddle with the scope in the service.
Am I thinking wrong here? Both are really needed for reusability but since their scopes are at closest siblings it's hard to reach them from each other.
What you can do is use angular events to trigger the "updateList" function.
Make the list-directive listen to an event like this:
//Whenever the 'update-list' event is broadcasted, the updateList() function will be called
scope.$on('update-list', updateList);
To broadcast the event from the create-directive you need to inject the $rootScope. With that you can now trigger the event:
$rootScope.$broadcast('update-list');
While events work fine in that scenario, please use them cautiously. If you have too many events all across your application it might be difficult to figure out what happens where and why.
The alternative that you would have is completely manage the list in the service that both directives use. In the list-directive you could bind to the list from the service, so that it'll get automatically updated whenever the list in the service changes. With that you don't need to change something in the scope of the directive from the Service, which should be avoided.
Related
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.
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 have a service in Angular that has one method in there - say GetName() that returns someones name
I want in my html to be able to do something like {{ $scope.GetName() }} and have this bound to my method result. My main issue is if the name in my service changes (through another controller) then I would like my {{ ..... }} change.
Hopefully this makes sense.
Regards,
Yannis
You can't bind dynamic service calls to a DOM element.
Problably the best way would be to $watch for the name changes within the service, and use a callback function on change. You'd also need a way to notify the controller that the name has changed, so an event listener should also be set up. The listener would call the function that will update the scope variable, say $scope.nameFromService. And then in your HTML you'd need only this inside the controller part:
{{nameFromService}}
Again, to make it clear, this is what happens:
another controller changes the value in the service
$watch gets triggered and calls a function that (among other things) triggers an event
the event listener in your controller receives the event and updates the scope variable
Angular automatically updates the DOM
UPDATE
If you can change your service, I'd actually avoid the whole $watch thing and just modify the name setter function so it triggers an event when called. That way, all your controllers can be aware of the name changes.
I've read this Q/A about databinding and $apply -> $digest in AngularJS :
How does data binding work in AngularJS?
While I understand the principle and the consequences, I'm still unsure about when AngulaJS is going to call $digest to do the dirty-checks. (And so, when should I consider to do something about the $watcher)
Every example I found was about using 'ng-click', 'ng-show', or 'ng-class'. But I'm pretty sure that it is also triggered by any change on variables of the scope ({{myData}}), and by many others directives (All of them maybe ?).
I would like to understand in which cases a $digest is called.
Can you give me any generic rule to knwo when it is called, or an exhaustive list of actions that will trigger a dirty-check ?
Have a look at this:
angularjs docs, specifically at "Integration with the browser event loop" section.
Basically the way it works is that AngularJS binds event handlers to any element that interacts with angular (any element that has a directive attached to it) and every time that event fires, $apply is called which internally calls $digest which will trigger the reevaluation of all the $watches which will check for values changed, etc...
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.