Calling module controller function from child directive - angularjs

My first try at angularjs.
I'm trying to create a directive that will, on click, call a function that is declared in the model's controller function.
Here is my simplified code: http://jsfiddle.net/vnj62xn8/
JS:
angular.module('tasks', [])
.controller('taskCtrl', function($scope){
$scope.complete = function(){
console.log('parent function');
}
})
.directive('taskList', function(){
return {
restrict: 'E',
scope: {
complete: '&'
},
template: '<button ng-click="complete()">Complete Task</button>',
controller: function($scope){
/*$scope.complete = function(){
console.log('child function');
}*/
}
}
});
HTML:
<div ng-app="tasks">
<task-list></task-list>
</div>
I've looked at other Q/As on SO and found that this seems to be the most generalized solution, although it will never call the complete() function found in the taskCtrl controller. It will, however, call the complete() function from the directive's controller, if uncommented. What am I missing?

You need to write the html as following:
<div ng-controller="taskCtrl">
<task-list complete="complete()"></task-list>
</div>
Here is the link to updated fiddle http://jsfiddle.net/paila/vnj62xn8/4/

Simply change your code to :
.directive('taskList', function(){
return {
restrict: 'E',
scope: {
complete: '&'
},
template: '<button ng-click="complete()">Complete Task</button>',
controller: 'taskCtrl' // Here you are referencing the taskCtrl
}
});
You just need to reference your existing controller inside the controller assignment

Related

how to call twice nested method in angular 1.x?

My HTML looks like this:
Controller HTML:
<div ng-controller="Ctrl">
<first-directive></first-directive>
</div>
first-directive HTML
<li>
<second-directive></second-directive>
</li>
Controller JS:
app.controller('Ctrl', (#scope) => {
$scope.foo = function() {
console.log('do smthn');
}
});
first directive:
app.directive('first-directive', function(){
return {
restrict: 'E',
templateUrl: '/partials/first-directive.html',
replace: true,
scope: {
// some data
}
controllerAs: function(){}
}
}
second directive:
app.directive('second-directive', function(){
return {
restrict: 'E',
templateUrl: '/partials/second-directive.html',
controllerAs: function(){}
}
}
So i have controller with nested directive and there is another nested directive. When i'm trying to call $parent.foo() from first-directive it works. When i'm trying to call $parent.$parent.foo() from second-directive it doesn't work. I tried also use ng-controller="Ctrl as ctrl" and ctrl.foo() from second-directive syntax, but it doesn't work either. Why?
have you added second directive inside
<div ng-controller="Ctrl">
are you using in ng-if before second directive? because ng-if also creates its own scope
You can use $rootScope.$broadcastin nested directive controller and then on parent controller use $rootScope.$on.
In second directive
app.directive('second-directive', function(){
return {
restrict: 'E',
templateUrl: '/partials/second-directive.html',
controller: function($rootScope){
$rootScope.$broadcast('parentCtrl',{});
}
}
}
In parent controller
app.controller('Ctrl', ($scope,$rootScope) => {
$scope.foo = function() {
console.log('do smthn');
}
$rootScope.$on('parentCtrl', function (event, data){
//do the parent task here
}
});
You can also use service function to communicate between the child controller and parent controller.
As second nested directive doesn't have isolated scope, try $parent.foo()

Cant use directive controller values in directive template

Im having a hard time accessing the attributes passed in to my directive from the template of that directive. I want to be able to access 'companyId' from album.tmpl.html but no matter what i try i can't get it. The strangest part is i can see it has made its way in to the controller, but somehow it's not getting from the controller to the template. I know the template is correctly calling the controller as it can succesfully print out the value of 'testVar' which is initialised inside the controller. Any advice would be appreciated.
directive + directive controller
(function () {
'use strict';
angular.module('erCommon')
.directive('erAlbum', albumDirective)
.controller('AlbumController', AlbumController);
function AlbumController() {
var vm = this;
vm.testVar = "test var initiated";
}
function albumDirective($log) {
function albumLink(scope, element, attrs, AlbumController) {
//watch vars in here
}
return {
restrict: 'E',
scope: {
companyId: '=companyId'
},
bindToController: true,
templateUrl: 'components/temp/album.tmpl.html',
controller: 'AlbumController',
controllerAs: 'albumCtrl',
link: albumLink
};
}
})();
template ( album.tmpl.html
<div ng-controller="AlbumController as albumCtrl">
testVar: {{albumCtrl.testVar}}<BR>
companyId:{{albumCtrl.companyId}}<BR>
</div>
usage
<er-album company-id="2"></er-album>
output
test var: test var initiated
companyId:
You need to remove ng-controller from your template:
<div>
testVar: {{albumCtrl.testVar}}<BR>
companyId:{{albumCtrl.companyId}}<BR>
</div>
To achieve the result you wanted i had to modify the structure of your code slightly. Hope this helps you to understand the issue. Look for materials about isolated scopes which Angular uses with directives.
HTML:
<div ng-app="erCommon" ng-controller="AlbumController as albumCtrl">
<er-album company-id="2" test = "albumCtrl.testVar"></er-album>
</div>
Controller:
angular.module('erCommon', [])
.directive('erAlbum', albumDirective)
.controller('AlbumController', AlbumController);
function AlbumController() {
var vm = this;
vm.testVar = "test var initiated";
}
function albumDirective() {
return {
restrict: 'E',
scope: {
test: '=test',
companyId: '#companyId'
},
template: '<div> testVar: {{test}}<BR> companyId:{{companyId}}<BR> </div>', // it will work fine with templateUrl as well, just didn't want to cr8 another file...
link: function(scope, element, attrs){
//do whatever else you might need;
}
};
}

controllerAs in directive, functions in controller can't see some variables

I have bellow definition of directive/controller. If you look, there's an onClick function defined. When function is being called, it can see this variable, with ftConditionButton bound to it as described by directive. The thing is, onClick doesn't see conditionButtonController which is against my understanding of JavaScript. Can someone explain to me what I am missing? Right now it looks to me like a new "Class" was created and was given all the methods of original controller.
angular
.module('app')
.directive('ftConditionButton', ftConditionButton);
function ftConditionButton() {
var directive = {
restrict: 'A',
scope: {},
require: ['ftConditionButton'],
templateUrl: 'conditionButton.html',
controller: ConditionButtonController,
controllerAs: 'conditionButtonController',
bindToController: {
ftConditionButton: '&'
}
};
return directive;
}
function ConditionButtonController() {
var conditionButtonController = this;
conditionButtonController.onClick = onClick;
////////////////
function onClick() {
this.ftConditionButton; // this works
conditionButtonController; // conditionButtonController is undefined
}
}
Not sure what you're doing wrong. Seems okay to me.
Few things I can recommend:
1) Make sure you're using angularjs-1.4.
2) Always wrap everything into local function, such that you don't expose anything globally.
(function() {
angular.module('experiment', [])
.controller('MyController', function($scope){
$scope.test = function() {
alert("Test!");
};
});
angular
.module('experiment')
.directive('ftConditionButton', ftConditionButton);
function ftConditionButton() {
var directive = {
template: '<button ng-click="conditionButtonController.onClick()">Hello </button>',
restrict: 'A',
scope: {},
bindToController: {
ftConditionButton: '&'
},
controller: ConditionButtonController,
controllerAs: 'conditionButtonController',
};
return directive;
}
function ConditionButtonController($scope) {
var conditionButtonController = this;
conditionButtonController.onClick = onClick;
function onClick() {
conditionButtonController.ftConditionButton();
}
}
})();
and view:
<div ng-app="experiment">
<div ng-controller="MyController">
<div ft-condition-button="test()" />
</div>
</div>
PS, use JSFiddle next time, to demonstrate your problem.

ng-show not working inside directive template

I would like to do something like this
fiddle, making the text disappear and reappear with every click.
Problem is, it seem that with an isolated scope you can't have access to the controller scope. I solved it in the link function, handling there click event and setting my "showMe" flag using scope.$apply, like:
scope.$apply('showMe = false');
Is this the right way to go or there is some more elegant method?
Here you go (http://jsfiddle.net/66d0t7k0/1/)
Put your click handler in the link function and expose showMe to the scope
app.directive('example', function () {
return {
restrict: 'E',
template: '<p ng-show=\"showMe\">Text to show</p><button ng-click=\"clickMe()\">Click me</button>',
scope: {
exampleAttr: '#'
},
link: function (scope) {
scope.clickMe = function () {
scope.showMe = !scope.showMe;
};
}
};
});
To expand on apairet's answer, since your directive is using an isolated scope, you could handle all of that in the template itself like so:
app.directive('example', function () {
return {
restrict: 'E',
template: '<p ng-show=\"showMe\">Text to show</p><button ng-init=\"showMe = false\" ng-click=\"showMe = !showMe\">Click me</button>',
scope: {
exampleAttr: '#'
},
link: function (scope) {
}
};
});
Another consideration is to use ng-if rather than ng-show as it doesn't render the element in the DOM until the expression evaluates to true.
You can hide the scope in the directive by setting scope: false
You can then put all your function in the main controller scope
angular.module('appMyApp').directive('appMyAppItem', function() {
return {
transclude: true,
templateUrl: 'link/to/url',
controller: 'appMyAppController',
scope: false
}
});
angular.module('appMyApp').controller('appMyAppController', ['$scope', function($scope){
$scope.showItem = true;
$scope.toggleItem = function(){
$scope.showItem = !$scope.showItem;
};
}]);
Hope this helps

AngularJS - pass function to directive

I have a example angularJS
<div ng-controller="testCtrl">
<test color1="color1" updateFn="updateFn()"></test>
</div>
<script>
angular.module('dr', [])
.controller("testCtrl", function($scope) {
$scope.color1 = "color";
$scope.updateFn = function() {
alert('123');
}
})
.directive('test', function() {
return {
restrict: 'E',
scope: {color1: '=',
updateFn: '&'},
template: "<button ng-click='updateFn()'>Click</button>",
replace: true,
link: function(scope, elm, attrs) {
}
}
});
</script>
</body>
</html>
I want when I click button, the alert box will appear, but nothing show.
Can anyone help me?
To call a controller function in parent scope from inside an isolate scope directive, use dash-separated attribute names in the HTML like the OP said.
Also if you want to send a parameter to your function, call the function by passing an object:
<test color1="color1" update-fn="updateFn(msg)"></test>
JS
var app = angular.module('dr', []);
app.controller("testCtrl", function($scope) {
$scope.color1 = "color";
$scope.updateFn = function(msg) {
alert(msg);
}
});
app.directive('test', function() {
return {
restrict: 'E',
scope: {
color1: '=',
updateFn: '&'
},
// object is passed while making the call
template: "<button ng-click='updateFn({msg : \"Hello World!\"})'>
Click</button>",
replace: true,
link: function(scope, elm, attrs) {
}
}
});
Fiddle
Perhaps I am missing something, but although the other solutions do call the parent scope function there is no ability to pass arguments from directive code, this is because the update-fn is calling updateFn() with fixed parameters, in for example {msg: "Hello World"}. A slight change allows the directive to pass arguments, which I would think is far more useful.
<test color1="color1" update-fn="updateFn"></test>
Note the HTML is passing a function reference, i.e., without () brackets.
JS
var app = angular.module('dr', []);
app.controller("testCtrl", function($scope) {
$scope.color1 = "color";
$scope.updateFn = function(msg) {
alert(msg);
}
});
app.directive('test', function() {
return {
restrict: 'E',
scope: {
color1: '=',
updateFn: '&'
},
// object is passed while making the call
template: "<button ng-click='callUpdate()'>
Click</button>",
replace: true,
link: function(scope, elm, attrs) {
scope.callUpdate = function() {
scope.updateFn()("Directive Args");
}
}
}
});
So in the above, the HTML is calling local scope callUpdate function, which then 'fetches' the updateFn from the parent scope and calls the returned function with parameters that the directive can generate.
http://jsfiddle.net/mygknek2/
In your 'test' directive Html tag, the attribute name of the function should not be camelCased, but dash-based.
so - instead of :
<test color1="color1" updateFn="updateFn()"></test>
write:
<test color1="color1" update-fn="updateFn()"></test>
This is angular's way to tell the difference between directive attributes (such as update-fn function) and functions.
How about passing the controller function with bidirectional binding? Then you can use it in the directive exactly the same way as in a regular template (I stripped irrelevant parts for simplicity):
<div ng-controller="testCtrl">
<!-- pass the function with no arguments -->
<test color1="color1" update-fn="updateFn"></test>
</div>
<script>
angular.module('dr', [])
.controller("testCtrl", function($scope) {
$scope.updateFn = function(msg) {
alert(msg);
}
})
.directive('test', function() {
return {
scope: {
updateFn: '=' // '=' bidirectional binding
},
template: "<button ng-click='updateFn(1337)'>Click</button>"
}
});
</script>
I landed at this question, because I tried the method above befire, but somehow it didn't work. Now it works perfectly.
use dash and lower case for attribute name ( like other answers said ) :
<test color1="color1" update-fn="updateFn()"></test>
And use "=" instead of "&" in directive scope:
scope: { updateFn: '='}
Then you can use updateFn like any other function:
<button ng-click='updateFn()'>Click</button>
There you go!
I had to use the "=" binding instead of "&" because that was not working.
Strange behavior.
#JorgeGRC Thanks for your answer. One thing though, the "maybe" part is very important. If you do have parameter(s), you must include it/them on your template as well and be sure to specify your locals e.g. updateFn({msg: "Directive Args"}.

Resources