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.
Related
In angularjs , I am making a treeview using directive.
return {
restrict: "E",
//replace: true,
scope: {member: '=', ShowDetailsCtrlFunc : '&'},
template: "<li><span ng-click=ShowDetailsCtrlFunc()>{{member.NodeName}}</span></li>",
controller: 'MainCtrl',
link: linkerfunc
}
Things are fine, but i observed that controller function calls multiple times while loading. Would be a great learning if explain me the concept behind this. Or is it possible to handle this and let the function execute only once ? I have created below plunker for same.
plunkr
Thanks
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've got an issue with a directive that is becoming a pain to resolve, which appears to be from a change in angular between version 1.0.8 and version 1.2.0.
In the first jsfiddle using version 1.0.8 you can see that upon changing an input file the fileChanged fn is called and the $scope.file is changed.
Yet in the second fiddle using version 1.2.0, the same code just doesn't update the directive
It looks like it has something to do with the a directive having an isolate scope, but not being able to have it's own scope variables. For instance, the controller of a directive:
controller: function ($scope, $timeout, $upload) {
$scope.fileCount = 0;
}
Am I missing something pretty key here?
The reason this doesn't work is due to no template being set for the directive:
...
scope: {
uploadPath: '#'
},
templateUrl: 'file-uploader.html',
controller: function ($scope, $timeout, $upload)
...
I'm sure using template: 'html here' would also work, but the reality is there much more html.
Working fiddle
There was a change with regard to isolated scopes. The expression $scope.file = element.files[0]; changes file in the directive's scope, not the scope outside. What you need to do is to establish a connection between file in the directive and file outside of it.
<div file-upload file="file">
<input type="file" />
<hr />
File selected: {{file.name}}
</div>
...
myApp.directive('fileUpload', function(){
return {
restrict: 'A',
scope: {
file: '='
},
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.