Unable to pass controller and directive scope into child directive using require - angularjs

I am trying to pass the controller scope of parent controller and parent directive into a child directive but facing an error saying that the controller is not available. Here is a plunk for that
http://plnkr.co/edit/aahgOK9oFFjcP2y5VkVa?p=preview
HTML:
<div ng-controller="MainCtrl as mc">
<new-dir>
<data-customer-details customer="mc.customers[0]" logger="mc.logger()" version-age="{{mc.age}}"></data-customer-details>
</new-dir>
</div>

OK, so I tinkered with your plunker a bit. I couldn't get it working using Controller As...I had to change it over to $scope injection on the main controller. Then I created a new scope on newDir by setting scope: true.
You don't actually need to require the MainCtrl because these directives are automatically children of that scope anyway.
I changed your 'MainCtrl' to this:
angular.module('plunker').controller('MainCtrl', function ($scope) {
$scope.name = 'World';
$scope.customers = [{
"name": "angularjs 1.4",
"version": "1.4"
}, {
"name": "angularjs 1.3",
"version": "1.3"
}, {
"name": "angularjs 1.2",
"version": "1.2"
}];
$scope.age = 30;
$scope.logger = function() {
console.log('clicked');
}
$scope.ctrlScopeVariable = 'im in controller scope';
})
Minor change to newDir:
function newDir (){
return {
scope: true, //you need this
controller: function($scope){
$scope.val= 'someval';
console.log($scope.$parent.ctrlScopeVariable)
},
link: function(scope, el, attr, ctrl) {
console.log(scope.$parent.name)
}
}
}
And the last directive:
function CustomerDetails() {
var directive = {
scope: {
customer: '=',
logger: '&',
myNewAge: '#versionAge'
},
restrict: 'EA',
require: ['^newDir'],
controllerAs: 'cd',
templateUrl: 'customer-details.html',
link: linkFunction,
controller: function($scope){
console.log($scope.$parent.$parent.ctrlScopeVariable);
var cd = this;
cd.newval = 'new val';
}
};
function linkFunction(scope, elem, attributes, controllers, transclude) {
console.dir(controllers);
scope.fromMainCtrl = scope.$parent.$parent.ctrlScopeVariable
}
return directive;
}
The Plunker
I added a binding to the customer details template that passes in the $scope.ctrlScopeVariable from the main controller, so you can see the MainCtrl scope is accessible form the child directive.
In regards to require, the relevant documentation is here, I think:
If it is necessary to reference the controller or any functions bound
to the controller's scope in the template, you can use the option
controllerAs to specify the name of the controller as an alias. The
directive needs to define a scope for this configuration to be used.
This is particularly useful in the case when the directive is used as
a component.
Looking back at myPane's definition, notice the last argument in its
link function: tabsCtrl. When a directive requires a controller, it
receives that controller as the fourth argument of its link function.
Taking advantage of this, myPane can call the addPane function of
myTabs.
Essentially, you can use it to reference a parent controller on which you need to access some functions or something. Notably, it becomes available under whatever alias you give it as the fourth argument of your link function.
EDIT:
In this Plunker I added a function to the controller of newDir, required newDir in the CustomerDetail directive, and then called that function in the CustomerDetail link function:
CustomerDetails directive:
//some stuff
require: '^newDir',
//some stuff
link: function(scope, el, attr, newDirCtrl) {
console.log(newDirCtrl.doubleNum(100));
}
newDir controller:
controller: function($scope){
this.doubleNum = function(num) {
return num*2
}
// some stuff
}

First you need to declare a variable as callback function:
var MainCtrlFn = function() { .... }
Then, you can set it as parameter to angularJS:
angular.module('plunker').controller('MainCtrl', MainCtrlFn);

Related

Get data back from angular directive

I've created a directive which I called my-tree, and I'm calling this directive from a view exemple-tree-view.html as following:
<my-tree ng-model="sampleTreeView.listNoeuds" ... />
this view's controller called sampleTreeView.
In my directive's link function I have a function that returns some data, which I affect to scope variable declared in the directive's controller, as following :
function linkFn(scope, element, attrs) {
//some code
scope.createNode = function ($event) {
var sel = $(element).jstree(true).create_node($($event.currentTarget)[0].closest('.jstree-node').id);
if (sel) {
$(element).jstree(true).edit(sel, '', function (node, success, cancelled) {
scope.treeActionsResult.createdNode = node;
});
}
};
//some code
}
My question is how can I get the scope.treeActionsResult.createdNode value in the sampleTreeView controller, since it's the controller for the exemple-tree-view.html where I call my directive.
You can use shared scope between the directive and controller by removing the scope property
like in this example:
MyApp.directive('studentDirective', function () {
return {
template: "{{student.name}} is {{student.age}} years old !!",
replace: true,
restrict: 'E',
controller: function ($scope) {
console.log($scope);
}
}
});
Still you have the $scope object, but in this case the scope object is shared with parent controller's scope.
You can read more about it fron the following link
Understanding Scope in AngularJs Custom Directive
If you don't create isolated scope for your directive then you can access directive scope values from your controller. like bellow
your controller and directive:
app.controller('MainCtrl', function($scope) {
$scope.value = 1;
});
app.directive('myTree', function() {
return {
restrict: 'AE',
link: function(scope, element, attrs) {
scope.values = {};
scope.values.price = 1234;
}
};
});
then use in your html like:
<body ng-controller="MainCtrl">
<p>value {{values.price}}</p>
<my-tree att="{{attValue}}"></my-tree>
</body>
here values.price shown from directive in MainCtrl

How to pass object to directive through attribute in AngularJS?

I want to pass object (reference - two way binding) through ATTRIBUTE not by isolated scope. How can I do this? Because code bellow passing string instead of object:
HTML
<tr ng-form="rowForm" myDirective="{{row.data}}">
Directive
angular.module("app").directive("myDirective", function () {
return {
require: ["^form"],
restrict: "A",
link: function (scope, element, attrs, ctrls) {
scope.$watch(function () {
return attrs.myDirective;
}, function (newValue, oldValue) {
// .....
Directives can do two-way data binding without parsing or compile anything manually, sorry for not delivering the plunker but it's rebelius and won't save for me
JS
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.myObj = {name: 'Tibro', age: 255}
})
.directive('myDirective', function(){
return {
scope: {
'myAttribute': '='
},
template: '{{myAttribute}}',
link: function(scope){
scope.myAttribute.age= 31
}
}
})
HTML
<body ng-controller="MainCtrl">
controller: {{myObj}} <br/>
directive: <my-directive my-attribute="myObj"></my-directive>
</body>
OUTPUT
controller: {"name":"Tibro","age":31}
directive: {"name":"Tibro","age":31}
you can see from the output that passed object has been binded two-way and change made in directive is reflected on controller level
The result of {{ }} interpolation is a string. An object can't be passed like that.
Bindings are idiomatic here and thus preferable. The whole thing becomes messy when the directive is forced to use parent scope. However, it can be done by parsing scope properties manually with $parse:
$scope.$watch(function () {
var myDirectiveGetter = $parse($attrs.myDirective);
return myDirectiveGetter($scope);
}, ...);
This is a job for binding (< or =, depending on the case). If isolated scope isn't desirable, this can be done with inherited scope and bindToController:
scope: true,
bindToController: {
myDirective: '<'
},
controllerAs: `vm`,
controller: function ($scope) {
$scope.$watch('vm.myDirective', ...);
}
Notice that directive attribute is my-directive, not myDirective.
Couple of things:
myDirective in the tr should be my-directive, as per Angular's
conventions.
{{row.data}} prints the variable, you need to pass it without the
{{}} for it to go through as an object.

angular directive with attributes linked to function outside the parent scope

I have an angular directive (restrict to an element), its have an attribute to link to a function (using "&" attribute)
for example:
app.directive('myDirective', function () {
return {
restrict: 'E',
scope: {
myonclick: "&",
},
controller: 'myController',
template: '<button ng-click="clicked()">Click me!</button>'
}
});
i'm assigning a function to the attribute like this:
<my-directive myonclick="inScopeFunction()"></my-directive>
its work fine if the function is inside the angular controller scope.
but i cant assign it to a function outside the angular scope.
there is a working example at plunker
Thanks
Since inScopeFunction() is a global method, you can call it directly inside your directive. Register a click listener on the element inside directive and call inScopeFunction() in listener callback. Here is the updated plunk
link: function postLink (scope, element, attrs) {
element.on('click', function () {
window[attrs.myonclick]()
});
}
html:
<my-directive myonclick="outScopeFunction"></my-directive>
Your controller will have access to global objects so you can access it from there
$scope.inScopeFunction = outScopeFunction
To access it from your directive controller you could pass in a string and call the global object from there
<my-directive myonclick="'outScopeFunction'"></my-directive>
...
app.controller('myController', function($scope) {
$scope.clicked = function(){
window[$scope.myonclick()]()
}
});

How to access the parent scope within a directive link

So on the angular documentation website, you can define Tobias and Jeff
angular.module('docsTransclusionExample', [])
.controller('Controller', ['$scope', function($scope) {
$scope.name = 'Tobias';
}])
.directive('myDialog', function() {
return {
restrict: 'E',
transclude: true,
scope: {},
templateUrl: 'my-dialog.html',
link: function (scope, element) {
scope.name = 'Jeff';
}
};
});
If you do The name is {{name}} it'll say
The name is Tobias
I'm trying to access Tobias in the link function. From inside the link function, how would I get the $scope.name value equal to Tobias?
Since the scope is isolated scope: {}, directive creates a new child scope. In this case the only way to directly access parent scope property is to use scope $parent, reference to parent scope object. For example:
link: function(scope, element) {
scope.name = 'Jeff';
scope.parentName = scope.$parent.name; // Tobias
}
However this is not ideal. This is why you may want to consider more flexible approach:
<my-dialog name="name"></my-dialog>
and define a scope configuration as:
scope: {
parentName: '=name'
}
You will have to use $parent property of the the scope:
scope.$parent.name = 'Jeff';
you can get it through $parent like this:
link: function (scope, element) {
scope.name = 'Jeff';
console.log(scope.name);
console.log(scope.$parent.name);
}
As you have used transclude:true, you can omit scope:{} if you do not have any local variables. Putting scope:{} does not make sense.
so the declaration would be like following
angular.module('docsTransclusionExample', [])
.controller('Controller', ['$scope', function($scope) {
$scope.name = 'Tobias';
}])
.directive('myDialog', function() {
return {
restrict: 'E',
transclude: true,
templateUrl: 'my-dialog.html',
link: function (scope, element) {
// scope.name = 'Jeff';
// if name is in your parent scope, you should be able to get it here
console.log(scope.name);
}
};
});
If you look at the template you will see ng-transclude directive has been used, this means where in template the parent scope's variables will be used there. Hope it makes sense.
I'm just wondering why would you want something like this.
This way you're creating a deppendency between the controller and the directive that shouldn't exist.
If you need input data on your directive, declare it explicitly.

In an AngularJs directive, how do I call its controller's function contained within an directive element's attribute?

I am trying to call a function in a controller, which is part of a custom angular directive, following is the code,
Method 1: (Doesn't work: Controller's function doesn't write to the console)
HTML:
<div ng-app="MyApp">
<my-directive callback-fn="ctrlFn(arg1)"></my-directive>
</div>
JS:
var app = angular.module('MyApp', []);
app.directive('myDirective', function() {
return {
restrict: 'E',
scope: { someCtrlFn: '&callbackFn' },
link: function(scope, element, attrs) {
scope.someCtrlFn({arg1: 22});
},
controller: function ($scope) {
$scope.ctrlFn = function(test) {
console.log(test);
}
}
}
});
When I remove the directive's controller from it and and create a new controller it works,
Method 2: (Works: Controller's function does write to the console)
HTML:
<div ng-app="MyApp" ng-controller="Ctrl">
<my-directive callback-fn="ctrlFn(arg1)"></my-directive>
</div>
JS:
var app = angular.module('MyApp', []);
app.directive('myDirective', function() {
return {
restrict: 'E',
scope: { someCtrlFn: '&callbackFn' },
link: function(scope, element, attrs) {
scope.someCtrlFn({arg1: 22});
}
}
});
app.controller('Ctrl', function ($scope) {
$scope.ctrlFn = function(test) {
console.log(test);
}
});
I would like know how to get the behavior of Method 2 in Method 1 i.e., to be able to call the directive's controller's function from directive's attribute.
Any help is greatly appreciated, Thank you!
In Method 1, you are creating an isolated scope and defining a scope value someCtrlFn that takes in a function from the parent scope that is using your directive. The function to use is specified by the attribute callbackFn.
The way directives work with these scope items is that they are expected to be assigned from things that are on the parent scope that is active when the directive is used. So, if you have a controller Ctrl as in your Method 2, then use the directive within that scope, your directive is trying to match the what you defined in the attribute to what is available on Ctrl's scope.
So, in your first example, it's looking for a function called ctrlFn on the parent scope, but there isn't one. It will not try to look for it on the directive's controller. This is why Method 2 works, because there is a parent scope where ctrlFn is defined, and the directive is able to properly invoke that expression.
The purpose of these scope attributes is to allow directives to bind to values or functions on a parent scope to facilitate communication. For example, to give the directive data that it will display or modify, or allow the parent to define a function the directive can invoke for a callback during an event or what have you. The parent scope cannot move into the directive's scope and force the directive's scope to use its own defined items (unless you set it up so your directive uses a default value or function if the attribute is omitted or whatever).
They are not used so a directive can define things on its scope that it uses internally. If these things are internal to the directive, you can simply add them to the scope during link or whatever is suitable.
Did you mean something like this?
var app = angular.module('MyApp', []);
app.directive('myDirective', function() {
return {
restrict: 'E',
scope: { },
link: function(scope, element, attrs) {
// defines ctrlFn that can be used later by this directive's template or controller
scope.ctrlFn = function(test) {
console.log(test);
}
// immediately invokes ctrlFn to log a message, just here to illustrate
scope.ctrlFn('Hello World!');
}
}
});
Achieved it using $rootScope instead of $scope within the directive's controller
HTML:
<div ng-app="MyApp">
<my-directive callback-fn="ctrlFn(arg1)"></my-directive>
</div>
JS:
<script>
var app = angular.module('MyApp', []);
app.directive('myDirective', function($rootScope) {
return {
restrict: 'E',
scope: { someCtrlFn: '&callbackFn' },
link: function(scope, element, attrs) {
scope.someCtrlFn({arg1: 22});
},
controller: function ($scope) {
$rootScope.ctrlFn = function(test) {
console.log(test);
}
}
}
});
</script>

Resources