In Angular, why should you always use objects in the your $scope? - angularjs

I have read in different places that it is important that you always use objects in your scope but I haven't found a definitive answer as to why that is. Can someone please help me?

I like Ryan Q's answer but after doing some more research I wanted to add an answer with a little more emphasis on javascript's prototypal inheritance.
The issue here is an object vs. primitive issue (pass by reference and pass by value) and how Javascript’s prototypal inheritance works.
When a javascript class inherits from a parent class, it copies the values over into the child class. There are two types of values that could be copied over, objects or primitives.
When an object is copied in through inheritance, it is passed by reference. This means that any updates I make in my child object will be seen in the parent object as well.
When a primitive is copied in through inheritance, it is passed by value. This means that any updates will NOT be seen in the parent class.
What does this have to do with Angular scopes? When we create a directive, a scope will be created for this directive and we can declare it to be an isolated scope or an inherited scope. If it is an inherited scope, it will inherit its parent’s scope items. Now, if I have primitive values in my parent scope, I will be inheriting them into my child scope as pass by value. Which means when I make a change in my parent scope, it won’t be seen in my child scope, and vice versa. Now I could have the same inherited variable in my child and parent scope with different values. This is going to lead to confusion…. And probably anger…lol.
So if you just use an object, then this problem will not happen. And that is why you should always objects in scopes.

Take a look at this:
https://github.com/angular/angular.js/wiki/Understanding-Scopes
and
Does my ng-model really need to have a dot to avoid child $scope problems?

Lets say you have 3 controllers, a main control, and two others which inherit from the main control.
Your html might look like this.
<div ng-controller="MainCtrl">
<div ng-show="helloWorld">Hello World</div>
<div ng-controller="Sub1Ctrl">
<button type="button" ng-click="helloWorld = false">Hide Hello World</button>
</div>
<div ng-controller="Sub2Ctrl">
<button type="button" ng-click="helloWorld = false">Hide Hello World</button>
</div>
</div>
Your Main Controller
angular.module('MyModule').controller('MainCtrl', function( $scope ){
$scope.helloWorld = true;
});
All is fine and dandy and your hello world element shows as expected. Now try clicking one of those buttons which sets helloWorld to false. Your Sub1Ctrl will now look like this:
angular.module('MyModule').controller('Sub1Ctrl', function( $scope ){
$scope.helloWorld = false;
});
But your MainCtrl will still be
angular.module('MyModule').controller('MainCtrl', function( $scope ){
$scope.helloWorld = true;
});
Why is that? Well because when Angular evaluates "helloWorld = false" it sets $scope["helloWorld"] = false internally. The issue is because your setting it on the lower Sub1Ctrl, the higher controller is never set.
If you change your Html and MainCtrl to this:
<div ng-controller="MainCtrl">
<div ng-show="myModel.helloWorld">Hello World</div>
<div ng-controller="Sub1Ctrl">
<button type="button" ng-click="myModel.helloWorld = false">Hide Hello World</button>
</div>
<div ng-controller="Sub2Ctrl">
<button type="button" ng-click="myModel.helloWorld = false">Hide Hello World</button>
</div>
</div>
angular.module('MyModule').controller('MainCtrl', function( $scope ){
$scope.myModel = {
helloWorld: true
}
});
Then Angular will look up and see if the object exists in the scope's prototype (which is how controllers inherit through the Scope Hierarchy) before setting the value.
So now Angular evaluates "myModel.helloWorld" on the Sub1Ctrl which ends up finding "myModel" on the parent MainCtrl and so properly sets the helloWorld property to false.

Because then you keep the data in one place. A child scope (which is not an isolated scope) can shadow a property "message" for example. So the parent scope and the child scope have a "message" property. But if the parent scope has "data.message" property, the child scope cannot shadow this, unless first a property "data" is created in the child scope, and then "data.message", but this is not what AngularJS does. But AngularJS indeed creates first "data" and then "data.message" in the parent scope.

Related

AngularJS ng-if and scopes [duplicate]

This question already has answers here:
What are the nuances of scope prototypal / prototypical inheritance in AngularJS?
(3 answers)
Closed 3 years ago.
I am trying to understand ng-if and scopes. As I am aware, ng-if creates a new child scope. Here is my issue:
View
<input ng-model="someValue1" />
<div ng-if="!someCondition">
<input ng-model="$parent.someValue2" />
</div>
Controller
$scope.someCondition = true;
if ($scope.someCondition) {
$scope.someValue2 = $scope.someValue1;
}
If someCondition is set to true, then someValue2 should be the same as someValue1.
My problem is that I can't access someValue2 in both situations (true or false). How could I achieve this?
Yes, ng-if creates a new child scope
To watch a model property in an ng-if, the rule-of-thumb is:
DO NOT USE THE SCOPE AS MODEL
e.g.
ng-if='showStuff' //here my scope is model **INCORRECT**
ng-if='someObject.showStuff' // ** CORRECT **
Use an object property in ng-model - then, even if ng-if creates the new child scope, the parent scope will have the changes.
To see a working Plunker, look here : http://jsfiddle.net/Erk4V/4/
ngIf does indeed create a new scope using prototypal inheritance. What that means is that the ngIf's scope's prototype object is that of its parent's scope. So if the attribute isn't found on the ngIf instance of its scope it will look into its prototype objects chain for that attribute. However, once you assign an attribute to the instance of the scope it will no longer look into its inheritance chain for the attribute. Here's a link explaining prototypal inheritance used in JS: https://github.com/angular/angular.js/wiki/Understanding-Scopes#javascript-prototypal-inheritance
How to solve this:
Parent controller:
$scope.data = {someValue: true};
Child controller:
$scope.data.someValue = false
Because you're not hiding an attribute on its parent's scope, you're just mutating an object on its parent's scope, this will indeed alter the parent's data object. So in your case:
<input ng-model="data.someValue1" />
<div ng-if="!data.someCondition">
<input ng-model="data.someValue2" />
</div>
From what I'm aware of, the ng-if is purely a display level statement. You can use it to make some elements visible / invisible given certain values but I don't think it creates any kind of scope. What your HTML code will do is toggle the visibility of your secondary input.
If you'd like to switch your Value 2 to equal Value 1 whenever "someCondition" changes between false and true, then you can use $watch with something like this:
$scope.$watch(someCondition, function(){
if (someCondition){
$scope.someValue1 = $scope.someValue2
}
})

Propagate changes from child to parent scope in AngularJS

I'm passing an object using an attribute from a parent scope to a child scope and I would like that changes made in the child scope are propagated to the parent scope.
Basically this is the parent view:
<foo oo-bar="object"></foo>
And this is foo directive:
angular.module('foo').directive('foo', function()
{
return {
restrict : 'E',
templateUrl : ConfigService.path.views + '/table/projects.html',
scope : {
bar : '=ooBar'
}
}
}
The problem is that the changes made in the parent scope are propagated to the child scope but not the other way around.
For example, if I try to $scope.bar = 4 in the child scope, the parent's bar won't change.
What I understood is that Angular is creating a new bar in the child scope that's not linked with the bar in the parent's scope.
If that's the case, how can I propagate the changes back to the parent scope?
I thought that I could use a callback function, but I'm not sure that's the best way.
Edit: Plunker
The solution I was looking for I located here: Understanding Scopes
The key part is the following:
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.
Having a '.' in your models will ensure that prototypal inheritance is in play. So, use
<input type="text" ng-model="someObj.prop1"> rather than
<input type="text" ng-model="prop1">.
If you really want/need to use a primitive, there are two workarounds:
Use $parent.parentScopeProperty in the child scope. This will prevent the child scope from creating its own property.
Define a function on the parent scope, and call it from the child, passing the primitive value up to the parent (not always possible)
In parent scope you should have an object not primitve value, in that case changing object property in child scope will update also in parent . let me bring fiddle
$scope.object = {};
$scope.bar = '' // wrong way its a primiive
Demo without object
Demo with object
<parent ng-controller="parentCtrl">
Parent bar: {{ bar }}
<child foo="bar">
Child bar: {{ foo }}
<input type="text" ng-model="foo">
</child>
</parent>
All the elements inside parent node will be compiled with the scope of parentCtrl.
{{ bar }} -- {{ foo }} will be evaluated with the scope of parentCtrl.
Initially there is no foo defined in the parentCtrl, hence it did not print any value.
As you type in the input field, ng-model will create a variable foo in the scope with which it (ng-model) is compiled (i.e, parentCtrl) and update it.
Now we have foo in the parentCtrl's scope.
As {{ foo }} is compiled with the scope of parentCtrl, it will display the value that is entered in the textbox.
Two-Way binding
scope : {
foo: '='
}
this will create a two way binding between the parentCtrl scope bar and childCtrl scope foo variables.
If you change the value of foo in the link function of child directive. It will update the bar value in the parentCtrl scope.

$scope.$on in Isolate scope

I have two controllers, the second one prototypically inherits from the first.
<div ng-controller="ParentCtrl">
<div ng-controller="ChildCtrl">
</div>
</div>
The ParentCtrl has $scope.$broadcast while the ChildCtrl has a $scope.$on that receives from the broadcast.
I have then decided to make a directive, whose controller definition is the ChildCtrl.
<div ng-controller="ParentCtrl">
<div my-directive>
</div>
</div>
So far, so good, $broadcast/$on event still working. (Correct me if I am wrong, the directive does not have a scope of its own, right? Unless you set scope: true as directive property, which in this case, the directive prototypically inherits from the ParentCtrl?)
I decided then to isolate the scope of my directive. However, I am surprised to see that $on (in the directive controller) still receives the event by $broadcast (in ParentCtrl).
As I understand, this should not work anymore because $broadcast propagates events downward to descendants and in this scenario the parent-child scope relationship no longer applies because of the isolate scope. Can someone explain this? Thanks.
you where right until the part you assume the event won't work
the events are propagated to all the children, no matter the nesting level, so as long as you have a scope listening for a specific event and this event is $broadcasted that listener will be triggered if you are in need of triggering the same event and have a selective react then you might need to consider using event parameters to react differently
Isolating the scope is done for a different reason than what you mention. It's not to cut the child scope free from the parent scope.
It's to keep the child scope separate from other child scopes on the same level. The following example is where isolating the scope would really shine:
<div ng-controller="ParentCtrl">
<div my-directive></div>
<div my-directive></div>
<div my-directive></div>
</div>
In order to keep the my-directives from freely using the same scope variable between each other, you need to isolate their scope.
Basically, the child scope and the parent scope are still linked. As a further example to demonstrate that they are still connected, you should try executing a function in an isolated child scope, and defining that function in the parent scope. It should still run in theory.

Is it possible to prevent a child div from inheriting its parent div's controller?

I'm wondering if its possible to prevent a child div within a nested div inheriting its parent controller.
<div id="parentDiv" ng-controller="parentCtrl">
<div id="childDiv" ng-controller="childCtrl">
</div>
</div>
Imagine I have a scope variable called name, how could I ensure that name is not accessible in side childDiv.
See plunk.
It's not problematic to have a child div accessing the variables of its parent controller but it just doesnt feel right. What about naming collisions on so on?
I do not believe there is a way to hide variables of a child scope from the parent scope with respect to angular controllers. The scope of the parent is always available to the scope of the children. I do have two points that may help.
1) Naming collision may not be a problem as you would expect. If you set $scope.name in the childCtrl to "testChild", the name variable's value parentCtrl is still "testParent". The child $scope has access to the parent but does not overwrite/share variables, only objects.
2) If you are worried about naming collision or confusion, I would recommend placing an object on scope and adding properties to that object. Example: $scope.parentData = { name = "testParent"} and $scope.childData = { name = "testChild"}

Angularjs: 2 way binding not working in included template

I think I'm missing something simple (and important) here. I'm using an included template that contains an input that's mapped to some value:
<div ng-controller="Ctrl">
<div ng-include src="'template.html'"></div>
</div>
<!-- template -->
<script type="text/ng-template" id="template.html">
<input ng-model="testvalue" />
</script>
Controller:
function Ctrl($scope) {
$scope.testvalue= "initial value";
}​
Alerting the value of $scope.testvalue always shows the initial value, not the updated value (when you type in the input). Help me Obi-Wan. You're our only hope.
Fiddle: http://jsfiddle.net/h5aac/
This is the all too common of binding to a primitive instead of an object. The value of the string gets passed around and not a reference to an object. If you use an object instead of a primitive, it works fine. Something like this in your scope.
$scope.foo = {testvalue: "initial value"};
See http://jsfiddle.net/h5aac/2/
Also:
Using `ng-model` within a transcluded directive in AngularJS
binding issue when a directive in a ngRepeat
AngularJS - updating scope value with asynchronous response
I'm sure there are more...
An alternative to referencing an object property in the parent scope is to use $parent to access the primitive in the parent scope:
<input ng-model="$parent.testvalue" />
ng-include creates a child scope. That scope prototypically inherits from Ctrl's parent scope. Here's how the 3 variations work:
$parent.testvalue ties the model to the property in the parent scope
testvalue by itself ties the model to a new property that will be created on the child scope. This property "shadows/hides" the parent scope property by the same name.
foo.testvalue (e.g., see #dnc253's answer) also ties the model to a parent property. It works like this: Javascript doesn't see/find 'foo' in the child scope, so it looks for it in the parent scope (due to prototypical inheritance) and finds it there.
To see what the child scope looks like, use your original fiddle, and add this code to your template somewhere:
<a ng-click="showScope($event)">show scope</a>
And add this code to your Ctrl:
$scope.showScope = function(e) {
console.log(angular.element(e.srcElement).scope());
}
Before you type into the textbox, click the "show scope" link. In the console (I'm using Chrome), you can expand the "Child" scope and see it does not contain a testvalue property yet (which I find surprising, because I don't know how it is displaying the "initial value" in the textbox). You can expand the $parent and you'll see the testvalue property there -- a property with this name appears to only be in the parent scope at this point.
Now, clear the console, type into the textbox, and click the "show scope" link again. You'll see that the "Child" scope now has a new testvalue property. It shadows/hides the parent property. So, things in the child scope see the child scope testvalue property, and things in the parent scope see the parent scope testvalue property.
Update: FYI, I recently wrote an extensive answer/article about scope prototypical inheritance that explains the above concepts in much more detail, with lots of pictures: What are the nuances of scope prototypal / prototypical inheritance in AngularJS?

Resources