I have an anonymous controller within a directive. That controller, upon a particular event, opens a generic dialog and provides a partial template that is used to add some buttons to the generic dialog via ng-include.
Now within the generic dialog, once the user clicks on any of the provided buttons, I want a particular function in the anonymous controller be called. Any thoughts on how this could be achieved?
Controller:
angular.module('abc')
.directive('xyz', [function() {
return {
restrict: 'E',
scope: {},
controller: ['$scope', function($scope) {
$scope.callThisFunc = function(){};
}]
}
}
Partial template:
<div>
<button label="CANCEL"
ng-click="callThisFunc()">
</button>
</div>
Generic dialog template (different module than the abc module above):
<div ng-include="partial"></div>
Try to use "controllerAs" syntax
angular.module('abc')
.directive('xyz', [function() {
return {
restrict: 'E',
scope: {},
controller: function() {
var vm = this;
vm.callThisFunc = function(){};
return vm;
}],
controllerAs: 'xyzCtrl'
}
}
partial template
<div>
<button label="CANCEL"
ng-click="xyzCtrl.callThisFunc()">
</button>
</div>
generic dialog template
<div ng-include="partial" xyz></div>
</div>
Related
So I have this directive that has its own scope but I want to access to a function inside its parent controller. I can do this if the parent controller exposes the function with a $scope.getElementsList(), although I'm trying to avoid the use of $scope and I have the function exposed with self.getElementsList() and the directive cannot reach it.
Directive:
angular.module('myApp').directive('accountBalance', function() {
return {
scope: {
elementId: '=elementid'
},
transclude: true,
restrict: 'E',
templateUrl: '../views_directives/account-balance.html',
controller: function($scope) {
$scope.removeElement = function(){
//this where I want to access the parent function
console.log($scope.$parent.getElementsList());
console.log("ALSO I WANT TO ACCESS THIS DIRECTIVE elementId WITHOUT USING $scope", $scope.elementId);
}
}
};
});
ParentController:
angular.module('myApp').controller('AppDesignCtrl', function ($scope) {
var self = this;
self.elementsList = [];
self.getElementsList = function(){
return self.elementsList;
}
});
I also want to know what is the best way to access, inside the directive controller, the data passed to the directive's $scope.
scope: {
elementId: '=elementid'
},
UPDATE
<div>
<i class="fa fa-arrows element-drag"></i>
<i class="fa fa-trash-o element-remove" ng-click="removeElement()"></i>
</div>
And what about calling functions from the directive template inside the controller of the directive? Do I need to expose them with something like $scope.removeElement()? How do I use this.removeElement() and be able to access it from the template?
Sorry about the long question. I'm trying to set the best practices to my new project since I've been away from angular for a year+.
Thanks in advance
(Going from bottom to top...)
To call functions in the controller without using the scope in Angular >= 1.2, use the controllerAs syntax:
<div ng-controller="AppDesignCtrl as appDesignCtrl">
...
<i class="fa fa-trash-o element-remove" ng-click="appDesignCtrl.removeElement()"></i>
</div>
And removeElement() must be a method of the controller:
angular.module('myApp').controller('AppDesignCtrl', function ($scope) {
...
this.removeElement = function() {
...
};
});
To access the scope data from the controller in Angular >= 1.3, use the new bindToController: true configuration (this is especially useful when combined with the new controllerAs syntax):
angular.module('myApp').directive('accountBalance', function() {
return {
...
scope: {
elementId: '=elementid'
},
controller: function() {
// now elementId is a member of the controller:
console.log(this.elementId);
}
};
});
Having said these, the answer to how you can call getElementsList from the directive would be:
angular.module('myApp').directive('accountBalance', function() {
return {
...
scope: {
elementId: '=elementid',
getElementList: '&'
},
controller: function() {
...
// invoking the expression that was passed to us
var theElements = this.getElementList();
}
};
});
The correct expression should be passed as:
<div ng-controller="AppDesignCtrl as appDesignCtrl">
<account-balance element-id="xxx"
get-elements-list="appDesignCtrl.getElementsList()"></account-balance>
</div>
It is generally not recommended, because directives are meant to be self-contained. It isn't critical if you don't plan to reuse the directive. And wise usage of isolate scope can solve this.
angular.module('myApp').directive('accountBalance', function() {
return {
scope: {
outerScope: '#'
elementId: '='
},
transclude: true,
restrict: 'E',
templateUrl: '../views_directives/account-balance.html',
controller: function($scope) {
console.log("we can use anything from other controller", $scope.outerScope.elementsList)
$scope.elementId = "and share data with any other scope";
}
};
});
Controller is defined as ng-controller="AppDesignCtrl as appDesign", and directive usage is
<account-balance element-id="sharedParentScopeVar" outer-scope="appDesign">
So there won't be any problem if the directive should be moved to other controller.
I guess 'best practice' may be to set up a service that embraces the data and is used by both app controller and directive, so directive controller operates on data items and not DOM elements.
And what about calling functions from the directive template inside
the controller of the directive? Do I need to expose them with
something like $scope.removeElement()?
You surely don't. If there's a need to use functions from outside, you're doing something wrong. Send a message to respective element to run the function if it is DOM-related. Or put the function into the service if it is data-related.
I have a set of tabs which all have a directive in them:
<div class="col-md-9 maincols" id="formEditor">
<tabset>
<tab heading="New subscriber" select="isSelected('newSub')">
<new-subscriber></new-subscriber>
</tab>
<tab heading="Existing subscriber" select="isSelected('existingSub')">
<existing-subscriber></existing-subscriber>
</tab>
<tab heading="Landing page" select="isSelected('landing')">
<landing-page></landing-page>
</tab>
</tabset>
</div>
All these 3 directives have been defined similarly like this:
angular.module('myApp')
.directive('newSubscriber', function () {
return {
restrict: 'E',
scope: {},
replace: true,
templateUrl: 'scripts/newsubscriber/newsubscriber.html',
controller: 'newsubscriberCtrl'
};
}); //... and so on
I am (probably wrongly) under the impression that because I have set scope: {} for all the directives, they should now have completely isolated scopes and leave each other alone.
But that is not the case and bindings from the first directive's controller manage to stop values in the second or third controller from being binded
for example in newsubscriberCtrl I have:
app.controller('newsubscriberCtrl', ["$scope", "$routeParams", "UserMessages", "FormProvider", function ($scope, $routeParams, UserMessages, FormProvider) {
$scope.formId = $routeParams.formId;
var newSubscriberForm = new FormProvider.Form($scope);
angular.extend($scope, newSubscriberForm);
$scope.title = UserMessages.exampleText.genericPageTitle;
$scope.description = UserMessages.exampleText.genericPageDescription;
$scope.validationMessages = {
contactNotSaved: UserMessages.validationMessages.contactNotSaved,
contactCreatedOk: UserMessages.validationMessages.contactCreatedOk,
contactNotCreated: UserMessages.validationMessages.contactNotCreated,
requiredField: UserMessages.validationMessages.requiredField,
passwordMismatch: UserMessages.validationMessages.passwordMismatch,
isOpen: false
}
}]);
which is overriding the similar object in existingSubscriber controller:
app.controller('existingsubscriberCtrl', ["$scope", "$routeParams", "UserMessages", "FormProvider", function ($scope, $routeParams, UserMessages, FormProvider) {
$scope.formId = $routeParams.formId;
var existingSubscriberForm = new FormProvider.Form($scope);
angular.extend($scope, existingSubscriberForm);
$scope.title = UserMessages.exampleText.genericPageTitle;
$scope.description = UserMessages.exampleText.genericPageDescription;
$scope.validationMessages = {
contactNotSaved: UserMessages.validationMessages.contactNotSaved,
contactSavedOk: UserMessages.validationMessages.contactSavedOk,
requiredField: UserMessages.validationMessages.requiredField,
passwordMismatch: UserMessages.validationMessages.passwordMismatch,
isOpen: false
}
}]);
So in the view of both directives <pre>{{validationMessages | json }}</pre> the validationMessages object has the props of the first controller.
Why is this happening? Am I missing to understand a concept here? How Can I isolate these controllers from each other and comfortably have similar props in the controllers without them affecting each other?
Side note: I strongly want to avoid having to prefix everything on all scopes with their controller name, e.g $scope.newSubscriber.validationMessages and so on... as that would defeat the whole point pretty much as I will effectively one big controller for the whole tab section and directives would also be pointless.
Angular is on v.1.3.0-beta.11
angular-ui-bootstrap is on v.0.10.0
You have reuse the same controller newsubscriberCtrl in /app/scripts/formbanner/formbanner.js:
.directive('formBanner', function () {
return {
restrict: 'E',
replace: 'true',
templateUrl: 'scripts/formbanner/formbanner.html',
controller: 'newsubscriberCtrl'
};
});
The existingSubscriber directive have the formBanner as a child directive, plus the formBanner directive doesn't have an isolated scope.
Therefore, the $scope that get injected into the newsubscriberCtrl of formBanner is the same as the scope of the existingSubscriber!!
I've tried removing the controller property in the formBanner directive and I saw it works as expected.
Have you tried this?
angular.module('myApp')
.directive('newSubscriber', function () {
return {
restrict: 'E',
scope: { someValue = "&validationMessages" },
replace: true,
templateUrl: 'scripts/newsubscriber/newsubscriber.html',
controller: 'newsubscriberCtrl',
link: function (scope, iElm, iAttrs) {
var x = scope.someValue();
// x = your messages
}
};
});
In your controller
$scope.someValue
EDIT Disclaimer: this is sort of from memory. When I was facing something similar I felt this to be rather enlightning:
http://umur.io/angularjs-directives-using-isolated-scope-with-attributes/
I'm making a widget system and I plan to have each widget be it's own directive. I want to be able to pass the directives that should be used from my controller to my view via an array.
This is what my code currently looks like:
app.controller('showDashboard', function ($scope){
$scope.widgets = ['widget1', 'widget2', 'widget3'];
});
In View:
<article ng-repeat="widget in widgets" class="widget">
<div {{widget}}></div>
</article>
Directive:
app.directive('widget1', function (){
return {
restrict: "A",
templateUrl: 'testWidget.html'
};
}).directive('widget2', function (){
return {
restrict: "A",
templateUrl: 'testWidget.html'
};
}).directive('widget3', function (){
return {
restrict: "A",
templateUrl: 'testWidget.html'
};
});
So rather than make the directive itself the widget, why not use ng-include to load in the templates which can themselves contain directives, controllers etc...
app.controller('showDashboard', function ($scope){
$scope.widgets = ['widget1.html', 'widget2.html', 'widget3.html'];
});
<article ng-repeat="widget in widgets" class="widget">
<div ng-include="widget"></div>
</article>
Here is a jsFiddle with an example.
And here is an updated fiddle that shows a slightly more complex widget with a controller.
It can be improved but just to get the idea
//html
<div wrapper mydir="widget"></div>
//js
.directive('wrapper', function ($compile){
return {
restrict: "A",
scope: {
mydir: "="
},
link: function ( scope, element ) {
var html = '<test '+scope.mydir+'></test>';
var compiled = $compile( html )( scope );
element.append(compiled);
}
}
})
I have the following directives:
Directive 1
app.directive('tableDiv', function () {
return {
templateUrl: 'js/directives/table-div/table-div.html',
replace: true,
scope: {
table: '=',
},
controller: function ($scope, $element, $attrs) {
},
link: function postLink(scope, element, attrs) {
}
}
});
Directive 1 template:
<div data-table-div-row value="row" sizes="table.tbody.sizes" ng-repeat="row in table.tbody.values">
</div>
Directive 2:
app.directive('tableDivRow', function ($rootScope) {
return {
templateUrl: 'js/directives/table-div/table-div-row.html',
replace: true,
scope: {value: '=', sizes: '='},
controller: function ($scope, $element, $attrs) {
$scope.showInfo = function () {
$scope.visible = true;
};
$scope.hideInfo = function () {
$scope.visible = false;
};
$scope.hasTemplate = function() {
return ($scope.value.template ? true : false);
}
},
link: function postLink(scope, element, attrs) {
scope.$watch(function () {
return scope.visible;
}, function (value) {
if (value === true) {
$(element).find('div.table-row').addClass('open');
$(element).find('div.table-row.edit').removeClass('hidden');
} else {
$(element).find('div.table-row').removeClass('open');
$(element).find('div.table-row.edit').addClass('hidden');
}
}, true);
}
}
});
Directive 2 template:
<div>
<div class="row-fluid">
<div class="table-row clearfix">
<div class="{{sizes.first}} first">{{value.display.first}}</div>
<div ng-repeat="cell in value.display.cells" class="{{sizes.cells[$index]}}">{{cell}}</div>
<div class="{{sizes.last}} last regular">
<div ng-switch on="value.display.last">
<div ng-switch-when="%editbutton%">
<div class="show-info closed" ng-click="showInfo()"></div>
</div>
<div ng-switch-default>
{{value.display.last}}
</div>
</div>
</div>
</div>
</div>
<div ng-if="hasTemplate()">
<ng-include src="value.template"></ng-include>
</div>
Inside the second directive template I'm including a dynamic template based on the controller $scope model. Inside that template and in the directive template I want to call a function from the controller $scope. Is there a way to achieve that?
A child scope is created for <ng-include src="value.template"></ng-include>, which means that the parent functions should be available in this template. In other words, you shouldnt have to do anything and it'll work - see this simple example: http://plnkr.co/edit/Es2UL09ASPSTa5Fstzjf?p=preview
So, it seems it's in the docs and it wasn't clear enough for me. Inside the directive declaration I needed to add: method: '&'
scope: {
table: '=',
method: '&'
},
and inside the template where I call the directive, the method html attribute MUST HAVE () at the end:
<div data-table-div-row method="method()" value="row" sizes="table.tbody.sizes" ng-repeat="row in table.tbody.values"></div>
In this way the method can be passed down to the second directive.
As #Direvius propose, to call a method in the controller scope from a directive you must call the method passing an object with the parameter rather the parameter itself :
scope.method({message : "text"});
So, to call a controller method from the nested directive you must to wrap the parameter inside n objects :
scope.method({message : {message : "text"}});
Don't forget to declare "message" as argument in the nested directive template and the outer-directive declaration in your html :
<outer-directive outer-method-arg="method(message)"></outer-directive>
also in your outer template :
<inner-directive inner-method-arg="method(message)"></inner-directive>
Can anyone tell me how to include a controller from one directive in another angularJS directive.
for example I have the following code
var app = angular.module('shop', []).
config(['$routeProvider', function ($routeProvider) {
$routeProvider.when('/', {
templateUrl: '/js/partials/home.html'
})
.when('/products', {
controller: 'ProductsController',
templateUrl: '/js/partials/products.html'
})
.when('/products/:productId', {
controller: 'ProductController',
templateUrl: '/js/partials/product.html'
});
}]);
app.directive('mainCtrl', function () {
return {
controller: function ($scope) {}
};
});
app.directive('addProduct', function () {
return {
restrict: 'C',
require: '^mainCtrl',
link: function (scope, lElement, attrs, mainCtrl) {
//console.log(cartController);
}
};
});
By all account I should be able to access the controller in the addProduct directive but I am not. Is there a better way of doing this?
I got lucky and answered this in a comment to the question, but I'm posting a full answer for the sake of completeness and so we can mark this question as "Answered".
It depends on what you want to accomplish by sharing a controller; you can either share the same controller (though have different instances), or you can share the same controller instance.
Share a Controller
Two directives can use the same controller by passing the same method to two directives, like so:
app.controller( 'MyCtrl', function ( $scope ) {
// do stuff...
});
app.directive( 'directiveOne', function () {
return {
controller: 'MyCtrl'
};
});
app.directive( 'directiveTwo', function () {
return {
controller: 'MyCtrl'
};
});
Each directive will get its own instance of the controller, but this allows you to share the logic between as many components as you want.
Require a Controller
If you want to share the same instance of a controller, then you use require.
require ensures the presence of another directive and then includes its controller as a parameter to the link function. So if you have two directives on one element, your directive can require the presence of the other directive and gain access to its controller methods. A common use case for this is to require ngModel.
^require, with the addition of the caret, checks elements above directive in addition to the current element to try to find the other directive. This allows you to create complex components where "sub-components" can communicate with the parent component through its controller to great effect. Examples could include tabs, where each pane can communicate with the overall tabs to handle switching; an accordion set could ensure only one is open at a time; etc.
In either event, you have to use the two directives together for this to work. require is a way of communicating between components.
Check out the Guide page of directives for more info: http://docs.angularjs.org/guide/directive
There is a good stackoverflow answer here by Mark Rajcok:
AngularJS directive controllers requiring parent directive controllers?
with a link to this very clear jsFiddle: http://jsfiddle.net/mrajcok/StXFK/
<div ng-controller="MyCtrl">
<div screen>
<div component>
<div widget>
<button ng-click="widgetIt()">Woo Hoo</button>
</div>
</div>
</div>
</div>
JavaScript
var myApp = angular.module('myApp',[])
.directive('screen', function() {
return {
scope: true,
controller: function() {
this.doSomethingScreeny = function() {
alert("screeny!");
}
}
}
})
.directive('component', function() {
return {
scope: true,
require: '^screen',
controller: function($scope) {
this.componentFunction = function() {
$scope.screenCtrl.doSomethingScreeny();
}
},
link: function(scope, element, attrs, screenCtrl) {
scope.screenCtrl = screenCtrl
}
}
})
.directive('widget', function() {
return {
scope: true,
require: "^component",
link: function(scope, element, attrs, componentCtrl) {
scope.widgetIt = function() {
componentCtrl.componentFunction();
};
}
}
})
//myApp.directive('myDirective', function() {});
//myApp.factory('myService', function() {});
function MyCtrl($scope) {
$scope.name = 'Superhero';
}