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';
}
Related
I have this directive, that i would like to make a component
angular.module('app')
.directive('year', function () {
var controller = ['$scope', function ($scope) {
$scope.setYear = function (val) {
$scope.selectedyear = val;
}
}];
return {
restrict: 'E',
controller: controller,
templateUrl: "views/year.html"
};
});
This is what I got so far:
angular.module('app')
.component('year', {
restrict: 'E',
controller: controller,
templateUrl: "views/year.html"
});
I am not sure how to move my var controller into .component
There are few things you should do convert your directive to component
There is no restrict property for component as it is restricted to elements only.
For controller you could just set as you did at directive declaration but outside of it.
Controllers for components use controllerAs syntax as default so get rid of $scope
So your component should look like this...
angular.module('app')
.component('year', {
controller: ComponentController,
templateUrl: "views/year.html"
});
function ComponentController(){
var $ctrl = this;
$ctrl.setYear = function (val) {
$ctrl.selectedyear = val;
}
}
Your component should look like this:
function YearController() {
var $ctrl = this;
$ctrl.setYear = function (val) {
$ctrl.selectedyear = val;
}
}
angular.module('app').component('year', {
templateUrl: 'views/year.html',
controller: YearController
});
For more details, please read the following Stack Overflow question for more deatils:
Angular 1.5 component vs. old directive - where is a link function?
and the official documentation:
https://docs.angularjs.org/guide/component
Also, please note that components are restricted to elements only by default.
I am developing an angular framework where user can configure header, menu, footer and selected pages using custom directives. To complete this requirement, at one point I need the following. I have seen example on the net, but does not really explain it well.
The requirement is that the templateUrl of the first custom directive shall be replaced with a template attribute that should call another custom directive.
The following code with templateUrl works fine.
angular.module("app",[]);
angular.module("app").controller("productController", ['$scope', function ($scope) {
}]);
angular.module("app").directive("tmHtml", function () {
return {
transclude: false,
scope: {
},
controller: "productController",
templateUrl: "/templates/HideShow.html"
};
});
However, when I change the above code as follows. I am making the change so that my custom directive tmHtml calls another custom directive.
angular.module("app").directive("tmHtml", function () {
return {
transclude: false,
scope: {
},
controller: "productController",
template: ``<hideShow></hideShow>``
};
});
New Directive for hideShow is written as follows
angular.module("app").directive("hideShow", function () {
return {
tempateUrl: "/templates/HideShow.html"
};
});
It's not working. I understand I am missing something here. I could not find out. Appreciate help
Working code:
var app = angular.module('plunker', []);
app.controller('productController', function($scope) {
});
app.directive("hideShow", function() {
return {
templateUrl: "hideshow.html"
};
});
app.directive("tmHtml", function() {
return {
transclude: false,
scope: {},
controller: "productController",
template: "<hide-show></hide-show>"
};
});
the problem is with the spelling of templateUrl in your hideShow directive.
Demo : http://plnkr.co/edit/TaznOeNQ7dM9lyFgqwCL?p=preview
Try define your controller with controllerAs:
angular.module("app").directive("tmHtml", function () {
return {
transclude: false,
scope: {
},
controllerAs: "productController",
templateUrl: "/templates/HideShow.html"
};
});
angular.module("app").directive("tmHtml", function () {
return {
transclude: false,
scope: {
},
controller: "productController",
template: ``<hideShow></hideShow>``
};
});
must be replaced by
angular.module("app").directive("tmHtml", function () {
return {
transclude: false,
scope: {
},
controller: "productController",
template: "<hide-show></hide-show>"
};
});
under the attribute template, you add Html. So, you still have to use snake-case there, like in your Html files
Your first directive may have an eventually scoped attribute that you observe.
Then it may wrap the second directive. If needed, your directives may communicates as parents and children.
I am working to add modals to my directives using ui-bootstrap and did so fine on the previous directive. I don't believe I am doing anything differently in this one but I get the ReferenceError: milestoneController is not defined when I run the edit() function from within the directive.
milestone.html (this is the template HTML for the directive below):
<div ng-controller = "milestoneController"></div>
milestone directive:
angular.module('ireg').directive('milestone', function (milestoneFactory,$modal) {
return {
restrict:'E',
scope: {
objectid:'#objectid'
},
templateUrl: '/ireg/components/milestone/milestone.html',
link: function ($scope, element, attrs) {
$scope.edit = function(data) {
milestoneController.editMilestoneDialog(data);
};
}
}
});
angular.module('ireg').controller('milestoneController', function ($scope, $modal){
$scope.editMilestonesDialog = function (objectid) {
//fun
}
});
EDIT: I allso felt I should mention that the milestone directive is repeated in a ng-repeat loop. Thanks!
ok you're going to want to use a transcluded scope in your directive to pass a controller function to the directive. Your directive now becomes:
angular.module('ireg').directive('milestone', function (milestoneFactory,$modal) {
return {
restrict:'E',
scope: {
objectid:'#objectid',
editMilestoneDialog:'&'
},
templateUrl: '/ireg/components/milestone/milestone.html',
link: function ($scope, element, attrs) {
$scope.edit = function(data) {
$scope.editMilestoneDialog(data);
};
}
}
and your markup becomes:
<milestone edit-milestone-dialog="editMilestoneDialog"></milestone>
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 have multiple AngularJS directives that are nearly identical - there are only two differences: the template URL and one single element in the linking function. Both are constant for each directive. So, for simplicity's sake, this is how it looks like:
app.directive("myDirective", [function() {
return {
templateUrl: "this/path/changes.html",
scope: true,
link: function(scope, element, attrs) {
var veryImportantString = "this_string_changes";
// a few dozen lines of code identical to all directives
}
};
}]);
Now, moving the linking function to a commonly available place is obvious. What is not so obvious to me is how to set that "very important string" on the scope (or otherwise pass it to the directive) without declaring it in the HTML.
Here's what I've tried.
app.directive("myDirective", [function() {
return {
templateUrl: "this/path/changes.html",
scope: {
veryImportantString: "this_string_changes"
},
link: someCommonFunction
};
}]);
Nope, apparently the scope config doesn't take values from nobody. I can bind a value coming from the HTML attribute, but this is precisely what I don't want to do.
Also tried this:
app.directive("myDirective", [function() {
return {
templateUrl: "this/path/changes.html",
veryImportantString: "this_string_changes",
scope: true,
link: function(scope, element, attrs) {
var veryImportantString = this.veryImportantString;
}
};
}]);
But alas, the linking function is then called with this set to something else.
I assume this might work:
app.directive("myDirective", [function() {
return {
templateUrl: "this/path/changes.html",
scope: true,
compile: function(element, attrs) {
// no access to the scope...
attrs.veryImportantString = "this_string_changes";
return someCommonFunction;
}
};
}]);
However, I am not 100% sure this is what I want either, as it reeks of being a dirty workaround.
What are my other options?
I have devised a completely different approach: using a factory-like function to spawn directives.
var factory = function(name, template, importantString) {
app.directive(name, [function() {
return {
scope: true,
templateUrl: template,
link: function(scope, element, attrs) {
var veryImportantString = importantString;
// directive logic...
}
};
}]);
};
Then, in order to create individual directives, I simply call:
factory("myDirective", "/path/to/template.html", "important");
factory("myDirective2", "/path/to/template2.html", "important2");
What about the following:
Before wherever you define someCommonFunction, add the line
var veryImportantString = "someOptionalDefault"
This then puts veryImportantString in scope of both your someCommonFunction and .directive()
Then you can change your directive code to:
app.directive("myDirective", [function() {
return {
templateUrl: "this/path/changes.html",
scope: true,
link: function(args){
veryImportantString = "thatUberImportantValue";
someCommonFunction(args);
}
};
}]);
Proof of concept fiddle