I'm trying to bind a function to a directive. Here is my fiddle code:
var myApp = angular.module('myApp', []);
myApp.directive("myFoo", function () {
return {
restrict: "E",
scope: {
callback: '&'
},
template: '<span ng-click="callback()">MyFoo</span>'
};
});
myApp.directive("myBar", function () {
return {
restrict: "E",
template: '<div>MyBar > <my-foo test="cb"></my-foo></div>',
link: function (scope) {
scope.cb = function (x) {console.log('click');}
}
};
});
DEMO
I must be doing something stupid because the callback is not working when I click the my-foo element. Any suggestions why this example doesn't work ?
You need to pass it into the attribute you're using which is callback, also associate it directly with an '=':
var myApp = angular.module('myApp', []);
myApp.directive("myFoo", function () {
return {
restrict: "E",
scope: {
callback: '='
},
template: '<span ng-click="callback()">MyFoo</span>'
};
});
myApp.directive("myBar", function () {
return {
restrict: "E",
template: '<div>MyBar > <my-foo callback="cb"></my-foo></div>',
link: function (scope) {
scope.cb = function (x) {console.log('click');}
}
};
});
jsFiddle: http://jsfiddle.net/xohv1syq/
jsFiddle using &: http://jsfiddle.net/hzxcptzv/
Related
I have an element directive (e-dir) and an attribute directive (a-dir) on the same element:
<e-dir a-dir attr="msg"></e-dir>
I pass msg into e-dir's isolate scope via the attr attribute:
app.directive('eDir', function eDir($timeout) {
return {
restrict: 'E',
scope: {
attr: '='
}
};
});
In this way, msg is bound (two-ways) with $scope.attr (in EDirCtrl) or scope.attr (in e-dir's link function).
Is there a simple way I can achieve the same two-way data-binding inside a-dir's directive? Or would you recommend another, simpler approach?
The closest thing I've been able to come up with is to set eDirCtrl.attr = $scope.attr; inside e-dir's controller (EDirCtrl):
app.directive('eDir', function eDir($timeout) {
return {
restrict: 'E',
scope: {
attr: '='
},
controller: function EDirCtrl($scope) {
var eDirCtrl = this;
eDirCtrl.attr = $scope.attr;
},
controllerAs: 'eDirCtrl'
};
});
Then, have a-dir require e-dir, and access attr via e-dir's controller (eDirCtrl.attr):
app.directive('aDir', function aDir($timeout) {
return {
restrict: 'A',
require: 'eDir',
link: linkFn
};
function linkFn(scope, element, attrs, eDirCtrl) {
eDirCtrl.attr = 'eDirCtrl.attr';
}
});
But, it's not bound two-ways. As you can see this code snippet:
var app = angular.module('app', []);
app.controller('Ctrl', function Ctrl($scope) {
$scope.msg = 'initial message';
})
app.directive('eDir', function eDir($timeout) {
return {
restrict: 'E',
scope: {
attr: '='
},
template: '<div>$scope.attr: {{attr}}</div>'+
'<div>eDirCtrl.attr: {{eDirCtrl.attr}}</div>',
controller: function EDirCtrl($scope) {
var eDirCtrl = this;
eDirCtrl.attr = $scope.attr;
$timeout(function() {
$scope.attr = 'changing $scope.attr also changes msg';
}, 2000);
},
controllerAs: 'eDirCtrl'
};
});
app.directive('aDir', function aDir($timeout) {
return {
restrict: 'A',
require: 'eDir',
link: linkFn
};
function linkFn(scope, element, attrs, eDirCtrl) {
$timeout(function() {
eDirCtrl.attr = 'changing eDirCtrl.attr does not effect $scope.attr or msg';
}, 4000);
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="Ctrl">
msg: <input type="text" ng-model="msg"><br>
<e-dir a-dir attr="msg"></e-dir>
</div>
The reason the two-way binding isn't working is that attr is being bound to a string rather than an object. In JavaScript, primitives (booleans, numbers, strings) are immutable, so when you change one, the previous instance is discarded and a new one is used. This breaks Angular's two-way binding and any changes to scope.msg are not propagated through attr into the directive.
You can get this to work as expected by setting msg on an object e.g. scope.test.msg and binding attr to test (the object) rather than msg (a string).
I've updated your code snippet to do this:
var app = angular.module('app', []);
app.controller('Ctrl', function Ctrl($scope) {
$scope.test = {msg : 'initial message'};
})
app.directive('eDir', function eDir($timeout) {
return {
restrict: 'E',
scope: {
attr: '='
},
template: '<div>$scope.attr: {{attr.msg}}</div>'+
'<div>eDirCtrl.attr: {{eDirCtrl.attr.msg}}</div>',
controller: function EDirCtrl($scope) {
var eDirCtrl = this;
eDirCtrl.attr = $scope.attr;
$timeout(function() {
$scope.attr.msg = 'changing $scope.attr also changes msg';
}, 2000);
},
controllerAs: 'eDirCtrl'
};
});
app.directive('aDir', function aDir($timeout) {
return {
restrict: 'A',
require: 'eDir',
link: linkFn
};
function linkFn(scope, element, attrs, eDirCtrl) {
$timeout(function() {
eDirCtrl.attr.msg = 'changing eDirCtrl.attr does not effect $scope.attr or msg';
}, 4000);
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="Ctrl">
msg: <input type="text" ng-model="test.msg"><br>
<e-dir a-dir attr="test"></e-dir>
</div>
I want to accomplish scroll-able content by clicking on Bootstrap module. Its working fine. This is following code of my directive:
'use strict';
angular.module('cbookApp')
.directive('scrollTo', scrollTo);
scrollTo.$inject = ['$anchorScroll'];
function scrollTo($anchorScroll) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
element.bind('click', function (event) {
event.stopPropagation();
var location = attrs.scrollTo;
if (scope.vm.isEdit || typeof scope.vm.isEdit =="undefined" ) {
$anchorScroll(location);
} else {
$anchorScroll(location+'1');
}
});
}
};
}
But only problem is i am not sure how to apply active class to current affix li. This DEMO way i found to apply class active to current li and remove from other. It was working without Controller as but once i added controller as it stopped working and give some error of scope.
var app = angular.module('app', ['directives']);
app.controller('firstController',[function(){
var vm = this;
vm.model = { value: 'dsf'};
}]);
angular.module('directives', []).directive('toggleClass', function () {
var directiveDefinitionObject = {
restrict: 'A',
template: '<span ng-click="localFunction()" ng-class="selected" ng-transclude></span>',
replace: true,
bindToController: true,
scope: {
model: '='
},
transclude: true,
link: function (scope, element, attrs) {
scope.localFunction = function () {
scope.model.value = scope.$id;
};
scope.$watch('model.value', function () {
if (scope.model.value === scope.$id) {
scope.selected = "active";
} else {
scope.selected = '';
}
});
}
};
return directiveDefinitionObject;
});
Can you please add this in your directive.
element.parent().parent().children().each(function() {
$(this).find('a').removeClass('active');
});
element.addClass('active');
http://jsfiddle.net/hngzxmda/1/
I suggest using controllerAs in your directive too
angular.module('directives', []).directive('toggleClass', function () {
var directiveDefinitionObject = {
restrict: 'A',
template: '<span ng-click="vmd.localFunction()" ng-class="selected" ng-transclude></span>',
replace: true,
bindToController: {
model: '=',
$id: '='
},
scope: {},
transclude: true,
controller: function() {
var _this = this;
this.localFunction = function () {
_this.model.value = _this.$id;
};
},
controllerAs: 'vmd'
};
return directiveDefinitionObject;
});
Recently I use angular to develop a directive, there is an directive which like ng-repeat to generate some records, I used transclude to implement it. but it raise an error that "Controller 'aArea', required by directive 'bSpan', can't be found!".
1. ModuleA code
var moduleA = angular.module("moduleA", []);
moduleA.directive("aArea", function () {
return {
restrict: 'E',
transclude:'element',
scope: {
amount:"="
},
template: '<div id=\"cc\" ng-transclude></div>',
controller: function ($scope,$element,$attrs) {
this.getData = function (data) {
return data + " is ok";
}
},
compile: function (tElement, attrs, linker) {
var parentElement = tElement.parent();
return {
pre: function () {
},
post: function (scope) {
linker(scope.$parent,function (clone,scope) {
parentElement.append(clone);
});
linker(scope.$parent, function (clone, scope) {
parentElement.append(clone);
});
linker(scope.$parent, function (clone, scope) {
parentElement.append(clone);
});
}
}
}
}
});
moduleA.directive("bSpan", function () {
return {
restrict: 'E',
scope: {
data: "=",
},
template: '<span style=\"background-color:gray;color:orange\">{{data}}</span>',
require: "^aArea",
link: function ($scope, $element, $attrs, controller) {
var data = "abc";
}
}
});
2. ModuleB COde
var moduleB = angular.module("moduleB", []);
moduleB.directive("myItem", function () {
return {
restrict: 'E',
scope: {
item: "=",
itemTemplate: '='
},
priority: 1000,
terminal:false,
template: '<ng-include src=\"itemTemplate\"/>',
controller: function ($scope, $element, $attrs) {
var data = "";
}
}
})
3. ModuleC Code
var moduleC = angular.module("moduleC", ["moduleA", "moduleB"]);
moduleC.controller("Ctr", function ($scope) {
$scope.item = {};
$scope.item.dataAmount = 1000;
$scope.item.templateUrl = "item-template.html";
})
4. Html Code
<body>
<div ng-app="moduleC">
<div ng-controller="Ctr">
<a-area>
<my-item item="item" item-template="item.templateUrl"></my-item>
</a-area>
</div>
</div>
</body>
5. template code
<div>
<span style="display:block">hello every one</span>
<b-span data="item.dataAmount"></b-span>
</div>
You should not use the transclude function (that you called linker) of the compile function - it is deprecated.
From $compile documentation:
Note: The transclude function that is passed to the compile function is deprecated, as it e.g. does not know about the right outer scope. Please use the transclude function that is passed to the link function instead.
Following this guidance (and a few other minor changes for the better), change the aArea directive as follows:
compile: function(tElement, tAttrs) {
// don't use the template element
//var parentElement = tElement.parent();
return function(scope, element, attrs, ctrls, transclude) {
transclude(function(clone, scope) {
element.after(clone);
});
transclude(function(clone, scope) {
element.after(clone);
});
transclude(function(clone, scope) {
element.after(clone);
});
};
}
In fact, you don't even need the transclude function at all and you don't need to do transclude: "element". You could just change to transclude: true and use <div ng-transclude> 3 times in the template.
app.directive('mainCtrl', function () {
return {
controller: function () {
this.funcA = function(){}
}
};
});
app.directive('addProduct', function () {
return {
restrict: 'E',
require: '^mainCtrl',
link: function (scope, lElement, attrs, mainCtrl) {
mainCtrl.funcA()
}
};
});
I don't want to use the link method but the controller method.
Is there a way to get the mainCtrl in the controller method of the directive addProduct.
something like:
app.directive('addProduct', function () {
return {
restrict: 'E',
require: '^mainCtrl',
controller: function (scope, mainCtrl) {
mainCtrl.funcA()
}
};
});
You'd still need to use the link function because the controllers are injected there. What you could, however, is request your directive's own controller and then set the other required controller as its property:
app.directive('addProduct', function () {
return {
restrict: 'E',
require: ['addProduct','^mainCtrl'],
controller: function ($scope) {
// this.mainCtrl is still not set here
// this.mainCtrl.funcA(); // this will cause an error
// but typically it is invoked in response to some event or function call
$scope.doFuncA = function(){
this.mainCtrl.funcA();
}
},
link: function(scope, element, attrs, ctrls){
var me = ctrls[0], mainCtrl = ctrls[1];
me.mainCtrl = mainCtrl;
}
};
});
Since AngularJS 1.5, you can use the $onInit lifecycle hook of the controller. As written in the documentation of require, when defining require as an object and setting bindToController to true, the required controllers are added to the controller as properties after the controller has been constructed, but before the $onInit method is run. So the code would look like this:
app.directive('mainCtrl', function () {
return {
controller: function () {
this.funcA = function(){}
}
};
});
app.directive('addProduct', function () {
return {
restrict: 'E',
require: {
myParentController: '^mainCtrl'
},
bindToController: true,
controller: function ($scope) {
this.$onInit = function() {
this.myParentController.funcA();
};
}
};
});
Here is my solution:
app.directive('mainCtrl', function () {
return {
controllerAs: 'main',
controller: function () {
this.funcA = function(){}
}
};
});
app.directive('addProduct', function () {
return {
restrict: 'E',
require: '^mainCtrl',
controller: function ($scope) {
$scope.main.funcA();
}
};
});
Pass the controller to the scope on the link function then accessing the scope on controller. Like this:
app.directive('mainCtrl', function () {
return {
controller: function () {
this.funcA = function(){}
}
};
});
app.directive('addProduct', function () {
return {
restrict: 'E',
require: '^mainCtrl',
link: function (scope, lElement, attrs, mainCtrl) {
scope.ctrl=mainCtrl;
},controller:function($scope){
$scope.ctrl.funcA();
}
};
});
Dealing with '&' and isolated scope.
Is it possible to pass a value up through a parent directive? I want to pass id from the textdisp directive to the controller.
HTML:
<body ng-controller="MainCtrl">
<builder removequest="deleteQuestion(id)"></builder>
</body>
ANGULAR:
app.controller('MainCtrl', function($scope) {
$scope.deleteQuestion = function(id) {
alert(id);
}
});
app.directive('builder', function() {
return {
restrict: 'E',
scope: {
removequest: '&'
},
template: '<div>Hello how are you? <textdisp removequest=removequest(id)></textdisp></div>'
}
});
app.directive('textdisp', function() {
return {
restrict: 'E',
scope: {
removequest: '&'
},
template: '<div ng-click="remove()">Click here!</div>',
link: function (scope, el) {
scope.remove = function(id) {
console.log('workin')
scope.removequest(1);
}
}
}
});
I believe there are 2 things going on with your code:
When you're placing removequest="removequest(id)" that is calling the function, and not just referring to the function.
I believe that the &attr binding isn't returning the function that you're expecting.
Try this Plunker; it essentially uses { removequest: '=' } for bi-directional binding, and removequest="deleteQuestion" / removequest="removequest" for function references rather than calling the function.
It's a little confusing, but you can use object parameter when you need to pass values into your function invoked via & binding. Take a look at this code it will make everything clear:
app.controller('MainCtrl', function($scope) {
$scope.deleteQuestion = function(id) {
alert(id);
}
});
app.directive('builder', function() {
return {
restrict: 'E',
scope: {
removequest: '&'
},
template: '<div>Hello how are you? <textdisp removequest="removequest({id: id})"></textdisp></div>'
}
});
app.directive('textdisp', function() {
return {
restrict: 'E',
scope: {
removequest: '&'
},
template: '<div ng-click="remove()">Click here!</div>',
link: function(scope, el) {
scope.remove = function(id) {
scope.removequest({id: 34534}); // <-- 1.
}
}
}
});
Demo: http://plnkr.co/edit/3OEy39UQlS4EyOu5cq4y?p=preview
Note how you specify scope.removequest({id: 34534}) parameter to be passed into <textdisp removequest="removequest({id: id})">.