Self contained Angular Template is not setting variables correctly - angularjs

I am trying to build a star ranking purely in an angular template file without using controllers, I have the following code which fails, I can build this using controllers (calling setRanking method ng-click) but I want to understand why the following code is not working. User should be able to click on a star and all the stars up to the selected star should be highlighted.
<div class="item" ng-init="user_rating = 0">
<i
ng-repeat="star in [1,2,3,4,5]"
ng-class="(star>user_rating) && 'ion-ios-star-outline' || 'ion-ios-star'"
ng-click="user_rating = star"></i>
<h3>Starts: {{user_rating}} </h3>
</div>

I think the issue is with the scope of the user_rating(as it is not getting updated).
As each ng-repeat creates the isolated scope, so the way you are updating user_rating is creating a local copy of user_rating associated to the local scope of each ng-repeat.
To fix it you can replace the code
ng-init="user_rating = 0"
with
ng-init="r={}; r.user_rating = 0;"
Also refer user_rating with r.user_rating wherever you are referring user_rating.
Also the correct way of using ng-class is what #Shailendra mentioned in his answer. Thanks!

You need to make changes in this code for 'ng-class' as like below-
<div class="item" ng-init="user_rating = 0">
<i
ng-repeat="star in [1,2,3,4,5]"
ng-class="star>user_rating? 'ion-ios-star-outline': 'ion-ios-star'"
ng-click="$parent.user_rating=star;"></i>
<h3>Starts: {{user_rating}} </h3>
</div>
I Hope this may help you..

Related

Have my code act as a p tag and not as a link if there is no URL

I am relatively new to angularJs so I am trying to learn how to do different things. I have been trying to make solutionName act as a p tag if there is no URL input for solutionUrl1, at the moment solutionName is acting as if it is hyperlinked even when its not. Any help would be appreciated.
<a ng-href="{{::data.solutionUrl1}}" class="card__title" style="text-align: center">
<span>{{::data.solutionName}}</span>
</a>
Use ng-if of angularjs to render either one or the other:
Something like this, you most probably have to change the condition to meet your needs. You can also create a new Variable in the JS files like showLink and set this variable to true/false depending on some conditions. And then just use this boolean variable to show/hide the link with the method outlined below:
<div ng-if="data.solutionUrl1">
<!-- code to render the link-->
</div>
<div ng-if="!data.solutionUrl1">
<!-- code to render just the span without the link -->
</div>

Using same ng-repeat on one page - best practice?

In need the data from a ng-repeat on two places within the view. The code below works, but I am not sure if this is the way to do it. (I am new to Angular).
If a user clicks on a outlet-option the outlet-products get displayed. First I loaded the products below the outlet-option and used jQuery to move to the productsWrapper if a user clicks on an option. But then I needed to compile it again which made it a bit messy in my opinion.
There can be only one "#productsWrapper" and one ".outlet", so I came up with using the ng-repeat twice. But is this the way to go?
<div id="productsWrapper">
<div ng-repeat="element in elements track by $index" class="products" style="display: none;" id="prod-{{element.wonelement_id}}">
<outlet-product ng-repeat = "option in element.options"
class = "product"
option-data = "option"
element-data= "element"
chosen-data = "chosen">
</outlet-product>
</div>
</div>
<div class="outlet">
<div ng-repeat="element in elements track by $index">
<outlet-option
element-data = "element"
option-data = "element.option"
chosen-data = "chosen"
app-settings = "app">
</outlet-option>
</div>
</div>
I think what you proposed looks fine in a typical AngularJS way.
If you were applying filters to the ng-repeat clause, then I would have suggested applying that filter ahead of time in the controller so that it would only be run the one time. But since you are using no filters at all on the data, I think it is fine as you have it.

ngRepeat $scope messing with nested ngClick

So i have four progress bars that on click open and close via the close button in the top right....problem is the ngrepeat is messing with something....i've tried adding $parent to the child ngClick but it doesnt work. I've looked at all the other stack examples of this and just can't seem to figure out how to apply it to this specific situation
http://codepen.io/anon/pen/JorZoE
<div class="progress-bar repeat-animation" ng-click="showClose = false" ng-class="!showClose ? 'grow' : ''" progress-morph style="width: {{item.percent}}%" ng-repeat="item in list">
<div class="close" ng-hide="showClose" ng-click="onClickClose($event)" ><img src="close42.svg" alt=""></div>
</div>
I assumed that you wanted to open/close the bars individually.
If that's the case, your code wasn't working because you were binding all the progress bars state to the same $scope variable.
Having that in mind, I tweaked your code a little bit to make it work, and also used a more readable logic (imho).
Please take a look and let me know:
http://codepen.io/anon/pen/WbZygb?editors=101

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

What magic does ngModel use that modifying the scope directly does not?

Learning angularjs at the moment, and I am confused as to how I can accomplish a task because I don't fully understand what ngModel is doing.
If I have a directive with two scope variables:
// An array of all my objects
$scope.allMyObjects
// The currently selected object from the array
$scope.selectedObject
and in the html
<span>{{ selectedObject.name }}</span>
<select id="select"
ng-model="selectedObject"
ng-options="object in allMyObjects">
</select>
This all works perfectly, when I select an object from the select, it updates the selectedObject on the scope and so the name of the currently selected object is displayed.
However, I don't want a select box, instead I want a list of all my objects with an editable name field, with a select button that I can use to select the specified object, so I came up with the following:
<div ng-repeat="object in allMyObjects">
<input class="object-name"
ng-model="object.name">
<a ng-click="loadObject(object)">Load</a>
</div>
and the loadObject() function on the scope:
function loadObject(object) {
$scope.selectedObject = object;
}
However, this doesn't work. I had assumed this was basically what ngModel was doing behind the scenes but am obviously confused. Is anyone able to shed some light or offer a better solution to what I wish to achieve?
Please see here :http://jsbin.com/jocane/1/edit?html,js,output
use ng-model="object.name" instead "sc.name"
<div ng-repeat="object in allMyObjects">
<input class="object-name"
ng-model="object.name">
<a ng-click="loadObject(object)">Load</a>
</div>
After an hour of debugging it came down to an issue with the scope being isolated by the ng-repeat, the problem didn't show up in any of the simplified jsfiddle examples because they used pure JS and it was the way I was accessing the scope via typescript that caused the issue.
Thanks for the answers that helped me narrow it down to my difficulty understanding typescript and not my difficulty understanding directives.

Resources