Inheritance of scope in angular directive - angularjs

What is the difference between:
scope: true / false
scope: {}
And not define that property in directive?
What impact does it have on inheritance from parent scope?

Ya, this really confused me too when I first started with Angular 1.x
Scope: false
Means this directive does not get its own scope. So no $scope.$new is called. Which means the scope your directive accesses, is its parent scope. Think of this as sharing the scope.
<div ng-controller="CtrlA">
<div ng-controller="CtrlB">
<my-directive></my-directive>
</div>
</div>
// In this example, the directive code's scope chain looks like this
// $rootScope --> CtrlA scope --> CtrlB scope
// So if our my-directive calls scope.doSomething()
// It looks in CtrlB first to see if that exists,
// because my-directive doesn't have its own scope
// Then it checks CtrlA, and finally $rootScope
// Standard prototypical inheritance
Scope: true
Means this directive wants its own scope, that inherits from the current scope. $scope.$new is called and sets the current scope as the parent. This is how scope chains work.
<div ng-controller="CtrlA">
<div ng-controller="CtrlB">
<my-directive></my-directive>
</div>
</div>
// In this example, the directive code's scope chain looks like this
// $rootScope --> CtrlA scope --> CtrlB scope --> my-directive scope
// So if our my-directive calls scope.doSomething()
// It looks in my-directive first to see if that exists
// Then it checks CtrlB, CtrlA, and finally $rootScope
// Standard prototypical inheritance
Scope: {}
This is called an isolated scope. An isolated scope is its own scope, without the inheritance chain. The directive is isolated. It can only access parameters handed in (usually attributes on the directive element) and the only way it can send messages it is through callbacks (that are set as attributes as well. beware, these have a funny way of being called)
<div ng-controller="CtrlA">
<div ng-controller="CtrlB">
<my-directive></my-directive>
</div>
</div>
// In this example, the directive code's scope chain looks like this
// my-directive scope
// So if our my-directive calls scope.doSomething()
// It only looks in my-directive to see if that exists

Related

Angular - Changing scope is not getting reflected

This is weird as it should be pretty straightforward. I will post my code first and then ask the question:
html -
<div ng-controller="myController" ng-switch on="addressCards">
<div>
{{addCustom}} // does not get changed
<div ng-if="addCustom === false">
{{addCustom}} // does get changed
<button type="button" class="btn btn-primary btn-icon-text" ng-click="addCustom = true">
<span class="icon icon-plus"></span>
click here
</button>
</div>
</div>
</div>
controller -
(function(){
'use strict';
angular.module('myApp')
.controller('myController',['$scope',myController]);
function myController($scope){
$scope.addCustom = false;
}
})();
So I simply introduced a scope variable - addCustom - in my controller and set it to false as default. This variable controls if a div is shown or not. I am also outputting the value of the scope on the html at 2 different locations. Please see above.
But when I change its value in an ng-click within this divs, its value is changing at the second location(within the div) but not the first one(outside the div). Because of this the div does not change state as well.
I am not able to figure what might be possibly wrong here. Can someone please help?
The thing happening is when you have ng-repeat,ng-switch and ng-if directive, angular creates child scope for those element wherever they are placed. Those newly created scope are prototypically inherited from there parent scope.
On contrast Prototypal Inheritance means?
If you have scope hierarchy, then parent scope property are accessible inside child scope, only if those property are object (originally object referenced is passed to child scope without creating its new reference). But primitive datatypes are not accessible inside child scope and if you looked at your code addCustom scope variable is of primitive dataType.
Lets discuss more about it.
Here you have myController controller which has addCustom scope variable of primitive type & as I said above ng-switch & ng-if directive are compiled they do create new child scope on that element. So in your current markup you have ng-switch on ng-controller="myController" div itself. For inner html it had created a child scope. If you wanted to access parent scope inside child(primitive type) you could use $parent notation before scope variable name. Now you can access the addCustom value by $parent.addCustom.
Here its not over when angular compiler comes to ng-if div, it does create new child scope again. Now inner container of ng-if will again have child scope which is prototypically inherited from parent. Unfortunately in your case you had primitive dataType variable so you need to use $parent notation again. So inside ng-if div you could access addCustom by doing $parent.$parent.addCustom. This $parent thing will solve your problem, but having it on HTML will make unreadable and tightly couple to its parent scope(suppose on UI you would have 5 child scope then it will look so horrible like $parent.$parent.$parent.$parent). So rather you should go for below approach.
Follow Dot rule while defining ng-model
So I'd say that you need to create some object like $scope.model = {} and add addCustom property to it. So that it will follow the prototypal inheritance principle and child scope will use same object which have been created by parent.
angular.module('myApp')
.controller('myController',['$scope',myController]);
function myController($scope){
$scope.model = { addCustom : false };
}
And on HTML you will use model.addCustom instead of addCustom
Markup
<div ng-controller="myController" ng-switch on="addressCards">
<div>
{{model.addCustom}} // does not get changed
<div ng-if="model.addCustom === false">
{{model.addCustom}} // does get changed
<button type="button" class="btn btn-primary btn-icon-text" ng-click="model.addCustom = true">
<span class="icon icon-plus"></span>
click here
</button>
</div>
</div>
</div>
Other best way to deal with such kind of issue is, use controllerAs pattern while using controller on HTML.
Markup
<div ng-controller="myController as myCtrl" ng-switch on="addressCards">
<div>
{{myCtrl.addCustom}} // does not get changed
<div ng-if="myCtrl.addCustom === false">
{{myCtrl.addCustom}} // does get changed
<button type="button" class="btn btn-primary btn-icon-text" ng-click="myCtrl.addCustom = true">
<span class="icon icon-plus"></span>
click here
</button>
</div>
</div>
</div>
From the Docs:
The scope created within ngIf inherits from its parent scope using prototypal inheritance. An important implication of this is if ngModel is used within ngIf to bind to a javascript primitive defined in the parent scope. In this case any modifications made to the variable within the child scope will override (hide) the value in the parent scope.
-- AngularJS ng-if directive API Reference
The rule of thumb is don't bind to a primitive, instead bind to an object.
Scope inheritance is normally straightforward, and you often don't even need to know it is happening... until you try 2-way data binding (i.e., form elements, ng-model) to a primitive (e.g., number, string, boolean) defined on the parent scope from inside the child scope. It doesn't work the way most people expect it should work. What happens is that the child scope gets its own property that hides/shadows the parent property of the same name. This is not something AngularJS is doing – this is how JavaScript prototypal inheritance works. New AngularJS developers often do not realize that ng-repeat, ng-if, ng-switch, ng-view and ng-include all create new child scopes, so the problem often shows up when these directives are involved. (See this example for a quick illustration of the problem.)1
This issue with primitives can be easily avoided by following the "best practice" of always have a '.' in your ng-models – watch 3 minutes worth. Misko demonstrates the primitive binding issue with ng-switch.1
Ng-if introduces a different scope. Try this as an attribute of your button:
ng-click="$parent.addCustom = false"
This will assure that you're accessing the same scope.
It's because of this that it's always good practice to use the ControllerAs syntax. All attributes are bound to the controller object and namespaced accordingly, meaning you never run in to this problem. I've updated your example using the ControllerAs syntax to demonstrate its use.
HTML
<div ng-controller="myController as vm" ng-switch on="addressCards">
<div>
{{vm.addCustom}}
<div ng-if="vm.addCustom === false">
{{vm.addCustom}}
<button type="button" class="btn btn-primary btn-icon-text" ng-click="vm.addCustom = true">
<span class="icon icon-plus"></span>
click here
</button>
</div>
</div>
</div>
Controller
(function(){
'use strict';
angular.module('myApp')
.controller('myController', [ myController ]);
function myController () {
var vm = this;
vm.addCustom = false;
}
})();
Here is an excellent article providing more detail about ControllerAs and it's advantages.
Both Classic Controller and Controller As have $scope. That's super important to understand. You are not giving up any goodness with either approach. Really. Both have their uses.

AngularJS : isolated scope + two-way binding + ng-repeat not working

the ng-repeat outputs nothing. in the link function, i can see $scope.clients is an array. if i remove the isolate scope, and use the parent scope, the ng-repeat works.
html with directive "clients".
<div container
ng-cloak
ng-app="summaryReportApp"
ng-controller="summaryReportController as summaryReport">
<fieldset clients="summaryReport.clients">
<legend>Clients</legend>
<div align="left">
<div ng-repeat="client in clients track by $index">
{{client}}
</div>
</div>
</fieldset>
</div>
directive
var clients = function(){
var definition = {
restrict: "A",
scope: {
clients:"=clients"
},
link: function($scope,$element,attributes){
}
}
return definition;
}
This is a common question I seem to answer frequently. Directives can have other HTML Elements nested in them, in the same way that an <input> can be nested inside a <div>. However, the Elements nested inside the Directive are not part of the directive, and are not scoped to the directive, they are scoped to the HTML they are in. The only items that have access to the Isolated Scope are the compile, link, controller, and template items in the directive definition. If you moved your inner html from inside the fieldset into a template, it would function as expected.
You can also reference http://angular-tips.com/blog/2014/03/transclusion-and-scopes/ for more examples and ways to test this.

Init anonymous function of directive instance

In directive we can define the isolate scope so that it can be reused
var app = angular.module('myModule',[])
.directive('btn',[function(){
return {
...,
scope:{}
}
}]);
In usage, it can create separate instance.
<scope>
<btn></btn>
<btn></btn>
</scope>
However, if the <btn> has events like 'click', 'hover' etc. and those event should be defined in the scope or controller at the very first beginning. If I have many <btn> and they are placed at different files, I have to defined as many handlers as the number of <btn> in one file. That means the page has to load lots of unnecessary functions.
Are there any method can let me initialize the instance of the directive so that it can accept anonymous function to become its handler before it render that direction. Like:
<scope>
<btn>this_btn.click=function(){alert(1)}</btn>
<btn>this_btn.hover=function(){alert(0)}</btn>
<scope>
Don't define functions in the html. scope is an abstraction of the DOM; it represents a piece of it, so adding functions in the scope is the angular way of putting them in the DOM.
That said, you can just add behaviour in the already existing directives.
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app>
<input type="text" ng-model="text">
{{text}}
<btn ng-click="text = 1">on click</button>
<btn ng-mouseenter="text = 2"> on hover</button>
</div>

Angular JS scope not updating from included template

So I have below index.html:
<div ng-controller="UsersController">
<div ng-include='"assets/users/partials/template.html"'></div>
<a ng-click="get_data()">Get</a>
</div>
Template.html:
<input type="text" ng-model="SearchUser" name="SearchUser" />
My controller:
app.controller('UsersController', ['$scope', function($scope) {
$scope.get_data = function(){ console.log($scope.SearchUser); };
}
]);
So in above case on the click anchor, I am getting undefined in the $scope.SearchUser scope value.
But if I take that input out of the template and put inside main HTML it works.
I checked for multiple controller declaration and other stuffs but nothing worked for me.
I am using angular 1.2.25 version.
ng-include defines its own scope, which inherits from the controller scope. So SearchUser is set, but as an attribute of the child scope.
As always, the solution is to have a dot in your ng-model, and to define the outer object in the controller scope:
$scope.state = {};
and, in the HTML:
<input type="text" ng-model="state.SearchUser" name="SearchUser" />
That way, angular will get the state field from the child scope. Since the child scope prototypically extends the controller scope, it will find it in the controller scope, and it will write the SearchUser attribute of the state object.

How do I share data between child directives in angular?

Lets say I have two directives parent-dir and child-dir
<div parent-dir>
<div child-dir>
<div child-dir>
</div>
<div parent-dir>
<div child-dir>
<div child-dir>
</div>
How do I share data between the first parent-dir directive and all of the child-dir directives within that element but isolated of the other parent-dir?
It depends on how you are setting up your directives. One way would be for the parent and child directives to create a new scope. Children scope automatically inherit the parent's scope and can specifically access the parent's scope with $parent. Siblings cannot [easily] access each others scope. For in your example, child-dir can access their parent-dir but parent-dir cannot access another parent-dir
You can create a new scope using the following in the object you return when defining your directives
scope: {}
Note: if you use scope: true, you will create an isolated scope which has the parent scope of rootScope and not its logical parent.

Resources