AngularJS Controller - Directive -> invokation of Controller function from directive - angularjs

I have a question concerning the visibility of function beween controllers and directives. I have a controller and a directive. The directive looks like this way
(function() {
'use strict';
angular
.module('myproject.schedule')
.directive('dirname', dirname);
function dirname() {
var directive = {
restrict: 'A',
replace: true,
scope: {
currentDateScheduler: "=",
...
},
controller: DirnameController,
controllerAs: 'vm',
bindToController: true,
templateUrl: ... directive.html
My controller looks like this:
(function() {
'use strict';
angular
.module('myproject.schedule')
.controller('MyController', MyController);
...
In the directive.html file I have a ng-click which invoked functions of my controller - and this works fine.
Actually now I am not sure why? I thought that a directive has its own namespace and the functions of my controller is not visible in ... directive.html.
Thanks a lot your help!

Controller scope is available to any directive that appears as a child within the DOM element on which the controller is declared. E.g.
<div ng-controller="ctrl1">
<dirname></dirnam> <!-- this has access to ctrl1 scope -->
</div>
So if you were to use the directive within another controller it would have access to that controllers scope. This means that if the function doesn't exist in the controller that the directive declared under then the ng-click will do nothing.
In the directive you can declare a controller, anything declared in this controller will override the controller function of the same name within the directive. E.g.
angular.module('myApp',[])
.controller('myController',function($scope){
$scope.clickMe = function(){
alert('clicked from the controller');
}
})
.directive('dirname', function(){
return {
controller: function($scope){
$scope.clickMe = function(){ alert('clicked from directive'); };
},
};
});
controllers can also be nested. In this case the scope again has a top down effect, whereby a function defined in the top most controller is available to the dom elements enclosed in a child controller. Also if this child controller has the same function declared then these will override the functionality of the parent controller.
Hope this helps

Related

Why ng-click not working on custom directive template?

I want to create a drop down filter in my project. So, I created the drop down list using directive, in which the list contains ng-click event in the template. I don't know where I was wrong. Kindly, provide solution. Thanks in Advance.
My HTML file
<div ng-controller="drop">
<a ng-click="toggleList">Select</a>
<div ng-if="toggleDrop">
<drop-down></drop-down>
</div>
</div>
My Controller Code
angular.module('myApp', [])
.controller('drop', ['$scope', function($scope){
$scope.toggleDrop = false;
$scope.filterList = ['One','Two','Three'];
$scope.toggleList = function() {
$scope.toggleDrop = !$scope.toggleDrop;
}
$scope.filterContent = function() {
alert('dfdf')
}
}]);
My Directive Code
angular.module('myApp', [])
.directive('dropDown', function() {
return {
restrict: 'E',
templateUrl: 'drop.html'
controller: drop
}
});
My Directive Template File
<ul>
<li ng-repeat="items in filterList" ng-click="filterContent()">{{items}}</li>
</ul>
Everything works fine except the ng-click behaviour. Thanks in Advance.
There are few issues with your code,
(i) Your directive should not have a new module with empty dependencies, change it as,
angular.module('myApp')
.directive('dropDown', function()
(ii) You are missing a comma after the controller inside directive,
angular.module('myApp')
.directive('dropDown', function() {
return {
restrict: 'E',
templateUrl: 'drop.html',
controller: 'drop'
}
});
(iii)Should be toggleList() which is a function,
<a ng-click="toggleList()">Select</a>
DEMO
You're missing parenthesis to call function on ng-click
ng-click="toggleList()"
Also in directive code don't declare angular module once again. If you that it will wipe out old registered component from that module. Use angular.module('moduleName')(that will return create module) while registering new component to module
angular.module('myApp', [])
should be
//module getter
angular.module('myApp')
Additionally directive has wrong DDO, correct it to below
return {
restrict: 'E',
templateUrl: 'drop.html', //added `,` here
//I don't think so you need controller here as you shared the parent controller
//wrap with `'` but it will create `drop` controller instance inside directive again
//controller: 'drop'
}
You have given the function name 'toggleList' but you have not called the function. It should work when you call the function as follows:
<a ng-click="toggleList()">Select</a>
You are missing parenthesis in the function.
Thanks

not able to call a function defined in directive into my controller

(function () {
'use strict';
angular
.module('app')
.directive('documentList', documentList);
documentList.$inject = ['$window'];
function documentList($window) {
var directive = {
restrict: 'E',
controller: controller,
controllerAs: "dl",
templateUrl: 'directives/document/document-list.html',
transclude: false,
scope: {
productRef: "=",
productSerialNumber: "=",
title: "#",
eid: "#"
},
};
return directive;
function controller($scope, $state, $element, documentService, ngProgressFactory, registrationService) {
var self = this;
self.goToDetailPage=goToDetailPage;
function goToDetailPage(docId) {
return "a";
}
})();
(function() {
'use strict';
angular
.module('app')
.controller('DetailCtrl', detailCtrl);
// Implementation of controller
detailCtrl.$inject = ['$scope', '$state', '$stateParams', '$rootScope'];
function detailCtrl($scope, $state, $stateParams, $rootScope) {
var self=this;
//alert($stateParams.docId)
self.a=$scope.dl.goToDetailPage();
}
})();
Above is the code in my directive and I have a controller where I want to call goToDetailPage function . But when I am trying to access it through var a=$scope.goToDetailPage() , I am getting error in console.
Not able to rectify.
Any help is appreciated!!!
Thanks
//DetailPage
.state('app.sr.detail', {
name: 'detail',
url: '/detail',
templateUrl: 'views/detail/detail1.html',
controller: 'DetailCtrl',
controllerAs: 'vm'
})
A cool pattern you can use when you want to be able to invoke a function that lives in a directive but you need to be able to invoke from your controller is to pass in an object using two way binding and then extend that object with a function inside the directive. Inside your directive pass in an additional value:
scope: {
productRef: "=",
productSerialNumber: "=",
title: "#",
eid: "#",
control: '=', // note that this is passed in using two way binding
}
Then extend that object inside your directive's controller by attaching a function to it:
// this is in your directive's controller
$scope.control.goToDetailPage = function() {
// function logic
}
Now define the control object in your Controller and pass it into the directive. Because it is two way bound the function will be applied and available to be called in either scope.
// in your controller, assuming controller as syntax
var vm = this;
vm.control = {};
// somewhere later you can invoke it
vm.control.goToDetailPage();
Maybe try,
$scope.goToDetailPage = function (docId) {
return "a";
}
Or, to make use of the "controller as" syntax,
var a = dl.goToDetailPage();
As it looks like from the snippets, DetailCtrl is parent of documentList directive. Functions in a child scope can't be accesses from parent controller. Consider defining goToDetailPage in DetailCtrl and inject that into the directive's scope using '&'
EDIT
If you have something like this:
<div ng-controller="DetailCtrl">
<document-list ...></document-list>
</div>
Then the controller DetailCtrl 's scope is the parent scope and the documentList directive's is the child. And since you are defining an isolate scope in documentList directive using scope: {...} parent and child scopes are different. And a method defined in the isolate scope can't be accessed directly in the parent scope using $scope.methodName().
You can however do the other way around, i.e. define the method in the DetailCtrl and inject into the directive's scope using this:
<div ng-controller="DetailCtrl">
<document-list .... detail-page="goToDetailPage()"></document-list>
</div>
And change your directive's scope to:
scope: { ...,
detailPage:'&'
},
Now you can make a call to the function in the directive controller by calling detailPage()
See the Angular Guide
Hope that helps

Access isolated scope in angular directive template

I am currently writing an angular directive that uses a template in a different HTML file and an isolated template. The directive gets some string via # to its scope and that value is available in teh controller function.
Somehow its not available via {{}} in the HTML template. Why is that so? How can I change that? I read something about the template using the parent scope but I don't fully understand that.
Here is a code example:
angular.module('moduleName')
.directive('aGreatDirective', function () {
return {
restrict: 'E',
scope: {
mapid: '#'
},
templateUrl: "path/to/template.html",
controller: ['$scope', function (scope) {
console.log($scope.mapid); // is defined
}
}
});
And the html code for the template:
<div id="{{mapid}}"></div>
The result in the browser is exactly the same where it should be:
<div id="theValueOfmapid"></div>
Thanks for your help!
PS Here is a jsfiddle: fiddle
Your fiddle was incorrect since you didn't have your controller defined or $scope injected properly. The following will work just fine:
template:
<div ng-controller="MyCtrl">
<a-great-directive mapid="thisisthemapid"></a-great-directive>
Some other code
</div>
js:
var myApp = angular.module('myApp', []);
myApp.controller('MyCtrl', function () {
});
myApp.directive('aGreatDirective', function() {
return {
restrict: 'E',
scope: {
mapid: '#'
},
template: "<div id='{{mapid}}'> {{mapid}} </div>",
controller: ['$scope', function($scope) {
console.log($scope.mapid); // is defined
}
]}
});
Fiddle
Note that in my example, the injected variable in your directive's controller should be $scope, not scope, for consistency reasons.

Why directives method is available from parent scope

AngularJS v1.2.28
Here is my sidebar controller:
angular.module('core').controller('SidebarController', ['$scope', '$location',
function($scope, $location) {
$scope.isItemActive = function (section) {
return ($location.path() === section);
};
}
]);
And sidebar's template:
<section data-ng-controller="SidebarController">
<a ui-sref="dashboard.new-subscription"
ui-route="/dashboard/subscription/new"
class="item "
ng-class="{active: isItemActive('/dashboard/subscription/new')}">
+ New Subscription
</a>
<subscriptions-list></subscriptions-list>
</section>
Subscription list's directive:
angular.module('core').directive('subscriptionsList', ['Subscription', '$state', '$location',
function(Subscription, $state, $location) {
return {
templateUrl: '/modules/core/views/subscriptions-list.client.view.html',
restrict: 'E',
link: function postLink(scope, element, attrs) {
....
scope.isItemActive = function(path, subscription) {
var curPath = path + '/' + subscription._id;
return ($location.path() === curPath);
};
}
};
}
]);
As you can see, both controller and directive have function isItemActive.
For some reason, when I call isItemActive('/dashboard/subscription/new') in sidebars template, I got error "Cannot read property '_id' of undefined" because it calls isItemActive() from subscriptionsList directive instead controllers directive.
How is that possible? How method from a directive can be accessible outside of directive's scope? I haven't used binding for this method..
Your directive doesn't use an isolated scope and doesn't create a child scope. The scope you use to declare isItemActive inside your directive is actually the scope the directive is used in, which is the same as the controller's scope. You basically override your controller's isItemActive.
You need to either use an isolated scope using scope: {} or create a child scope using scope: true.
You can read more about the differences here: https://github.com/angular/angular.js/wiki/Understanding-Scopes

AngularJS can't pass in different function when directive coupled with controller

I have a directive which I want to tightly couple with a controller as a component. I assumed I was following best practice by explicitly passing ion my functions even though I was declaring the controller to use. Here is an example:
app.js
var app = angular.module('plunker', [])
app
.controller('myCtrl', function($scope) {
$scope.output = '';
$scope.foo = function () {
$scope.output = 'foo';
}
$scope.bar = function () {
$scope.output = 'bar';
}
})
.directive('myDirective', function() {
return {
scope: {
output: '=',
foo: '&',
},
templateUrl: 'template.html',
replace: true,
controller: 'myCtrl',
};
})
template.html
<div>
<button ng-click="foo()">Click Foo</button>
<p>You clicked: <span style="color:red">{{output}}</span></p>
</div>
index.html
<body>
<my-directive
output="output"
foo="bar()"> <!-- pass in the *bar* function instead of the *foo* function -->
</my-directive>
</body>
Plunkr: http://plnkr.co/edit/Y4lhxuXbK9YbjAklR7v1?p=preview
Here, even though I'm passing in the bar() function, the output is 'foo' when the button is clicked. If I uncouple the controller by commenting out controller: 'myCtrl' in the directive, the output becomes 'bar'.
I thought I could declare the controller but still be free to pass in which functions I desire to the directive. It also seems that explicitly passing these functions in is a little redundant if the directive just looks up to the controller to find it (I can pass nothing into the directive and it still works).
This is especially problematic when testing as I would like to pass in my own stub functions to the directive, which at the moment I cannot do.
Is there some way to achieve what I want or am I doing something fundamentally wrong?
EDIT I meant to not have the controller declared in the HTML.
Remove the controller property on the directive:
.directive('myDirective', function() {
return {
scope: {
output: '=',
foo: '&',
},
templateUrl: 'template.html',
replace: true,
// controller: 'myCtrl',
};
})
You're wiring up the same controller to the directive as the parent, which is overwriting all the properties you're trying to pass in via isolate scope. The controller is wired up twice, once on the parent scope and then again on the directive. Removing this will allow you to pass in the function bar() and it will not be overwritten.
Here's the Plunker Demonstration
When running inside a directive, the $scope is initialized with output and foo variables before the controller constructor is called. Your controller is essentially overwriting these properties.
A simple check in your controller
if(!$scope.foo)
{
$scope.foo = function () {
$scope.output = 'foo';
}
}
Would work.
PS. I'm assuming your example is a simplification of your problem. If it's not, then the other answer's advice to simply remove the controller from the directive is the best approach.

Resources