AngularJS $broadcast and isolate scope - javascript

So I have two directives splitter and pane that may be used like so:
<splitter>
<pane></pane>
<pane></pane>
</splitter>
<splitter>
</splitter>
I want them all to have isolate or inherited scope. However I also want to be able to $broadcast (or equivalent) between them so that if I were to $broadcast on one directive's scope, the same event would be triggered on all the nested directives that are listening but not it's parent or siblings (no $rootScope here).
How would one go about doing this? My solution must be future friendly as I will be adding more directives in to the mix which also listen for this event.

To do inter-directive communication, the best is to use the parent directive controller and expose methods in this.
Then you just have to require it in your children directive (require: '^splitter') and the parent controller will be injected as the fourth argument of your link function.
For more information, you can see the official documentation about Creating Directives that Communicate.

Related

Angular directive disposal

I'm trying to figure out what would be the best way to perform cleanup on different angular directives.
I have different types of directives, some do not define their own scope, some have an isolated scope, and some have a child scope.
I need a generic mechanism that will take care of the cleanup in a separate component that my directives use.
So basically I'm looking at two different options, either register on angular's element.on('$destroy', function() {...}) or on jquery's scope.$on('$destroy', function () {...}).
Here's the problem:
If I register on the underlying element destruction then I miss destruction of directives that they're element was not destructed (not sure exactly how's that possible, noticed it via testing...).
If I register on the underlying scope destruction then (I think) I miss destruction of directives that they're element was destructed, for instance when the directive is not defining its own scope and is using its parent scope.
Looking at angular's directives documentation I came across this:
Best Practice: Directives should clean up after themselves. You can use element.on('$destroy', ...) or scope.$on('$destroy', ...) to run a clean-up function when the directive is removed.
There's no mention as far as I can tell about which strategy to use when.
Also, looking at Angular's documentation I came across this:
When child scopes are no longer needed, it is the responsibility of the child scope creator to destroy them via scope.$destroy() API.
I don't understand the scenario in which I am supposed to call the scope.$destroy() API on my own.

$emit, $broadcast, prototypical inheritance

Still on the basics of AngularJS, i understand the concepts, however, just looking at custom events,
$emit
and
$broadcast
for nested controller..
According to the docs, $emit bubbles the event, ie. passes it up the chain, for nested controllers,
My question, is, how is this different to just simply calling a function from the parent controller (prototypical inheritance). Or have i miss understood something?
The thing is, you can't always be certain that your direct parent, is the one you want to call. It's easy to break your code with that kind of anti-pattern.
And you must agree that
$scope.$parent.$parent.$parent.doSomething();
Is pretty ugly.
Instead you can $emit an event upwards, now it doesn't matter how far up the chain your parent controller is, as long as it is listening and reacting.
This gives you nice loose coupling between controllers, and just acts like a message pump.
The same goes for $broadcast, just downwards instead, and here I would argue that it is even more important.
Take the example of a child controller with many parents or a parent controller with many children. Should the developer need to maintain a list of children within the parent to invoke a function on each of them. $emit and $broadcast are utilities to allow a loose-coupling messaging along the lines of an Observer pattern. If all parents in the hierarchy need to know that a child controller has done some task or needs some task dome on its behalf then is can just generate an event and interested parties can listen.
Data can also be passed removing the need for controllers to share data on the inherited scope.
$emit helps you to pass event to the parent controllers.
You can't use $controller('ParentController', {scope: scope}) in all your child controllers to inherit the properties. To make the code clean and loosely coupled, $emit will help you to achieve that.
Assume you have three levels of hierarchy and you want the child controller to update the value of a particular parent controller. If you are going to do that via prototype chain, you need to inherit that particular controller using $controller('ParentController', {scope: scope}) but $emit will avoid that. You need not know which parent controller. instead just emit the event.
In the appropriate parent controller where you want to read the data, use
$scope.$on("eventname", function(event, data) {
// update value here
});

AngularJS: Passing data from a transclude directive to the isolated scope of a sub-directive

Based on this Plunker: http://plnkr.co/edit/GufJjrn3OxYVSf2oLD5n?p=preview
I have two directives, for the sake of simplicity, let's name them directiveBlue and directiveRed.
directiveRed has to be a sub element of directiveBlue.
The MainCtrl of our mini app has a simple array under the variable $scope.elements.
This variable is passed to the isolate scope that directiveBlue creates via the data-elements attribute. Notice that the directiveBlue has to be a transclude directive.
Then my main problem is, how do I pass the array of elements to the directiveRed without having to get it doing it via $scope.$parent.elements which seems to me, is a bad practice and then it makes the code tightly coupled.
Any changes to the elements in the deepest directive should then be synched with the rest of the scopes.
Is there any good practice or valid solution for this?
Thanks!
EDIT:
To be more concrete on my use case:
I've created a plunker (http://plnkr.co/edit/i2Busz6E8ehlkG3uEllh?p=preview) with a more concrete situation, where I want to have directive for an action group, I've implemented an option as a simple directive and I want to place my logic in the directives controllers. The method selectAll is pretty simple, but I can imagine having more complex actions which would require the elements from the top scope.
there are plenty of solutions, but without knowing what your goal is, it is more or less guessing.
The following quote is from the angular docs for $compile and describes the use of controller
(...)The controller is instantiated before the pre-linking phase and it is shared with other directives (see require attribute). This allows the directives to communicate with each other and augment each other's behavior. The controller is injectable(...)
a fork of your plnkr to show how to access MainCtrl's $scope.elements in directiveBlue and directiveRed

Difference between $scope and $rootScope

Can anyone explain the difference between $scope and $rootScope?
I think
$scope:
We can get ng-model properties in particular controller from the particular page by using this.
$rootScope
We can get all ng-model properties in any controller from any page by using this.
Is this correct? Or anything else?
"$rootScope” is a parent object of all “$scope” angular objects created in a web page.
$scope is created with ng-controller while $rootscope is created with ng-app.
The main difference is the availability of the property assigned with the object. A property assigned with $scope cannot be used outside the controller in which it is defined whereas a property assigned with $rootScope can be used anywhere.
Example: If in the example below you replace $rootScope with $scope the department property will not be populated from the first controller in the second one
angular.module('example', [])
.controller('GreetController', ['$scope', '$rootScope',
function($scope, $rootScope) {
$scope.name = 'World';
$rootScope.department = 'Angular';
}
])
.controller('ListController', ['$scope',
function($scope) {
$scope.names = ['Igor', 'Misko', 'Vojta'];
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="example">
<div class="show-scope-demo">
<div ng-controller="GreetController">
Hello {{name}}!
</div>
<div ng-controller="ListController">
<ol>
<li ng-repeat="name in names">{{name}} from {{department}}</li>
</ol>
</div>
</div>
</body>
According to Angular's Developer's Guide to Scopes:
Each Angular application has exactly one root scope, but may have several child scopes. The application can have multiple scopes, because some directives create new child scopes (refer to directive documentation to see which directives create new scopes). When new scopes are created, they are added as children of their parent scope. This creates a tree structure which parallels the DOM where they're attached.
Both controllers and directives have reference to the scope, but not to each other. This arrangement isolates the controller from the directive as well as from DOM. This is an important point since it makes the controllers view agnostic, which greatly improves the testing story of the applications.
$rootScope is available globally, no matter what controller you are in, whereas $scope is only available to the current controller and it's children.
In other way we can look at this; $rootScope is global while $scope is local. When Controller is assigned to a page, so a $scope variable can be use here because it binds to this controller. But when we want to share its value across to other controllers or services, then $rootScope is being used (**there are alternative ways, we can share values across but in this case we want to use $rootScope).
Your second question about how you define those two words are correct.
Lastly a bit off track, please use $rootScope with care. Similar to the way you use global variables, can be a pain to debug and you may accidentally change the global variable somewhere inside a timer or something which makes your reading incorrect.
Every application has atleast one single rootScope and its lifecycle is the same as the app and every controller can have it's own scope, that is not shared with others.
Have a look at this article :
https://github.com/angular/angular.js/wiki/Understanding-Scopes
I recommend you read the official in-depth Angular documentation for scopes. Start at the section 'Scope Hierarchies':
https://docs.angularjs.org/guide/scope
Essentially, $rootScope and $scope both identify specific parts of the DOM within which
Angular operations are carried out
variables declared as part of either the $rootScope or $scope are available
Anything that belongs to the $rootScope is available globally across your Angular app, whereas anything that belongs to a $scope is available within the part of the DOM to which that scope applies.
The $rootScope is applied to the DOM element that is the root element for the Angular app (hence the name $rootScope). When you add the ng-app directive to an element of the DOM, this becomes the root element of the DOM within which $rootScope is available. In other words, properties etc of $rootScope will be available throughout your entire Angular application.
An Angular $scope (and all of it's variables and operations) is available to a particular subset of the DOM within your application. Specifically, the $scope for any particular controller is available to the part of the DOM to which that particular controller has been applied (using the ng-controller directive). Note though that certain directives e.g. ng-repeat, when applied within a part of the DOM where the controller has been applied, can create child scopes of the main scope - within the same controller - a controller doesn't necessarily contain only one scope.
If you look at the generated HTML when you run your Angular app, you can easily see which DOM elements 'contain' a scope, as Angular adds the class ng-scope on any element to which a scope has been applied (including the root element of the app, which has the $rootScope).
By the way, the '$' sign at the start of $scope and $rootScope is simply an identifier in Angular for stuff that's reserved by Angular.
Note that using $rootScope for sharing variables etc. between modules and controllers isn't generally considered best practice. JavaScript developers talk about avoiding 'pollution' of the global scope by sharing variables there, since there may be clashes later on if a variable of the same name is used somewhere else, without the developer realising it's already declared on the $rootScope. The importance of this increases with the size of the application and the team that's developing it. Ideally the $rootScope will only contain constants or static variables, that are intended to be consistent at all times across the app. A better way of sharing stuff across modules may be to use services and factories, which is a another topic!
Both are Java script objects and the difference is illustrated by diagram as below.
NTB:
First angular application try to find the property of any model or function in $scope , if it doesn't
found the property in $scope , then it search in parent scope in upper hierarchy. If the property is
still not found in upper hierarchy then angular tries to resolve in $rootscope.
New styles, like John Papa's AngularJS Styleguide, are suggesting that we shouldn't be using $scope to save current page's properties at all. Instead we should use the controllerAs with vm approach where the view binds to the controller object itself. Then use a capture variable for this when using the controllerAs syntax. Choose a consistent variable name such as vm, which stands for ViewModel.
You will still need the $scope for its watching capabilities though.

Angularjs Inter directive communication (parent-child directives) with a named ctrl of the parent directive

from the docs, and numerous other examples, It seems that the best way to handle common functions and communication between directives is requiring a "parent" directive and then the controller of the parent directive is supposed to be available as another argument of the link function - something like: (scope, element,attrs, ctrl).
as seen in the plunker. this Ctrl doesn't really exist AFAIK. (the plunker isn't the real use case but simplfied.. ) So I guess I'm doing something wrong. I'm currently using events with emit, broadcast and on methods to pass information between the directives and the Ctrl, but that doesn't seem to be very angularish.. So I guess I'm missing something obvious
Thanks for the help
Seems that my mistake was the functions on the controller are on $scope while they should have been attached to this. attaching to this allows the child directive scope to "know" about the ctrl methods . I updated the original plunker with the answer.

Categories

Resources