communicating between directives controller - javascript

I know if I have two directives that are nesting I can communicate throw controller, require and pass it as the fourth parameter of the link function.
<my-first-div>
<my-seconded-div></my-seconded-div>
</my-first-div>
and every thing will work fine.
but I could do the same thing when they weren't nesting.
<my-first-div></my-first-div>
<my-seconded-div></my-seconded-div>
why ?
and how do I make them communicate ?

It happens since both of the directives have watchers on the same variable reference. So the changed value is being 'noticed' in all the relevant directives.
You could mimic this "communication" by passing the same variable (By Reference) (varName:'=') for both directives and place watchers on that variable inside each of these directives.
Then, the DOM hierarchy won't matter
For example:
Directive 1:
app.directive('directive1', function () {
return {
restrict: 'E',
scope: {
myVar: '='
}
link: function (scope, element, attrs) {
// do something with main directive
console.log("directive1", scope.myVar);
$scope.$watch("scope.myVar", function (value) {
console.log("directive1", "value changed to:" + scope.myVar)
});
}
}
});
The same for the second directive..
For both directives pass the same variable
And the magic will happen

I assume by saying communicating, you mean sharing data, state and events between two directives. I will list basic ways that I have in mind here:
The reason why you can pass data/state between two nested directives is because in AngularJS a child directive (nested one in your example) inherits the scope of it parents. As the results, two sibling directives can share same data from its parent controller.
<div ng-controller="ParentCtrl">
<ng-sibling></ng-sibling>
<ng-another-sibling></ng-another-sibling>
</div>
In the above piece of code, ng-sibling and ng-another-sibling will inherit the same data that is defined in their parent ParentCtrl
AngularJS support emitting/broadcasting event/data using $broadcast, $emit and $on function, document can be found here: https://docs.angularjs.org/api/ng/type/$rootScope.Scope.
$emit can be used to sent event upward the tree's hierarchy, while $broadcast's downward, and the direction is essential.
So one of your directive can dispatch the event, while the other listen to it. It's pretty similar to the way jQuery trigger events.
// In link function or controller of one directive
$scope.$broadcast("EVENT_A",my_data);
// Listen to EVENT_A on another directive
$scope.$on("EVENT_A",function($event,data){
....
})
While two-way binding or firing event arbitrarily can be useful at first, they can also lead to the situation when it's really difficult to keep track of the application's event and data flow. If you find this situation, maybe consider using Flux architecture with AngularJS not a bad idea. Redux fully supports AngularJS and all of your directives can be built as single-state components. The Github repo can be found here: https://github.com/angular-redux/ng-redux, and a simple tutorial on how to run AngularJS with Redux can be found here: http://blog.grossman.io/angular-1-using-redux-architecture/

Related

How do I change one Directive when I click another Directive?

So here's my problem:
I have a page which displays two different graphs. Each of these graphs are there own Directives which has their own isolate scope.
When a user clicks on one of the bar's in the chart in Directive #1, I need the graph in Directive #2 to change.
Currently both Chart Directives are being fed their respective data sets from the Controller of this page.
Now from what I've seen I really have about three options:
Pass a callback function into Directive #1 which be called when the chart is selected. This callback function will exist on the Controller of the page and then can change the necessary data in order to get Directive #2 to update via data-binding.
Events. Fire an event on $rootScope inside of Directive #1 when the chart is selected. I can then listen to this event on the Controller and change the data in Directive #2 to update it via data-binding.
Use a Library like Rx.JS in order to make an observable inside of Directive #1. I haven't used Rx.JS with Angular that much so to be honest I have no idea if this would even work or what it would look like. But if I could expose this Observable to page's Controller from within Directive #1 then I should be able to subscribe to it and update Directive #2 when necessary.
Now I have a good understanding of Solution #1 and #2 but they have their own issues:
This very quickly could turn into "callback hell" and doesn't seem to be a very "Angular" solution. This also creates a bit of a tight dependency between the page's Controller and this very generic Chart Directive. Out of my options I think this is the best solution but I would love a better one.
I have to build a way to specify id's on the event names that are unique to that explicit instantiation of the directive, since theoretically there could be more than one of these Chart Directives on the page.
I would love to know if anyone has any other ideas that I haven't thought of or a better approach? Maybe even something that I'm not aware of that Rx.JS offers with Observable's?
TLDR: I need to click on Directive #1 and have it effect what is currently being displayed in Directive #2.
I think this can be done by using two binding scopes in your directive like,
.directive('graphOne', function () {
return {
template: blah/blah.html,
scope: {
scopeToPass: '='
}
}
})
and
.directive('graphTwo', function () {
return {
template: blah1/blah1.html,
scope: {
scopeToGet: '='
}
}
})
and in html
<graph-one scope-to-pass="uniqueScope"></graph-one>
<graph-two scope-to-get="uniqueScope"></graph-two>
Since we are assign $scope.uniqueScope to both directives, and the scopeToPass is two way binding, when the value of scopeToPass get changed it will be passed to uniqueScope and from uniqueScope it will be passed to scopeToGet.

angularjs best practices - communication between directives without $rootScope?

So I have a nested directive that I need to communicate with a separate directive on the page (same controller). I tried the isolate scope approach but given how nested the first directive is, I abandoned that approach. I'm writing this code keeping in mind that $scope might not be around in 2.0. Is there an alternative solution to my approach that would fit with Angular Best practices?
Inside nested directive (3 levels deep):
$scope.chooseCard = function (selectedId) {
this.data = 'init value';
$rootScope.$emit('row chosen', selectedId);
this.data = selectedId;
};
Inside directive #2 that needs data from the nested directive:
$rootScope.$on('row chosen', function (e, data) {
ctrl.id = data;
console.log("this is the IDDDDDD", ctrl.id);
Service.func(ctrl.id);
});
$scope might not be around, but bindings sure will. You have two main options:
Use a service and set this piece of data on there, then watch it in the child directive. This will work, but I do feel like it harms composition and re-use since you can no longer have multiple instances of the directive (they would all depend on the singleton service).
Use an isolate scope as you mentioned earlier and then watch the binding using an '&' expression. This will be the closest you're going to get to Angular2 without using something like ngForward since the flow of data from parent -> child is still the primary method of data-binding in Angular2. This is the preferred way to accomplish this imo even if it ends up being more verbose.
No matter what solution you choose, make sure that you don't leak memory; if you don't unbind that $rootScope.$on handler, then you will leak memory every time that an instance of the directive is created and subsequently destroyed.

In AngularJS, how does $scope get passed to scope?

I'm a bit confused with the use of $scope in controllers and of scope in directives. Please verify if my understanding is correct (and also provide some alternative ways how to do this).
Let's say I have an html:
<div ng-controller="app1_Ctrl">
.
.
.
<input type="text" ng-model="value"/>
<input type="checkbox" />
<button ng-click="submit()"></button>
</div>
And my main.js
(function() {
angular.module('mainApp', ['app1']);
})();
And my app1 looks like this (based on official AngularJS documentation here)
(function() {
var app = angular.module('app1', []);
app.controller('app1_Ctrl', ["$scope", function($scope) {
.
.
.
}]);
app.directive('app1_Dir1', [function() {
function link(scope, element, attr) {
scope.$watch(attr.someAttrOfCheckBox, function() {
// some logic here
});
function submit() {
// some logic here
}
}
return link;
}]);
})();
How does $scope.value passed in scope in directive so that I can do some manipulations there? Will ng-click fire the function submit() in the directive link? Is it correct to use scope.$watch to listen for an action (ticked or unticked of course) in checkbox element?
Many thanks to those who can explain.
By default, directive scope is controller $scope; but it means the directive is directly dependent on your controller and you need a different controller for each instance of the directive you want to use. It is usually considered a best practice to isolate your directive scope and specifically define the variables you wish to pass it from your controller.
For this, you will need to add a scope statement to your directive :
scope {
label :'#',
context : '=',
function : '&'
}
and update your view :
<my-directive label="labelFromController" context="ctxtFromController" function="myFunction()" ></my-directive>
The symbols denote the kind of thing you wish to pass through : # is for one-way binding (as a string in your directive), = is for two-way binding of an object (which enables the directive to update something in your controller), and & is for passing a function.
There are a lot of additional options and subtleties that are best explained by the Angular doc https://docs.angularjs.org/guide/directive. There are also some nice tutorials out there (e.g. http://www.sitepoint.com/practical-guide-angularjs-directives/)
Your submit() function is not attached to anything, so you won't be able to call if from your viewer. You need to define it as scope.submit = function() ... in your link function if you wish to access it.
You can use $watch for this kind of thing, but there are usually other more elegant ways to achieve this by leveraging the fact that angular already "watches" the variables it is aware of and monitors any changes he can (this can be an issue when some external service changes data for exemple, because angular cannot listen to events it is not made aware of). Here, you can probably simply associate the ng-model directive to your input checkbox to store its true/fale (checked/unchecked) value, and the ng-change or ng-click directives to act on it. The optimal solution will mostly depend on the exact nature of your business logic.
Some additional thoughts :
The HTML insides of your directive should be packaged in an inline template field, or in a separate HTML file referenced by the templateUrl field in your directive.
In your HTML code above, your directive is not referenced anywhere. It should be an element, attribute or class (and your directive definition should reflect the way it can be called, with the restrict field). Maybe you have omitted the line containing the directive HTML, but as it stands, your directive doesn't do anything.
To my knowledge, you don't need to return link. Think of it as the "body" of your directive, where you define the variables and functions you will call in the HTML.
Your directive doesn't actually need HTML code and the above thoughts might be irrelevant if you are going in a different direction, but encapsulating some kind of view behaviour that you want to reuse is probably the most common use of directives.

Add AngularJS directive at runtime

I am creating a game where the first thing that needs to happen is some state is loaded in from an external JSON file - the contents of one of my directives are dependent on this data being available - because of this, I would like to delay applying the directive until after the data has loaded. I have written the following:
window.addEventListener('mythdataLoaded', function (e) {
// Don't try to create characters until mythdata has loaded
quest.directive('character', function() {
return {
restrict: 'A',
scope: {
character: '#'
},
controller: 'CharacterCtrl',
templateUrl: 'partials/character.html',
replace: true,
link: function(scope, element) {
$(document).on('click', '#'+scope.character, function () {
$('#'+scope.character+'-popup').fadeToggle();
});
}
};
});
});
// Load in myth data
var myth_data;
$.getJSON("js/mythdata_playtest.json", function(json) {
myth_data = json;
window.dispatchEvent(new Event('mythdataLoaded'));
});
However, it appears that my directive's link function never runs - I'm thinking this is because angular has already executed the part of it's cycle where directives are compiled/linked by the time this directive gets added. Is there some way to force angular to compile this directive after it is created? I googled around a bit, and some people suggested adding $compile to the link function for similar issues - but the link function is never run, so that doesn't work for this case. Thanks!
It seems to me it would be better to always configure the directive, to do the JSON call in the directive, and attach logic to the element in the JSON call's success handler. This would, if I understand you correctly, do what you want.
AngularJS is meant as a framework, not a library, so using it in the way you mentioned is not recommended. Exactly as you mentioned, AngularJS does a lot of things for you when it runs. AngularJS, by default, runs on document loaded, and your $.getJSON callback arrives after that. When AngularJS runs it does all its magic with compiling the content and all that.
As a sidenote, it's also more the Angular way to use $http over $.getJSON.
I think you're thinking about this the wrong way. A major ideology in angular is that you set up declarative elements and let it react to the state of the scope.
What I think you might want to do is pass in what you need through the directive scope, and use other angular built in directives to hide or show your default ("non directive") state until the scope gets set from the controller for example.
Example:
You want a box to be hidden until an api call comes back. Your directive sets special styles on your element (not hidden). Instead of delaying to dynamically set your directive, you can pass in a scope var with a default value and use something like ng-show="data.ready" in your directive template to handle the actual dom stuff.

Angular JS: What is the need of the directive’s link function when we already had directive’s controller with scope?

I need to perform some operations on scope and the template. It seems that I can do that in either the link function or the controller function (since both have access to the scope).
When is it the case when I have to use link function and not the controller?
angular.module('myApp').directive('abc', function($timeout) {
return {
restrict: 'EA',
replace: true,
transclude: true,
scope: true,
link: function(scope, elem, attr) { /* link function */ },
controller: function($scope, $element) { /* controller function */ }
};
}
Also, I understand that link is the non-angular world. So, I can use $watch, $digest and $apply.
What is the significance of the link function, when we already had controller?
After my initial struggle with the link and controller functions and reading quite a lot about them, I think now I have the answer.
First lets understand,
How do angular directives work in a nutshell:
We begin with a template (as a string or loaded to a string)
var templateString = '<div my-directive>{{5 + 10}}</div>';
Now, this templateString is wrapped as an angular element
var el = angular.element(templateString);
With el, now we compile it with $compile to get back the link function.
var l = $compile(el)
Here is what happens,
$compile walks through the whole template and collects all the directives that it recognizes.
All the directives that are discovered are compiled recursively and their link functions are collected.
Then, all the link functions are wrapped in a new link function and returned as l.
Finally, we provide scope function to this l (link) function which further executes the wrapped link functions with this scope and their corresponding elements.
l(scope)
This adds the template as a new node to the DOM and invokes controller which adds its watches to the scope which is shared with the template in DOM.
Comparing compile vs link vs controller :
Every directive is compiled only once and link function is retained for re-use. Therefore, if there's something applicable to all instances of a directive should be performed inside directive's compile function.
Now, after compilation we have link function which is executed while attaching the template to the DOM. So, therefore we perform everything that is specific to every instance of the directive. For eg: attaching events, mutating the template based on scope, etc.
Finally, the controller is meant to be available to be live and reactive while the directive works on the DOM (after getting attached). Therefore:
(1) After setting up the view[V] (i.e. template) with link. $scope is our [M] and $controller is our [C] in M V C
(2) Take advantage the 2-way binding with $scope by setting up watches.
(3) $scope watches are expected to be added in the controller since this is what is watching the template during run-time.
(4) Finally, controller is also used to be able to communicate among related directives. (Like myTabs example in https://docs.angularjs.org/guide/directive)
(5) It's true that we could've done all this in the link function as well but its about separation of concerns.
Therefore, finally we have the following which fits all the pieces perfectly :
Why controllers are needed
The difference between link and controller comes into play when you want to nest directives in your DOM and expose API functions from the parent directive to the nested ones.
From the docs:
Best Practice: use controller when you want to expose an API to other directives. Otherwise use link.
Say you want to have two directives my-form and my-text-input and you want my-text-input directive to appear only inside my-form and nowhere else.
In that case, you will say while defining the directive my-text-input that it requires a controller from the parent DOM element using the require argument, like this: require: '^myForm'. Now the controller from the parent element will be injected into the link function as the fourth argument, following $scope, element, attributes. You can call functions on that controller and communicate with the parent directive.
Moreover, if such a controller is not found, an error will be raised.
Why use link at all
There is no real need to use the link function if one is defining the controller since the $scope is available on the controller. Moreover, while defining both link and controller, one does need to be careful about the order of invocation of the two (controller is executed before).
However, in keeping with the Angular way, most DOM manipulation and 2-way binding using $watchers is usually done in the link function while the API for children and $scope manipulation is done in the controller. This is not a hard and fast rule, but doing so will make the code more modular and help in separation of concerns (controller will maintain the directive state and link function will maintain the DOM + outside bindings).
The controller function/object represents an abstraction model-view-controller (MVC). While there is nothing new to write about MVC, it is still the most significant advanatage of angular: split the concerns into smaller pieces. And that's it, nothing more, so if you need to react on Model changes coming from View the Controller is the right person to do that job.
The story about link function is different, it is coming from different perspective then MVC. And is really essential, once we want to cross the boundaries of a controller/model/view (template).
Let' start with the parameters which are passed into the link function:
function link(scope, element, attrs) {
scope is an Angular scope object.
element is the jqLite-wrapped element that this directive matches.
attrs is an object with the normalized attribute names and their corresponding values.
To put the link into the context, we should mention that all directives are going through this initialization process steps: Compile, Link. An Extract from Brad Green and Shyam Seshadri book Angular JS:
Compile phase (a sister of link, let's mention it here to get a clear picture):
In this phase, Angular walks the DOM to identify all the registered
directives in the template. For each directive, it then transforms the
DOM based on the directive’s rules (template, replace, transclude, and
so on), and calls the compile function if it exists. The result is a
compiled template function,
Link phase:
To make the view dynamic, Angular then runs a link function for each
directive. The link functions typically creates listeners on the DOM
or the model. These listeners keep the view and the model in sync at
all times.
A nice example how to use the link could be found here: Creating Custom Directives. See the example: Creating a Directive that Manipulates the DOM, which inserts a "date-time" into page, refreshed every second.
Just a very short snippet from that rich source above, showing the real manipulation with DOM. There is hooked function to $timeout service, and also it is cleared in its destructor call to avoid memory leaks
.directive('myCurrentTime', function($timeout, dateFilter) {
function link(scope, element, attrs) {
...
// the not MVC job must be done
function updateTime() {
element.text(dateFilter(new Date(), format)); // here we are manipulating the DOM
}
function scheduleUpdate() {
// save the timeoutId for canceling
timeoutId = $timeout(function() {
updateTime(); // update DOM
scheduleUpdate(); // schedule the next update
}, 1000);
}
element.on('$destroy', function() {
$timeout.cancel(timeoutId);
});
...

Categories

Resources