Using ControllerAs with a Directive - angularjs

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>

Related

How can I share a scope with a directive in another module without using "scope: false"?

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.

Angular Controller and controllerAs keyword usage in directive

learning angular so some time things not clear when read article on angular. here i stuck to understand what is the usage or importance of this keywords Controller and controllerAs in directive.
code taken from here http://blog.thoughtram.io/angularjs/2015/01/02/exploring-angular-1.3-bindToController.html
app.controller('SomeController', function () {
this.foo = 'bar';
});
app.directive('someDirective', function () {
return {
restrict: 'A',
controller: 'SomeController',
controllerAs: 'ctrl',
template: '{{ctrl.foo}}'
};
});
i like to know understand the importance of this two keywords in directive and they are controller: 'SomeController', and controllerAs: 'ctrl',
please tell me if we do not use these two keyword controller: 'SomeController', and controllerAs: 'ctrl', then what would happen or what would be worse ?
please help me to understand the usage or importance of this keywords controller: 'SomeController', and controllerAs: 'ctrl', in directive. thanks
You need the controller if you plan on referencing a controller object. This is how you hook it up.
The controllerAs allows you to create a variable that you can reference the controller with in lieu of using the $scope.
Refined answer:
<html ng-app="app">
<head></head>
<body>
<script src="node_modules/angular/angular.js"></script>
<script>
var app = angular.module('app', []);
app.directive('fooDirective', function() {
return {
restrict: 'A',
controller: function($scope) {
// No 'controllerAs' is defined, so we need another way
// to expose this controller's API.
// We can use $scope instead.
$scope.foo = 'Hello from foo';
},
template: '{{foo}}'
};
});
app.directive('barDirective', function() {
return {
restrict: 'A',
controller: function() {
// We define a 'vm' variable and set it to this instance.
// Note, the name 'vm' is not important here. It's not public outside this controller.
// The fact that the 'controllerAs' is also called 'vm' is just a coincidence/convention.
// You could simply use 'this.bar' if you prefer.
var vm = this;
vm.bar = 'Hello from bar';
},
// This allows us to reference objects on the controller's instance by
// a variable called 'vm'.
controllerAs: 'vm',
// Now we can reference objects on the controller using the 'controllerAs' 'vm' variable.
template: '{{vm.bar}}'
};
});
</script>
<div foo-directive></div>
<div bar-directive></div>
</body>
</html>
One of its main advantages, especially if you're new to AngularJS, is that it ensures proper data binding between child scopes.
Just play around with this code sample and try to notice something strange:
angular
.module('myApp', [])
.controller('MainCtrl', ['$scope',
function($scope) {
$scope.truthyValue = true;
$scope.foo = 'hello';
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.3/angular.min.js"></script>
<div ng-app="myApp" ng-controller="MainCtrl">
<p>Start by writing something in the first input. Then write something in the second one. Good job, you've broke AngularJS!</p>
1.
<input type="text" ng-model="foo">
<div ng-if="truthyValue">
2.
<input type="text" ng-model="foo">
</div>
<div>$scope.foo: {{ foo }}</div>
</div>
The reason behind it is that ngIf creates a child scope which inherits from the parent scope. You're basically changing the value inside ngIf's scope which doesn't affect the value from its parent scope.
Finally, I consider controllerAs syntax an important AngularJS best practice. If you get accustomed to it early in your learning process, you'd be avoiding a lot of head-scratching wondering why your code doesn't work, especially when everything seems in order.
You don't need to use both controller and controllerAs. You can use the shorthand:
controller: 'SomeController as ctrl'
The relationship is that a new instance of the controller is created and exposed to the template using the instance handle you provide as ctrl.
Where this comes in handy is if you are using nested controllers -- or using multiple instances of a controller in a view.
UPDATE TO ANSWER COMMENTS
You do not need to use controllers with AngularJS directives. Infact as of AngularJS 1.5 you should probably only use controllers when creating components rather than directives.
Directives and Components are conceptually similar. Up until AngularJS they all components would be defined as a directive.
In many ways a directive interacts with an element (like ng-href) or events (like ng-click).
The simplest way to differentiate Components and Directives is a Component will have a template.
Can't I just create a component using the directive link method?
You can, but I wouldn't recommend it unless you have a good reason. Using controllers allows you to use object oriented classes or prototypes to define the action behaviors with the template and user.
As well these controllers are extremely more easy to unit test than the directive link functions.
Check out this plunkr code
Here is my simple Directive code:
angular.module('app', [])
.directive('someDirective', function () {
return {
scope: {},
controller: function ($scope) {
this.name = 'Pascal';
$scope.color = 'blue';
},
controllerAs: 'ctrl',
template: '<div>name: {{ctrl.name}} and Color: {{color}}</div>'
};
});
And The HTML
<body ng-app="app">
<some-directive />
</body>
So, as you can see, if you need to access some variable which were defined against this keyword in the controller, you have to use controllerAs. But if it was defined against $scope object you can just access it with its name.
For example, you can get the variable color just by using {{color}} as it was defined against $scope but you have to use {{ctrl.name}} as "name" was defined against this.
I don't think there really is much difference, as this answer says,
Some people don't like the $scope syntax (don't ask me why). They say
that they could just use this
Also from their own website you can read the about the motivation behind this design choice,
Using controller as makes it obvious which controller you are
accessing in the template when multiple controllers apply to an
element
Hope it helps.

How to access global angular object in isolated scope of a directive?

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>

Angularjs directive private scope/model

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: '='
},

Define a dynamic controller in an AngularJS directive?

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.

Resources