Access parent as well as self controller in Angularjs directive - angularjs

I have following fiddle where I am using require property of angular directive for accessing parent controller. Now I want to access both parent and self controller in link function of component directive.
<div ng-controller="MyCtrl">
<div screen>
<div component>
</div>
</div>
</div>
Is it possible to access self as well as parent directive controller in postlink function of directive

Have found the solution. I just need to mention same directive name in array of require property of directive definition object. FIDDLE
.directive('component', function() {
return {
scope: true,
require: ['component','^screen'],
controller: function($scope) {
this.componentFunction = function() {
$scope.screenCtrl.doSomethingScreeny();
}
},
link: function(scope, element, attrs, screenCtrl ) {
scope.screenCtrl = screenCtrl
console.log(screenCtrl);
}
}
})

Related

AngularJs - Directive scope methods not available in controller

I have a directive which inherits its scope from a controller. This Directive has a few methods, when I try to call these methods from the Parent Controller it says method undefined.
Parent Controller:
app.controller('ParentController', ['$scope', function($scope){
$scope.items = []; //Array of Objects.
$scope.someMethod = function(item){
$scope.directiveMethod(item.id);
}
}]);
Directive:
app.directive('someDirective', function(){
return {
restrict: 'A',
link: function(scope, el, attrs){
scope.tableGreen = function(id){
var element = angular.element('[data-cell='+id+']')
element.removeClass('some-class')
element.addClass('new-class')
}
}
}
})
HTML:
<div ng-controller="ParentController">
<div ng-repeat="item in items">
<button ng-click="someMethod(item)" >Green</button>
</div>
<div ng-include="/path/to/some/place">
<div class="some-class" some-directive></div>
</div>
</div>
Is this a right approach? Or is there a better way?
If you are trying to toggle the class of an element triggered from outside of an ng-include one approach could be to utilize ng-class on the element itself:
<div ng-class="{'some-class': item.selected, 'new-class': !item.selected}">content</div>
This could avoid doing the class manipulation via another function and utilize a built-in angular directive, ng-class (https://docs.angularjs.org/api/ng/directive/ngClass).
If you still need to attach other functionality to the element you can of course include ng-class within a directive template.
Take this plnkr for example: http://plnkr.co/edit/Jpr2ayywYYBwiIesmfko?p=preview, it has a set of items in a parent controller, then an ng-include that references those items. The class is changed on elements inside the ng-include from their directive templates with ng-class.
Assign the method to the $parent scope in the directive link.
app.directive('someDirective', function(){
return {
restrict: 'A',
link: function(scope, el, attrs){
scope.$parent.directiveMethod = function(id){
var element = angular.element('[data-cell='+id+']')
element.removeClass('some-class')
element.addClass('new-class')
}
}
}
})

Using "require" in Directive to require a parent Controller

I try to "require" a parent controller (not directive) but AngularJS returns an exception. The code is like this:
JS
app.controller("myController", function ($scole) {
...
});
app.directive("myDirective", function ($q) {
return {
require: "^myController",
template: "",
link: function (scope, element, attrs, myCtrl) {
...
}
};
});
HTML
<div ng-controller="myController as myCtrl">
...
<div my-directive>...</div>
...
</div>
Error
Error: [$compile:ctreq] Controller 'myController', required by
directive 'myDirective', can't be found!
Why?
Maybe, require property must be reference to a controller of directive?
Thanks
Require is of using other directives controllers in another directive , please refer the below example
var App = angular.module('myApp',[]);
//one directive
App.directive('oneDirective',function(){
return {
restrict: 'E',
controller:function($scope){
$scope.myName= function(){
console.log('myname');
}
}
}
});
//two directive
App.directive('twoDirective',function(){
return {
require:'oneDirective' //one directive used,
link : function(scope,ele,attrs,oneCtrl){
console.log(oneCtrl.myName())
}
}
})
Notation require: "^myController" means that your directive will try to access another directive called myController and defined on some of the ancestor tags as my-controller attribute or <my-controller> tag. In your case you don't have such directive, hence the exception.
This is not very conventional what you are trying to do, but if you really want to require outer controller in your directive you can require ngController:
app.directive("myDirective", function($q) {
return {
require: "^ngController",
template: "",
link: function(scope, element, attrs, myCtrl) {
// ...
console.log(myCtrl);
}
};
});
However, this is not very good idea. I can't imagine why you might need it like this. I would recommend to look into scope configuration properties and how you can pass executable function references into your directive from outer controller.
<div my-directive some-callback="test()"></div>
and in directive define scope:
scope: {
someCallback: '&'
}
where in controller you would have $scope.test = function() {};. Then you would not need to require controller explicitly in directive.

how to call a controller function from ng-click directive?

I have two directives and a controller, the problem is that i can't call a function 'addMarkers()' of the controller from my directive .
i have the following codes:
derectives.js
app
.directive('collection', function () {
var tpl = '<ul><member ng-repeat="member in collection" member="member"></member></ul>';
return {
restrict: "E",
replace: true,
scope: {
collection: '='
},
template: tpl
}
})
app
.directive('member', function ($compile) {
var tpl = '<li><a ng-click="addMarkers(member)" >{{member.title}}</a>'+
'<input class="align" ng-if="!member.children" type="checkbox" ng-checked="true"/></li>';
return {
restrict: "E",
replace: true,
scope: {
member: '='
},
template: tpl,
link: function (scope, element, attrs) {
if (angular.isArray(scope.member.children)) {
element.append("<collection collection='member.children'></collection>");
$compile(element.contents())(scope)
}
}
}
})
controller.js
app
.controller('IndexCtrl', function($scope, itemProvider){
itemProvider.getItems().success(function(data){
$scope.items = data;
});
$scope.addMarkers = function(item){
alert("Helloo");
$scope.markers = itemProvider.addMarkers();
}
});
index.html
<div id="menu" ng-controller="IndexCtrl">
<nav>
<h2><i class="fa fa-reorder"></i>All Categories</h2>
<collection collection='items'></collection>
</nav>
</div>
$rootScope is the global scope which should be used only when required. It should be kept as clean as possible to avoid pollution of scope variables.
In order to access parent method from isolated scope you can use $parent service, as shown below:
scope.$parent.addMarkers();
example: basic example
In your case, The directive that wants to access the parent is again called from inside another directive,hence for such cases you can use $parent.$parent,
scope.$parent.$parent.addMarkers(); as shown in the following:
example:example for your case
This can be done if the number of directives using the parent scope is limited. If the hierarchy is long, then using multiple $parent makes the code clumsy. In those cases, it is preferable to add the parent method inside a service and call the service itself from the directives.
Example: service example
I should use $rootScope instead of $scope like below,
$rootScope.addMarkers = function(item){
alert("Helloo");
$scope.markers = itemProvider.addMarkers();
}

AngularJS : Directive Isolated Scoping

I'm developing a AngularJS Directive that is a table. The parent most control ng-table needs to have a isolated scope for its options and model.
However, the children should 'inherit' that scope so that you don't have to pass tons of options to a very interconnected group of components.
For example:
<div ng-table="tableOptions" ng-model="results">
<div ng-table-header>
<div ng-table-header-column="column"
ng-repeat="column in tableOptions.columns">
{{column.name}}
</div>
</div>
<div ng-table-body>
<div ng-table-row="row"
ng-repeat="row in $results">
<div ng-table-cell="column"
ng-repeat="column in tableOptions.columns">
{{row[column.id]}}
</div>
</div>
</div>
<div ng-table-footer></div>
</div>
In the above example, ng-table-header, ng-header-column, etc all need to access properties from the parent control ng-table. Furthermore, all the directives replace and transclude.
I know I could broadcast events to the children/parent directives but is there a better way to restrict scope at the parent level and auto pass it to the children?
Oh dude, you have forced me to look how forms and inputs directives are implemented in angular.
So, angularjs has similar thing to what you want to implement - ng-form directive with ng-input.
When you use form or ng-form directive, it creates controller(directives can do it) and transclude html-code will inherit it. ng-input(input) asks for it in require option.
require: ['ngModel', '^?form', '^?ngModelOptions'],
So you get this controller in link function. So... you can call functions, do things, you know.
You can do it like this(just an idea, this is how it works):
.directive('coolTable', function() {
return {
...
controller: function($scope) {
$scope.columns = []; /* Here you will have all table columns, so you can send it's data to service and do any work */
$scope.registerColumn = function(column) {
$scope.columns.push(column);
};
},
...
};
})
.directive('column', function() {
return {
...
require: '^?coolTable',
...
link: function(scope, element, attrs, coolTable) {
coolTable.registerColumn(this);
},
};
})
<coolTable>
<column></column>
<column></column>
<column></column>
</coolTable>
I was able to find a work around looking at angular-ui code that created their own transclude directive to apply scope.
so my master table directive starts off like:
module.directive('ngTable', function ($rootScope, $timeout, $window, $compile) {
return {
restrict: 'AE',
transclude: true, // NOTE THIS
replace: true, // NOTE THIS
templateUrl: 'common/components/table/views/table.tpl.html',
scope: {
ngModel: "=",
tableOptions: "=ngTable"
},
controller: 'ngTableCtrl',
link: function ($scope, $element, $attributes, controller, ngModel) {
....
}
}
});
then I create my own transclude directive like:
module.directive('tableTransclude', function () {
return {
link: function ($scope, $element, $attrs, controller, $transclude) {
$transclude($scope, function (clone) {
$element.empty();
$element.append(clone);
});
}
};
})
and the usage in the template of table looks like:
<div table-transclude />
And presto!!! A little hacky but gets the job done. I understand why Angular did this but in some cases you need this type of scoping.

Set attribute value of angular directive from another controller

Angular directive;
.directive('ngFilemanager', function () {
return {
restrict: 'EA',
scope: {
thefilter: '=',
},
link: function (scope, element, attrs) {
},
templateUrl: '/templates/filemanager.html',
controller: FileManagerController
}
Html:
<div id="testcontainer" ng-controller="OtherController">
...
<div ng-click="vm.myfunction">Set Filter</div>
...
<div id="thefilemanager" ng-filemanager thefilter=""></div>
...
</div>
How can i set thefilter value in a function of OtherController?
I tried setting the attribute value by jquery but my ng-view isn't updated correctly then.
You've got bi-directional isolated scope so:
function OtherController($scope){
$scope.myfilter= "";
$scope.setFilter = function(what){
$scope.myfilter = what;
}
}
and HTML:
<div id="testcontainer" ng-controller="OtherController">
<div ng-click="setFilter('fun')">Set Filter</div>
<div id="thefilemanager" ng-filemanager thefilter="myfilter"></div>
</div>
Then when you change $scope.myfilter in the OtherController's scope, scope.thefilter changes in your directive's scope.
If the "other" controller is not a direct parent, you could use $emit or $broadcast depending on where the target is.
Here's an example using $broadcast instead:
app.controller('MainCtrl', function($scope) {
$scope.setFilter = function(what){
$scope.$broadcast('setFilter', what);
}
});
then inside your directive you can listen:
link: function (scope, element, attrs) {
scope.$on('setFilter', function(e, what){
scope.thefilter = what;
});
},
To make it work anywhere, you can $broadcast from $rootScope, but at that point you might want to re-evaluate why you have to do this. Angular itself does this a lot, for example, routeChangeSuccess event, but that doesn't mean you should do it.
This will work if the other controller is a parent of ngFilemanager
<div id="thefilemanager" ng-filemanager thefilter="theFilterValue"></div>
In some other controller
...
$scope.theFilterValue = 'some other value';
...
Look the doc's isolate scope on directives section

Resources