I'm having an issue with binding an input from inside a view. I thought it would bind to the controller scope, but it seems to be binding to a child scope, so it's not updating above.
Other items will bind like I expect if they're inside an ng-repeat (I'm not sure why).
Here's an example:
http://jsfiddle.net/hMpsB/1/
What's the best way to bind the input to the correct scope if it's not inside an ng-repeat?
In your example you will have better luck binding your $scope.test to an object instead of a primitive type like this:
$scope.test = { val: "test value" };
You can see this fiddle for a working example.
The child scope that gets created in the ngView will copy your value and since your original $scope.test is a primitive string it has no link to the parent value so your input will be modifying the child scope copy. When binding to an object your child scope has a copy of the object reference but will ultimately modify the same instance of the object.
You can take a look at this question for more information on creating a service to persist data across multiple controllers (which is a little similar to your question).
You can also look into using $parent as described in this answer though as Mark mentions it's undocumented and might get messy if another child scope ever gets introduced somewhere.
Related
I was reading this article on scope inheritance in AngularJS and was confused by this code example:
angular.module("Demo", [])
.controller("ChildCtrl", function($rootScope, $scope) {
$rootScope.rootyThing = "I am groot";
console.log($scope.rootyThing); // "I am groot"
console.log(Object.getPrototypeOf($scope)); // Scope
});
I don't understand why $scope.rootyThing is set instead of undefined.
The article's explanation seems incomplete. The fact that the child scope "prototypically inherits" from $rootScope would not explain this, seeing as rootyThing is not set on the prototype, and moreover was set after the creation of the child scope $scope.
The only explanation is if the scopes in Angular are deeply modified such that all variables set on them are broadcast to existing child scopes. Unless I'm missing something, more than possible.
Can anyone explain this?
Edit: My current understanding is that $rootScope is in fact the Scope function itself rather than an instance of Scope, and all $scope instances use this as a root prototype, so when variables are set on the function Scope then they're naturally accessible to the various $scope instances.
Is this accurate?
All scopes are added on $rootScope object. If you add a property(for example someProperty)on $rootScope and you try to access it using $scope.someProperty, then it will be checked that this property exists on $scope(i.e current scope). If that property does not exist, then it will be checked on higher level in scope chain(i.e $rootScope).
ng-controller will create a new Scope.
this scope's prototype is set to parent Scope(i.e, $rootScope in this case)
And it's the default javascript behavior to look in the prototype chain if the property which we are looking for is not found in the object.
It is set to the prototypes, try console.log
$scope.__proto__.rootyThing
And you should see it there.
Furthermore, objects are by reference in javascript so it doesnt matter when $scope was set
for example
//say this is your rootScope
objRoot = {
obj: {
test: 'hello'
}
}
//Now lets create a scope
var temp = objRoot.obj
//Update rootScope
objRoot.obj.test = "changed"
//log your temp
console.log(temp.test); //changed
In AngularJs, as far as I know, the scopes are inherited from the parent scope, all the variables, but if u have a sibling scope then those values will not be inherited. The broadcasting is done for events.
So, Angular is working as it has to be. If you have set some variable on the $rootScope that will be accesible throughout the App.
Is it always safe to use this inside a template to get a reference on the current scope?
<span ng-click="log(this)">
Is my onclick parameter always the current controllers scope?
</span>
Here is a fiddle to show what I mean.
Searched around as well, but couldn't find any point on this in the docs. It seems to work, however I don't want to depend on an undocumented feature. If you can, please link the proper doc for this.
The expression log(this) is evaluated against the scope, and scope object has a this property in it as well which points to its own reference. So it will evaluate this against the scope will yield the scope itself. For example you could also pass $parent and you will see it will point to the parent scope since the argument in the expression gets evaluated against the current scope will yield scope['$parent'] similarly it happens to scope['this'] too. Using ng-click="method(this)" in angular context completely different from doing onclick="somefunc(this)" on a DOM element. Angular is able to expand it only because it has a property with the name this attached to the scope object.
Another thing is you do not need to pass this even otherwise the method is run by evaluating against the scope object, so referring to this will yield the scope itself. i.e you could as well do ng-click="log()" and in the log method you can refer to this as the current scope where the element is bound to. For instance if you were using controllerAs syntax and you are calling:
<div ng-controller="MyOtherCtrl as vm">
<span ng-click="vm.log(this)">Click on me, if background gets teal, then this points on the child controllers scope</span>
</div>
Now this will not be the controller instance (which is what you would generally expect it to be), instead it will just be the scope object itself as it evaluates the expression as scope.vm.log(scope.this)
It would always be safer to use $scope in the context of the controller, unless you specifically want to refer to some child scope created on the view due to some directive(by some ng-repeat, ng-if etc..) wrapping that particular context. Also remember properties get prototypically inherited (except for isolated scoped directive) in the child scopes.
And there are no links documented or anything as far as i know, but source code itself is the official record. Angular uses $parse to expand the expressions and if you see the source code you can see that angular invokes the expressions with function.apply with the context.
this refers to the $scope of the controller directive.
For eg:
<div ng-controller="myCntrl">
<span ng-click="log(this)">
Is my onclick parameter always the current controllers scope?
</span>
</div>
So, this is referring to the myCntrl's $scope.
I noticed that methods get called automatically whenever the underlying $scope variable changes.
$scope.getLength = function() {
return $scope.length;
}
My html looks like the following
<div class="test">{{getLength}}</div>
Whenever I change $scope.length, the method updates the value on the UI. I know that variables get updated because of the MVVM binding in Angular. WHy does a method get called?
It is a binding is this case too; just a binding to a function. AngularJS updates all bound elements whenever it goes through a digest cycle.
Angular does invoke those bound function everytime the scope is getting changed, no matter if this $scope.length changed or any other scope variable.
But be careful with binding too much functions into scope like this... console.log something in the function and you will see.
working on enterprise angularjs app, binding functions within big scopes can cost like 5 % of cpu power invoking bound functions containing big calculations.
I have two controllers, one wrapped within another. Now I know the child scope inherits properties from the parent scope but is there a way to update the parent scope variable? So far I have not come across any obvious solutions.
In my situation I have a calendar controller within a form. I would like to update the start and end dates from the parent scope (which is the form) so that the form has the start and end dates when submitted.
You need to use an object (not a primitive) in the parent scope and then you will be able to update it directly from the child scope
Parent:
app.controller('ctrlParent',function($scope){
$scope.parentprimitive = "someprimitive";
$scope.parentobj = {};
$scope.parentobj.parentproperty = "someproperty";
});
Child:
app.controller('ctrlChild',function($scope){
$scope.parentprimitive = "this will NOT modify the parent"; //new child scope variable
$scope.parentobj.parentproperty = "this WILL modify the parent";
});
Working demo: http://jsfiddle.net/sh0ber/xxNxj/
See What are the nuances of scope prototypal / prototypical inheritance in AngularJS?
There is one more way to do this task and to not use the $scope.$parent variable.
Just prepare a method for changing the value in parent scope and use it in child one. Like this:
app.controller('ctrlParent',function($scope) {
$scope.simpleValue = 'x';
$scope.changeSimpleValue = function(newVal) {
$scope.simpleValue = newVal;
};
});
app.controller('ctrlChild',function($scope){
$scope.changeSimpleValue('y');
});
It also works and give you more control over the value changes.
You can then also call the method even in HTML like: <a ng-click="changeSimpleValue('y')" href="#">click me!</a>.
This also works (but not sure whether this follows best practice or not)
app.controller('ctrlParent',function($scope) {
$scope.simpleValue = 'x';
});
app.controller('ctrlChild',function($scope){
$scope.$parent.simpleValue = 'y';
});
When you assign a primitive attribute to a scope, it is always local to the scope (possibly created on the fly), even if a parent scope has an attribute with the same name. This is a design decision, and a good one IMHO.
If you need to change some primitive (ints, booleans, strings) in the parent scope, from the view, you need it to be an attribute of another object in that scope, so the assignment may read:
<a ng-click="viewData.myAttr = 4">Click me!</a>
and it will, in turn:
get the viewData object from whatever scope it is defined in
assign 4 to its myAttr attribute.
For accessing variables declared in the parent, we should use $parent in child controller or template file
In controller
$scope.$parent.varaiable_name
In html template
ng-model="$parent.varaiable_name"
I've a textbox in a controller which is bound to model name. There's a directive inside the controller and there's another textbox inside the directive which is bound to the same model name:
<div class="border" ng-controller="editCtrl">
Controller: editCtrl <br/>
<input type="text" ng-model="name" />
<br/>
<tabs>
Directive: tabs <br/>
<input type="text" ng-model="name"/>
</tabs>
</div>
mod.directive('tabs', function() {
return {
restrict: 'E',
transclude: true,
template:
'<div class="border" ng-transclude></div>',
};
});
When you type something in the outer textbox it's reflected in the inner textbox but if you type something in the inner textbox it stops working i.e. both textbox no more reflects the same value.
See example at: http://jsfiddle.net/uzairfarooq/MNBLd/
I've also tried using two way binding attr (scope: {name: '='}) but it gives syntax error.And using scope: {name: '#'} has same effect.
Any help would be greatly appreciated.
In addition to the accepted answer, this article really helped me in understanding the prototypical inheritance in child scpoes. I'd highly recommend anyone having problem with scopes to read it thoroughly.
A directive with transclude: true results in the directive creating a new (transcluded) child scope. This new scope prototypically inherits from the parent scope. In your case, the parent scope is the scope associated with the editCtrl controller.
Using two-way databinding in a child scope (i.e., ng-model) to bind to a parent scope property that holds a primitive value (e.g., name) always causes problems -- well, I should say that it doesn't work as expected. When the scope property is changed in the child (e.g., you type into the second textbox) the child creates a new scope property that hides/shadows the parent scope property of the same name. If the parent property holds a primitive value, that value is (essentially) copied to the child property when the child property is created. Future changes in the child scope (e.g., the second textbox) only affect the child property.
Before typing into the second textbox (i.e., before the property is changed in the child), the child/transcluded scope finds the name property in the parent scope via prototypal inheritance (dashed line in picture below). This is why the two textboxes initially remain in synch. Below, if you type "Mark" into the first text box, this is what the scopes look like:
I created a fiddle where you can examine the two scopes. Click the "show scope" link next to the second textbox before typing into the second textbox. This will allow you to see the transcluded child scope. You will notice that it does not have a name property at this point. Clear the console, type into the second text box, then click the link again. You will notice that the child scope now has a name property, and the initial value was the value that parent property had ("Mark"). If you typed " likes Angular" into the second text box, this is what the scopes look like:
There are two solutions:
do what #pgreen2 suggests (this is the "best practice" solution) -- use an object instead of a primitive. When an object is used, the child/transcluded scope does not get a new property. Only prototypal inheritance is in play here. In the picture below, assume the editCtrl's $scope has this object defined: $scope.myObject = { name: "Mark", anotherProp: ... }:
use $parent in the child scope (this is a fragile solution, and not recommended, as it makes assumptions about HTML structure): use ng-model="$parent.name" inside the <input> that is within the <tabs> element. The first picture above shows how this works.
A syntax error occurs when using scope: {name: '='} because when using two-way databinding (i.e., when using '='), interpolation is not allowed -- i.e., {{}} can't be used. Instead of <tabs name="{{name}}"> use <tabs name="name">.
Using '#' works the same as the transclude case because ng-transclude uses the transcluded scope, not the isolate scope that is created by using scope: { ... }.
For (lots) more information about scopes (including pictures) see What are the nuances of scope prototypal / prototypical inheritance in AngularJS?
I believe that the problem has to do with scoping. Initially the inner textbox doesn't have name set, so it is inherited from the outer scope. This is why typing in the outer box is reflected in the inner box. However, once typing in the inner box occurs, the inner scope now contains name which means it is no longer bound to the outer name so the outer text box doesn't sync up.
The appropriate way to fix is only storing models in the scope, not your values. I fixed it in http://jsfiddle.net/pdgreen/5RVza/ The trick is to create a model object (data) and referencing values on it.
The incorrect code modifies the scope in the directive, the correct code modifies the model in the scope in the directive. This subtle difference allows the scope inheritance to work properly.
I believe the way Miško Hevery phrased it was, scope should be write-only in the controller, and read-only in directives.
update: reference: https://www.youtube.com/watch?v=ZhfUv0spHCY#t=29m19s
Syntax error means that you miswrote something. It is not related to a particular framework / library. You probably forgot to add "," or close a paranthesis. Check it out again