Angularjs directive private scope/model - angularjs

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

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.

Using ControllerAs with a Directive

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>

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 isolated directives inheriting surrounding or parent scope

I have put up my code at jsbin: http://jsbin.com/fewom/1/edit
If any one can guide me, what am I doing wrong there. I have specified scope to be isolated inside myDirective with scope: {} but still when i write myProperty inside my directive in html I am able to read myProperty.
I was using AngularJS https://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.min.js
When i changed my library to it started to work,
https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0-rc.3/angular.js
then I have tried,
Angular JS version 1.2.16
https://ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.min.js
and
Angular JS version 1.3.0-beta.5
https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.5/angular.min.js
and the problem for isolated scoping shows up again.
You don't have a controller, so nothing ties your directive to your html.
Change your js to this (I just added a blank controller):
angular.module('myApp', [])
.directive('myDirective', function() {
return {
restrict: 'A',
scope: {}
};
})
.controller('ctrl', function (){
});
and in your html, change to this (referenced the controller):
<html ng-app="myApp" ng-controller='ctrl'>

Resources