(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
Related
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
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
Using Angular I created a directive like this:
angular
.module('my-module', [])
.directive('myDirective', function () {
return {
restrict: 'E',
templateUrl: currentScriptPath.replace('.js', '.html'),
scope: {
scenarios: '='
},
controller: MyDirectiveController,
controllerAs: 'vm',
bindToController: true,
replace: true
}
});
MyDirectiveController:
MyDirectiveController.$inject = ['$scope'];
function MyDirectiveController($scope) {
var vm = this;
vm.scenarios = $scope.scenarios;
}
My directive HTML template is this:
<div>{{vm.scenarios[0].name}}</div>
In my parent view HTML I'm using the directive this way:
<my-directive scenarios="vm.scenarios"></my-directive>
The parent controller has a property:
vm.scenarios = [] // could be [{ name : "test"}]
As the vm.scenarios of the parent controller gets set after an $http call it is not available when the vm.scenarios of the directive controller is bound to the $scope.scenarios and it doesn't get updated when the parents controller vm.scenarios gets populated eventually.
When adding this to my directives controller, it works but the solution seems wrong to me:
$scope.$watch('scenarios', function(newValue) {
if (newValue !== undefined) {
vm.scenarios = $scope.scenarios;
}
});
This is how you should define your directive controller:
MyDirectiveController.$inject = [];
function MyDirectiveController() {
// nothing here
}
You don't need to use $scope because you already bind to controller instance this. It means that scope config
scope: {
scenarios: '='
},
populates controller instance this object, not $scope object, and hence $scope.scenarios is undefined. With vm.scenarios = $scope.scenarios; in controller you just overwrite correct binding with undefined value.
Demo: http://plnkr.co/edit/lYg15Xpb3CsbQGIb37ya?p=preview
If a directive is using a controller directly, why is calling a method on the controller by referring the controller by its alias, not doing anything?
Imagine we have the following piece of code:
var app = angular.module('App', []);
app.controller('MyController', ['$scope', function($scope) {
$scope.doAction = function() {
alert("controller action");
}
this.doAction2 = function() {
alert("controller action 2");
}
}]);
app.directive('myDirective', [function() {
return {
restrict: 'E',
scope: {},
controller: 'MyController',
controllerAs: 'myCtrl',
bindToController: true,
template: "<a href='#' ng-click='myCtrl.doAction()'>Click it!<a><br><a href='#' ng-click='myCtrl.doAction2()'>Click it #2!<a> " ,
link: function($scope, element, attrs, controller) {
console.log($scope);
}
}
}]);
While the first link won't work, the second will. To make the the first one work, I'd have to drop the alias, i.e. instead of calling the action by ng-click='myCtrl.doAction()' to call it as: ng-click='doAction()'
Shouldn't it work using the alias too? I mean, you are much more likely to find and reuse a controller, where the developers have attached actions to the $scope object and not to this
ControllerAs exposes the controller instance on the scope under $scope[alias].
In your example, the scope looks (conceptually) like this:
$scope = {
$id: 5,
myCtrl: {
doAction2: function(){...}
},
doAction: function(){...}
}
So, you can see why ng-click="myCtrl.doAction()" doesn't work.
The Controller-As approach has some benefits over directly exposing properties on the scope - one is that it does not pollute the scope (and its descendants) with properties that they may not need. It also inherently provides the dot-approach (.) to work properly with ng-model. You can find more information in this SO question/answer.
I have a couple nested directives. I'm trying to be consistent and use the controllerAs syntax. But I'm struggling to find a clean way for children to call parent methods that doesn't include a parent putting seemingly random functions on its scope.
angular.module('app', [])
.directive('parent', function(){
return {
restrict: 'EA',
controller: 'ParentController',
controllerAs: 'parentCtrl',
}
})
.directive('child', function(){
return {
restrict: 'EA',
require: '^parent',
controller: 'ChildController',
controllerAs: 'childCtrl'
}
})
.controller('ParentController', function($scope){
var ctrl = this;
ctrl.type = "hot";
ctrl.nonInheritedFunction = nonInheritedFunction;
$scope.inheritedFunction = inheritedFunction; // <-- trying to avoid this
function nonInheritedFunction(type){
ctrl.type = type;
if(ctrl.type == 'cold'){
//... do some Parent related things
}
}
function inheritedFunction(type){
// to illustrate that it does the same thing. Not a solution
ctrl.nonInheritedFunction(type);
}
})
.controller('ChildController', function($scope){
var ctrl = this;
ctrl.doAction = doAction;
function doAction(action){
if(action == 'flip_temperature'){
// bah
$scope.parentCtrl.nonInheritedFunction('hot');
// much cleaner feeling
$scope.inheritedFunction('hot');
// wishing I could do something like
// ctrl.nonInheritedFunction('hot');
}
}
/**
* template has access to type through `parentCtrl.type`
* and the function through `parentCtrl.nonInheritedFunction`
*
* The only access the ChildController has is `$scope.parentCtrl.type`
* Is there a way to keep $scope out of the equation?
*/
})
put transclude: true in your parent directive and manually pass the scope in the transclude function in your link function of parent.
Suppose,if the directive creates an isolate scope, the transcluded scope is now a child of the isolate scope. The transcluded and isolate scopes are no longer siblings. The $parent property of the transcluded scope now references the isolate scope.
In angular > 1.3, you're able to use bindToController to attach scope variable to your controller.
Let say you have a parent's function that bind in to scope
scope: {
onSomethingHappen: '&parentFunction'
},
controller: 'DirectiveController',
controllerAs: 'vm',
bindToController: true
You're able to call: this.onSomethingHappen() on your controller
You must use child and parent directives nested, so you can access the outer controller.
<parent >
<child >
</child></parent>