AngularJs: Calling contoller method from directive inside another directive - javascript

I have a directive inside another directive. Outer directive shares its scope with controller, while inner one has its own. I'd like to pass a reference to controller's function to inner directive so it can be called from there. But I cannot figure out how to pass the function and its parameters to inner directive so it can properly call the controller's function.
Here is planker to illustrate my problem.
If you click on "Dir 2 Click me" the alert says the parameters have came undefined.

You can pass in the outer controller method using '=' and adjust the code accordingly...
angular.module('app', [])
.controller('ctrl', function($scope){
$scope.myCtrlMethod = function(msg, b) {
alert(msg + ' and b='+b);
};
})
.directive('dir1', [function(){
return {
restrict: 'E',
replace: true,
template: '<div><p ng-click="myDir1Method(\'my dir1 method\',\'b\')">Dir 1 Click me</p><dir2 my-ctrl-method="myCtrlMethod"></dir2></div>',
link: function(scope, elem, attrs){
scope.myDir1Method = function(msg,b){
scope.myCtrlMethod(msg, b);
};
}
};
}])
.directive('dir2', [function(){
return {
restrict: 'E',
scope: {
myCtrlMethod: '='
},
replace: true,
template: '<p ng-click="myDir2Method(\'my dir2 method\',\'b\')">Dir 2 Click me</p>',
link: function(scope, elem, attrs){
scope.myDir2Method = function(msg,b){
scope.myCtrlMethod(msg, b);
};
}
};
}]);
Plunker: http://plnkr.co/edit/xbSNXaSmzWa3G1GSH6Af?p=preview
Edit: '=' evaluates the expression in the context of the parent scope and its result is bound to the property on the inner scope. In this example, 'myCtrlMethod' is evaluated against the parent scope, which returns myCtrlMethod from the parent scope (a function). This function is bound to myCtrlMethod on the inner scope, and can be invoked with scope.myCtrlMethod(msg, b).

you can use controller as a reference to your directive
See: http://jsbin.com/vayij/1/edit
directive('sonDirective', function(){
return {
restrict: 'E'
scope: {},
replace: true,
template: '<div....'
controller: 'MainController' //controller as a reference
}
})

Just put the controller on the scope : $scope.$b=this;
See : http://plnkr.co/edit/skDF8D1scFJYrTUmcXIL?p=preview

Related

Resolving scope variables in an angular directive

I'm working on creating an angular directive (element) that will apply some transformation to the text within.
Here is the directive:
module.directive('myDir', function() {
return {
restrict: 'E',
link: function(scope, elem, attrs) {
console.log(elem.text());
},
};
});
For now I've just placed a console.log in there to make sure I am seeing the expected text.
If my html is like this it works as expected:
<my-dir>Hello World!</my-dir>
However, if I use a variable from the scope such as this:
<my-dir>{{myMessage}}</my-dir>
Then that is the text I seen in the console output instead of the variables value. I think I understand why the output is this way, however I'm not sure how to get the correct value. The requirement is that the directive should be able to transform text from both examples.
Thoughts?
Use $interpolate service to convert the string to a function, and apply the scope as parameter to get the value (fiddle):
module.directive('myDir', function($interpolate) {
return {
restrict: 'E',
link: function(scope, elem, attrs) {
console.log($interpolate(elem.text())(scope));
},
};
});
If you really interested get the interpolated content then do use $interpolate service to evaluated the interpolated content.
Code
link: function(scope, elem, attrs) {
console.log($interpolate(elem.text())(scope));
},
Don't forget to add $interpolate as dependency on directive function.
Have a look on $compile or http://odetocode.com/blogs/scott/archive/2014/05/07/using-compile-in-angular.aspx
This should work:
module.directive('myDir', function($compile) {
return {
restrict: 'E',
link: function(scope, elem, attrs) {
console.log($compile(elem.text())(scope));
},
};
});
I would suggest you use an isolated scope on directive which will give you access to the value without having to get it from the dom. You will also be able to manipulate it directly in the link function as part of scope
<my-dir my-message="myMessage"></my-dir>
JS
module.directive('myDir', function() {
return {
restrict: 'E',
template: '{{myMessage}}',
scope:{
myMessage: '=',
},
link: function(scope, elem, attrs) {
console.log(scope.myMessage);
},
};
});

How to access both require: 'ngModel' and controller properties on directive

I would like to define both controller, and require on a directive. Something like this:
return {
controller: 'ValidateElementCtrl as validate',
require: 'ngModel',
scope: {
ngModel: '='
},
link: link
};
Now when defining either controller, or requiring ng model, in the link function you get access only to the fourth argument. I know that the fourth argument can be an object and contain multiple controllers and such, but that's in case when you are defining the controllers as an array.
This is what I have and I don't know how to access the controller, I get the required ngModel as a fourth argument:
function link(scope, element, attrs, ctrlGetsNgModel) {
console.log(ctrlGetsNgModel) // Returns the ngModel, no sign on the controller
}
I know I could define a controller on a directive, and pass it in as a scope property, but in this case I would like to define a controller for the directive which would handle the validation and similar, and that controller would be assigned only to this directive.
EDIT:
Found a way how to do this:
To have both ngModel and controller in the link function you can assign the controller to the template like this:
Then define the scope access to the someDirectiveName: '=', and you can access the controller on your directive scope `scope.someDirectiveName' =< the controller scope.
Not exactly sure what you are trying to achieve but I don't think you can put 'controller as' in a string like that in a directive definition. Use the controllerAs property, so something like:
return {
// ...
controller: controller,
controllerAs: 'validate'
// ....
};
You will to also use bindToController: true if you want to access any properties defined on an isolate scope. But I'm not sure if you need an isolate scope at all..
Could you clarify what your actual goal is? Is this what you are aiming for???
DEMO
html
<body ng-controller="MainCtrl">
<foo ng-model="someModel"></foo>
</body>
js
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
// the element on which you apply the directive will need to have a model
// defined if you use `require: 'ngModel'`
$scope.someModel = {modelValue: 'blah'};
});
app.directive('foo', function(){
return {
controller: controller,
controllerAs: 'validate',
template: '<div>{{validate.someProp}}</div>',
require: 'ngModel',
link: link
};
function controller(){
this.someProp = 'bar';
}
function link(scope, element, attrs, ctrlGetsNgModel) {
console.log('ctrlGetsNgModel',ctrlGetsNgModel) // Returns the ngModel, no sign on the controller
}
});
Suppose your directive's name is "validateField", then you can pass an array like this:
return {
controller: controller,
controllerAs: 'validate',
require: ['ngModel', 'validateField'],
scope: {
ngModel: '='
},
link: link
};
Then in the link function the fourth parameter will be an array that contains both ngModel controller and the directive's controller.
function link(scope, element, attrs, ctrlArray) {
console.log(ctrlArray[0]) // Returns the ngModel controller
console.log(ctrlArray[1]) // Returns your controller
}

Angular directive scope evaluation with function binding ('&')

We are seeing some unexpected behavior in how a function is being bound to a directive scope. Here is a jsbin example.
To summarize - we have a directive that has a scope object as follows:
scope: { fn: '&', val: '#' }
The directive displays the result of fn twice. First we display the result as it is evaluated in the template, then we display the result when evaluated in the link function:
<div><code>fn (&)</code>: {{fn()}}</div>
<div><code>fn result ($scope.result = $scope.fn()) </code>: {{result}}</div>
We then use this scope in another directive:
app.directive('rootDirective', function() {
function link($scope, $elem, $attrs) {
$scope.name = 'directive with scope';
}
return {
scope: 'isolate',
replace: true,
restrict: 'E',
link: link,
template: [
'<div add-scope-directive="">',
' <div ng-repeat="n in [1]">',
' <sub-dir val="{{val}}" fn="fn()" name="{{n}}"></sub-dir>',
' </div>',
' <sub-dir val="{{val}}" fn="fn()" name="{{name}}"></sub-dir>',
'<div>'
].join('\n')
};
});
On the root node of this directive we have another directive add-scope-directive. In this directive we define fn - which returns "add-scope-directive - fn".
We would now expect to see that the result of fn ("add-scope-directive - fn") to be the same throughout the directive. However the result from the link function of the child directive 'sub-dir' when it is not used in a repeater is different - instead it is coming from the function on the MainCtrl.
The question is - are our expectations correct and is this a bug? Or should we be expecting what we see here and if so why?
Not a proper solution, but a workaround could be to put a timeout into the link function of sub-dir, like so:
setTimeout(function() {
$scope.result = $scope.fn();
$scope.$apply();
}, 0);

Call function on directive parent scope with directive scope argument

I am developing a directive which shows and hides it's contents based on a click event (ng-click) defined in it's template. On some views where the directive is used I'd like to be able to know if the directive is currently showing or hiding it's contents so I can respond to the DOM changes. The directive has isolated scope and I am trying to notify the parent scope when the directive has been "toggled". I'm attempting to accomplish this by passing a callback function to the directive where it is used that can be called when the directive's state changes i.e hides or shows
I'm not sure how to correctly implement this being that the state of the directive (hidden or shown) is stored in the directive's isolated scope and is determined after the ng-click. Therefore I need to call the parent scope's function from within the directive and not from withing the view.
This will make WAAY more sense with an example. Here is a plunked demonstrating what I'd like to do:
http://plnkr.co/edit/hHwwxjssOKiphTSO1VIS?p=info
var app = angular.module('main-module',[])
app.controller('MainController', function($scope){
$scope.myValue = 'test value';
$scope.parentToggle = function(value){
$scope.myValue = value;
};
});
app.directive('toggle', function(){
return {
restrict: 'A',
template: '<a ng-click="toggle();">Click Me</a>',
replace: true,
scope: {
OnToggle: '&'
},
link: function($scope, elem, attrs, controller) {
$scope.toggleValue = false;
$scope.toggle = function () {
$scope.toggleValue = !$scope.toggleValue;
$scope.OnToggle($scope.toggleValue)
};
}
};
});
I'm relatively new to Angular. Is this a bad idea to begin with? Should I be using a service or something rather than passing around function refs?
Thanks!
Update
You can also use & to bind the function of the root scope (that is actually the purpose of &).
To do so the directive needs to be slightly changed:
app.directive('toggle', function(){
return {
restrict: 'A',
template: '<a ng-click="f()">Click Me</a>',
replace: true,
scope: {
toggle: '&'
},
controller: function($scope) {
$scope.toggleValue = false;
$scope.f = function() {
$scope.toggleValue = !$scope.toggleValue;
$scope.toggle({message: $scope.toggleValue});
};
}
};
});
You can use like this:
<div toggle="parentToggle(message)"></div>
Plunk
You could bind the function using =. In addition ensure the property name in your scope and tag are matching (AngularJS translates CamelCase to dash notation).
Before:
scope: {
OnToggle: '&'
}
After:
scope: {
onToggle: '='
}
Furthermore don't use on-toggle="parentToggle({value: toggleValue})" in your main template. You do not want to call the function but just passing a pointer of the function to the directive.
Plunk

Angular - Passing variables to the directive scope

I would like to pass an object to the directive scope:
JS:
app.directive('validatePrice', function() {
return {
link: function(scope, el, attrs){
console.log(attrs.validatePrice);
}
};
});
HTML
<button validate-price="{priceValid: {'disabled': 'disabled'}}">Checkout</button>
where priceValid is a boolean from the controller scope and {'disabled': 'disabled'} is just a plain object. I expect my attrs.validatePrice to return eg:
{
true: {'disabled': 'disabled'}
}
Yet it returns string. How do I do that? :)
I don't think what you want is possible. priceValid will be interpreted as an object key by JavaScript – it will not be interpreted as true.
Anyway, $parse or $eval is what you need to use to pass an object to a directive (if you are not using an isolated scope):
<button validate-price="{priceValid: {'disabled': 'disabled'}}">Checkout</button>
app.directive('validatePrice', function($parse) {
return {
link: function(scope, el, attrs){
var model = $parse(attrs.validatePrice);
console.log(model(scope));
console.log(scope.$eval(attrs.validatePrice));
}
};
});
fiddle
Use $parse if you need to alter the object. See https://stackoverflow.com/a/15725402/215945 for an example of that.
<validate-price-dir validate-price="{priceValid: {'disabled': 'disabled'}}">Checkout</validate-price-dir>
app.directive('validatePriceDir', function() {
return {
restrict: 'E',
scope: { validatePrice: '=validatePrice' },
link: function(scope, el, attrs){
console.log(scope.validatePrice);
}
};
});

Categories