I am working on a proof of concept where I am using angularjs. I have to use a click function on the links for which I am using ng-click. This ng-click is inside ng-repeat. I went through so many solved problems here in stackoverflow on how to use ng-click inside ng-repeat. But somehow, none of the solutions worked. Can some one help me to point out where I am doing the mistake?
HTML Code :
<collection collection='tasks'></collection>
<collection collection='articleContent'></collection>
app.directive('collection', function () {
return {
restrict: "E",
replace: true,
scope: {
collection: '='
},
template: "<ul><member ng-repeat='member in collection' member='member'></member></ul>"
}
});
app.directive('member', function ($compile) {
return {
restrict: "E",
replace: true,
scope: {
member: '='
},
template: "<li><a href='#' ng-click='getArticleContent(member.itemId)'>{{member.title}}</a></li>",
link: function (scope, element, attrs) {
if (angular.isArray(scope.member.tocItem)) {
if(scope.member.hasChildren == "true")
{
for(var i=0;i<scope.member.tocItem.length;i++){
if(scope.member.tocItem.title) {
scope.member.tocItem.title.hide = true;
}
}
}
element.append("<collection collection='member.tocItem'></collection>");
$compile(element.contents())(scope)
}
}
}
});
app.controller('AbnTestController', function($scope) {
var bookId ="";
$scope.tasks = data;
$scope.getArticleContent = function(itemId){
alert('inside article content');
$scope.articleContent = articleData[0].articleContent;
}
});
on click of the link, getArticleContent method is never called here.
Directive member has it's own scope, meaning isolated scope.
And the template has an ng-click to execute getArticleContent, that the member directive does not contain the getArticleContent function.
You have two choices:
Add the getArticleContent function to the directive member.
Create member directive without isolated scope.
There are issues with this though, having two instances of the same directive may cause conflicts.
Updated after OP comment:
I'm adding OP code with some data passed to directives for manipulation:
app.directive('collection', function() {
return {
restrict: "E",
replace: true,
scope: {
collection: '=',
articleData: '=',
articleContent: '='
},
template: "<ul><member ng-repeat='member in collection' member='member' article-data='articleData' article-content='articleContent'></member></ul>"
}
});
app.directive('member', function($compile) {
return {
restrict: "E",
replace: true,
scope: {
member: '=',
articleData: '=',
articleContent: '='
},
template: "<li><a href='#' ng-click='getArticleContent(member.itemId)'>{{member.title}}</a></li>",
link: function(scope, element, attrs) {
scope.getArticleContent = function(itemId) {
alert('inside article content');
scope.articleContent = articleData[0].articleContent;
}
if (angular.isArray(scope.member.tocItem)) {
if (scope.member.hasChildren == "true") {
for (var i = 0; i < scope.member.tocItem.length; i++) {
if (scope.member.tocItem.title) {
scope.member.tocItem.title.hide = true;
}
}
}
element.append("<collection collection='member.tocItem'></collection>");
$compile(element.contents())(scope)
}
}
}
});
app.controller('AbnTestController', function($scope) {
var bookId = "";
$scope.tasks = data;
$scope.getArticleContent = function(itemId) {
alert('inside article content');
$scope.articleContent = articleData[0].articleContent;
}
});
It would be more helpful if OP can create a jsfiddle for us to review and revise.
Related
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 can I pass a child attribute directive's scope or attr value to a parent directive?
Given widget directive, with in-viewport attribute directive, I want to update the attribute inView each time the document is scrolled, and pass the updated value to the parent directive widget:
<widget in-viewport></widget>
In Viewport directive: passed in as an attribute of parent directive "widget"
angular.module('app').directive('inViewport', function() {
return {
restrict: 'A',
scope: false, // ensure scope is same as parents
link: function(scope, element, attr) {
angular.element(document).on('scroll', function() {
// I've tried binding it to attr and parent scope of "widget" directive
attr.inView = isElementInViewport(element);
scope.inView = isElementInViewport(element);
});
}
};
});
Widget Directive:
angular.module('app').directive('widget', function() {
return {
restrict: 'AE',
scope: {
inView: '='
},
transclude: false,
templateUrl: 'directives/widgets/widgets.tpl.html',
link: function(scope) {
console.log('In Viewport: ', scope.inView); // Null
Here is the way you can access parent directive variables,
angular.module('myApp', []).directive('widget', function() {
return {
restrict: 'E',
template: '<viewport in-view="variable"></viewport> <h1>{{variable}}</h1>',
link: function(scope, iAttrs) {
scope.variable = 10;
}
}
}).directive('viewport', function() {
return {
restrict: 'E',
scope: {
inView: "=",
},
template: '<button ng-click="click()">Directive</button>',
link: function(scope, iElement, iAttrs) {
scope.click = function() {
scope.inView++;
}
}
}
});
HTML
<div ng-app="myApp" ng-controller="Ctrl1">
<widget></widget>
</div>
Here is the working jsfiddle
http://jsfiddle.net/p75DS/784/
If you have any question, ask in the comment box
Here is a working fiddle using your directive structure:
http://jsfiddle.net/ADukg/9591/
Markup is like this:
<div ng-controller="MyCtrl" style="height: 1200px;">
{{name}}
<hr>
<widget in-viewport></widget>
</div>
Just scroll the window to trigger the event. Note that the parent directive has a watch just to prove that the var gets updated...
var myApp = angular.module('myApp',[]);
myApp.directive('inViewport', function($timeout) {
return {
restrict: 'A',
scope: false, // ensure scope is same as parents
link: function(scope, element, attr) {
angular.element(window).bind('scroll', function() {
console.log('Called');
$timeout(function() {
scope.inView++;
}, 0);
});
}
};
});
myApp.directive('widget', function() {
return {
restrict: 'AE',
transclude: false,
template: '<p>This is a widget</p>',
link: function(scope) {
scope.inView = 0;
console.log('In Viewport: ', scope.inView); // Null
scope.$watch('inView', function(newVal, oldVal) {
console.log('Updated by the child directive: ', scope.inView);
});
}
}
});
function MyCtrl($scope) {
$scope.name = 'Angular Directive Stuff';
}
You can expose an API on your parent directive and use isolateScope() to access it.
Here's a working fiddle.
var app = angular.module("app",[]);
app.directive("widget", function($rootScope){
return {
template: "<div>Scroll this page and widget will update. Scroll Y: {{scrollPosY}}</div>",
scope: {}, // <-- Creating isolate scope on <widget>. This is REQUIRED.
controller: ['$scope', function DirContainerController($scope) {
$scope.scrollPosY = 0;
// Creating an update function.
$scope.update = function(position) {
$scope.scrollPosY = position;
$scope.$digest();
};
}],
}
});
app.directive("inViewport", function($window, $timeout, $rootScope){
return {
restrict: 'A',
link:function(scope, element, attrs, parentCtrl){
// Get the scope. This can be any directive.
var parentScope = element.isolateScope();
angular.element(document).on('scroll', function() {
// As long as the parent directive implements an 'update()' function this will work.
parentScope.update($window.scrollY);
console.log('parentScope: ', parentScope);
});
}
}
});
Here is the problem. I have some 3rd party directive called main-directive.
app.directive('mainDirective', function() {
return {
scope: {
foo: '&'
// attrs
},
controller: function($scope) {
$scope.click = function() {
window.alert($scope.foo());
}
},
template: '<button ng-click="click()">Click me</button>'
}
});
So I want to make my own directive called parent-directive which assign application specific default values to third party directive attributes.
app.directive('parentDirective', function() {
return {
scope: {
foo: '&?',
attr2: '='
// lots of attrs
},
controller: function($scope) {
$scope.attr1 = "some default value"
$scope.foo = function() {
return "not overrided"
}
if (this.foo) {
$scope.foo = this.foo
}
},
template: '<div class="some-styling"><main-directive foo="foo()" attr1="attr1" attr2="attr2"></main-directive></div>'
}
});
What if I want to make another child-directive that keeps parent-directive logic.
Overloading attribute is easy i can use "compile" function. But what about overriding functions is it possible?
app.directive('childDirective', function() {
return {
scope: false,
require: 'parentDirective',
link: function(scope, element, attr, controller) {
controller.foo = function() {
return "overrided";
}
},
compile: function(element, attr) {
attr.attr2 = "attr2";
}
}
});
Whole thing can be easily done by using child scope instead of isolated.
Or by using extending by template. But if I extends directive with template I would have to copy parent "scope" and "template" definition to child-directive and forward all the non-default attributes this doesn't seem like an elegant solution.
So the key question, is there a way to override parent-directive function using isolated scope without forwarding attributes.
Here is the DEMO
Ok, I have done some research and it turns out that there can be several approaches there
Scope inheritance
Since child-directive is not creating own scope it just creating new methods at parent-directive parent scope. So we can modify attributes during compile and specify overridden foo method.
app.directive('parentDirective', function() {
return {
scope: {
fooImpl: '&?',
// lots of attrs
},
controller: function($scope) {
$scope.foo = function() {
if ($scope.fooImpl) {
return $scope.fooImpl();
}
return "not overrided";
}
},
template: '<div class="some-styling"><main-directive foo="foo()"></main-directive></div>'
}
});
app.directive('childDirective', function() {
return {
scope: false,
require: 'parentDirective',
controller: function($scope) {
$scope.foo = function() {
return "overrided";
}
},
compile: function(element, attr) {
attr.fooImpl = "foo()";
}
}
});
Here is the DEMO1
Add to isolated scope
Angular provides special function. That can get isolated scope from element. So we can override our method during linking phase.
app.directive('parentDirective', function() {
return {
scope: {
fooImpl: '&?',
// lots of attrs
},
controller: function($scope) {
$scope.foo = function() {
if ($scope.fooImpl) {
return $scope.fooImpl();
}
return "not overrided";
}
},
template: '<div class="some-styling"><main-directive foo="foo()"></main-directive></div>'
}
});
app.directive('childDirective', function() {
return {
scope: false,
require: 'parentDirective',
link: function(scope, element, attr) {
var innerScope = angular.element(element[0]).isolateScope();
innerScope.foo = function() {
return "overrided";
}
}
}
});
Here is the DEMO2
Controller method
If we use controllerAs syntax. That means we exposing controller object variables as a scope. We can override function in child directive during linking phase.
app.directive('parentDirective', function() {
return {
scope: {
fooImpl: '&?',
// lots of attrs
},
controller: function($scope) {
var vm = this;
vm.foo = function() {
return "not overrided";
}
},
controllerAs : 'vm',
template: '<div class="some-styling"><main-directive foo="vm.foo()"></main-directive></div>'
}
});
app.directive('childDirective', function() {
return {
scope: false,
require: 'parentDirective',
link: function (scope, element, attr, controller) {
controller.foo = function() {
return "overrided";
}
}
}
});
Here is the DEMO3
Transclusion
Practically you can do the same thing with seperate parent and child directive and using transclusion. But anyway it would be combination of above approaches. Thanks for "Extending an existing directive in AngularJS"
let assume that I want to create directive that matched only for element that match amInput[type=dropdown] how can I do that?
I can for example:
.directive('amInput',function () {
return {
restrict: "E",
scope: {
data:'#'
},
compile:function(tElement, tAttrs){
if (tAttrs.type != 'dropdown') return;
return function link ($scope, element, attr, ctrl) {
var parseResult = parse($scope.data);
}
}
}
});
but if I define another directive with isolate scope for am-input[type=checkbox]
.directive('amInput',function () {
return {
restrict: "E",
scope: {
data2:'#'
},
compile:function(tElement, tAttrs){
if (tAttrs.type != 'checkbox') return;
return function link ($scope, element, attr, ctrl) {
var parseResult = parse($scope.data2);
}
}
}
});
angular#$compile throw exception about two directives define isolate scope.
Error: [$compile:multidir] Multiple directives [amInput, amInput] asking for new/isolated scope on: <am-input type="checkbox"></am-input>
directive name should be unique (as long as they match the same restrict)
in your case you should probably merge them into one.
(just as a reference, this might help: Angular directive name: only lower case letters allowed?)
Ok, I end up with that solution that make the differntes between the directives by postlink. when prelink is for everythig is similar between them:
directive('amInput',function () {
var template = {
'dropdown' :require('./dropdown.drv.html'),
'checkbox-group' :require('./checkbox-group.drv.html')
};
var postLinkPerType = {
'dropdown': require('./postLink-OneFromMany'),
'checkbox-group':require('./postLink-ManyFromMany')
};
return {
restrict: "E",
require: 'ngModel',
scope:{
selectAll:'='
//,...
},
transclude:true,
controller: function(scope, element, attr) {
/*for binding stuff that use by post links */
},
controllerAs:'inputCtrl',
bindToController:false,
template: function (element, attr) {
return template[attr.type];
},
compile: function (element, attr, ctrl, transcludeFn) {
return {
pre: function (scope, element, attr, ctrl, transcludeFn) {
/*******************************************/
/* some general/init code for all collection*/
/*******************************************/
/* init options list */
if( attr.data ){
/***********/
/* some code just if there is some attribute*/
/***********/
}
},
/*the little different code that make every directive in collection different*/
/*different by TYPE attribute*/
post: postLinkPerType[attr.type]
}
}
}
})
I want to have an attribute directive a bit similar to ng-model. I just want to additionally bind an input fields value to a scope variable (just in one direction input field -> scope variable). So I have just tried this directive but I can not get the directive called anyway.
script:
.directive('passivemodel', function () {
return {
restrict: "A",
scope: {
passivemodel: '='
},
link: function (scope, element, attrs) {
scope.$watch('passivemodel', function(newPassivemodel, oldPassivemodel) {
console.log("passive model", newPassivemodel);
});
}
};
})
html:
<input data-passivemodel="keyword">
Edit:
Hmmm .. based on vilo20 answer I am running into a very strange behavior.
while this code is working very well:
<input data-ng-model="keyword" data-passivemodel="keyword">
this one does not (note the value of passivemodel):
<input data-ng-model="keyword" data-passivemodel="keyword2">. Sure I have defined the variable in the controller.
Controller:
.controller('SearchController', function($scope, $routeParams, $search) {
$scope.search = new $search();
$scope.keyword = "";
$scope.keyword2 = "";
})
Edit2: here is a fiddle http://jsfiddle.net/HB7LU/12107/
try this:
.directive('passivemodel', function () {
return {
restrict: "A",
scope: {
passivemodel: '='
},
link: function (scope, element, attrs) {
console.log("passive model", scope.passivemodel);
$scope.$watch('passivemodel', function(newPassivemodel, oldPassivemodel) {
//put your logic when passivemodel changed
});
}
};
})
Hope it helps
Edit: Here is a plunker http://plnkr.co/edit/T039I02ai5rBbiTAHfzv?p=preview
Use the scope attribute:
.directive('passivemodel', function () {
return {
restrict: "A",
scope: {
"passivemodel": "="
},
link: function (scope, element, attrs) {
console.log("access passivemodel: ", scope.passivemodel);
}
};
})
Finally it was as simple as that:
.directive('modelRed', [function(){
return {
require: 'ngModel',
restrict: 'A',
scope: {},
link: function (scope, element, attrs, ngModel) {
scope.$watch(function () {
return ngModel.$modelValue;
}, function(newValue) {
scope.$parent[attrs.modelRed] = newValue;
//console.log(attrs.modelRed, newValue, scope);
});
}
}
}])
And in the html:
<p><input type="text" data-ng-model="testInput" data-model-red="redInput">{{redInput}}</p>
Of course you have to define testInput and redInput in the $scope object.