ng-repeat --> ng-switch --> ng-bind: how to make it work? - angularjs

I have a ng-repeat loop, used in combination with ng-switch and ng-bind (I assume ng-switch is not the problem here).
<div ng-repeat="message in messages track by message.id">
<div ng-switch="message.yesno">
<div ng-switch-when="true">
text 1
</div>
<div class="click-me" element_id = "{{message.id}}" ng-switch-when="false" ng-bind="message.texts[message.active_text]">
</div>
</div>
</div>
messages is defined as an array of objects with the following structure:
{
id: int,
yesno: boolean,
texts: array, // containing N strings
active_text: int // index of the current displayed string
}
in addition to a setter method (which is within the same function which returns the object, let's say this function is the object constructor)
this.setNextText = function(){
this.active_text= (++this.active_text)%(this.texts.length);
}
When yesno property of message object is false, the user can click on the text and change it to the next available text (and then back to the original one when the array bonduary is reached).
so, when the user clicks on the text, the following event is triggered:
$(document).on("click",".click-me",function(){
var message_id = $(this).getAttribute("element_id");
$.grep($scope.messages, function(e){ return e.id == message_id; })[0].setNextText(); // obj containing the message
});
which basically:
takes the index on the clicked messages (corresponding to the index of the current ng-repeat loop)
increases it
This way, nd-bind should bind the next text available in message.texts.
However, it doesn't.
I'm pretty sure that:
ng-bind: is set up correctly (the first element of message.texts is printed correctly)
message.active_text is updated correctly
Also, I tried to use:
$scope.apply();
but I even get an error (usually it works):
Uncaught TypeError: $scope.apply is not a function
Any clue?

Related

Angular 2: Array with checkbox not working properly

I'm binding one array with Checkbox. However, When I try to change bool attribute for single element of array but it changes for all elements.
My HTML Component is as below.
<div class="col-sm-4" *ngFor="let karyalay of karyalayListFinal">
<div class="checkbox-fade fade-in-primary">
<label>
<input formControlName="karyalay_group" type="checkbox" name="karyalaysCheckbox" value="{{karyalay.karyalayId}}" [(ngModel)]="karyalay.isChecked"
(click)="callEvents(karyalay.karyalayId)">
<span>{{karyalay.karyalayName}}</span>
</label>
</div>
</div>
Now I'm trying to change value of single or selected element as below.
for (let karyalay of this.karyalayListFinal) {
let tempInd = _.findIndex(this.roleMasterEventList, {'KARYALAY_ID': karyalay.karyalayId});
if (tempInd > -1) {
this.karyalayListFinal[tempInd].isChecked = true;
}
}
Actually, if tempInd > -1 then and then only that element's value should be changed. But it changes for all.
Don't know whether this is ngModel issue or what?
Thanks
Putting input tag inside *ngFor will create multiple input tags. And name property for input must be different for all. As HTML configure changes for input property using the name property. So, make this name property unique for every input by keeping a name field in the model passed in *ngFor.
And target the checked property instead of ngModel. And do something like
[checked]="yourmodel.isChecked", keep default value of isChecked property in your model as false.
Hope, it helps.
Sorry, but it seems that your approach is too complicated.
I don't know when you call this for each-loop, but I suppose that you already know or have the id you are searching for at this point. Let's call it searchID.
If you want to find the object with the matching id simply use the for each loop as follows:
this.karyalayListFinal.forEach( el => {
if(el.karyalayId === searchID) {
el.isChecked = true;
}
});
That should solve your problem.
you are running this loop for all the elements in "this.karyalayListFinal"
for (let karyalay of this.karyalayListFinal) {
let tempInd = _.findIndex(this.roleMasterEventList, {'KARYALAY_ID': karyalay.karyalayId});
if (tempInd > -1) {
this.karyalayListFinal[tempInd].isChecked = true;
}
}
might be possible that the condition "tempInd > -1" is satisfied for all the elements in "this.karyalayListFinal" and hence all are changing.
I used [checked]="karyalay.isChecked" instead of ngModel in the HTML Code and it started working.
"name" attribute written in input field must be unique for every element created by the loop
you must bind the id of every object coming in an array "karyalayListFinal" to make them unique.

AngularJS How to set value of variable

I need to show/hide a tab based on the value of JSON, i.e. If the value of Region (In JSON, 'Region':'US') is equal to US in at least one of the items within JSON response I should show the tab, if not hide...
I'm using the following code to check if at least the item.region has US value, I'm able to read the values from the JSON but when I try to set the value to true to a variable inside the tag I don't get the value in the variable
<span ng-repeat="item in res.objects" ng-if="item.region === 'US'">
// Set myVariable = true
</span>
And after the variable has been set, display the div with ng-show and the value of the bool variable.
<div class="action-tab" ng-click="res.viewUS()" ng-show="<valueOfBoolVariable">
View US
</div>
Is this good approach to accomplish this? Any idea would be appreciated.
try using ng-init it allows you to evaluate an expression of your current scope
<span ng-repeat="item in res.objects" ng-if="item.region === 'US'">
<!-- Evaluate your variable -->
<div ng-init="evaluate(item)"> </div>
</span>
In your controller
$scope.evaluate = function(item){
//you do the job
}

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

Angularjs function call issue with nested elements

I have an angulars setup as follows, trying to mimic some excel functionality where I have a controller nested inside an ng-repeat.
<tr ng-repeat="lw in lw_list" my-lw ng-model="lw"
<td>
<!-- next two elements act as an excel cell, one for inputing data, they other for displaying calcualtion result -->
<div ng-controller="MyCellCtrl">
<input type="text" class="inputdiv" ng-model="lw.library.name" >in</input>
<div class="output" ng-bind="getCellValue(lw.library.name)" syle="postion:absolute" contenteditable="True" >out</div>
</div>
<div ng-controller="MyCellCtrl">
more input / div pairs to act as a new cell
.....
</div>
</td>
I have the stylesheets set up so that input and output are in the same position, and get hidden / unhidden, so that they act like an excel cell (you type a formula, then when you leave focus, it updates the content).
Anyway, when I put a console.log() inside the getCellValue() function, to show what instance of the controller is being called, then typing in one particular cell, I can see that getCellValue() is being called on every cell.
Is there some way to call getCellValue() when the input is updated without calling the method on every instance?
(I based this code on the code from this tutorial:
https://github.com/graunked/spreadsheet
you can see the same behaviour by putting a console.log in the compute function. If you increase the arrays to 20 x 20 elements, it starts to get slow when you type anything.)
Is there some way to call getCellValue() when the input is updated without calling the method on every instance?
<div class="output" ng-bind="foo">
then use $watch:
function MyCellCtrl($scope)
{
$scope.foo = $scope.lw.library.name;
$scope.$watch('foo', function(newValue) {
$scope.foo = getCellValue($scope.foo);
});
}
or use viewChangeListeners as an alternative:
function MyCellCtrl($scope)
{
$scope.foo = $scope.lw.library.name;
this.$viewChangeListeners.push(function(newValue) {
$scope.foo = getCellValue($scope.foo);
});
}
References
Effective Strategies for avoiding watches in AngularJS
Compile, Pre, and Post Linking in AngularJS

AngularJS equivalent of a next element toggle

I have a list of "cards" that have two parts. By default I only show the first part.
<div ng-repeat="card in cards">
<div ng-click="displayEditView(card)">{{card.title}}</div>
<div ng-show="card.displayEditView">
<input type="text" ng-model="card.title">
</div>
</div>
displayEditView sets card.displayEditView to true (or false if already true).
But I implemented a setInterval that reloads $scope.cards. Of course when that happens the card.displayEditView doesn't exist anymore so the view gets hidden again.
Any idea how I could keep the state of the ng-show the same even when the $scope.cards reloads ?
You are overwriting the value of the cards list everytime you fetch new data, so the card you are saving a view for doesn't exist anymore.
You need to maintain your current list and append or remove cards if you want it to update live.
Pseudo-code example:
getCards().then(function(cards) {
forEach(card in cards) {
if ($scope.cards.indexOf(card) < 0) {
// If something in the new list doesn't exist in the existing list, append it.
// This way you don't overwrite the existing array.
$scope.cards.push(card);
}
}
});

Resources