AngularJS - parent scope is not updating - angularjs

I'll try to explain the problem with the example. Example is simplified, so it's not very logical, but anyway..
Let's say I have such view
<div ng-app="TestApp" ng-controller="MyController">
<button ng-add-item="items">Add Item</button>
<ul>
<li ng-repeat="item in items">{{item.name}} <button ng-change-item="item">Change item</button></li>
</ul>
</div>
And I have two directives:
1) ngAddItem - which adds dummy item (after click)
2) ngUpdateItem - which changes item (after click)
The problem is that 1) works great, but second - not..
http://jsfiddle.net/pbmnL/2/
P.S. I know I can use ng-click and many other things in this example. This is just simplified example.
Thanks for a help!..
UPDATE: If I change just a name property everything works fine!
http://jsfiddle.net/pbmnL/4/
But in real example I still need to change entire object..

Your code does not work because with this line:
_item = _changed_item;
you just point local variable _item to another object but do not update the item in the list.
As a solution you may use angular.copy - it replaces all properties of one object with properties from another one.
angular.copy(_changed_item, _item);
Here is jsfiddle that works.

Do this:
$element.bind('click', function () {
$scope.$parent.item.name = 'changed name';
$scope.$apply();
});

Related

How to update $index when list changes in angular

I'm trying to implement a keyboard navigation in a multi-level List.
Therefor i try to give every Item in the List a unique ID, like 5.2.1(category.item.subitem).
I already tried a lot of stuff, like ng-init the index to a variable on ng-repeat, an other approaches using directives, but all had the problem so far that when i change the list (delete items for example) my Custom Index doesn't update!
I made a simple Plnkr here:
You can delete an item or subitem and see that the "real" index and the customindex don't relate.
http://plnkr.co/edit/BsRCNAqM7jWkeAJ9rkLN?p=preview
at the moment i have a custom directive called customIndex that gets the Index as attribute.
<li ng-repeat="subitem in item.subitem" custom-Index="$parent.$index+'.'+$index">
and inside the directive i simply $eval the attribute:
.directive('customIndex', function(){
return{
restrict:'A',
link: function(scope, el, attrs){
scope.myIndex = scope.$eval(attrs.customIndex);
}
}
})
But this, like all other solutions i tried, doesn't work.
I think this must be a common kind of problem.
Does anyone have any suggestions for me?
THANKS
Markus
I think you overcomplicated yourself. Try only this piece of markup and no need for custom directive or something else:
<ul>
<li ng-repeat="item in data">
"Real"-index:{{$index}} | myIndex:{{myIndex = $index}}} | Item:{{item.itemName}} <button ng-click="data.splice(data.indexOf(item),1)">Del</button>
<ul>
<li ng-repeat="subitem in item.subitem">
"Real"-index:{{$index}} | myIndex:{{myIndex = $parent.$index+'.'+$index}} | Subitem:{{subitem}} <button ng-click="item.subitem.splice(item.subitem.indexOf(subitem),1); ">Del</button>
</li>
</ul>
</li>
</ul>
ng-init will never work for you because it is executed only when directive is compiled. With custom directive it will never work because the scope item will be of the item that you delete and you dont' have access to the entire list.
Here is the demo:
http://plnkr.co/edit/Nr4cJ2m3JqI8NyLFnQhC?p=preview
so, in the meantime i found a solution. Maybe not the best, but it works.
i could attach the needed parameters to a directive attribute and set a $watch on this attribute to stay synchronised with any changes caused by deleting/filtering etc.
for example:
<div item-Index="[$parent.$index,$index,-1,$first, $last]"</div>
and the directive hast the following function in the link function:
scope.$watch(attrs.itemIndex, function(value) {
scope.itemIndex = value;
});
now every item has a unique index defined.
The multilevel keyboard navigation also works now, but this is beyond the scope of this question.

How can I change text elsewhere using AngularJS function with parameters?

Alright so I found out what might be my solution for a task I am trying to accomplish: to use some sort of toggle feature which I have seen represented a couple of different ways in other questions here on Stack Overflow. However, mine is a bit of a special case that I can't seem to figure out how to adjust to get it to work.
Here is the accordion where the text needs to appear:
<div ng-controller="Ctrl">
<accordion id="myID">
<accordion-group heading="My Heading">
{{toggleText}}
</accordion-group>
</accordion>
</div>
The text that needs to appear depends on what is clicked however:
<area ng-repeat="x in Object" alt="{{x.name}}" title="" ng-click="thisClick(x.name,x.address);toggle = !toggle" shape="poly" coords="{{x.htmlcoords}}" />
I have an image that has hot spots on it. I used ng-click="thisClick(x.name,x.address)" to easily capture the data from my Object and I was able to alert it in my thisClick(name,address) function. This part of the HTML is in a div that separately calls the same controller as the one above, I don't know if that would be relevant. I couldn't get my code working before trying this toggle stuff unless I kept the controller where it was and just called it again. Anyway, now to apply the toggle feature I tried changing the ng-click to what is shown above and the function to:
$scope.thisClick = function(name,address){
$scope.toggle = true;
$scope.$watch('toggle',function(){
$scope.toggleText = $scope.toggle ? '' : name+address;
});
};
Ultimately the name and address won't be squished together but this is for testing purposes only.
When I run the code, simply put nothing happens.
Either there is a way to clean this up or a way to approach this entirely differently? I hope I provided enough information.
I wish it were as simple as:
<area ng-repeat="x in building" alt="{{x.name}}" title="" ng-click="thisBuilding = x.name+x.address" shape="poly" coords="{{x.htmlcoords}}" />
$scope.buildingName = name;
$scope.buildingAddress = address;
$scope.thisBuilding = function(){
return $scope.buildingName + " " + $scope.buildingAddress;
};
};
:
{{thisBuilding()}}
This sounds quite a lot like a scope issue. As ng-repeat creates a new child scope for each item, most likely you end up setting the $scope.toggleText property in "wrong" scope, i.e. not in the scope of accordion-group where you are trying to display the property.
You might want to play around a bit with some Angular inspector tool, for example ng-inspector to verify this.
If the problem indeed is with toggleText property ending up in the wrong child scope, one possible workaround could be introducing a container object for the property in the root scope. The reason why this could work is that objects are passed as references for child scopes, not as copies like primitive properties are. A whole lot more on this topic can be read from Understanding Scopes article in AngularJS wiki.
So, something along these lines, starting from controller Ctrl:
// inside the controller code:
// initialize the toggleContainer object
$scope.toggleContainer = {};
Then in html template:
<div ng-controller="Ctrl">
<accordion id="myID">
<accordion-group heading="My Heading">
{{toggleContainer.text}}
</accordion-group>
</accordion>
</div>
And finally in the thisClick function, store the value you want to property toggleContainer.text instead of simple toggleText:
$scope.thisClick = function(name,address){
$scope.toggle = true;
$scope.$watch('toggle',function(){
$scope.toggleContainer.text = $scope.toggle ? '' : name+address;
});
};

How to set a boolean flag to collapse/expand a row with ng-repeat

I have this plunker code.
What I'm trying to do, is to display the gray box one time per row.
To achieve this, I thought to modify the partition filter in order to return a JSON to add it a new property by row to know if the gray box is expanded or not.
But, I could Not successfully return a JSON.
Do you know how to modify the filter to return a JSON or a better way to show the gray box by row?
Related questions:
Push down a series of divs when another div is shown
Update 1
The issue could be easily resolved by using the correct scope for the ng-repeat for the row without modifying the filter, thanks to #m59.
http://plnkr.co/edit/eEMfI1lv6z1MlG7sND6g?p=preview
Update 2
Live Demo
If I try to modify the item, it seems the ng-repeat would be called again losing the props values.
<div ng-repeat="friendRow in friends | partition:2"
ng-init="props = {}">
<div ng-repeat="item in friendRow"
ng-click="collapse(item)"
ng-class="{myArrow: showArrow}">
{{item.name}} {{item.age}} years old.
<div>{{item.name}}</div>
</div>
<div collapse="!props.isExpanded">
some content
<br/>
<input type="text" ng-model="currentItem.name">
</div>
</div>
js
$scope.collapse = function(item){
this.props.isExpanded = !this.props.isExpanded;
this.showArrow = !this.showArrow;
$scope.currentItem = item;
};
This causes the gray box to collapse each time the item is modified. Any clue?
I've updated my code/answer regarding partitioning data. It's important to fully understand all of that before deciding on an approach to your project.
The problem you have in your plnkr demo is that you're modifying the parent $scope and not the scope of the ng-repeat for that row.
Just set a flag on the row and toggle it when clicked:
Live Demo
<div
class="row"
ng-repeat="friendRow in friends | partition:2"
ng-init="isExpanded = false"
ng-click="isExpanded = !isExpanded"
>
<div ng-repeat="item in friendRow">
{{item.name}} {{item.age}} years old.
</div>
<div collapse="!isExpanded">
some content
</div>
</div>
To access the correct scope within a function in the controller, you can use the this keyword instead of $scope. this will refer to the scope the function is called from, whereas $scope refers to the scope attached to the element with ng-controller (a parent of the ng-repeat scopes you want to target).
<div
class="row"
ng-repeat="friendRow in friends | partition:2"
ng-click="collapse()"
>
JS:
$scope.collapse = function() {
this.isExpanded = !this.isExpanded;
};
If you want to keep the ng-click directive on the item element instead of putting it on the row element as I have done, then you're dealing with another child scope because of that inner ng-repeat. Therefore, you will need to follow the "dot" rule so that the child scope can update the parent scope where the collapse directive is. This means you need to nest isExpanded in an object. In this example, I use ng-init="props = {}", and then use props.isExpanded. The dot rule works because the children share the same object reference to props, so the properties are shared rather than just copied, just like in normal JavaScript object references.
Live Demo
<div
class="row"
ng-repeat="friendRow in friends | partition:2"
ng-init="props = {}"
>
<div ng-repeat="item in friendRow" ng-click="collapse()">
{{item.name}} {{item.age}} years old.
</div>
<div collapse="!props.isExpanded">
some content
</div>
</div>
JS:
$scope.collapse = function(){
this.props.isExpanded = !this.props.isExpanded;
};
Update
We keep going through more and more issues with your project. You really just need to experiment/research and understand everything that's going on on a deeper level, or it will just be one question after another. I'll give it one last effort to get you on the right track, but you need to try in the basic concepts and go from there.
You could get past the issue of props reinitializing by putting $scope.expandedStates and then passing the $index of the current ng-repeat to your function (or just using it in the view) and setting a property of expandedStates like $scope.expandedStates[$index] = !$scope.expandedStates[$index]. With the nested ng-repeat as it is, you'll need to do $parent.$index so that you're associating the state with the row rather than the item.
However, you'll then have another problem with the filter: Using my old partition code, the inputs inside the partitions are going to lose focus every time you type a character. Using the new code, the view updates, but the underlying model will not. You could use the partition filter from this answer to solve this, but from my understanding of that code, it could have some unexpected behavior down the road and it also requires passing in this as an argument to the filter. I don't recommend you do this.
Filters are meant to be idempotent, so stabilizing them via some kind of memoization is technically a hack. Some argue you should never do this at all, but I think it's fine. However, you definitely should ONLY do this when it is for display purposes and not for user input! Because you are accepting user input within the partitioned view, I suggest partitioning the data in the controller, then joining it back together either with a watch (continuous) or when you need to submit it.
$scope.partitionedFriends = partitionFilter($scope.friends, 2);
$scope.$watch('partitionedFriends', function(val) {
$scope.friends = [].concat.apply([], val);
}, true); // deep watch

How can I bind ng-repeat to the previously clicked ng-click?

I'm having a bit of trouble trying to use an ng-repeat that only references the previously clicked ng-click function.
What I'm trying to create is a demo app with dynamic dummy content.
The concept is:
Click a link from a content set and display the json data (headlines) just pertaining to that link.
I've noted the problematic repeater in the html
demo: plnkr.co
I'm kinda new to angular so if there is an easier method to doing this, any guidance would be greatly appreciated. Thank you!!
Your second block contains two nested ng-repeats: one that iterates over the contentSets, and one that iterate over the headlines of the current content. You want a single ng-repeat, which iterates over the content that has been clicked in the top block. And this clicked content is stored in the scope variable useContent by your setContent() function (called by the ng-click). So that's what you must iterate over:
<ul class="list-unstyled" >
<li ng-repeat="headline in useContent.headlines">
{{headline.headline}}
</li>
</ul>
You got it right for the <h1>, which displayed the appropriate title, BTW.
Demo: http://plnkr.co/edit/guAapCDRzPI1i51OfRAS?p=preview
Demo Plunker Here
You only need to put ng-show on the ul element, and only show it when content == useContent
<ul class="list-unstyled" ng-repeat="content in contentSets" ng-show="content == useContent">
<li ng-repeat="headline in content.headlines">
{{headline.headline}}</li>
</ul>

Bound Input gets unfocused in angularjs

I am running this simple code with angularjs :
HTML :
<div ng-app ng-controller="AController">
<code>{{ itemsInArray }}</code>
<div ng-repeat="item in itemsInArray">
<input ng-model="itemsInArray[$index]" />
</div>
</div>
JavaScript :
function AController($scope) {
$scope.itemsInArray = ["strA", "strB", "strC"];
}
Binding appears to be working correctly when indexing into the array but after entering one character the input loses focus.
You can find the working code here on this fiddle : http://jsfiddle.net/QygW8/
I think this is happening because you are manipulating the same item which is iterated over ng-repeat. So ng-repeat sees a change in the item and re-runs the `ng-repeat which regenerates the items.
If you look at your fiddle html, you may notice this effect.
To make it work, one way you can do this
http://jsfiddle.net/cmyworld/CvLBS/
where you change your array to object array
$scope.itemsInArray = [{data:"strA"}, {data:"strB"}, {data:"strC"}];
and then bind to item.data
Try to change the model:
<div ng-repeat="item in itemsInArray">
<input ng-model="item" />
</div>
Even am an newbie to the angularjs, up-to my findings ng-repeat updates/repeats and recreates the whole HTML elements when there is an change in the model. Hence when a single character added to model causes ng-repeat to react and creates the all the HTML elements again which results to losing the focus.
This is an fiddle , In which u will be able to observer the changes with the model inside the ng-repeat and outside the ng-repeat.
Sorry i don't have the solution, Hope using ng-change apart of ng-model may help.

Resources