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
Related
(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
I've written my own directive from which I want to call a method on the parent ng-controller. I've tried doing that by adding require:'ngController' in my directive. However the returned controller is just an empty object. I'm running 1.4.8.
<div ng-controller="myController">
<div my-directive></div>
</div>
app.directive('myDirective', function() {
return {
restrict: 'A',
scope: false,
require: '^ngController',
link: function (scope, $element, attrs, controller) {
//controller is empty object..
};
}
});
Update: My misstake was that I added a method to the controllers scope when I should add it directly to the controller instead.
app.controller('myController', function($scope) {
$scope.myMethod = function() {}; //Not callable from controller passed to directive
this.myMethod = function() {}; //IS callable from controller passed to directive
});
You can call the parent controller via scope if your directive doesn't have an isolated scope, and you don't need to require ngController.
angular.module('app', [])
.controller('controller', function($scope) {
$scope.greet = function() {
return 'hi'
};
})
.directive('testDirective', function() {
return {
restrict: 'E',
template: '<h1>{{ greet() }}</h1>'
};
});
Output:
hi
Plnkr: http://plnkr.co/edit/uGXC5i1GjphcZCPDytDG?p=preview
The function needs to be available in the directive's scope. So if your directive is in the scope of the controller it is:
scope.fn();
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
Lets say I have this directive. I want to change variable of directive scope from inside of parent controller. Is it possible? Or Is this the right way to perform what I am trying to achieve.
<div ng-show="devices.autocompletelist.length > 0" auto-complete url="/search" model="device.name" template-url="/themes/dashboard/assets/js/angular/filter/views/autocomplete-template.html" filtertype="device" autocompletelist="devices.autocompletelist"></div>
Directive
angular.module('autocomplete.directives',[]).directive('autoComplete',['$http',function($http){
return {
scope:{
selectedTagsData:'=model',
autocompletelist: '='
},
restrict: 'AE',
templateUrl: function(elem, attrs) {
return attrs.templateUrl
},
link: function(scope, elem, attrs) {
// I want to access and change value of selectedTagsData.data from parent controller
scope.selectedTagsData = {
data: ''
}
},
}
}]);
App
var app = angular.module('filterlist', ['autocomplete.directives']);
Controller
app.controller('filterCtrl', ['$rootScope', 'filterService', '$scope', '$compile', '$location', '$http', '$timeout', '$q',
function($rootScope, filterService, $scope, $compile, $location, $http, $timeout, $q) {
$scope.deviceSelect = function(devicedata) {
// Can I access and change value of directive scope variable 'scope.selectedTagsData.data' from here
}
}
]);
if you don't define scope in your directive, then all the $scope controller will be accessed through you directive, but this is not perfect solution, because you do not follow the separation of concern and Single Responsibility principle,
To summarize: your directive should not know anything about the parent scope and your controller should not know anything about a directive's internals. They are separate components in separate layers.
I think if you use transclude option it will solve your problem because transclude makes the contents of a directive with this option have access to the scope outside of the directive rather than inside.
I've got an Angular view thusly:
<div ng-include="'components/navbar/navbar.html'" class="ui centered grid" id="navbar" onload="setDropdown()"></div>
<div class="sixteen wide centered column full-height ui grid" style="margin-top:160px">
<!-- other stuff -->
<import-elements></import-elements>
</div>
This is controlled by UI-Router, which is assigning the controller, just FYI.
The controller for this view looks like this:
angular.module('pcfApp')
.controller('ImportElementsCtrl', function($scope, $http, $location, $stateParams, $timeout, Framework, OfficialFramework) {
$scope.loadOfficialFrameworks();
// other stuff here
});
The <import-elements> directive, looks like this:
angular.module('pcfApp').directive('importElements', function($state, $stateParams, $timeout, $window, Framework, OfficialFramework) {
var link = function(scope, el, attrs) {
scope.loadOfficialFrameworks = function() {
OfficialFramework.query(function(data) {
scope.officialFrameworks = data;
$(".ui.dropdown").dropdown({
onChange: function(value, text, $item) {
loadSections($item.attr("data-id"));
}
});
window.setTimeout(function() {
$(".ui.dropdown").dropdown('set selected', data[0]._id);
}, 0);
});
}
return {
link: link,
replace: true,
templateUrl: "app/importElements/components/import_elements_component.html"
}
});
I was under the impression that I'd be able to call the directive's loadOfficialFrameworks() method from my controller in this way (since I'm not specifying isolate scope), but I'm getting a method undefined error on the controller. What am I missing here?
The problem is that your controller function runs before your link function runs, so loadOfficialFrameworks is not available yet when you try to call it.
Try this:
angular.module('pcfApp')
.controller('ImportElementsCtrl', function($scope, $http, $location, $stateParams, $timeout, Framework, OfficialFramework) {
//this will fail because loadOfficialFrameworks doesn't exist yet.
//$scope.loadOfficialFrameworks();
//wait until the directive's link function adds loadOfficialFrameworks to $scope
var disconnectWatch = $scope.$watch('loadOfficialFrameworks', function (loadOfficialFrameworks) {
if (loadOfficialFrameworks !== undefined) {
disconnectWatch();
//execute the function now that we know it has finally been added to scope
$scope.loadOfficialFrameworks();
}
});
});
Here's a fiddle with this example in action: http://jsfiddle.net/81bcofgy/
The directive scope and controller scope are two differents object
you should use in CTRL
$scope.$broadcast('loadOfficialFrameworks_event');
//And in the directive
scope.$on('loadOfficialFrameworks_event', function(){
scope.loadOfficialFrameworks();
})