I have a directive that has a dynamic template, now I want the directive to have the ability to use different controllers. Is it possible to dynamically assign a controller to a directive? If possible, would that be the same "ctrlr" then passed to the link function?
.directive('myDirective',['$compile',function($compile){
return {
restrict: 'AE',
replace: true,
transclude: true,
scope: {},
templateUrl: function(el,attrs){
return (angular.isDefined(attrs.template)) ? attrs.template : '/tmpls/default';
},
link : function(scope,el,attrs,ctrlr,transFn){
[... Do Stuff Here ...]
},
controller: [ DYNAMIC CONTROLLER ASSIGNMENT?? ]
};
}]);
While I didn't find the corresponding documentation for it in the official API, you can dynamically pass in the name of the controller you want to use by utilizing the "name" attribute in conjunction with providing the "controller" attribute with a value that uses the similar syntax you'd use for an isolate scope.
Using your sample code, assuming a controller called "myController":
HTML:
<my-directive ctrlr="myController"></my-directive>
JS:
.directive('myDirective',['$compile',function($compile){
return {
restrict: 'AE',
replace: true,
transclude: true,
scope: {},
templateUrl: function(el,attrs){
return (angular.isDefined(attrs.template)) ? attrs.template : '/tmpls/default';
},
link : function(scope,el,attrs,ctrlr,transFn){
[... Do Stuff Here ...]
},
controller: '#',
name: 'ctrlr'
};
}]);
This is how it is done:
Inside your directive element all you need is an attribute which gives you access to the name of the controller:
<card-dealer ng-repeat="card in cards" card="card"></card-dealer>
in my case my card attribute holds a card object which has a name property.
In the directive you set the isolate scope to:
scope: { card: '=' }
This isolates and interpolates the card object to the directive scope. You then set the directive template to:
template: '<div ng-include="getTemplateUrl()"></div>'
this looks to the directive's controller for a function named getTemplateUrl and allows you to set the templateUrl dynamically as well.
In the directive controller the getTemplateUrl function looks like this:
controller: ['$scope', '$attrs', function ($scope, $attrs) {
$scope.getTemplateUrl = function () {
return '/View/Card?cardName=' + $scope.card.name;
};
}]
I have an mvc controller which links up the proper .cshtml file and handles security when this route is hit, but this would work with a regular angular route as well.
In the .cshtml/html file you set up your dynamic controller by simply putting
<div ng-controller="CardContactController"></div>
as the root element. The controller will differ for each template. This creates a hierarchy of controllers which allows you to apply additional logic to all cards in general, and then specific logic to each individual card. I still have to figure out how I'm going to handle my services but this approach allows you to create a dynamic templateUrl and dynamic controller for a directive using an ng-repeat based on the controller name alone. It is a very clean way of accomplishing this functionality and it is all self-contained.
Related
In angularjs, I have already made view templates consisting of multiple controllers. Now while routing can we specify multiple controllers to one template or there should be only one controller for a template?
If you do not understand this question please specify in comment.
Actually, the answer is no. You can't use multiple controller in router state, however you can add multiple controller in template HTML.
Alternate solution is using dynamic template in a directive. If you use the dynamic template, then for each router state you can use multiple controller in different different HTML template.
app.directive('View', function () {
return {
restrict: 'E',
scope: {
templateUrl: "=",
},
replace: true,
template: "<div ng-include = 'templateUrl'></div>",
};
})
Then in directive you can do that by just passing different template url.
And in your template you can use different different controller.
In html you can use directive as follows:
<view templateUrl = "templateUrl0.html"> <view>
<view templateUrl = "templateUrl1.html"> <view>
Finally, in the templateUrl0.html and templateUrl1.html, you can add Controller0 Controller1.
I have 2 modules, jsTag and mainApp. mainApp injects jsTag to use it's functionality
var jsTag = angular.module('jsTag')
angular.module('mainApp', ['jsTag']);
The jsTag module has a directive I'll call jsTagDirective
jsTag.directive('jsTagDirective', function(){
restrict: 'E',
scope: true,
controller: 'jsTagMainCtrl',
templateUrl: 'jsTag/source/Templates/js-tag.html'
The template of the above directive has an input tag inside of it, with an ng-model reference to jsText. I want to be able to capture the value of this model in the controller of my mainApp module. Changing scope to false does work - I can access $scope.jsText in the mainApp - but I understand this is bad practice. I can't figure out how to get inherited scope or isolated scope to pass the value upwards, though.
That's a perfect example why you should use isolated scope along with two-way binding. Docs here.
JS
jsTag.directive('jsTagDirective', function () {
return {
restrict: 'E',
scope: {
jsText: '='
},
controller: 'jsTagMainCtrl',
templateUrl: 'jsTag/source/Templates/js-tag.html'
};
});
HTML
<js-tag-directive js-text="jsText"></js-tag-directive>
This will make the directive have its jsText property in sync with a parent scope, even creating the said property.
I'm trying to follow John Papa's angularJS style guide here and have started switching my directives to using controllerAs. However, this is not working. My template cannot seem to access anything assigned to vm. See this very simple plnkr example that exhibits the behavior.
http://plnkr.co/edit/bVl1TcxlZLZ7oPCbk8sk?p=preview
angular
.module('app', []);
angular
.module('app')
.directive('test', test);
function test() {
return {
restrict: 'E',
template: '<button ng-click="click">{{text}}</button>',
controller: testCtrl,
controllerAs: 'vm'
}
}
angular
.module('app')
.controller('testCtrl', testCtrl);
function testCtrl() {
var vm = this;
vm.text = "TEST";
}
When using the controllerAs syntax you don't access the $scope as you would normally, the variable vm is added to the scope, so your button needs to become:
<button ng-click="click">{{vm.text}}</button>
Notice the vm. added to the beginning of text.
Here is a fork of your Plunk with the fix applied
Q: Do you know how I would access attributes passed through as attributes to the directive, example "scope: { text: '#' }"? Am I then forced to use $scope on the controller and set vm.text = $scope.text?
A: In the article you reference, there is a section y075 that talks about just this scenario. Look into bindToController:
return {
restrict: 'E',
template: '<button ng-click="click">{{text}}</button>',
controller: testCtrl,
controllerAs: 'vm',
scope: {
text: '#'
},
bindToController: true // because the scope is isolated
};
Then you should be able to access vm.text
With "controllerAs", the controller instance alias - vm, in your case - is published on the scope as the .vm property of the scope.
So, to access its properties (i.e. the properties of the controller), you need to specify {{vm.text}} or ng-click="vm.click".
When using 'controllerAs' syntax ,as above,the scope is bound to the controller’s 'this' reference.
So it allows us to introduce a new namespace('vm' here) bound to our controller without the need to put scope properties in an additional object literal(say $scope).
So accessing anything in controller's scope,requires 'vm' namespace, as,
'<button ng-click="click">{{vm.text}}</button>'
When you use controllerAs syntax, then you have to use
bindToController: true
it will work in your directive.
I realise this ticket is quite old. I'm adding my $0.02 in case anyone stumbles on to this in the future.
Firstly, it would be nice to have some context around what you're trying to achieve, because you seem to be breaking design rules. Your directives shouldn't need to know the inner workings of your controller, or vice-versa.
I have created a simple example of how to set the caption of button in a directive. There is no need for a controller here, and I think it just makes your example difficult to follow.
var myApp = angular.module('myApp', []);
myApp.directive('myDirective', function() {
return {
scope: {
caption: "#"
},
template: '<button>{{caption}}</button>'
};
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.5/angular.min.js"></script>
<div ng-app="myApp">
<my-directive caption="Hello World" />
</div>
I need to call angular.isString() method in html. But I don't know how to access it. My solution is to add a controller property and bind angular object to the isolated scope. It's quite strange! I'm wondering if there is a better way to do that?
Javscript:
App.directive "tfsTaskDetails", ()->
restrict: 'AE'
replace: true
templateUrl: '../../templates/task_details.html'
scope:
task: '='
controller: ($scope)->
$scope.angular = angular
html:
<p ng-if="angular.isString(task.name)">task.name</p>
Instead of exposing the entire angular object, I would probably just expose the function you want:
$scope.isString = angular.isString
Which you can use as:
<p ng-if="isString(task.name)">task.name</p>
I had a working angular app, but I want to restructure it a bit. I had a pile of directives that weren't really doing anything, and although the entire app was supposed to be encapsulated by a single directive, I still had my two main controller declarations outside that directive.
I merged those two controllers, because although they take care of different concerns (one handles functional data, the other navigation state), they are both necessary for the entire app.
Secondly, I wanted to get rid of the loose declaration and go from:
<div ng-app="myApp" class="myApp" ng-cloak ng-controller="myController">
<myAppdirective ng-controller="myNavigationController"></myAppdirective>
</div>
to:
angular.module('myApp').
directive('myAppDirective', ['myController', function(myController) { {
return {
restrict: 'AE',
replace: true,
scope: true,
controller: myController,
template: '<div>' +
'<ng-include src="\'partials/navigation.html\'">' +
'<ng-view></ng-view>' +
'</div>'
};
}]);
Mysteriously, this doesn't work. Shouldn't this work?
I get this error: https://docs.angularjs.org/error/$injector/unpr?p0=myControllerProvider%20%3C-%20myController%20%3C-%20myAppDirective
I tried using ngController inside the template, but that gave me TypeError: Cannot read property 'insertBefore' of null somewhere deep in angular code.
I'm at a loss. I'm probably doing somthing fundamentally wrong. But what?
Solution: I reverted the merging of my two controllers. This restores the separation of concerns I originally had, and it fixes that mysterious TypeError.
My directive now looks like this:
(function() {
'use strict';
/*global angular */
angular.module('myApp').
directive('myAppDirective', function() {
return {
restrict: 'AE',
replace: true,
scope: true,
controller: 'myController',
template: '<div ng-controller="myNavigationController">' +
'<ng-include src="\'partials/navigation.html\'"></ng-include>' +
'<ng-view></ng-view>' +
'</div>'
};
});
})();
This seems to work fine.
I don't think you can inject controller instances. That's probably where the error is coming from (based on that error, it looks like you renamed your actual controller name when posting the question). You should just need to use a string for the controller property.
angular.module('myApp').
directive('myAppDirective', [function() { {
return {
restrict: 'AE',
replace: true,
scope: true,
controller: 'myController',
template: '<div>' +
'<ng-include src="\'partials/navigation.html\'">' +
'<ng-view></ng-view>' +
'</div>'
};
}]);
See How can I use a registered controller in my angular directive?
By default, the scope property of every directive is set to false unless other specified. You are setting your scope to true, then applying the same controller that the directive is located in.
Simply put, remove scope: true, and controller: myController - your directive will inherit the scope from the parent controller and all will be well.