I'm trying to set variable in isolate scope, but I can't access variable that I set in linked function in isolate scope and also I can access controller's variable.
app.directive('myDir', function($timeout) {
return {
restrict: 'A',
scope: {
'propVar': '='
},
link: function(scope, elem) {
console.log(elem)
scope.linkVar = 'It works!'
console.log(scope);
},
}
})
I created plunker to show what I mean: http://plnkr.co/edit/lUBvIkF4fKXkEgujJpuU?p=preview
It work's all as it should be.
1) If you define 'propVar' : '=' it means that your directive element has an attribute prop-var. This is not the case. If you would like to use the prop attribute you have to define your isolated scope in this way: 'propVar' : '=prop'.
2) The child elements of your directive are bound to the controllers scope not to the directive scope. If you would like the the child elements to be part of your directive you may use a template in your directive:
app.directive('myDir', function() {
return {
restrict: 'A',
template: '<div><p>linkVar: {{ linkVar }}</p><p>propVar: {{ propVar }}</p><p>foo: {{ foo }}</p><button ng-click="foo=\'directive sucks\'">press me</button></div>',
scope: {
propVar: '=prop'
},
link: function(scope, elem) {
console.log(elem)
scope.linkVar = 'It works!'
console.log(scope);
},
}
})
see the modified PLUNKR: http://plnkr.co/edit/MHXXkmPdtfAUqtre4Fbg?p=preview
Related
I have a directive treeview which contains a nested directive (being the branches) of each item rendered.
In the scope of both directives I have declared two parameters that should be talking to the parent controller.
filter: '&' //binds the method filter within the nested directive (branch) to the method doSomething() in the tree directive attribute which is bound to the html directive that binds to the controller.
iobj: '=' is the two way binding paramter that should be passing the scoped object to the controller. (but currently isn't)
Directive:
app.directive('tree', function () {
return {
restrict: 'E',
replace: true,
scope: {
t: '=src',
filter: '&',
iobj: '='
},
controller: 'treeController',
template: '<ul><branch ng-repeat="c in t.children" iobj="object" src="c" filter="doSomething()"></branch></ul>'
};
});
app.directive('branch', function($compile) {
return {
restrict: 'E',
replace: true,
scope: {
b: '=src',
filter: '&',
iobj: '='
},
template: '<li><input type="checkbox" ng-click="innerCall()" ng-hide="visible" /><a>{{ b.name }}</a></li>',
link: function (scope, element, attrs) {
var has_children = angular.isArray(scope.b.children);
scope.visible = has_children;
if (has_children) {
element.append('<tree src="b"></tree>');
$compile(element.contents())(scope);
}
element.on('click', function(event) {
event.stopPropagation();
if (has_children) {
element.toggleClass('collapsed');
}
});
scope.innerCall = function () {
scope.iobj = scope.b;
console.log(scope.iobj);
scope.filter();
}
}
};
});
HTML:
<div ng-controller="treeController">
<tree src="myList" iobj="object" filter="doSomething()"></tree>
<a ng-click="clicked()"> link</a>
</div>
Controller:
app.controller("treeController", ['$scope', function($scope) {
var vm = this;
$scope.object = {};
$scope.doSomething = function () {
var item = $scope.object;
//alert('call from directive');
console.log(item);
}
$scope.clicked = function () {
alert('clicked');
}
...
Currently I can invoke the function $scope.doSomething from the directive to the controller. So I know that I have access to the controllers scope from the directive. What I cannot figure out is how to pass an object as a parameter from the directive back to the controller. When I run this code, $scope.object
is always an empty object.
I'd appreciate any help or suggestions on how to go about this.
The & directive binding supports parameter passing. Given your example
scope.filter({message: 'Hello', anotherMessage: 'Good'})
The message and anotherMessage become local variables in the expression bound to directive:
<tree src="myList" iobj="object" filter="doSomething(anotherMessage, message)"></tree>
Here's a sample plunker where the callback parameters are set inside a template.
The documentation clearly states that:
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}).
I'm relative new to AngularJS and trying to create a directive for add some buttons. I'm trying to modify the controller scope from inside the directive but I can't get it to work. Here is an example of my app
app.controller('invoiceManagementController', ['$scope', function ($scope) {
$scope.gridViewOptions = {
isFilterShown: false,
isCompact: false
};
}]);
app.directive('buttons', function () {
return {
restrict: 'A',
template: '<button type="button" data-button="search" title="Filter"><i class="glyphicon glyphicon-search"></i></button>',
scope: {
gridViewOptions: '='
},
transclude: true,
link: function (scope, element, attr, ctrl, transclude) {
element.find("button[data-button='search']").bind('click', function (evt) {
// Set the property to the opposite value
scope.gridViewOptions.isFilterShown = !scope.gridViewOptions.isFilterShown
transclude(scope.$parent, function (clone, scope) {
element.append(clone);
});
});
}
};
});
My HTML like following
{{ gridViewOptions.isFilterShown }}
<div data-buttons="buttons" data-grid-view-options="gridViewOptions"></div>
The scope inside the directive does change but is like isolated, I did try paying with the scope property and transclude but I'm probably missing something, would appreciate some light here
When you modify scope inside of your directive's link function, you are modifying your directive's isolated scope (because that is what you have set up). To modify the parent scope, you can put the scope assignment inside of your transclude function:
transclude(scope.$parent, function (clone, scope) {
// Set the property to the opposite value
scope.gridViewOptions.isFilterShown = !scope.gridViewOptions.isFilterShown
element.append(clone);
});
Ok finally found a solution for this after some more research today. Not sure if the best solution, but this works so good for now.
app.controller('invoiceManagementController', ['$scope', function ($scope) {
$scope.gridViewOptions = {
isFilterShown: false,
isCompact: false
};
}]);
app.directive('buttons', function () {
return {
restrict: 'A',
template: '<button type="button" data-button="search" data-ng-class="gridViewOptions.isFilterShown ? \'active\' : ''" title="Filter"><i class="glyphicon glyphicon-search"></i></button>',
scope: {
gridViewOptions: '='
},
link: function (scope, element, attr, ctrl, transclude) {
element.find("button[data-button='search']").bind('click', function (evt) {
scope.$apply(function () {
// Set the property to the opposite value
scope.gridViewOptions.isFilterShown = !scope.gridViewOptions.isFilterShown;
});
});
}
};
});
preamble: It seems like this question has been asked and answered before, but I cannot seem to get it working, so if my question boils down to "can you debug my code?", I apologize.
I would like to write the following code:
<radio-set ng-model="obj.prop" name="obj_prop">
<radio-set-button ng-value="'public'">Public</radio-set-button>
<radio-set-button ng-value="'protected'">Protected</radio-set-button>
<radio-set-button ng-value="'private'">Private</radio-set-button>
</radio-set>
This renders a bunch of radio buttons and labels which need to populate whatever is passed to the <radio-set>'s ngModel. I'm missing something scope-related.
.directive("radioSet", function () {
return {
restrict: 'E',
replace: true,
scope: {
ngModel: '=?',
ngChange: '&',
name: '#'
},
transclude: true,
template: '<div class="radio-set" ng-transclude></div>',
controller: function () {}
};
})
.directive("radioSetButton", function () {
return {
restrict: 'E',
replace: true,
require: ['^radioSet', '?ngModel'],
scope: {
ngModel: '=?', // provided by ^radioSet?
ngValue: '=?',
ngChange: '&', // provided by ^radioSet?
name: '#' // provided by ^radioSet?
},
transclude: true,
link: function (scope, element, attr) {
element.children().eq(0).attr("name", scope.name); // scope.name is null
},
template: '<label class="radio-set-button">' +
'<input type="radio" name="name" ng-model="ngModel" ng-value="ngValue" ng-change="ngChange()">' +
'<div class="radio-content" ng-transclude></div>' +
'</label>'
};
})
Both the parent and child directives need their own scope definition, but it is unclear to me how to access to the radioSet's scope from within radioSetButton.
thanks for the help.
fiddle: http://jsfiddle.net/pmn4/XH5K2/2/
Transclusion
I guess i have to tell you that the transclusion you used in your directive does not work as you expect because in short: The transcluded directive doesn't inherit the scope you'd expect, actually it inherits the scope of the outer controller, but there are plenty of answers on this topic:
Access Parent Scope in Transcluded Directive
How to solve
To access a parents directive there are basically two ways:
1.) Require the parents directive's controller and create some API to do stuff on the parent
2.) Use the fifth parameter of the link function to access the transclude function, here you could change the injected scope and you could set it to the parents directive scope
Since the first solution is more intuitive i will go with this one:
On the radioSetdirective i set up a bidirectional databinding to the object in my Controller and i create a getter and setter method to interact with the value.
In the "child"'s directive i require the parent directive's controller which i get passed as the fourth parameter in my link function. I setup a click handler on the element to get the click and here i call the parents setter method with my value. To visualize the current selected object i add an ng-class directive which conditionally adds the active class.
Note: This way you can use the ngModel directive as well. It has an API to interact with the model.
The second solution uses the transclude function which you can use to pass in a scope. As i dont have time right now and as it adds more complexity i'd recommend using the first solution.
Recommendation
For your example transclusion might not be the right choice, use one directive and add the choices to the template or pass them into the directive. As i dont know what your intentions are i provided this solution. (I didn't know what the purpose of this name property is?)
The Code
Fiddle: http://jsfiddle.net/q3nUk/
Boilerplate:
var app = angular.module('myApp', []);
app.controller('MainController', function($scope) {
$scope.object = {
'property' : 'public'
};
});
The Directives:
app.directive('radioSet', function() {
return {
scope : {
radioValue : '='
},
restrict : 'E',
transclude : true,
replace : true,
template : '<div class="radioSet" ng-transclude></div>',
controller : function($scope) {
this.getRadioValue = function() {
return $scope.radioValue;
}
this.setRadioValue = function(val) {
$scope.$apply(function() {
$scope.radioValue = val
});
}
}
};
});
app.directive('radioSetButton', function() {
return {
restrict : 'E',
transclude : true,
replace : true,
scope : true,
template : '<div class="radioSetButton" ng-class="{active:isActive()}" ng-transclude></div>',
require : '^radioSet',
link : function(scope, elem, attrs, radioSetController, transclude) {
scope.isActive = function() {
return attrs.buttonValue === radioSetController.getRadioValue();
};
elem.on('click', function() {
radioSetController.setRadioValue(attrs.buttonValue);
});
}
};
});
The HTML:
<html>
<body ng-app="myApp">
<div ng-controller="MainController">
<p>{{ object.property }}</p>
<radio-set radio-value="object.property">
<radio-set-button button-value="public">Public</radio-set-button>
<radio-set-button button-value="private">Private</radio-set-button>
<radio-set-button button-value="protected">Protected</radio-set-button>
</radio-set>
</div>
</body>
</html>
CSS:
.radioSetButton {
display : block;
padding : 10px;
border : 1px solid black;
float : left;
}
.radioSetButton:hover {
cursor : pointer;
}
.radioSetButton.active {
background-color : grey;
}
I have the following directive:
CorrelatorApp.directive('correlator', function ($WebApi) {
return {
restrict: 'A',
scope: {
crOptions: '=',
},
link: function (scope, element, attrs) {
var options = scope.crOptions;
}
}
});
then in my index.html I use it like this:
<form correlator cr-options="correlatorOptions" name="CorrelatorForm" ng-controller="PortalMerchantController">
and my correlatorOptions are defined in the controller:
CorrelatorApp.controller("PortalMerchantController", function
PortalMerchantController($scope, $http) {
$scope.correlatorOptions = {
dependant: {
controller: 'PortalMerchant',
model: 'portalMerchants',
nameField: 'PortalsMerchantName'
},
principal: {
controller: 'Merchant',
model: 'merchants',
nameField: 'Name'
}
};
});
when the directive links, the value of scope.crOptions is undefined. If I set crOptions to & and then call it (var options = scope.crOptions()), the code executes correctly and I get the object defined in the controller. What am I missing?
Move your ngController directive outside of the form element.
In 1.2.0 and greater, the ngController and form have sibling scopes (previously they would have shared the isolate scope). Here's the change that causes this
You want form to be a child of ngController so it can access it's scope:
<div ng-controller="PortalMerchantController">
<form correlator cr-options="correlatorOptions" name="CorrelatorForm"></form>
working fiddle
I'm trying to use ngRepeat in a directive - but I really don't know how to do it. Am I close?
Mowers is an array in my controller where this directive is used.
.directive('slider', function() {
return function() {
template: '<slider><film><frame ng-repeat="mower in mowers">{{mower.series}}</frame></film></slider>';
replace: true;
scope: true;
link: function postLink(scope, iElement, iAttrs) {
// jquery to animate
}
}
});
Yes, you're close, but the syntax was off. and you need to assign something to mowers on your scope.
.directive('slider', function() {
return {
restrict: 'E', //to an element.
template: '<film><frame ng-repeat="mower in mowers">{{mower.series}}</frame></film>',
replace: true,
scope: true, // creates a new child scope that prototypically inherits
link: function postLink(scope, iElement, iAttrs) {
scope.mowers = [
{ series: 1 },
{ series: 2 },
{ series: 3 }
];
}
}
});
Also, you need to not reference <slider> in itself.
The above code also assumes you've made directives for <film> and <frame>.
Finally, if you wanted to pass the mowers array in from an external scope, you'd change your scope setting to this:
scope: {
mowers: '='
}
And you could then set it like so:
<slider mowers="myMowers"></slider>
where myMowers is a variable on your controller or parent directive's scope.
I hope that helps.