What is scope inside directive does in AngularJS - angularjs

I am learning angular and I am from jQuery background and facing problem to get hold on angular. so i often stumble to understand many things in angular code.
Just seen the code below and I do not understand what scope is doing in below directives?
But if I remove the scope from below directive then what will not work?
so please help me to understand the usage of scope and its importance with a example if possible. Thanks
<li my-directive price="item.price" ng-repeat="item in products">{{item.name}} — {{item.price}}</li>
myApp.directive('myDirective', function(){
return {
scope: { price: '=' },
require: 'ngModel',
link : function(scope){
console.log(scope.price)
},
controller: function(scope, element, attrs, ngModel){
console.log(ngModel.price);
console.log(scope.price);
}
}
});

In that sample you have scope: { price: '=' }, states that internal scope variable price of the directive is exactly binded to parent's scope. It has access to it and i value changes in parent scope, directive's value of price will change as well.
Remove this line from your directive scope: { ... } and then your directive will not create a new scope. But it will still work - meaning it won't create an error. There are use cases when you need or need not to have isolated scopes.
To figure out better how things work with angular and scopes - please check the following great resources:
angular vs jQuery -
"Thinking in AngularJS" if I have a jQuery background?
$scope itself in angular -
How does data binding work in AngularJS?
various types of scope variables in directives -
What is the difference between '#' and '=' in directive scope in AngularJS?

If you don't mention the scope configuration object your directive will use is parent's scope, but when you mention the scope object it creates its own isolated scope.
So if you don't mention the scope object you will have access to variables from your parent controller directly.
ref : http://www.undefinednull.com/2014/02/11/mastering-the-scope-of-a-directive-in-angularjs/

Related

Along with Transclude element, can i pass its scope too to a directive?

Moment i felt i have understood enough about Transclude i came across this statement :
Transclude allows us to pass in an entire template, including its scope, to a directive.
Doing so gives us the opportunity to pass in arbitrary content and arbitrary scope to a directive.
Does this mean, if there is a scope attached to Transclude element and it can be passed on to the directive ? If that's true then am not able to access that scope property inside directive template.
Let me take couple of steps back and explain with code about what am trying to do :
JSFiddle Link
My directive is directive-box and transclude: true is defined in Directive Definition Object(DDO).
Now there is a Child Div, which is the element to be Transcluded
<div ng-controller='TransCtrl'>Inside Transclude Scope : {{name}}</div>
and it has controller TransCtrl attached to it.
Now am trying to access $scope.name property which is part of TransCtrl from directive level after defining this in DDO :
scope: {
title: '#directiveTitle',
name: '='
}
Is this possible ?
This is more like a Parent scope trying to access Child scope property, is this permitted in JavaScript Protoypical inheritance ? Or is there something else i need to know ??
If this is not possible what does first statement mean ?
Transclude allows us to pass in an entire template, including its scope, to a directive.
UPDATE 1 :
My primary concern is Controller should remain with Transclude element, still we should be able to pass its (Transclude element) scope to Directive and then Directive should be able to consume that scope i.e., name from TransCtrl controller .
<div ng-controller='TransCtrl'>Inside Transclude Scope : {{name}}</div>
Above line of code should remain as is.
I may be completely wrong with my question but please let me if this can be accomplished.
The problem seems to be with the way the controller is defined within the ng-transcluded html.
I have made it clearer by using
the bindToController construct
using a controller at the directive level
Refer this fiddle for a working example.
controllerAs: "TransCtrl",
bindToController: true
And your statement, 'Parent scope trying to access Child scope property' is incorrect right? Since we are trying to use the parent scope property, i.e. name from within the child (ng-transcluded content), which is possible with protypical inheritance, and not the other way around.
Does this answer your question: https://jsfiddle.net/marssfa4/4/?
In it I have created a new controller on the outside (effectively replacing the functionality of your rootScope for inside the directive) and I made the directive's controller be set inside your controller template.
The long and short of it is though that you can see that it is possible to transclude html along with its scope even into a directive with its own scope.
The html:
<div ng-app='myApp' ng-controller="OutsideScope">
<h1>{{externalWorld}}</h1>
<div directive-box directive-title='{{directiveWorld}}' name='name'>
<div>Inside Transclude Scope : {{name}}</div>
</div>
</div>
JS (includes Update 1):
angular.module('myApp', [])
.directive('directiveBox', function() {
return {
restrict: 'EA',
scope: {
title: '#directiveTitle',
name: '='
},
transclude: true,
template: '<div ng-controller="TransCtrl">\
<h2 class="header">{{ title }}</h2>\
<div class="dirContent">Directive Element</div>\
<div>Outside Transclude Scope : {{name}}</div>\
<div class="content" ng-transclude></div>\
</div>'
}
})
.controller('TransCtrl', function($scope) {
$scope.name = 'Transclude World'
})
.controller('OutsideScope', function($scope) {
$scope.name = 'External World'
})
.run(function($rootScope) {
$rootScope.externalWorld = 'External World',
$rootScope.directiveWorld = 'Here comes directive'
});
UPDATE 1: JSFIDDLE
I restored the original scope declarations as the scope: false was a mistake.
If I understand your comment correctly you want to leave the controller on the element to be transcluded but still have the {{name}} within that element ignore its immediate controller and use as controller its parent (i.e. the directive's) scope.
The reason I placed the controller within the template directive is because that is the only way to limit the directive's scope on the directive and not its transcluded elements. If you are explicitly placing a controller on an element, then regardless of whether it is contained within a directive with another scope, its closest scope will override whatever scope has been declared on the directive. In other words, regardless of what the directive's scope is, the {{name}} in
<div ng-controller='TransCtrl'>Inside Transclude Scope : {{name}}</div>
will always be whatever $scope.name is in TransCtrl.

How to refactor directive and change functionality by checking parent scopes?

I have a form with a ton of duplicate functionality in 2 different Controllers, there are slight differences and some major ones in both.
The form sits at the top of a products view controller, but also inside of the products modal controller.
Test plunker: http://plnkr.co/edit/EIW6xoBzQpD26Wwqwwap?p=preview
^ how would you change the string in the console.log and the color of the button based on parent scope?
At first I was going to create a new Controller just for the form, but also the HTML was being duplicated, so decided to put that into a Directive, and just add the Controller code there.
My question now is this: How would I determine which parent scope the form-directive is currently being viewed in? Because depending on the parent scope the functions/methods behave differently.
So far I've come up with this:
.directive('productForm', function() {
return {
templateUrl: "views/products/productForm.html",
restrict: "E",
controller: function($scope) {
console.log('controller for productForm');
console.log($scope);
console.log($scope.$parent);
/*
If parent scope is the page, then this...
If parent scope is the modal then this instead...
*/
}
}
});
However it's giving me back $parent id's that look like 002 or 00p. Not very easy to put in if / else statements based on that information.
Have you guys run into this issue before?
You can define 'saveThis' in your controller and pass it to directive using '&'
scope: {
user: '=',
saveThis : '&'
},
please see demo here http://plnkr.co/edit/sOY8XZtEXLORLmelWssS?p=preview
That gives you more flexibility, in future if you want to use saveThis in another controller you can define it inside controller instead adding additional if statement to directive.
You could add two way binding variables in the directive scope, this allows you to specify which Ctrl variable gets bound to which directive variable
<my-directive shared="scopeVariable">
this way you achieve two way binding of the scopeVariable with the shared directive variable
you can learn more here
I advice against this practice and suggest you to isolate common logics and behaviours in services or factories rather than in directives
This is an example of a directive that has isolated scope and shares the 'title' variable with the outer scope.
You could declare this directive this way:
now inside the directive you can discriminate the location where the directive is defined; just replace the title variable with a location variable and chose better names.
.directive('myPane', function() {
return {
restrict: 'E',
scope: {
title: '#'
},
link: function(scope, element, attrs, tabsCtrl) {
},
templateUrl: 'my-pane.html'
};
});

Isolated scope within directive controller bindings

The problem
Given the following directive
angular.module('sandbox.directive.controllers', [])
.directive('simpleDirective', function() {
return {
restrict: 'E',
replace: true,
scope: {user: '='},
controller: 'simpleController',
templateUrl: 'directive-controllers/templates/simpleDirective.html'
};
})
.controller('simpleController', function($scope, $element) {
$scope.title = "Hello from the controller!";
});
Why does this test fail?
it('should allow us update when we change the title', function() {
element.scope().title = "Marco Polo!";
element.scope().$digest();
expect(element.html()).toContain('Marco Polo!');
});
My full implementation can be seen here and the tests here.
My understanding
I am currently trying to get a better understanding of scope within directives. I have written a few tests which illustrate most of the use cases quite clearly. However I am having issues with how a directives controller relates to the isolated scope.
My understanding was that the scope accessible within the controller is a reference to the scopes directive.
Please help
I understand the issue with two-way bindings on primitives, however this is within the isolated scope and not the parent scope. Any help to get a better understanding of this would be appreciated.
There appears to be an element.isolateScope() property.
The two-way binding assigned for a user was passing as the parent scope was returned from element.scope(), therefore when user was changed the binding was in affect and the value was updated within the isolated scope.
The element.isolateScope() returns the scope when it is isolated(as it is in this case) and allows me to update this value correctly.
The element.scope() and element.isolateScope() functions are explained here.

angularjs custom directive isolated scope one way data binding doesn't work

i am a new to angularjs, I read some literature and followed a lot of tutorials, but i am still have the feeling that i completely confused.
My current issue is with custom directive and isolated scopes. All i trying to do is pass "strings" with # binding to my directives that use isolated scopes and I can't understand what am i doing wrong. Specifically WHY when i use template everything just works fine and when the template already in the DOM the one way data binding doesn't work.
JSBin fiddle link
major parts from my code:
HTML
<div my-directive my-title="TITLE ONE WAY Data Binding">
<div>
<div>This directive is <span style="color:red;">NOT using template</span></div>
<div>
$scope.title = <small><pre>{{title}}</pre></small>
</div>
</div>
</div>
<div my-directive-with-template my-title="TITLE ONE WAY Data Binding"
>
<!-- this directive use a template -->
</div>
JS
var app = angular.module('app', []);
app.directive('myDirective', function() {
return {
restrict: 'AE',
scope:{
title: "#myTitle"
},
link: function(scope, ele, attrs, c) {
console.log('non template directive link:',scope.title,attrs.myTitle);
},
controller:['$scope', function($scope){
console.log('non template directive controller:',$scope.title);
}]
};
});
app.directive('myDirectiveWithTemplate', function() {
return {
restrict: 'AE',
scope:{
title: "#myTitle"
},
link: function(scope, ele, attrs, c) {
console.log('using template directive link:',scope.title,attrs.myTitle);
},
controller:['$scope', function($scope){
console.log('using template directive link:',$scope.title);
}],
template:'<div><div>This directive is using template</div><div>$scope.title = <small><pre>"{{title}}"</pre></small></div></div>',
replace:true
};
});
JSBin fiddle link
In your non-template scenario the title is not being bound to any scope and therefore not showing anything.
What you call the DOM template is really HTML outside the directive that has no access to it's isolated scope. You could embed this div inside a controller and then title could be bound to the controller's $scope.title
For what I understand it only makes sense to create an isolated scope to make it available to the directive's template.
Clarification
Isolated scopes allow the directive to have state independent of the parent scope (avoiding it's pollution) and also avoiding sharing this state with sibling directives.
Supposing you're creating this directive to reuse that piece of UI somewhere else in your code, you start by creating its template with the shared HTML.
Ok, but you need to go a bit further and parameterize it passing some data to it.
You can then use attributes on the directive to communicate with the outside (parent scope, or just to pass static data).
The directive's template can now bind to this data without needing to have any knowledge of it's "outside world", and it's done through it's isolated scope.
Conclusion, why create an isolated scope, if not to provide the template with this data?
Hope I've made this a bit clear :)
Now after thinking a bit about my affirmation... well you could also create a directive without any template, by using the compile or link function and do it manually through DOM manipulation. And in this case it might make sense to have an isolated scope for the reasons presented above :)

Custom directive interfering with ngClick

In this fiddle, why does the ngClick in the top link work, but the ngClick in the link to which I have added a custom directive completely fail to function?
<a class="regular" ng-click="clickTheLink()">A regular ng-click link</a>
<a class="disableable" disable="disableTheLink" ng-click="clickTheLink()">A disableable link!</a>
As far as I can tell, nothing I'm doing in the directive should be interfering at all with ngClick behavior, as all it does is manipulate CSS classes:
app.directive('disableable', function(){
return {
restrict: 'C',
scope: { disable: '&' },
link: function (scope, elem, attrs) {
scope.$watch(scope.disable, function (val) {
if (val){
elem.addClass('disabled');
elem.removeClass('enabled');
}
else {
elem.addClass('enabled');
elem.removeClass('disabled');
}
});
}
};
});
The thing is, each DOM element only has one scope. So if any directive uses isolate scope like you're using here, that becomes the one and only scope on the element. That scope is completely disconnected from any parent scopes, and in your example, clickTheLink isn't in there.
The simple answer is to not use isolate scope. It's a real nice syntax but you can do everything it does manually. For the '&' params, you can just use the parse service to parse the attribute expressions.
See updated working fiddle:
http://jsfiddle.net/SNQQV/3/
It's because you're creating an isolate scope on line 14 of the fiddle, the clickTheLink function only exists in the controller and not in the directive. While I highly suggest against doing it this way, you can quickly access the parent scope via $parent
<a class="disableable" target="_blank" disable="disableTheLink" ng-click="$parent.clickTheLink()">A disableable link!</a>
Putting this code in allows the fiddle to work correctly. Here's the fiddle of it: http://jsfiddle.net/bpN9b/10/
My suggestion would be to look into how ngClass work as well as ngDisabled. I think both of those will allow you to not use this directive at all.

Resources