Change Controller variable value from change in isolate scope directive - javascript

So I know with two way binding, =, in a directive a controller's value can be passed into the directive. But how do you pass a change in the isolated directive back to the controller?
So for example, I have a form that has a slider built with a directive with an isolated scope. Initial value is set from controller, then changes in directive with isolate scope.
What I'm trying to do is change the controller's value when the directive variable with two way binding changes as well.
Any suggestions?

You have two possible ways of achieving this. One is creating a watch statement in the controller on the variable that you passed into the directives isolate scope.
// code in view controller
$scope.sliderValue = 0;
$scope.$watch('sliderValue', function(newValue) {
if (angular.isDefined(newValue)) {
// Do stuff with new slider value
}
});
Note that we need the isDefined, because every watch fires on scope compilation with the initial value being undefined.
The other way is enhancing your directive with a parameter which is evaluated as your slider value changes (much like a callback function).
// sample directive code
angular.module('my-ui-controles', []).directive('mySlider', [function() {
return {
template: '...',
scope: {
value: '=mySlider',
onChange: '&'
},
link: function(scope, elem, attrs) {
// assume this is called when the slider value changes
scope.changeValue = function(newValue) {
// do other internal stuff and notify the outside world
scope.onChange({value: newValue});
}
}
}
}])
And now you can use this in the template likes this:
<div my-slider="sliderValue" on-change="doStuff(value)"></div>
What happens now is as soon as the slider value changes we evaluate the onChange expression that is passed into the directive. The value in doStuff is filled with your new slider value. The object that we have passed on to onChange actually is the scope with which the expression is evaluated and doStuff can be any method from your controller.
The main benefit is that you have the logic for notifying somebody in your directive rather than implicitly in your controller through a watch.
Hope that gets you in the right direction.

If you have pass any variable to isolated scope you can bind it (set any listener on it) in both sides.You can event use Angularjs Events to send any signal, if variable as been changed.
maybe this articles can help you.
http://www.w3docs.com/snippets/angularjs/bind-variable-inside-angularjs-directive-isolated-scope.html
http://www.w3docs.com/snippets/angularjs/change-variable-from-outside-of-directive.html

Related

Angular directives that call methods on child directives

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.

get the argument of directive function attribute and change it in another contoller angularjs

I have a tree directive and I want when user clicked on some thing happens..it should be implement with who wants to use this tree. for example node's text changes.
index.html
<div ng-controller='TestController'>
<tree model="treedata" on-node-clicked="changeNodeText($node)"></tree>
</div>
Directive
function treeDirectiveFactory() {
return {
restrict: 'E',
scope: {
model: '=model',
collapseIcon: '=',//IIconProvider//TODO:
expandIcon: '=',//IIconProvider//TODO:
onNodeClicked:'&',//param:$node//TODO:
isExpanded:'#'
},
templateUrl: '/_Core/DirectiveCore/Tree/TreeTemplate.html',
controller: 'TreeController',
controllerAs: 'c'
}
};
part of template
<a href={{node.link()}} class="node-link" ng-click="c.onNodeClicked(node)"
ng-class="{'margin-no-child':node['__$extension'].isLeaf(node)}">
<span ng-bind-html="node.iconProvider().htmlPath()"></span>
{{node.text()}}
</a>
when user click on node, ng-click="c.onNodeClicked(node)" will call and the node which is clicked is passed to onNodeClicked function. below is the implementation of this function in controller as c of tree directive
onNodeClicked(node: Core.INode) {//TODO:
if (this.scope["onNodeClicked"]) {
this.scope["onNodeClicked"] = ({$node:node});
}
}
I want to tell the function that you have an argument named $node and set the value of $node. then I want to change the $node text in outer controller TestController in index.html...this is the changeNodeText function in TestContoller
f($node: Core.TestNode) {
if($node !== undefined)
$node.t = "Clicked!";
}
but nothing changes, actually changeNodeText function never called. I know there is something wrong but unfortunately I can not figure it out. any help would be appreciated.
Answer for #Parud0kht
Instead of setting the function, invoke it.
onNodeClicked(node: Core.INode) {//TODO:
if (this.scope["onNodeClicked"]) {
//Do THIS
this.scope["onNodeClicked"]({$node:node});
//Not THIS
//this.scope["onNodeClicked"] = ({$node:node});
}
}
Answer for Other Readers
This example does it with components, but the same principle applies to directives.
angular.module('app.dashboard')
.component('dashboardComponent', {
templateUrl: 'app/dashboard/directives/dashboard-container.html',
controller: DashboardComponent,
controllerAs: 'DashboardCtrl',
bindings: {
onTileChange: "&"
}
})t
To communicate event data from a component to a parent controller:
Instantiate the dashboard-component with:
<dashboard-component on-tile-change="HomeCtrl.onTileChange($tile)">
</dashboard-component>
In the component controller invoke the function with locals:
this.onTileChange({$tile: tile});
The convention for injected locals is to name them with a $ prefix to differentiate them from variables on parent scope.
From the Docs:
& or &attr - provides a way to execute an expression in the context of the parent scope. If no attr name is specified then the attribute name is assumed to be the same as the local name. Given <my-component my-attr="count = count + value"> and the isolate scope definition scope: { localFn:'&myAttr' }, the isolate scope property localFn will point to a function wrapper for the count = count + value expression. Often it's desirable to pass data from the isolated scope via an expression to the parent scope. This can be done by passing a map of local variable names and values into the expression wrapper fn. For example, if the expression is increment($amount) then we can specify the amount value by calling the localFn as localFn({$amount: 22}).
-- AngularJS Comprehensive Directive API Reference
someone answered my question completely right, but unfortunately the answer was removed so quickly, I was so lucky to saw it but I could not accept it as an answer..thanks to anonymous user for his or her great answer. hope he\she will see this answer and contact me.
I should invoke the function in onNodeClicked not set it..how crazy I am !! :))) here is the answer which is working well.
onNodeClicked(node: Core.INode) {
if (this.scope["onNodeClicked"]) {
//invoking, function should be called.
this.scope["onNodeClicked"]({ $node: node });
//this is wrong and it will set an object
//this.scope["onNodeClicked"] = ({ $node: node });
}
}

Three state checkbox with angular directive

I'm new to angularjs and I'm having a very difficult time attempting to create a directive to implement a three-state checkbox. I'd like to create a two-way binding between the isolated scope variable in my directive and my controller scope variable. Obviously somehow I've cocked it up.
My goal is to be able to update the state of the checkbox in the dom whenever the state variable gets updated in the controller, and to update the state variable in the controller whenever the user clicks on the checkbox.
After snooping around on the google machine i decided I needed to set up a $watch on my directive attribute (my knowledge of which is tenuous as best). I haven't been able to get that to work. It never gets called when my variable is changed from the controller. Below is a snippet of my markup and the meat of my directive. See my fiddle below for the details.
<input checkbox-three-state='item.state' type='checkbox' />
directive('checkboxThreeState', ['$compile', function($compile) {
return {
restrict: 'A',
scope: {
state: '=checkboxThreeState'
},
link: function (scope, element, attributes) {
element.on('click', function () {
// update the controller scope
});
scope.$watch(attributes.checkboxThreeState, function (newVal, oldVal) {
// update the dom
});
}
}
}]);
I think I have my other objective (update controller scope on click) down.
Here's my fiddle.
I realize now that my question was poorly framed, it really should have been asking why the watch I set up in my directive wasn't working. Now i know that I was telling it to watch the wrong scope property, one that didn't actually exist. Mea culpa. Previously I was telling it to watch my directive attribute -- I had just copied somebody else's example, because I'm a monkey.
scope: {
state: '=checkboxThreeState'
},
link: function (scope, element, attributes) {
// other stuff
scope.$watch(attributes.checkboxThreeState, function (newVal, oldVal) {
// update the dom
});
}
After doing a lot more snooping -- I don't know why this isn't spelled out more ubiquitously for idiots like myself -- I recognized that I had to indicate which property on my scope to watch, namely 'state'.
scope.$watch('state', function (newVal, oldVal) {
// do my jam
});
Now that i've figured that out it seems entirely obvious, but I can be pretty dense.
Here's my now working fiddle.
Updated Question
Why doesn't the watch function in my directive get fired when the controller changes the value of the variable that gets passed into my directive?
Answer Summary:
The watch function should watch a variable that actually exists on your directive scope variable. This is never spelled out in the documentation and although it may be obvious to most, I didn't put that together immediately. I was under the impression -- and to be fair to myself there are a lot of really bad examples out there that severely misled me -- that I should be watching my directive attribute, whose value comes from the controller scope.

Isolate Scope 2 way bindings doesn't update the value of parent scope

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.

Passing into a directive without adding it to scope

I have a directive where a list(array) of items is passed in through the scope of the controller into the scope of the directive whereby the template of the directive then has access to the items.
I would like to have it so that the list of items is passed to the directive (where it is then used within the link function) and then not directly accessible through the directive's template.
i.e. if we had the following directive:
directive('itemList', function(){
return {
scope: {
items: '='
}
link: function(scope, elem, attrs){
var item-list = scope.items;
// ... want to manipulate first ...
}
}
})
the variable scope.items is now available to any template that the directive uses. Whereas I don't want that to be the case and would like to pass in something to the directive without making it known to the scope. Is this possible?
Since the directive scope pretty much by definition is the scope used by the directive's template, I don't see any obvious way of doing this in a strict information hiding way. But, why not just use a different name for the passed scope variable and what the template binds to? For example, if you said scope: { myPrivatePassedItems: '=items' }, you can now work with scope.myPrivatePassedItems as much as needed before setting it as scope.items. With this approach, the HTML in both the usage of the directive and the directive's template just sees "items", but internally your directive has its own "private" variable.
I should add that the above is the simple change needed for the one-way data flow from the consumer to the directive template. If you also need to update the original items array, you will also want to add a scope.$watch on the scope.items array after you have done your initial setup. You would then need to carry those changes (possibly with modifications) back to scope.myPrivatePassedItems in order to update the consumer's array.
You can use the $parse service to retrieve the value without using scope: {}, but you will lose the 2 way data binding that you inherently get from using scope: { items: '=' }. As mentioned by Dana Cartwright, if you need 2 way binding, you have to set this up manually with watches.
directive('itemList', function($parse){
return {
link: function(scope, elem, attrs){
var itemList = $parse(attrs['items'])(scope);
// do stuff with itemList
// ...
// then expose it
scope.itemList = itemList;
}
};
});

Categories

Resources