Is there a way to allow the user to give a method name to a directive, let the directive create that method on the scope, and then handle calls to that method?
So, first I let the user define a method name HELLO, and then I let the user call HELLO from elsewhere (still in the same scope)
<div ng-controller="AppController">
<div mydirective="" mydirective-data="MyJson" mydirective-fx="HELLO" />
<button ng-click="HELLO()">Click me</button>
</div>
Internally, the directive should see the HELLO and map it to its own method. In the directive, I am looking at the method name being passed in and assigning it
app.directive('mydirective', function() {
return {
restrict: 'A',
scope: {
data: '=mydirectiveData',
fx: '=mydirectiveFx'
},
link: function(scope, element, attrs) {
scope.fx = function () { console.log(scope.data); } ;
}
}
}
);
as you can see, I am assigning scope.fx, which should be HELLO, to a function which should read scope.data, defined in the controller.
Attempting this does not do anything nor does it throw an error. It makes me wonder if I am doing this the wrong way.
For clarity, I have created a plunker. Remember to open the console.
Use # instead of = then scope[scope.fx] to create the property:
app.directive('mydirective', function() {
return {
restrict: 'A',
scope: {
data: '=mydirectiveData',
fx: '#mydirectiveFx'
},
link: function(scope, element, attrs) {
scope[scope.fx] = function () { console.log(scope.data); };
}
}
}
);
http://plnkr.co/edit/a2c14O?p=preview
Related
I have a set of directives that create a nested list with arbitrary depth. I am looking to use ng-click to send a selected <li>'s object back to the controller. The current attempt looks as so:
angular.module('crmProductgallery').directive('categorytree', function() {
return {
template: '<ul><categorytree-node ng-repeat="item in items" getCategories="getCategories"></categorytree-node></ul>',
restrict: 'E',
replace: true,
// controller: 'CrmProductgallerygallery',
scope: {
items: '=items',
getCategories: '&',
}
};
});
angular.module('crmProductgallery').directive('categorytreeNode', function($compile) {
return {
restrict: 'E',
template: '<li ng-click="getCategories()" getCategories="getCategories">{{item.title}}</li>',
// replace: true,
// controller: 'CrmProductgallerygallery',
// scope: {
// getCategories: '&',
// items: "="
// },
link: function(scope, elm, attrs) {
scope.$watch('items', function() {
$(elm).on('click', function(e) {
scope.getCategories(scope.item);
console.log(scope.item);
// https://stackoverflow.com/questions/15390393/two-nested-click-events-with-angularjs
e.stopPropagation();
});
if (scope.item.children) {
var children = $compile('<categorytree items="item.children" getCategories="getCategories"></categorytree>')(scope);
elm.append(children);
}
});
}
};
});
The code produces the following unordered list:
Currently, when I click on a <li>, scope.item is logged inside my scope.$watch() function, but the controller function getCategories(item) is not called.
If I uncomment out the scope declaration...
...in the categorytreeNode directive, I will get Cannot read properties of undefined (reading 'children). The list does not get created. scope.item no longer exists when trying that.
If I uncomment controller: 'CrmProductgallerygallery', the ng-click works and my controller function is called, but the list elements get duplicated over and over again. Here is what I mean:
Controller Function works on click now:
But now my list is duplicating:
You will also notice I placed getCategories="getCategories" everywhere I could think of trying. None of it works though. I think it is because the <ul> element is getting assigned ng-isolate-scope:
The controller function simply is this:
$scope.getCategories = function(item) {
console.log("getCategories");
console.log(item);
};
Question:
How can I get ng-click on the <li> elements to call my getCategories() controller function
I am working on an angular project and I use a directive to create an isolated scope. The directive looks like this:
var directive = module.directive('question', function () {
return {
restrict: 'E',
templateUrl: 'question.html',
transclude: true,
scope: {
quiz: '=quiz'
},
link: function (scope, attr, element) {
scope.$watch(function () {
return scope.quiz;
},
function (oldVal, newVal) {
scope.currentQuestion = scope.quiz;
});
}
};
});
For I do not want to bind to a property (or field) in my Controller, I created a function and call the directive this way:
<question quiz="quiz.getCurrentQuestion()">... (transcluding stuff)</question>
Please note that quiz is my Controller using the as-Syntax.
The way I process the directive is working, but I don't like to create a two-way-binding ( to an R-value?).
Now I tried to just pass the function using &-binding but this just turns out odd results in the link-function and breaks everything.
Can I use the function-binding using & and somehow call the function (in my template or in the link-function) to get the result I need to make it work like two-way-binding?
Thank you for your help.
EDIT
The return value of the getCurrentQuestion-function is an object which looks like
{
questionNumber: 1,
answers: [],
getQuestionText() : function(...),
...
}
So nothing to special, I hope...
EDIT 2
When I use
...
scope: {
quiz: '&quiz'
}
then in the $watch-function I get
function(locals) { return parentGet(scope, locals); } for scope.quiz
And if I call the function like scope.quiz() I get undefined as result.
Couldn't find any way to watch a function in scope binding. However, there are other solutions. If you want single way binding you can use '#', but that means that you would have to parse the JSON in the watch ( working example):
var directive = module.directive('question', function () {
return {
restrict: 'E',
templateUrl: 'question.html',
transclude: true,
scope: {
quiz: '#'
},
link: function (scope, attr, element) {
scope.$watch('quiz', function (newVal, oldVal) {
scope.currentQuestion = angular.fromJson(newVal);
});
}
};
});
It works, but if you have a high rate of updates, the overhead can be annoying. What I would do, is use a service that holds all the questions, and both controller and directive can talk to. When the current question is changed, the controller should pass to the directive only the id of the new question (using simple # bind), and the directive would query the service for the question.
I'm relative new to AngularJS and trying to create a directive for add some buttons. I'm trying to modify the controller scope from inside the directive but I can't get it to work. Here is an example of my app
app.controller('invoiceManagementController', ['$scope', function ($scope) {
$scope.gridViewOptions = {
isFilterShown: false,
isCompact: false
};
}]);
app.directive('buttons', function () {
return {
restrict: 'A',
template: '<button type="button" data-button="search" title="Filter"><i class="glyphicon glyphicon-search"></i></button>',
scope: {
gridViewOptions: '='
},
transclude: true,
link: function (scope, element, attr, ctrl, transclude) {
element.find("button[data-button='search']").bind('click', function (evt) {
// Set the property to the opposite value
scope.gridViewOptions.isFilterShown = !scope.gridViewOptions.isFilterShown
transclude(scope.$parent, function (clone, scope) {
element.append(clone);
});
});
}
};
});
My HTML like following
{{ gridViewOptions.isFilterShown }}
<div data-buttons="buttons" data-grid-view-options="gridViewOptions"></div>
The scope inside the directive does change but is like isolated, I did try paying with the scope property and transclude but I'm probably missing something, would appreciate some light here
When you modify scope inside of your directive's link function, you are modifying your directive's isolated scope (because that is what you have set up). To modify the parent scope, you can put the scope assignment inside of your transclude function:
transclude(scope.$parent, function (clone, scope) {
// Set the property to the opposite value
scope.gridViewOptions.isFilterShown = !scope.gridViewOptions.isFilterShown
element.append(clone);
});
Ok finally found a solution for this after some more research today. Not sure if the best solution, but this works so good for now.
app.controller('invoiceManagementController', ['$scope', function ($scope) {
$scope.gridViewOptions = {
isFilterShown: false,
isCompact: false
};
}]);
app.directive('buttons', function () {
return {
restrict: 'A',
template: '<button type="button" data-button="search" data-ng-class="gridViewOptions.isFilterShown ? \'active\' : ''" title="Filter"><i class="glyphicon glyphicon-search"></i></button>',
scope: {
gridViewOptions: '='
},
link: function (scope, element, attr, ctrl, transclude) {
element.find("button[data-button='search']").bind('click', function (evt) {
scope.$apply(function () {
// Set the property to the opposite value
scope.gridViewOptions.isFilterShown = !scope.gridViewOptions.isFilterShown;
});
});
}
};
});
I created two directives:
directivesModule.directive("capital", function () {
return {
scope: {
capital: "#"
},
link: function () {}
}
})
directivesModule.directive("country", function () {
return {
scope: {
country: "#"
},
link: function () {}
}
})
Next, I use them in the same element:
<div country="Russia" capital="Moscow"></div>
As a result, I get an error: Error: [$compile:multidir] Multiple directives [capital, country] asking for new/isolated scope on: <div country="Russia" capital="Moscow">
How do I get the attribute values without scope?
These directives will not necessarily be used in conjunction.
According to your code, you don't need isolated scope to get attribute value. Just use this:
directivesModule.directive("capital", function ($parse) {
return {
link: function (scope, element, attrs) {
// get attrs value
attrs.capital
}
}
})
I have an angular directive which is initialized like so:
<conversation style="height:300px" type="convo" type-id="{{some_prop}}"></conversation>
I'd like it to be smart enough to refresh the directive when $scope.some_prop changes, as that implies it should show completely different content.
I have tested it as it is and nothing happens, the linking function doesn't even get called when $scope.some_prop changes. Is there a way to make this happen ?
Link function only gets called once, so it would not directly do what you are expecting. You need to use angular $watch to watch a model variable.
This watch needs to be setup in the link function.
If you use isolated scope for directive then the scope would be
scope :{typeId:'#' }
In your link function then you add a watch like
link: function(scope, element, attrs) {
scope.$watch("typeId",function(newValue,oldValue) {
//This gets called when data changes.
});
}
If you are not using isolated scope use watch on some_prop
What you're trying to do is to monitor the property of attribute in directive. You can watch the property of attribute changes using $observe() as follows:
angular.module('myApp').directive('conversation', function() {
return {
restrict: 'E',
replace: true,
compile: function(tElement, attr) {
attr.$observe('typeId', function(data) {
console.log("Updated data ", data);
}, true);
}
};
});
Keep in mind that I used the 'compile' function in the directive here because you haven't mentioned if you have any models and whether this is performance sensitive.
If you have models, you need to change the 'compile' function to 'link' or use 'controller' and to monitor the property of a model changes, you should use $watch(), and take of the angular {{}} brackets from the property, example:
<conversation style="height:300px" type="convo" type-id="some_prop"></conversation>
And in the directive:
angular.module('myApp').directive('conversation', function() {
return {
scope: {
typeId: '=',
},
link: function(scope, elm, attr) {
scope.$watch('typeId', function(newValue, oldValue) {
if (newValue !== oldValue) {
// You actions here
console.log("I got the new value! ", newValue);
}
}, true);
}
};
});
I hope this will help reloading/refreshing directive on value from parent scope
<html>
<head>
<!-- version 1.4.5 -->
<script src="angular.js"></script>
</head>
<body ng-app="app" ng-controller="Ctrl">
<my-test reload-on="update"></my-test><br>
<button ng-click="update = update+1;">update {{update}}</button>
</body>
<script>
var app = angular.module('app', [])
app.controller('Ctrl', function($scope) {
$scope.update = 0;
});
app.directive('myTest', function() {
return {
restrict: 'AE',
scope: {
reloadOn: '='
},
controller: function($scope) {
$scope.$watch('reloadOn', function(newVal, oldVal) {
// all directive code here
console.log("Reloaded successfully......" + $scope.reloadOn);
});
},
template: '<span> {{reloadOn}} </span>'
}
});
</script>
</html>
angular.module('app').directive('conversation', function() {
return {
restrict: 'E',
link: function ($scope, $elm, $attr) {
$scope.$watch("some_prop", function (newValue, oldValue) {
var typeId = $attr.type-id;
// Your logic.
});
}
};
}
If You're under AngularJS 1.5.3 or newer, You should consider to move to components instead of directives.
Those works very similar to directives but with some very useful additional feautures, such as $onChanges(changesObj), one of the lifecycle hook, that will be called whenever one-way bindings are updated.
app.component('conversation ', {
bindings: {
type: '#',
typeId: '='
},
controller: function() {
this.$onChanges = function(changes) {
// check if your specific property has changed
// that because $onChanges is fired whenever each property is changed from you parent ctrl
if(!!changes.typeId){
refreshYourComponent();
}
};
},
templateUrl: 'conversation .html'
});
Here's the docs for deepen into components.