I am stuck with a very weird issue with angular. I have created one simple array in my ts file and I am displaying a table with input by iterating the same array. Now when I change the array (reverse it) it's working fine but if I have entered something in input before changing it, text will stay in input.
here is the code
component.ts
import { Component } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'practice';
myArray = [1,2,3,4,5,6,7];
change() {
// this.myArray = [];
this.myArray.reverse();
}
}
my HTML:
<html>
<table>
<tr *ngFor="let item of myArray; let i = index">
<td>
<input type="text" [value]='item'>
</td>
</tr>
</table>
<button (click)="change()">Change</button>
</html>
Sample video for issue explanation:
https://www.loom.com/share/6f4887183bb94150ad7390f25e5b466a
So as you can see, when I enter something in the input and change the array the value stays with array. I have checked the original array is not changing.
I mean it just got reversed, but nothing else.
What is the issue?
You should use trackby:
<table>
<tr *ngFor="let item of myArray; let i = index ;trackBy: trackItem" >
<td>
<input type="text" [value]='item' >
</td>
</tr>
</table>
<button (click)="change()">Change</button>
in ts:
trackItem (index, item) {
return this.myArray ? this.myArray : undefined;
}
and if you want to keep that value you should bind it using ngModel:
<table>
<tr *ngFor="let item of myArray; let i = index ;trackBy: trackItem" >
<td>
<input type="text" [value]='item' [(ngModel)]="myArray[i]" >
</td>
</tr>
</table>
<button (click)="change()">Change</button>
check this Demo
Why using trackby:
By default, when you use *ngFor without trackBy, *ngFor tracks array of objects changing through object identity. So, if new reference of array of objects is passed to the directive, even if the array is with the same values, Angular will not be able to detect that they are already drawn and presented in the current DOM. Instead, old elements will be removed and a new collection with the same values will be redrawn.
We can help Angular to track which items added or removed by providing a trackBy function. The trackBy function takes the index and the current item as arguments and needs to return the unique identifier for this item. Now when you change the collection, Angular can track which items have been added or removed according to the unique identifier and create or destroy only the items that changed.
Use trackBy when:
1 - Iterating over large array of objects collection
2 - Your business logic might need to modify any of these elements through reordering, modifying specific item, deleting item, or adding a new one
Related
enter image description hereFollowing is just the overview of the code,as given in html code i just want to show the options from options array from set object and have to set checkbox checked to option which is an answer from answer array and have to add new answer to answer if i check more options with checkbox clicked, and have to remove answer if checkbox is unchecked.
<script>
var adminApp = angular.module('app',[]);
adminApp.controller('EditController', function ($scope) {
$scope.viewQuestions=function(){
set={}; //object in which answer array and option array is present //assume;
var answer= ["answer1", "answer2"]; //answer array assume
var options=["option1,option2,option3,option4"]; //option array assume
var answerType="Multiple";
}
$scope.updateAnswer =function(isSet,index,answer,set)
{
for(var i=0;i<set.answer.length;i++)
{
if(isSet===set.answer[i])
{
set.answer[index]=isSet;
}
else
{
set.answer.splice(index, 1);
}
}
}
}
</script>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.js"></script>
</head>
<body ng-app="app" ng-controller="EditController" ng-init="viewQuestions()">
<table>
<tr>
<td ng-show="s.answerType === 'Multiple'">
<p ng-repeat="o in s.options">{{$index + 1}}. <input type="checkbox"
name="answer-{{index}}"
ng-model="isSet" value="{{o}}"
ng-change="updateAnswer(isSet,$index,answer,s)">{{o}}</p>
</td>
</tr>
</table>
</body>
</html>
This is not exactly what you want but it's something. I change the concept to do the same in a cleaner way and more angular style. (in my opinion)
I have an array of objects (name: The option title & active: Checked or not) And after each change I update the set. With filter & map; So, the set is always up to date
(If you receive a array of string as options, you can assume that all of them are Active: false)
Maybe it can works for you in general, or you can get an idea from the code.
http://plnkr.co/edit/GucWDwF66A56IkXHHpwG?p=preview
In angular, you can bind checkbox's checked property to a method that returns ids of your mini_array that is in main_array;
---your.html---
enter code here
enter code h
<div class="mb-2"><b>Permissions list</b></div>
<div formArrayName="permissions" *ngFor="let permission of main_array; let i=index">
<input class="mr-2" [checked]="isChecked(permission.id)" type="checkbox">
{{permission.name}}
</div>
---your.ts---
isChecked(id) {
return this.mini_array.includes(id);
}
How can we initialize and manipulate a check box value? I've looked at quite a number of examples, but haven't been able to get any to work.
I'm trying to present a N x M table where the rows represent tasks, and the columns students. The idea is that checking one of the check-boxes in the table assigns a task to a student.
There is a typescript hash map which contains the value of all the checkboxes;
assigned : { [key:string]:boolean; } = {};
the hash key is:
var key = a.TaskId + '_' + a.StudentId;
The table is generated with a nested ngFor:
<tr *ngFor="let t of tasks">
<td>Task Name for task... {{t.taskId}} </td>
<td *ngFor="let s of students">
<input type="checkbox" name=#{{t.TaskId}}_{{s.StudentId}} change="onAssignmentChange(t,s)" [checked]="cbValue(t, s)">
</td>
</tr>
the cbValue(t, s) is below:
cbValue(taskItem, studentItem) {
var key = taskItem.TaskId + '_' +studentItem.StudentId;
return this.assigned[key];
}
This doesn't work, all the checkboxes in the table come up unchecked, no matter what the values in the hash.
I've also tried:
<input type="checkbox" change="onAssignmentChange(t,s)" [checked]="cbValue(t, s)">
<input type="checkbox" change="onAssignmentChange(t,s)" [(ngModel)]={{t.TaskId}}+'_'+{{s.StudentId}} >
<input type="checkbox" change="onAssignmentChange(t,s)" [(ngModel)]="assigned[t.TaskId"+'_'+"s.StudentId"]>
none of which works.
I seem to be quite in the dark here; onAssignmentChange doesn't get triggered either, there are no Errors in console.
Also,
... name=#{{t.TaskId}}_{{s.StudentId}} ...
is this supposed to be a local target or something?
thanks in advance
This is fairly trivial as we're just going to bind straight to the assigned object, since we're using JavaScript's bracket property accessor we also get a free dynamic instantiation of the property through the template (dirty, maybe, but powerful). Additionally, wherever you're processing this later assume that a missing value is false:
template
<tr *ngFor="let t of tasks">
<td>Task Name for task... {{t.taskName}} </td>
<td *ngFor="let s of students">
<input type="checkbox" name="{{t.taskId}}_{{s.studentId}}" [(ngModel)]="assigned[t.taskId + '_' + s.studentId]">
</td>
</tr>
Here's a plunker to demonstrate: http://plnkr.co/edit/9RorhJnv42cCJanWb80L?p=preview
I am creating a table of users where I want to add a checkbox on each row and a delete button. When I click the delete button, I want to delete all users who were selected.
Now I am creating these user entries from an API response which gives me say id, name and email.
So my view looks something like this:
<tr ng-repeat="user in MyCntrl.data.users track by $index">
<td><input type="checkbox"></td>
<td>{{user.name}}</td>
<td>{{user.email}}</td>
</tr>
What I want in my controller is to have an object with id of all the users for whom the checkbox was clicked.
Even if I create an object and assign it as model for checkbox, how do I add a key as id in that object?
You could simply do <input type="checkbox" ng-model="user.isSelected">
And then just filter MyCntrl.data.users for those that have isSelected === true
Because of JavaScript dynamic typing nature, nothing stops you from adding a field named 'isSelected' (or alike) to your models. Then, you can add ng-model="user.isSelected" to your checkbox tag.
Then, on deletion, check which entries have isSelected set to true and delete them.
Here's an example for how you can track all the selected users in an another array:
Example: Plunker
<tr ng-repeat="user in MyCntrl.data.users track by $index">
<td><input type="checkbox" ng-model="tempVar" ng-click="toggleSelection($index)"></td>
<td>{{user.name}}</td>
<td>{{user.email}}</td>
</tr>
<!-- AngularJS Code -->
<script type="text/javascript">
var app = angular.module('myApp', []);
app.controller('MyCtrl', function($scope) {
$scope.selectedUsers = []; // initially the selected users array is empty
$scope.toggleSelection = function(index){
var positionInSelectedArray;
var arr = $scope.MyCntrl.data.users;
var tempUser = arr[index]; // refers to the selected user object in $scope.MyCntrl.data.users array (further, we'll call it "arr")
var userAlreadySelected = $scope.selectedUsers.filter(function( obj ) {
return obj.userId == tempUser.userId;
})[0]; //checks whether the user is already selected or not (if selected, then returns the user object)
if (angular.isUndefined(userAlreadySelected)) {
$scope.selectedUsers.push(tempUser); //insert the object in array containing selected users
}else{
positionInSelectedArray = $scope.selectedUsers.indexOf(userAlreadySelected);
$scope.selectedUsers.splice(positionInSelectedArray, 1); //removes the user object from array containing selected users
}
};
});
</script>
Consider this example:
const items = data.get('$$items');
const row = items.map((item) =>
<tr key={item.get('id')} onClick={this.handleClick}>
<td>{item.get('id')}</td>
<td>{item.get('value1')}</td>
<td>
{
this.props.editing ?
<input value={item.get('value2')}> :
<span>item.get('value2')</span>
}
</td>
<td>{item.get('value3')}</td>
<td>{item.get('value5')}</td>
<td>{item.get('value5')}</td>
</tr>
);
return (
<section>
<table>
<thead>
...
</thead>
<tbody>
{ row }
</tbody>
</table>
</section>
Does React provide a way to conveniently (without interacting with the DOM) gather values of each td in a single object? Or even to iterate through each tr, processing data into objects. What I mean is like having a Backbone model, without actually having one.
Thank you for your attention.
There are two ways you could handle this.
onChange event handlers.
On your input field, include;
<input value={item.get('value2')} onChange={this.handleChange}>
And then elsewhere in your component, add a handleChange function:
handleChange: function(event) {
this.setState({value2: event.target.value});
}
Using your component's state to maintaint the current values of "item".
use Refs, and pull that data dynamically when requested.
If you don't want to store the information in your state, you can instead expand your input as such:
<input value={item.get('value2')} ref="value2_input">
After doing so, you'll be able to access this input field from any function on the component through;
this.refs.value2_input.value
This is how I populate the Table and attach checkbox to controller
<tr ng-repeat="key in queryResults.userPropNames">
<td><input type="checkbox"
data-ng-checked="selectedKeys.indexOf(key) != -1"
data-ng-click="toggleSelect(key)">
</td>
<td>{{key}}</td>
<td ng-repeat="user in queryResults.users">
{{user.properties[key]}}
</td>
</tr>
This is how my HTML for button looks
<div>
<span ng-if="!validKeys" class="button-terminal primary save-user-keys"
data-disabled="false">Save Keys</span>
<span ng-if="validKeys" class="button-terminal primary save-user-keys"
data-ng-click="saveUserKeys()">Save Keys</span>
</div>
and my Controller looks like
$scope.toggleSelect = function (attribute) {
if ($scope.selectedKeys.indexOf(attribute) === -1) {
$scope.selectedKeys.push(attribute);
} else {
$scope.selectedKeys.splice($scope.selectedKeys.indexOf(attribute), 1);
}
};
$scope.saveUserKeys = function() {
$scope.customAttributes.mappingForUser = $scope.selectedKeys;
$scope.saveMappings();
};
$scope.validKeys = !!$scope.selectedKeys;
But my button is always enabled, even if I de-select all the checkboxes
What is wrong with this code?
Thank you
$scope.selectedKeys is an Array, even when no keys are selected. However empty Arrays are truthy (!![] // => true).
One fix would be to check the length of selectedKeys instead:
$scope.validKeys = $scope.selectedKeys && $scope.selectedKeys.length;
Alternatively, if assigning validKeys was just an attempt to get the view to render correctly, on the view you could just update the ngIf to ng-if="selectedKeys.lengh"
If you print validKeys (i.e. {{validKeys}}, do you see it changing between true/false? Also, if I understand this correctly, you should be testing for the length of validKeys - if higher than 0, enable the button, otherwise disable it.