I have a directive defined as following -
.directive('codesection', ['$compile', function ($compile) {
return {
restrict: 'E',
scope: { current: '=', parent: '=', index: '=', params: '=' },
controller: ['Messages', '$scope', 'Modals', 'framewidth', '$http', '$rootScope', function (Messages, $scope, Modals, framewidth, $http, $rootScope) {
//code
}],
link: function (scope, element, attr) {
element.bind('mouseover', function (ev) {
ev.stopPropagation();
var wrappers = angular.element(document.getElementsByClassName('codesection'));
angular.forEach(wrappers, function (value, key) {
angular.element(value).children('span').removeClass('br');
});
element.children('.codesection').children('span').addClass('br');
});
},
compile: function (tElement, tAttr, transclude) {
var contents = tElement.contents().remove();
var compiledContents;
return function (scope, iElement, iAttr) {
if (!compiledContents) {
compiledContents = $compile(contents, transclude);
}
compiledContents(scope, function (clone, scope) {
iElement.append(clone);
});
};
},
templateUrl: './partials/directives/codesection.html',
replace: true
}
}])
The issue I am having is that Link functions is never called. Thank you!
P.S. The reason for the Compile logic is that the directive is recursive.
If you mean that link: function (scope, element, attr) { isn't called then it's pretty clear: The compile function already returns a link function. What is defined as link: doesn't matter anymore and is ignored.
Related
Is it possible to have a parent directive and a child directive, both with their own controller?
Something like:
.controller('ParentController', function () {
var self = this;
self.action = function () {
console.log('parent action');
}
})
.controller('TestSomethingController', function () {
var self = this;
self.something = function () {
console.log('something');
}
})
.directive('parent', function () {
return {
restrict: 'A',
controller: 'ParentController',
link: function (scope, element, attrs, controller) {
controller.action();
}
}
})
.directive('test', function () {
return {
restrict: 'A',
require: 'parent',
controller: 'TestSomethingController',
link: function (scope, element, attrs, controller) {
controller.something();
}
};
});
I tried to do this one codepen like this:
http://codepen.io/r3plica/pen/bdygeP?editors=101
If I remove the require, it obviously works, but I would like to keep the require.
Does anyone know if that is possible?
You can require multiple directives. Have it require itself as well as parent. With this syntax, the last parameter of link will be an array of the controllers of the given directives.
.directive('test', function () {
return {
restrict: 'A',
require: ['parent', 'test'],
controller: 'TestSomethingController',
link: function (scope, element, attrs, controllers) {
controllers[0].action(); // parent
controllers[1].something(); // self
}
};
});
Here is a forked, working version of your CodePen.
I have 3 directives which require themselves this way:
.directive('grandParent', function () {
return {
retrict: 'E',
controller: function ($scope, $element, $attrs) {
this.foo = function () {
};
},
link: function(scope, element, attrs, ctrl) {
// ...
}
}
})
.directive('parent', function () {
return {
retrict: 'E',
require: ['parent', '^grandParent'],
controller: function ($scope, $element, $attrs) {
this.foo = function () {
};
},
link: function(scope, element, attrs, ctrls) {
var parentCtrl = ctrls[0],
grandParentCtrl = ctrls[1];
// ...
}
}
})
.directive('child', function () {
return {
retrict: 'E',
require: ['child', '^parent', '^grandParent'],
controller: function ($scope, $element, $attrs) {
this.foo = function () {
};
},
link: function(scope, element, attrs, ctrls) {
var childCtrl = ctrls[0],
parentCtrl = ctrls[1],
grandParentCtrl = ctrls[2];
// ...
}
}
})
I need in the child to use parent and grand parent controller functions, that why I didn't populate their link function with the business code.
I didn't find anywhere this kind of call of current directive controller.
I'm wondering if there is any problem with this kind of practice (circular reference...) or if it's a bad practice and there are other way to do that.
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.
I have two isolated scope directives. Ideally I like both to work independently and not require any custom templates. The first directive is going to be page scroll watcher, when it hits a certain point I want it to trigger an update in the other directive. Is it possible for a child directive to watch a variable in the parent directive?
I've created a simple plunkr to illustrate the issue, http://plnkr.co/edit/wwfBzmemyrj1r1R54riM?p=preview
/*
<div ng-outer>Outer directive {{myvar}}
<div ng-inner="myvar">Inner directive</div>
</div>
*/
app.directive('ngOuter', [ '$timeout', function ($timeout) {
var directive = {
restrict: 'A'
,scope:{}
}
directive.link = function (scope, element, attrs) {
$timeout(function(){
scope.myvar = "test 001"
},1000)
}
return directive;
}]);
app.directive('ngInner', [ function () {
var directive = {
restrict: 'A'
,scope:{ data: '=ngInner', myvar: '=myvar' }
}
directive.link = function (scope, element, attrs) {
scope.$watch('data', function(newVal, oldVal){
if(newVal)
element.text("new inner val", newVal);
});
scope.$watch('myvar', function(newVal, oldVal){
if(newVal)
element.text("new myvar", newVal);
});
}
return directive;
}]);
Solved this issue by using
angular.element(element.parent()).isolateScope();
The child directive can access the scope of the parent directive and watch variables etc.
http://plnkr.co/edit/RAO6q81ZE4tClMDMiLFb?p=preview
First approach of passing variable from parent directive to the child is by using an 'attr' in child scope with '#' and initialize it in parent controller as following:
var app = angular.module('app', []);
app.directive('ngOuter', [ '$timeout', function ($timeout) {
return{
restrict: 'E',
scope:{},
template: '<ng-inner attr="{{myvar}}">Inner directive {{myvar}}</ng-inner>',
controller: ['$scope', function($scope) {
$scope.myvar = "test 001";
}],
link: function (scope, elem, attrs) {
}
}
}]);
app.directive('ngInner', [ function () {
return{
restrict: 'E',
scope:{'attr' : "#"},
link: function (scope, element, attrs) {
console.log(scope.attr);
}
}
}]);
html:
<ng-outer></ng-outer>
2nd approach is utilizing a function in a parent controller which returns the "myvar" value and call that function in child directive:
app.directive('ngOuter', [ '$timeout', function ($timeout) {
return{
restrict: 'E',
scope:{},
template: '<ng-inner attr="{{myvar}}">Inner directive {{myvar}}</ng-inner>',
controller: ['$scope', function($scope) {
$scope.myvar = "test 001";
this.getMyvar = function() {
return $scope.myvar;
};
}],
link: function (scope, elem, attrs) {
}
}
}]);
app.directive('ngInner', [ function () {
return{
restrict: 'E',
require: '^ngOuter',
scope:{'attr' : "#"},
link: function (scope, element, attrs, parentDirCtrl) {
console.log(parentDirCtrl.getMyvar());
}
}
}]);
3rd Approach: you can inject a service to both inner and outer directives.Then use the service.
var app = angular.module('app', []);
app.service('myService', [
function() {
var service = {
myvar: 'test001',
setMyVar: function(value) {
this.myvar = value;
},
getMyVar: function() {
return this.myvar;
}
}
return service;
}
]);
app.directive('ngOuter', ['$timeout', 'myService',
function($timeout, myService) {
var directive = {
restrict: 'A',
scope: {}
}
directive.link = function(scope, element, attrs) {
$timeout(function() {
scope.myvar = myService.getMyVar();
}, 1000)
}
return directive;
}
]);
app.directive('ngInner', ['myService',
function(myService) {
var directive = {
restrict: 'A',
scope: {}
}
directive.link = function(scope, element, attrs) {
var variable = myService.getMyVar();
console.log("myvar", variable);
}
return directive;
}
]);
I have attempted the following which seems to generate the correct html, but doesn't perform the data binding i.e. the new directives that I add do not have access to the $modelValue in ngController:
.controller('MyController', ['$scope', function($scope) {
$scope.aModel = {"key": "value"}
function init() {
$scope.aModel = {"key": "value"}
}
}])
.directive('innerDirective', [function (){
return {
restrict: 'AE',
require: 'ngModel',
template: '<ul></ul>',
link: function(scope, elem, attr, ngModelController) {
console.log(ngModelController.$modelValue) // Doesnt work
}
}
}])
.directive('outerDirective', ["$log", "$compile", function($log, $compile) {
return {
restrict: 'AE',
require: 'ngModel',
scope: {},
template: '<div></div>',
link: function(scope, elem, attrs, ngModelController) {
ngModelController.$render = function() {
var aModel = ngModelController.$modelValue; // Works
var modelName = attrs['ngModel']; // aModel
var html = '<inner-directive ng-model="' + modelName + '"></inner-directive>';
var angularElement = angular.element(html);
elem.append(angularElement);
$compile(angularElement)(scope);
}
}
};
}])
HTML looks like:
<div ng-controller="MyController">
<outer-directive ng-model="aModel"></outer-directive>
</div>
This is based on reccomendations from another question: Adding ngModel to input with a directive
What is it that I'm doing wrong here?
Cheers.
There is two issue:
1- outerDirective is using isolated scope. so aModel value is undefinded in the directive scope.
2-You can't access ngModelController.$modelValue in link function immediately. Look at outerDirective that you access it in the render function.
So you can change your directives as follows:
var app = angular.module('app', []);
app.controller('MyController', ['$scope', function ($scope) {
$scope.aModel = { "key": "value" }
function init() {
$scope.aModel = { "key": "value" }
}
}]);
app.directive('innerDirective', ['$timeout', function ($timeout) {
return {
restrict: 'AE',
require: 'ngModel',
template: '<ul></ul>',
link: function (scope, elem, attr, ngModelController) {
$timeout(function () {
console.log(ngModelController.$modelValue) // It works
});
}
}
}]);
app.directive('outerDirective', ["$log", "$compile", function ($log, $compile) {
return {
restrict: 'AE',
require: 'ngModel',
template: '<div></div>',
link: function (scope, elem, attrs, ngModelController) {
ngModelController.$render = function () {
var aModel = ngModelController.$modelValue; // Works
var modelName = attrs['ngModel']; // aModel
var html = '<inner-directive ng-model="' + modelName + '"></inner-directive>';
var angularElement = angular.element(html);
elem.append(angularElement);
$compile(angularElement)(scope);
}
}
};
}]);