I have a directive for parent scope, I also have another directive for child scope. In a template, I have several parent-child scope. Like this.
ParentScope1
- ChildScope1
ParentScope2
- ChildScope2
If I change a value in Parent, I will broadcast it to Child. I am using $rootScope.$broadcast to broadcast from parent. I am using $rootScope.$on to accept this change in child.
My problem is:
Now, If I change a value in ParentScope1, it will broadcast to ChildScope1. Then I will change a value in ParentScope2, it will broadcast to ChildScope2, but it will also broadcast to ChildScope1.
I want: Change a value in ParentScope1, it will broadcast to ChildScope1. Change a value in ParentScope2, it will broadcast to ChildScope2. I search online for some time but did not find the solution for it. Maybe I did not use the correct keywords for searching it. Please advise. Thank you.
In your definition of directive set
scope : true
then use
$scope.$broadcast
$scope.$on
this should probably works fine
post your code so we have a better view of the problem
You are looking for communication between parent and child directive, you can use below approach but both directive will be tightly coupled using this-
Require a controller - Get the handle of same node of parent node directive controller.
Require: '^parnetDireName' is used to find the controller on parent node. Without ^, it will find for same node only. '?' is used when controller may not be available.''?^", "?^^", "^^". Fourth parameter of link function is used to get the controller. It can use the controller prop/method. You can also access multiple controller because Require will have array - require: ['^dir1','^dir2']. Link function will have cntrl array and it can be access through array element in the same sequence
Pre link and post link function for nested directive -
Default link function is post link function.
Use keyword post to define it explicitly
Child post link function is executed first if parent and child both has Post link function
Parent link function is executed first if both parent and child has pre-link function.
Controller is executed before link function
---------------------------------Another decoupled way ---------------------
There are three ways to setup the relation between directive scope and containing controller scope-
- Directive with shared scope with containing controller. Any new
item/modified item by directive will be part of parent scope. It is
default or scope:false
- Directive with inherited scope. Any new item added by directive will not be visible by containing controller. Directive scope can read all data from parent scope. Use scope:true property to activate it. Child can see parent data and can override or create new variable.
- Isolated scope. Both scope cannot read each other data. Object mapping is required to read the parent data. Directive scope mapping will have object name and same object will be passed from html. There are three ways to receive the parameter-
- Complete object Data -> '=' is used
- Simple value like flag as a String- '#' is used . '#sampleVar', where sampleVar is the name of variable in the html.
Scope {
cntrollerStrVarName: '#htmlStrVarName'
}
- Function parameter - '&' is used to pass the parameter. Method parameter can be overridden using ({paramName: 'value'})
Related
I am looking for advice on how to implement a hierarchical structure in Angular, where a directive (<partition>) can call a method on a child directive's controller (<property-value>).
I have put together a detailed example here:
https://jsfiddle.net/95kjjxkh/1/
As you can see, my code contains an outer directive, <partition>, which displays one or more <property-value> directives within.
The <property-value> directive offers an editing method, editItem(), which allows the user to change the value of a single entry. (To keep my example short, I simply assign a random number here, but in my production app, a modal will appear, to query the user for a new value.)
This works fine. However, in the outer directive, <partition>, I would like to add the ability to create a new, blank <property-value> directive and then immediately call its editing method so that the user can enter an initial value. If no initial value is entered, the new item would be discarded.
I have seen examples of inner directives calling methods on enclosing directives, but not the other way around.
Is there a way to do this? Alternatively, is there a better way for me to build this kind of view?
You can always use $broadcast to talk both ways. To your parent as well as to your childrens.
In your Child controller you can do the following
app.directive('propertyValue', function() {
return {
require : '^partition'
restrict: 'E',
scope: {
item: '='
},
with this you will get the parent controller in child directive's link function like this
link:function(scope,element,attrs,partitionCtrl){
partitionCtrl.getChildCtrl(element)
}
in partition controller create getChildCtrl function and with that call "propertyvalue" controller function
controller: function ($scope, ItemFactory) {
// your code
var propValueCtrl =undefined;
this.getChildCtrl =function(elem)
{
propValueCtrl = elem.controller();
}
this.callChildFunction = function()
{
propValueCtrl.Edit();// whatever is the name of function
}
call this function when needed in property link function.
Hope this helps.
It is clear that a method should be set to scope in order to be visible or available for the view (in html) or in a directive or in any other place where the method should be accessed, so that the method can be accessed through the $scope. My question is to know whether $scope is always necessary or a good practice to use when a method is defined. For instance, following are different method declarations:
Scenario 1. $scope.myMethod = function(){};
Scenario 2. var myMethod= function(){};
if 'myMethod' is only used in one controller, is it required to set it to the $scope? What are the advantages or why scenario 1 or 2 is good ?
What if someone has declared it as $scope.myMethod = function(){} ? is it not good or an unnecessary load to the $scope ? What can be the best practice?
NB: I don't need to make any poll here, please let me know any pros and cons
You mainly use the first scenario for things like binding click events. If you will only call the myMethod you don't really need to define it in scope.
For example following will need the first definition:
<button ng-click="myMethod()">My Button</button>
But following can use the second:
angular.module('myCtrl', [])
.controller('myController', function($scope) {
var myMethod = function (text) {alert(text)};
$scope.mySecondMethod = function () { myMethod('second'); }
$scope.myThirdMethod = function () { myMethod('third'); }
In second case you can use mySecondMethod and myThirdMethod in event binding.
Scenario 1. $scope.myMethod = function(){};
Scenario 2. var myMethod= function(){};
Scope is the glue between the controller and the view. If you really
need any variable and methods for the current view, then this should
be added to the scope variable. Please see the controller as property
if you don't want to add the methods to scope.
Scenario 1
If you declare a method using the scope, then this will be available/accessed from the view.
Scenario 2
If you really don't need this method to be accessed from the view, then you can remove this method from the scope.
You need $scope to define a method only if you are calling that function from your html code.
You can use this or some_names also instead of $scope.
$scope is just to mean that the function's (mehtod's) scope is inside that controller function and can be accessible from the html code that written inside the controller
If the function is calling inside the javascript (controller) only, then use normal definition
function myMethod(){}
OR
var myMethod = function(){}
Declaring a method or variable as $scope variable is only for accessing from DOM. If you are creating a new variable in $scope. It just adding that variable to the clousure of $scope as $scope: {first, seccond, third}
When ever you are calling a $scope function that just returns from the closure. There is not much load to the $scope I guess
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.
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 the DOM. This is an important point since it
makes the controllers view agnostic, which greatly improves the
testing story of the applications.
From the documentation
I created a directive, I´m using in my template:
<data-input> </data-input>
In my directive (dataInput) I have the template (...data-input.html).
In that template the html-code says:
<input ng-change="dataChanged()" ... > ...
In the JavaScript it says:
scope.dataChanged= function(){ //change the data }
Works perfectly, but know I need to safe the changed data to a variable in my controller (that has a different scope of course).
The template, where I put the <data-input> </data-input> belongs to the final scope, I´m trying to reach.
I tried it via parameter, but it didnt work.
Thanks
Here are your options:
You can nest controllers if possible. This will create scope inheritance and you will be able to share variables of the parent.
You can use $rootscope from both the controllers to share data. Your dataChanged function can save anything to the $rootscope
You can use angular.element to get the scope of any element. angular.element('data-input').scope() returns it's cope.
This is not recommended. But there are circumstances in which people use global space to communicate between angular and non-angular code. But I don't think this is your case.
You can use Angular Watches to see changes is some variable, like this
eg:
$scope.$watch('age + name', function () {
//called when variables 'name' or 'age' changed
//Or you can use just 'age'
});
Hello I have directive foo in which controller I have
$scope.valid = false
I am passing this variable inside another directive through isolated scoping in my template
<bar valid="valid">
and using an ng-if inside my template
<span ng-if="valid">Validated<span>
Now when I update valid in my child directive. It shows validated in my template. But the variable did not update in my parent directive controller. Why this is happening?
Note: In my child controller I am attaching the variable to controller instead of scope. Is this the reason it is behaving like this.
Indeed, if, in your child directive controller code, you write
function MyController($scope) {
this.valid = $scope.valid;
}
then setting the controller object's valid property is not going to change the $scope.valid, because you performed a copy of valid.
Instead, keep using $scope to pass the information about the change back up to the parent.
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);
});
...