I am looking out for logic how to create relationship between child element selected with its parent to differentiate on server to know that option is selected under particular parent.
e.g. In my following question i wanted to know that Math is selected under which parent (Andy, John or Murray) on server side.
I have the following code
<li ng-repeat="student in Students">
<input type="checkbox" class="checkbox" id="{{student[0].Id + $index + $parent.$index}}"/>
<label for="{{student[0].Id+ $index + $parent.$index}}"><span class="Radiobox-txt" id="{{student[0].Name}}">{{student[0].Name}}</span></label>
<ul>
<li ng-repeat="subject in student.Subjects">
<input type="checkbox" id="{{subject.Id+ $index + $parent.$index}}" ng-model="subject.Checked" />
<label for="{{subject.Id+ $index + $parent.$index}}"><span id="{{subject.Name}}">{{subject.Name}}</span></label>
</li>
</ul>
</li>
The above code generates UI like following with checkbox option showing with each:
Andy
Math
English
Computer
John
Math
English
Computer
Murray
Math
English
Computer
Currently, the following is my code where i am pushing selected child's Id into array.
Probably i need to modify this code to also push parent Id along with child Id so on server i can know the relationship but not sure whether to concatenate parentId or some other way around. Idea please?
$scope.studentDetail.PermissionIds = [];
angular.forEach($scope.permissions, function (p) {
var selectedPermissions = $filter('filter')(p.Children, { Checked: true });
for (var i in selectedPermissions) {
$scope.studentDetail.PermissionIds.push(selectedPermissions[i].Id);
Use student.id rather than student [0].id. I'm guessing that is unique?
Related
I'm creating a component for a toggle button. The styling is dependant on the input label, so I need to use id and for.
If I have multiple of these components on the page it will clearly mess up... What's the best way to deal with this? Random generate id through a function?
<div class="toggle-group checkbox-group-inline btn btn-link">
<input class="tgl tgl-light" id="toggle" type="checkbox" [checked]="value" (change)="stateChange($event.target.checked)">
<label class="tgl-btn" for="toggle"></label>
</div>
Use index on for:
<div *ngFor="let item of items; let i = index" class="toggle-group checkbox-group-inline btn btn-link">
<input class="tgl tgl-light" id="toggle_{{i}}" type="checkbox" [checked]="value" (change)="stateChange($event.target.checked)">
<label class="tgl-btn" for="toggle_{{i}}"></label>
</div>
This looks like a valid use case for a dynamic ids, as the correct way to associate labels with inputs is through ids.
To do so, add a randNum property to your class that contains a random number created on instantiation. Then, you can use this random number throughout your template to create unique identifiers like so:
Random Number (class property)
randNum = Math.floor(Math.random() * 1000000000);
Template
<div class="toggle-group checkbox-group-inline btn btn-link">
<input class="tgl tgl-light" [id]="'toggle' + randNum" type="checkbox" [checked]="value" (change)="stateChange($event.target.checked)">
<label class="tgl-btn" [for]="'toggle' + randNum"></label>
</div>
Now, if you have this component 10 times on the same page, those 10 instantiations of your component will have unique values of randNum, producing the desired effect.
Edit:
If you'd rather stay away from random numbers, you can ensure uniqueness by defining a static property as the basis for randNum like so:
static num = CustomComponent.num || 0;
randNum = ++CustomComponent.num;
In the code above, each component is guaranteed to have a unique randNum, as it is incremented for every instantiation.
I have an array of ints in my model which is meant to represent the indices of a list of options the user will pick from using a select list. The idea is there's a list you can expand and you keep picking things from the select list. I have code along these lines:
<ul>
<li ng-repeat="item in model.arrayOfInts">
<select ng-model="item"
ng-options="choice.id as choice.name for choice in common.options">
<option value="">---</option>
</select>
<button ng-click="model.arrayOfInts.splice($index, 1)" type="button">
Delete
</button>
</li>
</ul>
<button ng-click="model.arrayOfInts.push(null)" type="button">
Add
</button>
I have two problems which seem to be related:
the ng-model does not seem to actually bind properly; if I inspect the scope I can see that newly-pushed members of arrayOfInt are still set to null even after I select something from the select menu representing them.
When I attempt to push another item into the array, Angular complains that duplicate options are not allowed.
What is the proper way to do what I'm trying to do?
JSFiddle sample
The first issue ("ng-model does not bind properly") is because you're not targeting the array with ng-model. item in your ng-repeat is a new property on the scope (created by the ng-repeat). It doesn't contain any information on where it came from (arrayOfInts).
The following code tells ng-model to point to the right index of the array, rather than a new scope property.
<select ng-model="model.arrayOfInts[$index]"
ng-options="choice.id as choice.name for choice in common.options">
The second issue is because model.arrayOfInts can have multiple duplicates in it (click "add" twice and you've just added two nulls). Angular needs some way to tell the difference between them, so you need to add a tracking property, in this case $index.
<li ng-repeat="item in model.arrayOfInts track by $index">
I have a products variable, which holds a nested array of hierarchical products, such as:
Product
- cat_id: 1
- name: Some Product
- Children
- Child Product 1
- Child Product 2
- Children
- I can also have more children here
- And another
- Child 3
Product 2
- cat_id: 2
- name: Some other Product
- Children
- A child product
- another
I also have another variable which is an array of products that have been purchased.
What I want to do is to display the full product list as above, but if the user has purchased it, to apply a class.
Here's where I'm at so far:
<ul>
<li ng-repeat="product in all_products track by product.cat_id">
{{ product.cat_name }}
<ul ng-if="product.children.length>0">
<li ng-repeat="l1_child_product in product.children track by l1_child_product.cat_id">
{{ l1_child_product.cat_name }}
<ul ng-if="l1_child_product.children.length>0">
<li ng-repeat="l2_child_product in l1_child_product.children track by l2_child_product.cat_id">
{{ l2_child_product.cat_name }}
</li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
What I want to do is for each is to apply the class, if the contents of the second array, contains the current product's cat_id, for instance:
<li ng-repeat="product in all_products track by product.cat_id" ng-class="foreach(otherarray as owned){ if(owned.cat_id==product.cat_id){ 'some_class' } }">
I'm still very new to Angular so i'd like to know the proper way of achieving this.
I'm currently porting my application from being purely server side with a static front end, to Angular. I was able to perform this sort of logic extremely quickly using a few nested for-loops with conditional statements in my old app.
However, with Angular, this seems to cause the application to grind down to a very slow pace.
I've heard of dirty-checking, which Angular uses and I think I'm hitting the bottlenecks that occur as a result as my datasets are generally fairly large (around 250 products, with around 40 purchases), but with up to 20 users being shown on a page. What's a good way of avoiding these performance issues when using Angular?
Updated
Here's the code I'm using at the moment:
<div class="row" ng-repeat="user in ::users">
<script type="text/ng-template" id="product_template.html">
{{ ::product.cat_name }}
<ul ng-if="product.children">
<li ng-repeat="product in ::product.children track by product.cat_id"
ng-include="'product_template.html'"></li>
</ul>
</script>
<ul>
<li ng-repeat="product in ::all_products track by product.cat_id"
ng-include="'product_template.html'"></li>
</ul>
<table>
<tr ng-repeat="licence in ::user.licences">
<td>{{::licence.product.cat_name}}</p></td>
<td>{{::licence.description}}</td>
<td>{{::licence.start_date}}</td>
<td>{{::licence.end_date}}</td>
<td>{{::licence.active}}</td>
</tr>
</table>
</div>
This gives the desired output of:
Iterate over the users
Iterate over ALL of the products available
Display a list of all of their purchases (licences)
However, it's incredibly slow. I just timed the page load, and it took 32 seconds to parse all of the data and display it (the data was available after around 100ms).
I'm using the new :: syntax to prevent lots of two-way bindings but this doesn't seem to improve the speed at all.
Any ideas?
Your question is 2 parts:
How do I display products and their children recursively?
In an efficient way, how do I add a class if a product has been purchased?
Displaying Products and their Children
This has already answered well by a previous question on Rending a Tree View with Angular.
Efficiently adding a Purchased class
The inefficiency you currently have is caused from looking through otherarray for every single product.
There are various solutions on how to improve upon this but I think the easiest change for you would to make would be to use an {} instead of an array to track purchased products.
{ cat_id: true }
For more information on why using an Object or Hash is faster looking at this question on Finding Matches between Arrays.
Combined Solution
Displaying Products and their Children
<script type="text/ng-template" id="product_template.html">
{{ product.cat_name }}
<ul ng-if="product.children">
<li ng-repeat="product in product.children"
ng-include="'product_template.html'"
ng-class="{ purchased : product.purchased }"></li>
</ul>
</script>
<ul ng-app="app" ng-controller="ProductCtrl">
<li ng-repeat="product in all_products"
ng-include="'product_template.html'"
ng-class="{ purchased : purchasedProducts[product.cat_id] }"></li>
</ul>
Effiecntly adding a Purchased class aka. otherarray -> purchasedProducts object
I don't know exactly where otherarray is being constructed but a simple conversion would go as follows:
var purchasedProducts = {};
for (var i = 0; i < otherarray.length; i++) {
var cat_id = otherarray[i];
purchasedProducts[cat_id] = true;
}
Remember that ng-class can be a function call.
Starting with Angular 1.3, there is native Bind Once support. when iterating over a large number of items, you can eliminate the watchers on static elements which will not change.
For Example:
<li ng-repeat="product in ::all_products track by product.cat_id">
Will iterate through the all_products array once to populate the ngRepeat, but will not continue to be bound to $watch for change tracking. Smart use of :: can drastically improve performance.
Also, converting ng-class= to a function instead of an inline expression evaluation can improve performance and give you greater control over your output.
I have a triple nested ng-repeat which I am tyring to allow the user to delete items inside when they are displayed using a simple .splice. I can send the index of the highest level item, the $parent.$index for the next level down, But on the third level down I need somehting like $parent.$parent.$index to pass the index of the correct item int he json object to delete, can I do something like this?
Here's what I tried
ng-click="deleteMe($parent.$parent.$index, $parent.$index, $index)
How could I properly send the index of the highest parent? Thanks!
Use ng-init please see more here https://docs.angularjs.org/api/ng/directive/ngInit
or sample demo here http://plnkr.co/edit/qRJlAzPfwaZr8NENs7K0?p=preview
<body ng-controller="MainCtrl">
<div ng-repeat="father in data" ng-init="indxFirst = $index">
<h3>{{father.person}}</h3>
<ul>
<li ng-repeat="kids in father.kids" ng-init="indxSecond = $index">{{kids.name}}
<p ng-repeat="color in kids.colors" ng-init="indxThird=$index">Colour: {{color.color}}
<button ng-click="delete(indxFirst, indxSecond, indxThird)">delete</button>
<p>
</li>
</ul>
</div>
</body>
In my application i have array of colors and i want to create a list of colors with checkbox.
var app = angular.module('app',[]);
app.controller('mainCtrl',function($scope){
$scope.colors = ['red','blue','green','yellow'];
});
so i create an ng-repeat to create a list :
<body ng-controller="mainCtrl">
<ul>
<li ng-repeat="c in colors">
<input type="checkbox" ng ng-true-value="{{c}}" ng-false-value=""/> {{c}}
</li>
</ul>
</body>
now i need to bind ng-model of each checkbox to something like f.tags.red or f.tags.blue so i change the code to something like this :
<li ng-repeat="c in colors">
<input type="checkbox" ng-model="f.col.{{c}}" ng-true-value="{{c}}" ng-false-value=""/> {{c}}
</li>
but this make my app broken.so ho to fix this for ng-model and ng-true-value also i create this jsbin .
thanks
There were a few things going wrong here. In general, inside properties of Angular that takes expressions (check the docs), you should not use {{x}}, but rather just x itself. So, you'r ng-model should not be f.col.{{c}} but rather f.col.blue and f.col.red etc. Now, in javascript, doing a.b and a['b'] is identical, so in this case, since c is a string, the correct model is f.col[c]. The same goes for the true-value, it should also simply be c.
Lastly, to get the example working, you need to actually create the objects maintaining your model (in this case $scope.f.col. Working example can be seen here: http://jsbin.com/citupepa/1/edit
Here is working demo for selection of colors:
jsbin
<ul>
<li ng-repeat="c in colors">
<input type="checkbox" ng-model="f.col[c]" ng-true-value="true" ng-false-value="false"/> {{c}}
</li>
Selected Colors: {{f.col}}