I'm in the process of learning Angular, and have come across a bit of a problem. I'm trying to create a set of directives that represent different levels within a javascript object. The object contains a number of different properties that depend on the state of other parts of the model. For example, if one of the sub properties is in an error state, the parent is also. I have an extremely over-simplified example HERE. Any help would be greatly appreciated. Especially if someone could explain what's going wrong with the example and offer advise on high-level best practices for angular design. Thanks.
The problem with your example has to do with the new scope that is created by ng-repeat. I'll refer you here for a very detailed explanation, but here's the takeaway:
For each item/iteration, ng-repeat creates a new scope, which prototypically inherits from the parent scope, but it also assigns the item's value to a new property on the new child
scope.
If item is a primitive, essentially a copy of the value is assigned to the new child scope property. Changing the child scope property's value (i.e., using ng-model) does not change the array the parent scope references.
It's a confusing issue with a simple solution: Make your bindable values objects instead of primitives.
In your example, replace
scope.innerValues = [1,2,3];
with
scope.innerValues = [{value: 1}, {value:2}, {value:3}];
Here's your example modified to work: http://plnkr.co/edit/IXKk75721MHNsI0zeBEG?p=preview
Related
Attempting to assign a scope object to a JavaScript variable to do minor manipulation before sending to my API. However, any changes made to the JavaScript variable change the scope object.
var recruitingCallListOutput = $scope.RecrutingCallingList.Recruit;
// manipulation of recruitingCallListOutput
The manipulation actually still updates the scope object which is not desired. Feel I am not understanding something in AngularJS correctly. Is there a way to grab the data and detach it from the scope?
In your example, recruitingCallListOutput is a reference to $scope.RecrutingCallingList.Recruit (see https://codeburst.io/explaining-value-vs-reference-in-javascript-647a975e12a0 for more detail.) You will want to make a copy of $scope.RecrutingCallingList.Recruit.
If Recruit is a shallow object, meaning no nested objects (property values are primitives only), you can simply do
var recruitingCallListOutput = Object.assign({}, $scope.RecrutingCallingList.Recruit);
If you have nested objects/arrays as property values, you'll need to deep copy. It's been a while since I have been in the angular world, but
var recruitingCallListOutput = angular.copy($scope.RecrutingCallingList.Recruit)
you could actually use angular.copy in both examples.
This is nothing to do with AngularJS. It's Javascript, and it's expected behaviour.
For example, if you open the browser console (F12->Console) right now and run this:
var foo = {x:1};
var copy=foo;
copy.x=2;
console.log(foo.x);
you will see {x:2} printed out.
This is the same behaviour you would expected for any object reference in Javascript, C#, Java, etc. Because you are making a reference and not a copy, any changes to the reference are actually changes to the original.
The simplest way to solve this problem in your case is to copy the values you are interested in from the item in question into a totally separate object and modify that copy.
e.g.
var recruitingCallListOutput = {
name: $scope.RecrutingCallingList.Recruit.name,
age:$scope.RecrutingCallingList.Recruit.age,
modifiedSomething: $scope.RecrutingCallingList.Recruit.something + 42 //or whatever modifications you need to make
...and so on.
};
There are ways to "clone" an object in Javascript but unless your object is really really complex I would be careful. And consider if you really need all of the properties of the original object anyway, perhaps you only need to send some of them to your backend.
I am trying to create a directive for md-autocomplete. I tried using the answer provide by AngularJS - Create a directive that uses ng-model, but it does not work for me.
My CodePen is here: http://codepen.io/colbroth/pen/QyMaQX/?editors=101.
The code is based on the Angular material demo for autocomplete at https://material.angularjs.org/latest/demo/autocomplete. I have multiple pages, which need an autocomplete field to select a state. I do not want to repeat the code for each web page component.
state-directive takes an md-autocomplete input, I need demoCtrl.selected state to reflect the same value in both cases. But when I update the input element, the state-directive does not reflect this and vice versa.
<input ng-model="demoCtrl.selectedState">
<state-directive ng-model="demoCtrl.selectedState">...</state-directive>
You are on the right track. Your problem is that your model is a string - a primitive in javascript but a ngModel always needs to be an object if you want to avoid these kind of problems.
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-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.)
This issue with primitives can be easily avoided by following the "best practice" of always have a '.' in your ng-models.
Taken from Understanding-Scopes
It also links to this Youtube video - 3 minutes of well invested time
function DemoCtrl() {
var self = this;
self.state = {
selected: "Maine"
};
}
Fixed codepen
Is it possible to bind more than one data object to a template? I find the single object philosophy a bit resistant to scalability.
How can I add more observant data objects to a template without modifying the one already attached to it.
When you instanciate the ractive instance you could do something like this.
var r = new Ractive({data : {Object1:{},
Object2:{} } });
Then you can attached as many objects as you want.
Then you can scope this in your templates by doing
{{#Object1}}
<div>{{Prop1}}
{{/Obj1}}
{{#Object2}}
<div>{{PropForObject2}}
{{/Obj2}}
Was reading this
https://github.com/angular/angular.js/wiki/Understanding-Scopes
and there is same thing on stack overflow:
What are the nuances of scope prototypal / prototypical inheritance in AngularJS?
Here is the thing which I do not understand:
Suppose we then do this:
childScope.aString = 'child string'
The prototype chain is not consulted, and a new aString property is added to the childScope. This new property hides/shadows the parentScope property with the same name.
Suppose we then do this:
childScope.anArray[1] = '22'
childScope.anObject.property1 = 'child prop1'
The prototype chain is consulted because the objects (anArray and anObject) are not found in the childScope.
So in the object array example - parent scope is updated because the objects (anArray and anObject) are not found in the childScope
But in the first example its the same. Except that it is the string. It is not found in the child scope, so should be updated in parent scope. Why it is not updated in parent scope?
Or why array and object in 2nd example are not created in child scope?
Hope its ok to create new thread based on original thread, because just for comment it is long text and would not be easy to read.
In a sense, all three of those examples are really the same. In the second two examples, there's simply an extra object-to-object "hop". The first one:
childScope.aString = "child string";
There's just one object involved: the "childScope" object. (By the way, the word "scope" here makes me uneasy, but let's move on.)
The second pair of examples involve two objects:
childScope.anArray[1] = "22";
childScope.anObject.property1 = 'child prop1';
The two objects in the first of those two statements is the "childScope" object, and then the "anArray" property of the "childScope" object. Presumably, if the statement doesn't cause an error then the value of that property is an array. I stress value because that's the key difference between this reference to a property of "childScope" and the first example ("aString") - here, we want the value of the property because it must be used for the subsequent reference. In the first "aString" example, the statement was setting the value, so there was no interrogation of the current value.
Once the value of childScope.anArray has been fetched, then that object reference is in turn used in a property reference expression. Here, as in the first "aString" example, we're setting the value of the "1" property of the "anArray" object. That's pretty much exactly the same operation as in the "aString" example. (The "anObject" example is much the same.)
When setting a property value (or an array element value; that's really the same thing), JavaScript does not look for a property up the prototype chain. When using the value of a property, it does. An interesting effect of that is this. In the first "aString" example, if there were an "aString" property on the prototype, then once that property is set on the instance ("childScope"), then the prototype property is effectively hidden from view as far as that instance object is concerned.
I'm quite new to the superheroic framework AngularJS, so please excuse my noobness! So, while I was trying to develop a simple SPA that allowed admins to change permissions of other users in my website through a table filled with selection checkboxes, I came by an issue. I defined $scope.checked so I could know how many users the administrator chose and also $scope.mainCheckBox which would hold the correct CSS class for the major checkbox (like the one from Gmail, which you use to select all/none) in each of the three situations: all users selected, no users selected and partial selection. This simple logic goes as follows:
$scope.updateMainCheckBox = function(){
if(this.checked==this.users.length) this.mainCheckBox = "qm-icon-checked";
else if(!this.checked) this.mainCheckBox = "";
else this.mainCheckBox = "qm-icon-minus";
};
Running this at the end of the ng-click event callback of the other checkboxes, this code was capable of choosing the class correctly but it wouldn't assign the class to the mainCheckBox. When I changed every this.mainCheckBoxto $scope.mainCheckBox it all went well and the page would behave as expected.
So, I know in vanilla Js this is a reference to the window object but since AngularJS has its own event handling loop, I believe that's not the case now. I expected it to be a ref to $scope, since checked and users were found, but apparently it's not. So, what's happening here? And also, is there a more "angular" way to implement this?
You're mostly right--in the case of your function, this did reference a scope. But, it did not reference $scope; instead, it referenced one of $scope's children.
Try this: add some console.log statements to log out the ID of the $scope variable, and then the ID of the this scope in your callback:
$scope.updateMainCheckBox = function(){
console.log('$scope:', $scope.$id);
console.log('this:', this.$id);
// ...
};
You'll find the IDs are different. This is because the ngRepeat directive creates a child scope, which is a scope that inherits prototypally from its parent. This means that when you assign directly to a value on that scope, it's not reaching the parent scope, which is $scope.
I highly recommend a read through the excellent (and lengthy) "The Nuances of Scope Prototypal Inheritance" by Mark Rajcok; it esplains why you see this seemingly strange behavior on child scopes. (You can also find a copy on the AngularJS wiki.)