Nested directives with ng-repeat causes to strange behaviour - javascript

Let's assume that i have a 2 directives: outer and inner. In first directive, i have some data and i want to insert second directive with ng-repeat in it. So my directives looks like:
outer.js
directive('outer', function($compile) {
return {
restrict: 'E',
template: '<div class="options"></div>',
scope: true,
bindToController: {
options: '='
},
link: function(scope, element) {
var list = $(element).find('.options');
// Here we dynamically insert html for second directive
$('<inner><span ng-bind="$select.getValue(item)"></span></inner>').appendTo(list);
$compile(list.contents())(scope);
},
controllerAs: '$select',
controller: function($rootScope) {
this.items = ['aaa', 'bbb'];
this.getValue = function(item) {
$rootScope.log += '\n' + item;
return item;
};
}
};
}
inner.js
directive('second', function($compile) {
return {
restrict: 'E',
require: '^first',
replace: true,
transclude: true,
template: '<ul><li><span></span></li></ul>',
link: function(scope, element, attrs, $select, transclude) {
var choices = $(element.find('li')[0]);
choices.attr('ng-repeat', 'item in $select.items');
var inner = $(choices.find('span')[0]);
transclude(scope, function(clone) {
inner.append(clone);
});
$compile(choices)(scope);
}
};
});
So, in my example, in each $digest cycle $select.getValue should be called twice: for 'aaa' and for 'bbb'. But actually it calls 3 times and first time is called for undefined, but i can't understand why
Any ideas?
See example:
var myApp = angular.module('myApp', []);
myApp.directive('outer', function($compile) {
return {
restrict: 'E',
template: '<div class="options"></div>',
scope: true,
bindToController: {
options: '='
},
link: function(scope, element) {
var list = $(element).find('.options');
$('<inner><span ng-bind="$select.getValue(item)"></span></inner>').appendTo(list);
$compile(list.contents())(scope);
},
controllerAs: '$select',
controller: function($rootScope) {
this.items = ['aaa', 'bbb'];
this.getValue = function(item) {
$rootScope.log += '\n' + item;
return item;
};
}
};
})
.directive('inner', function($compile) {
return {
restrict: 'E',
require: '^outer',
replace: true,
transclude: true,
template: '<ul><li><span></span></li></ul>',
link: function(scope, element, attrs, $select, transclude) {
var choices = $(element.find('li')[0]);
choices.attr('ng-repeat', 'item in $select.items');
var inner = $(choices.find('span')[0]);
transclude(scope, function(clone) {
inner.append(clone);
});
$compile(choices)(scope);
}
};
});
.log {
white-space: pre;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body ng-app="myApp">
<span class="log" ng-bind="log"></span>
<outer></outer>
</body>

Related

communication between nested directives not working

I have a directive Foo in Directive Bar i am trying to call a function in Foo
but it is not working.
http://jsfiddle.net/4d9Lfo95/3/
example fiddle is created.
angular.module('ui', []).directive('uiFoo',
function() {
return {
restrict: 'E',
template: '<p>Foo</p>',
link: function($scope, element, attrs) {
$scope.message = function() {
alert(1);
};
},
controller: function($scope) {
this.message = function() {
alert("Foo Function!");
}
}
};
}
).directive('uiBar',
function() {
return {
restrict: 'E',
template: '<button ng-click="callFunction()">Bar</button> <ui-foo></ui-foo>',
require: 'uiFoo',
scope: true,
link: function($scope, element, attrs, uiFooController) {
$scope.callFunction = function() {
alert('Bar Function');
uiFooController.message();
}
}
};
}
);angular.module('myApp', ['ui']);
where as the UI looks like this
<div ng-app="myApp"> <ui-bar> </ui-bar></div>
You left out this error message:
Controller 'uiFoo', required by directive 'uiBar', can't be found!
The problem is that the require hierarchy searches up the tree, not down it. So, ui-bar is trying to find a uiFoo directive controller either on itself or (with the ^ symbol) in one of it's ancestors, not one of it's children.
If you want to call a method from the child directive, just use the scope: http://jsfiddle.net/4d9Lfo95/5/
angular.module('ui', []).directive('uiFoo',
function() {
return {
restrict: 'E',
template: '<p>Foo</p>',
controller: function($scope) {
$scope.message = function() {
alert("Foo Function!");
}
}
};
}
).directive('uiBar',
function() {
return {
restrict: 'E',
template: '<button ng-click="callFunction()">Bar</button> <ui-foo></ui-foo>',
scope: true,
controller: function($scope) {
$scope.callFunction = function() {
alert('Bar Function');
$scope.message();
}
}
};
}
);

How to append element in post function of compile - AngularJS

So, this is my problem. I have two directives (say parent directive and child directive) and i am calling child directive from parent directive like this :
angular.module('components', [])
.directive('helloWorld', function() {
return {
restrict: 'E',
compile: function(element, attrs) {
var x = '<directive2></directive2>';
element.append(x);
}
}
})
.directive("directive2", function($compile, $parse) {
return {
restrict: 'E',
compile: function(iElement, iAttrs, transclude) {
iElement.append('<p>directive2</p>');
}
}
});
angular.module('HelloApp', ['components'])
This works fine. But now i am writing a condition in post function of compile and when that condition satisfy, the child directive should append.
I just added the append function inside the post function, but its not working.
angular.module('components', [])
.directive('helloWorld', function() {
return {
restrict: 'E',
compile: function(element, attrs) {
return {
post: function(scope, element, attrs) {
var x = '<directive2></directive2>';
element.append(x);
}
}
}
}
})
.directive("directive2", function($compile, $parse) {
return {
restrict: 'E',
compile: function(iElement, iAttrs, transclude) {
iElement.append('<p>directive2</p>');
}
}
});
angular.module('HelloApp', ['components'])
I dont know what went wrong. Guide me friends
jsFiddle
You need to use the $compile service before appending as below:
angular.module('components', [])
.directive('helloWorld', function($compile){
return {
restrict: 'E',
link: function(scope, element, attrs) {
var x = angular.element('<directive2></directive2>');
element.append($compile(x)(scope));
}
}
})
.directive("directive2", function() {
return {
restrict: 'E',
compile: function(element, attrs, transclude) {
element.append('<p>directive2</p>');
}
}
});
angular.module('HelloApp', ['components']);
http://jsfiddle.net/2zbabkjb/2/
Try to define template to your first directive :
angular.module('components', [])
.directive('helloWorld', function() {
return {
restrict: 'E',
template: '<directive2></directive2>'
}
}
})

Call function from Directive AngularJS

I am trying to call a function inside a directive. Here is a html:
<span class="delete-link">
<delete></delete>
<input type="button" data-ng-click="removeRow(task)"/>
</span>
And here is the directive:
.directive('delete', function() {
return {
restrict: 'E',
replace: true,
template: '<div></div>',
link: function(scope, element) {
element.click(function(){
$scope.removeRow = function (task) {
$scope.tasks.splice($scope.tasks.indexOf(task), 1);
}
});
}
}
});
This is the example I used:
http://jsfiddle.net/mrajcok/T96Zu/
But it doesn't delete an element. What I'm missing?
.directive('delete', function() {
return {
restrict: 'E',
replace: true,
template: '<div></div>',
link: function(scope ,element) {
scope.removeRow = function (task) {
scope.tasks.splice(scope.tasks.indexOf(task), 1);
}
}
}
});
Use scope instead of $scope
.directive('delete', function() {
return {
restrict: 'E',
replace: true,
template: '<div></div>',
link: function(scope, element) {
element.click(function(){
scope.removeRow = function (task) {
scope.tasks.splice(scope.tasks.indexOf(task), 1);
}
});
}
}
});

How to define an angular directive which contains a method and can be called by `ng-click`?

I want to define a directive which will show the first child which contains a button at first. If we click on the button, the second child will be replace the first one.
HTML:
<div show-more>
<div>short <button ng-click="showMore()">click-me to show more</button></div>
<div>full</div>
</div>
Angular:
angular.module("app", [])
.directive("showMore", function() {
return {
restrict: 'A',
scope: {},
link: function(scope, element, attrs) {
var children = element.children();
var short = children[0];
var full = children[1];
element.empty().append(short);
scope.showMore = function() {
element.empty().append(full);
};
}
};
});
The problem is that when I click the button, there is nothing happen. I tried a lot but still not work.
You can see a live demo here: http://jsbin.com/rugov/2/edit
How to fix it?
Since you are changing the DOM of the element of your directive, you will need to re-$compile the element, like this:
.directive("showMore", function($compile) {
return {
restrict: 'A',
scope: {},
link: function(scope, element, attrs) {
var children = element.children();
var short = children[0];
var full = children[1];
element.empty().append(short);
$compile(element.contents())(scope);
scope.showMore = function() {
element.empty().append(full);
};
}
};
});
Working Example
The problem is that your directive creates new isolated scope, but ng-click directive is placed on element, that is in another scope.
I would implement your requirements via 2 directive, one depends from another.
angular.module("app", [])
.directive("showMoreWrapper", function() {
return {
restrict: 'A',
scope: {},
controller: function($element) {
this.showMore = function() {
var children = $element.children();
children.eq(0).hide();
children.eq(1).show();
};
},
link: function(scope, element, attrs) {
var children = element.children();
children.eq(1).hide();
}
};
})
.directive("showMore", function() {
return {
restrict: 'A',
require: '^showMoreWrapper',
link: function(scope, element, attrs, showMoreWrapper) {
element.on('click', showMoreWrapper.showMore);
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div show-more-wrapper>
<div>short
<button show-more>click-me to show more</button>
</div>
<div>full</div>
</div>
</div>
I would take a custom component approach
angular.module("app", [])
.directive("showMore", function() {
return {
restrict: 'E',
scope: {},
transclude: true,
template: '<button ng-click="toggle()">{{title}}</button><div ng-transclude></div>',
controller: function($scope) {
var self = this;
$scope.title = "Show More";
$scope.toggle = function() {
self.short.show = !self.short.show;
self.full.show = !self.full.show;
$scope.title = self.short.show ? "Show More" : "Show Less";
};
this.addShort = function(short) {
self.short = short;
};
this.addFull = function(full) {
self.full = full;
};
}
};
})
.directive("short", function() {
return {
restrict: 'E',
scope: {},
require: '^showMore',
transclude: true,
template: '<div ng-if="show" ng-transclude></div>',
link: function(scope, element, attrs, showMoreCtrl) {
scope.show = true;
showMoreCtrl.addShort(scope);
}
};
})
.directive("full", function() {
return {
restrict: 'E',
scope: {},
require: '^showMore',
transclude: true,
template: '<div ng-if="show" ng-transclude></div>',
link: function(scope, element, attrs, showMoreCtrl) {
scope.show = false;
showMoreCtrl.addFull(scope);
}
};
});
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.min.js"></script>
<div ng-app="app">
<show-more>
<short>short</short>
<full>full</full>
</show-more>
</div>

why can not the controller of the required directive be found in angularjs?

I wrote two directives in angularjs, one is embedded in the other one.
below is the scripts for the directives:
module.directive('foo', [
'$log', function($log) {
return {
restrict: 'E',
replace: true,
transclude: true,
template: '<div id="test" ng-transclude></div>',
controller: function($scope) {
this.scope = $scope;
return $scope.panes = [];
},
link: function(scope, element, attrs) {
return $log.log('test1', scope.panes);
}
};
}
]);
module.directive('bar', [
'$log', function($log) {
return {
restrict: 'E',
replace: true,
transclude: true,
require: '^?foo',
controller: function($scope) {
return this.x = 1;
},
template: '<div ng-transclude></div>',
link: function(scope, element, attrs, fooCtrl) {
return $log.log('test2', fooCtrl);
}
};
}
]);
below is the piece of html:
<foo ng-controller="IndexController">
<bar></bar>
</foo>
below is the element generated, inspected from the chrome developer tools
<div id="test" ng-transclude="" ng-controller="IndexController" class="ng-scope">
<div ng-transclude="" class="ng-scope"></div>
</div>
below is chrome console output:
test2
Array[0]
length: 0
__proto__: Array[0]
angular.js:5930
test1
Array[0]
length: 0
__proto__: Array[0]
Question:
The child directive can not get the parent directive's controller, so the fourth parameter "fooCtrl" for link function of "bar" is an empty array. what's the thing that I do it wrong?
update and answer:
At last I found the reason that made the weird result. It's just a stupid mistake that I have made:
// in directive "foo"
controller: function($scope) {
this.scope = $scope;
// This line below is wrong. It is here
// because I transcompiled coffeescript to js.
// return $scope.panes = [];
// It should be like below:
$scope.panes = []
// I should have written .coffee like below
// controller: ($scope) ->
// #scope = $scope
// $scope.panes = []
// return # prevent coffeescript returning the above expressions.
// # I should rather have added the above line
}
After correcting the mistake, I tried and found there's nothing to prevent using controller or providing empty content in child directives.
AFAIK, you cannot have a controller in the child directive.
Demo: http://plnkr.co/edit/kv9udk4eB5B2y8SBLGQd?p=preview
app.directive('foo', [
'$log', function($log) {
return {
restrict: 'E',
replace: true,
transclude: true,
template: '<div id="test" ng-transclude></div>',
controller: function($scope) {
$scope.panes = ['Item1','Item2','Item3']
return {
getPanes: function() { return $scope.panes; }
};
},
link: function(scope, element, attrs, ctrl) {
$log.log('test1', ctrl, ctrl.getPanes(), scope.panes);
}
};
}
]);
I removed the child controller.
app.directive('bar', [
'$log', function($log) {
return {
restrict: 'E',
replace: true,
transclude: true,
require: '^?foo',
template: '<div ng-transclude></div>',
link: function(scope, element, attrs, ctrl) {
scope.x = 1;
$log.log('test2', ctrl, ctrl.getPanes(), scope.panes, scope.x);
}
};
}
]);

Categories