I have following angular 6 component to add new users with a html table:
user-add.component.html
[...]
<tbody>
<tr *ngFor="let value of data.pos; let i = index">
<td><input matInput [(ngModel)]="value.username" name="pos-{{i}}-username"></td>
<td><input matInput [(ngModel)]="value.firstname" name="pos-{{i}}-firstname"></td>
<td><input matInput [(ngModel)]="value.lastname" name="pos-{{i}}-lastname"></td>
</tr>
</tbody>
[...]
user-add.component.ts
export class UserAddComponent {
data: any = {
pos: [{modell: '', user: '', asset: '', serial: ''}]
};
addPosition(){
this.data.pos.push({modell: 'c', user: '', asset: '', serial: ''});
console.log(this.data);
}
removePosition(pos){
this.data.pos.splice([pos],1);
console.log(this.data);
}
When I click on a button calling the addPosition() button and filling all the input fields it is working without a problem.
The problem: When I click the remove button calling the function removePosition(1) to remove the second row it disappears as it should. But when I click on addPosition again to add a new row the data of the forst row 0 disappears from the html table. The console.log() in addPosition still outputs the correct data for pos 0 but it is not visible in the input fields for row 0.
This is a tracking issue. By looping over objects and using two-way binding on their properties, you make Angular lose its mind.
Provide it with a custom track by function, either like this
<tr *ngFor="let value of data.pos; let i = index; trackBy: value.id">
(ID = a unique identifier for each object)
Or like this
<tr *ngFor="let value of data.pos; let i = index; trackBy: customTB">
customTB(index, item) {
return `${index}-${item.id}`;
}
(You can return what you want as long as its unique)
Are you testing on which browser? Because I tested your code and did not realize the symptoms that you specified.
I have a table of read-only phone numbers. A phone number has a number and a type (e.g. Mobile or Home). Each phone number has an "Edit" button that, when clicked, allows you to edit the phone number and type in a modal dialog. I'm using Knockoutjs for the read-only table and the editor. The table binds to an observableArray of PhoneVMs and the editor works on a single PhoneVM. Because I want the user to have to click OK on the modal before any of their changes are applied, the modal works on a copy of the selected PhoneVM and when they click OK, it replaces the originally clicked PhoneVM in the observableArray that the table is bound to. That's all working great.
Now I have a need to allow the first phone in the list to be edited on the same page as the read only table (without a modal). The idea is for it to be easier to enter the first phone earlier in the workflow. So you would enter your phone on the page and it would automatically appear in the read only list below where you could also edit it in the modal as normal. I thought Knockout would make this easy but I hit a snag. From this point it will be easier to just show an example of what is going wrong. Do the following in this fiddle: https://jsfiddle.net/ph4mhsof/
Edit the phone number and tab out of the textbox. Notice the first phone in the All Phones list updates too.
Change the Phone Type in the dropdown. Notice both the Type ID and the Type Name change appropriately in the All Phones table.
Click Remove First Phone. The 2nd phone becomes the new first phone.
Edit the phone number and tab out of the textbox. Notice the first phone in the All Phones list updates as expected.
Change the Phone Type in the dropdown. Notice only the Type ID updates in the All Phones list. The Type Name does not update.
I am using a custom binding to bind the type name to the select's text. It seems the valueAccessor in that binding's init function must be pointing specifically to the original first PhoneVM's PhoneTypeName property but what I need it to do is point to the firstPhone computed property's PhoneTypeName. Is there any way to fix this?
Copy of original jsfiddle:
function phoneListVM() {
var _self = this;
this.phones = ko.observableArray([
new phoneVM(1, "Mobile", "123-234-3456"),
new phoneVM(2, "Home", "654-343-3211")
]);
this.firstPhone = ko.computed(function() {
return _self.phones()[0];
});
}
function phoneVM(typeID, typeName, Number) {
this.PhoneTypeID = ko.observable(typeID);
this.PhoneTypeName = ko.observable(typeName);
this.PhoneNumber1 = ko.observable(Number);
}
ko.bindingHandlers.selectedTextValue = {
init: function(element, valueAccessor) {
var value = valueAccessor();
$(element).change(function() {
value($("option:selected", this).text());
});
},
update: function(element, valueAccessor) {}
};
$(document).ready(function() {
var phoneList = new phoneListVM()
ko.applyBindings(phoneList);
$("button").click(function() {
phoneList.phones.shift();
});
});
.editor{
background-color:rgba(200,200,250, 0.2);
border: 1px solid rgba(0,0,0, 0.2);
padding: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<h4>
Edit First Phone
</h4>
<div class="editor">
<p>Phone Number:
<input data-bind="value: firstPhone().PhoneNumber1" />
</p>
<p>Phone Type:
<select data-bind="value:firstPhone().PhoneTypeID, selectedTextValue:firstPhone().PhoneTypeName">
<option value="0"></option>
<option value="1">Mobile</option>
<option value="2">Home</option>
</select>
</p>
</div>
<h4>
All Phones
</h4>
<table>
<thead>
<tr>
<th>Type ID</th>
<th>Type Name</th>
<th>Number</th>
</tr>
</thead>
<tbody data-bind="foreach:phones">
<tr>
<td><span data-bind="text:PhoneTypeID"></span></td>
<td><span data-bind="text:PhoneTypeName"></span></td>
<td><span data-bind="text:PhoneNumber1"></span></td>
</tr>
</tbody>
</table>
<button type="button">
Remove First Phone
</button>
In my opinion the custom binding is overkill. I've updated your code to a little with some methods and a dedicated selected observable so you always know which phone is selected.
Let me know if that's what you were looking for.
function phoneListVM() {
var _self = this;
this.phones = ko.observableArray([
// Removed typeName
new phoneVM(1, "123-234-3456"),
new phoneVM(2, "654-343-3211")
]);
// Observable to see which phone is currently selected
this.SelectedPhone = ko.observable(_self.phones().length > 0 ? _self.phones()[0] : '');
// Allow editing whichever phone they want
this.EditPhone = function(obj) {
_self.SelectedPhone(obj);
};
// Remove first phone and check if there are any more phones, if so add it to the selected phone
this.RemoveFirstPhone = function() {
var firstPhone = _self.phones()[0];
if(firstPhone) {
_self.phones.remove(firstPhone);
_self.SelectedPhone(_self.phones().length > 0 ? _self.phones()[0] : '');
}
}
}
// Removed typeName and made it computed. Could be replaced with some lodash _.find if you are storing an array of types in the global space
function phoneVM(typeID, Number) {
var self = this;
this.PhoneTypeID = ko.observable(typeID);
this.PhoneNumber1 = ko.observable(Number);
this.PhoneTypeName = ko.computed(function() {
switch (self.PhoneTypeID().toString()) {
case '1':
return 'Mobile';
break;
case '2':
return 'Home';
break;
}
});
}
$(document).ready(function() {
var phoneList = new phoneListVM()
ko.applyBindings(phoneList);
});
.editor {
background-color: rgba(255, 255, 255, 0.7);
border: 1px solid rgba(0, 0, 0, 0.2);
padding: 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<h4>
Edit First Phone
</h4>
<div class="editor" data-bind="with: SelectedPhone, visible: SelectedPhone()">
<p>Phone Number:
<input data-bind="value: PhoneNumber1" />
</p>
<p>Phone Type:
<select data-bind="value:PhoneTypeID">
<option value="0"></option>
<option value="1">Mobile</option>
<option value="2">Home</option>
</select>
</p>
</div>
<h4>
All Phones
</h4>
<table>
<thead>
<tr>
<th>Type ID</th>
<th>Type Name</th>
<th>Number</th>
</tr>
</thead>
<tbody data-bind="foreach:phones">
<tr>
<td><span data-bind="text:PhoneTypeID"></span></td>
<td><span data-bind="text:PhoneTypeName()"></span></td>
<td><span data-bind="text:PhoneNumber1"></span></td>
<td>
<button data-bind="click: $root.EditPhone">
Edit
</button>
</td>
</tr>
</tbody>
</table>
<button type="button" data-bind="click: RemoveFirstPhone, visible: phones().length > 0">
Remove First Phone
</button>
I agree with the other two answers that customBinding is overwork.
But as you said that you can't easily change the code, I will show you your problem.
In your custom binding declaration, you've just defined the init function but left update function blank. That's the problem. When you make change on the select box, the change event is fired but there is no event handler, so there's nothing happened.
Infact, your custom binding has added change event handler to the select box successfully. That's why before removing the first phone number, everything's ok. But the event handler is removed when you remove the first phone number, because you are able to add only one event handler to select box for change event.
The solution is: Leave your init function blank and move all the current init function contents to the update function as below.
ko.bindingHandlers.selectedTextValue = {
init: function(element, valueAccessor) {
},
update: function(element, valueAccessor) {
var value = valueAccessor();
$(element).change(function() {
value($("option:selected", this).text());
});
}
};
You're probably over thinking this. There's no need to create a custom binder. You can use knockout's options binding.
// create an array
var phoneTypes = [{
text: "Mobile",
value: 1
}, {
text: "Home",
value: 2
}];
function phoneListVM() {
var _self = this;
// this will be bound to the dropdown
_self.phoneTypes = ko.observableArray(phoneTypes)
this.phones = ko.observableArray([
new phoneVM(1, "Mobile", "123-234-3456"),
new phoneVM(2, "Home", "654-343-3211")
]);
this.firstPhone = ko.computed(function() {
return _self.phones()[0];
});
}
function phoneVM(typeID, typeName, Number) {
var self = this;
this.PhoneTypeID = ko.observable(typeID);
this.PhoneNumber1 = ko.observable(Number);
// get the value from the phonetypes array using the PhoneTypeID
self.PhoneTypeName = ko.computed(function() {
var type = phoneTypes.filter(function(a) {
return a.value === self.PhoneTypeID()
});
return type.length > 0 ? type[0].text : undefined;
})
}
And change the HTML to:
<select data-bind="options: phoneTypes,
optionsText: 'text',
optionsValue: 'value',
optionsCaption: 'Choose',
value: firstPhone().PhoneTypeID">
</select>
You can have a complex object as selected value in knockout. So you could have a PhoneType property in phoneVM and bind the 2 properties of PhoneType to the text.
Here's an updated fiddle
I didn't understand a great deal about why you are allowing editing only on the first option or how would a user edit the second option. But, you can take a look at this fiddle on how to make each item editable in a list of items.
Update after comments:
Even if the select isn't created by knockout's bindings, you still wouldn't need custom binding. You can make the PhoneTypeName a computed property like I suggested before and get the text from the options based on the PhoneTypeID.
function phoneVM(typeID, typeName, Number) {
var self = this;
this.PhoneTypeID = ko.observable(typeID);
this.PhoneNumber1 = ko.observable(Number);
self.PhoneTypeName = ko.computed(function() {
var type = $('#select option[value='+self.PhoneTypeID() +']');
return type.length > 0 ? type.text() : undefined;
});
}
Here's an updated fiddle
The reason your change event wasn't getting fired is probably because the element you add the event to is now removed from the DOM.
I want to bind only one checkbox value from a table containing 5 rows. I'm displaying the table using ng-repeat from database
<table>
<tr data-ng-repeat="serviceTable in dataTable ">
<td> {{ serviceTable.serviceId }} </td>
<td> {{ serviceTable.serviceName }} </td>
<td> {{ serviceTable. amount }} </td>
<td><input type="checkbox" ng-model="service.serviceId"></td>
</tr>
</table>
I want to pass only one integer value(service Id's like 1, 2,3) in ng-model after selecting the checkbox and its value should be passed to the ng-model.
Assuming you are trying to achieve group radio button behaviour from checkbox
In your checkbox add a model that will store specific checkbox status and a onchange listener. Inside listener make them all unchecked except current.
<input type="checkbox" ng-change="onChange($index)" ng-model="checkboxStatus[$index]">
In controller
$scope.checkboxStatus = {};
var previousCheckedIndex = null;
$scope.onChange = function (idx) {
if (previousCheckedIndex > -1 && previousCheckedIndex !== idx)
$scope.checkboxStatus[previousCheckedIndex] = false;
previousCheckedIndex = idx;
};
Using loop:
$scope.checkboxStatus = {};
$scope.onChange = function (idx) {
for (var key in $scope.checkboxStatus) {
if(key != idx) $scope.checkboxStatus[key] = false;
}
};
Since breeze can not submit to the database without looking out for mapped entity, then there is a need to extend the model to give you freedom to create your own calculated properties. reference: http://www.johnpapa.net/new-breeze-angular-service/
I have an extended model, using John Papa SPA project pattern,
I have created a table that require submitting a computed properties along with the users input, i.e Date/Time/UsersId i think the only way to achive this is creating an extended model check this link - http://prntscr.com/77cipy
The extended model auditId is a field in the database and bindable object,
Now the problem is, if i want to call the record from the database using ngRepeat and bind the field auditid to view using {{m.auditId}} i keep getting the object from the computed property. Now, i create another property called {{m.formattedAuditId}}
Object.defineProperty(Medication.prototype, 'formattedAuditId', {
get: function () {
var fn = this.auditId;
return fn;
}
});
The Other View Page that get auditId from the database.
<table class="table table-striped">
<thead>
<th>Generic Name</th>
</thead>
<tr data-ng-repeat="m in vm.medication">
<td>{{m.formattedAuditId}}</td>
<td><strong>{{m.genericName}}</strong></td>
</tr>
</table>
It is still inheriting from the auditId Property i created.
Please what can i do, to bind value from the database instead of the computed property that i created.
Javascript Code:
function registerMedication(metadataStore) {
metadataStore.registerEntityTypeCtor('Medication', Medication);
function Medication() {
//this.isPartial = false;
}
/// This part is the computed property
Object.defineProperty(Medication.prototype, 'auditId', {
get: function () {
var value = moment().format('llll');
return value;
},
set: function (value) {
return value;
}
});
/// This part is where i want the the value to come from the database
Object.defineProperty(Medication.prototype, 'formattedAuditId', {
get: function () {
var fn = this.auditId;
return fn;
}
});
}
The HTML Part
<form class="contact_form" role="form" id="contact_form">
<fieldset class="left">
<label>Audit Id</label>
<div class="block">
<input data-ng-model="vm.medication.auditId"
placeholder="Patient ID" class="text_input"
type="text" />
</div>
<label>Generic Name</label>
<div class="block">
<input data-ng-model="vm.medication.genericName"
placeholder="Generic Name" class="text_input"
type="text" />
</div>
</fieldset>
</form>
Thanks.
Hi I have following form in which I have two input boxes and a ng-repeat that displays the results. Basically in my controller it fetches data from db and is loaded in the nt-repeat.
<td>
<div>
<input type="text" ng-model="item.logo">
</div> </td>
<td>
<div>
<input type="text" ng-model="item.name" >
</div>
</td>
<tr ng-repeat="aa in addlaborrates | filter:{name:item.name} ">
<td>{{aa.name}}</td>
<td>{{aa.lago}}</td>
<td>{{aa.id}}</td>
</tr>
When i type in the item.name input box filter finds the matching items in the ng-repeat.
What i am trying to do is when I type in name input box as it filters I want the logo fields also be auto populated with corresponding value. Please let me know how to achieve this.
Thanks
You can create a filter like in http://jsfiddle.net/V9bgn/1/
.filter('getLogo', function() {
return function(array, value) {
var ret = [];
angular.forEach(array, function(item) {
if (item.name.indexOf(value) > -1) {
ret.push(item.logo);
}
});
return ret[0] || '';
};
});
And then use it in the controller:
$scope.$watch('item.name', function(val) {
$scope.item.logo = $filter('getLogo')($scope.addlaborrates,val);
});