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.
Related
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
I have an table created using ng-repeat and there hundreds of rows, up to 600 or 700. Each row includes a checkbox and I have a "Check All" box at the top to check all the boxes in one go. However I'm running into browser performance issues, IE11 (the clients preferred choice) in particular becomes unresponsive. After several minutes all the checkboxes appear checked but you still can't scroll or do anything so it is effectively useless.
I have created a controller array and when the checkAll box is clicked it loops through the model (the one used in ng-repeat) and adds a value to the array. I presume it's this looping through the array that is causing the slow-down but I'm not sure. Pagination has been ruled out, they want all the rows on one page.
<table>
<tbody>
<tr>
<th>Table Header</th>
<th><input type="checkbox" id="checkAllCheckBox" ng-model="vm.allChecked" ng-change="vm.tickOrUntickAllCheckBoxes()" />
</tr>
<tr ng-repeat="payment in vm.payments>
<td>{{ payment.somePaymentValue }}</td>
<td>
<input type="checkbox" class="paymentsApprovalCheckbox"
ng-checked="vm.approvedPayments.indexOf(payment.payId) > - 1"
ng-value="payment.payId" ng-model="payment.approved"
ng-click="vm.handleCheckBoxClick(payment.payId)" />
</td>
</tr>
</tbody>
</table>
Here is the angular function that checks/unchecks all
vm.tickOrUntickAllCheckBoxes = function(){
if (vm.allChecked == false) {
vm.approvedPayments = [];
} else {
vm.payments.forEach(function(payment){
vm.approvedPayments.push(payment.payId);
});
}
};
Swapping out the angular vm.tickOrUntickAllCheckBoxes() function for a plain old javascript option makes the checkAll box work almost instantaneously in IE11 however I lose access to the checked payment.payId values. I wonder is there away for angular to get them? Here is the plain javascript checkAll() function:
<script>
function checkAll(x) {
var checkBoxes = document.getElementsByClassName('paymentsApprovalCheckbox');
for (var i = 0; i < checkBoxes.length ; i++) {
checkBoxes[i].checked = (x.checked == true);
}
}
</script>
Then I update the checkAll checkbox like this:
<input type="checkbox" id="checkAllCheckBox" ng-model="vm.allChecked" onclick="checkAll(this)" />
If you check one checkbox individually then the ng-model="payment.approved" in the repeating checkboxes is updated but this does not happen if they are checked with the checkAll function. Is it possible for angular to detect the boxes checked with checkAll()? I guess this is just putting off the same old inevitable slow-down to a slightly later point in the process.
Anyone have any ideas or work-arounds? Thanks!
I would use the ng-model to the best of its abilities. In your controller:
$onInit() {
// If you need this from a REST call to populate, you'll have to
// remember to do that here;
this.model = {
all: true,
items: {}
};
}
In your loop:
<tr>
<th>Table Header</th>
<th>
<input type="checkbox"
id="checkAllCheckBox"
ng-model="vm.model.all"
ng-change="vm.tickOrUntickAllCheckBoxes()" />
</tr>
<tr ng-repeat="payment in vm.payments track by $index">
<td ng-bind="payment.somePaymentValue"></td>
<td>
<input type="checkbox"
class="paymentsApprovalCheckbox"
ng-change="vm.approvedPayments($index)"
ng-model="vm.model.items[$index]" />
</td>
</tr>
Then in your controller:
tickOrUntickAllCheckBoxes() {
const boxes = this.model.items.length;
this.model.all = !this.model.all;
// Several ways to do this, forEach, map, etc.,
this.model.items.forEach((item) => { item.checked = !this.model.all });
}
And for setting it individually:
approvedPayments(idx) {
// Sets all the item's boxes checked, or unchecked;
this.model.items[idx].checked = !this.model.items[idx].checked;
// Possible call to extended model to get payment info;
handleCheckBoxClick(idx);
}
You should be able to put all the payment information into the one approvedPayments() method rather than have two separate methods (move logic out of template and into the controller or a service). I.e., your model could look like:
this.model.items = [
// One 'option' with id, payment etc;
{
id: 73,
paymentId: 73,
somePaymentValue: 210.73,
currencyType: 'GBP',
checked: false
},
{
// Another 'option' etc...
}
]
One issue to note is the incompatibility of ngChecked with ngModel, had to look it up (which is why I haven't used ng-checked in the above).
Thank to everyone for the suggestions. The solution I came up with was to push some of the work back to the server side. Instead of just loading the payments model (in which each payment record contains a lot of info) i am now loading two additional models when the page loads, one of which is a set of key/value pairs where the keys are payId and the values are all false and another one with the same keys and all values are true. Example:
{
"1": false,
"2": false
}
These are used for the checkAll/Uncheck all - just set the vm.approvedIDs variable to the true or false one. Then, the vm.approvedIDs variable is used as the model in the ng-repeat checkbox.
I have to do a bit of extra work on the server side when the user sends the approvedIDs back to the server to get only the key/id of the 'true' entries. Here are the relevant angular controller functions:
$onInit() {
// call http to get 'data' from server
vm.payments = data.payments;
vm.paymentIDsFalse = vm.approvedIDs = data.paymentIDsFalse;
vm.paymentIDsTrue = data.paymentIDsTrue;
};
// tick/untick all boxes
vm.tickOrUntickAllCheckBoxes = function(){
if (vm.allChecked == false) {
vm.approvedPayments = vm.paymentIDsFalse;
} else {
vm.approvedPayments = vm.paymentIDsTrue;
}
};
// tick/untick one box
vm.handleCheckBoxClick = function(payId, currentValue){
vm.approvedPayments[payId] = currentValue;
};
vm.submitApprovedIds = function(){
// post vm.approvedPayments to server
};
HTML:
<table>
<tbody>
<tr>
<th>Table Header</th>
<th><input type="checkbox" id="checkAllCheckBox" ng-model="vm.allChecked" ng-change="vm.tickOrUntickAllCheckBoxes()" />
</tr>
<tr ng-repeat="payment in vm.payments>
<td>{{ payment.somePaymentValue }}</td>
<td>
<input type="checkbox" class="paymentsApprovalCheckbox"
ng-value="payment.payId"
ng-model="vm.approvedPayments[payment.payId]"
ng-click="vm.handleCheckBoxClick(payment.payId, vm.approvedPayments[payment.payId])" />
</td>
</tr>
</tbody>
</table>
It looks to me as if there must be a better way than creating these additional models but it is working pretty smoothly for now and I can move on to the next thing!
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.
This app allows user to enter the quantity of questions for specific topic inside a workbook. I’m trying to sum up all the questions and give an output of totalQuestions.
I created a function (findTotalQuestions().) that will run when Quantity input box is changed. I’m getting an error in Console: Cannot read property 'quantity' of undefined. I’m thinking its because $scope.skill.quantity is not being passed into function. How can I fix this?
<div class="input-field col m3" ng-model="totalQuestions">
<medium>Total Questions in Workbook: </medium>{{totalQuestions}}
</div>
HTML
<table>
<tr>
<th> Skill </th>
<th> Quantity </th>
</tr>
<tr ng-repeat="skill in newWorkbook.skills">
<td>
{ skill.topicText }}
</td>
<td>
<p>
<input id="quantity{{ skill.TopicId }}" type="number" class="validate" ng-model="skill.quantity" required="true" autocomplete="off" ng-change="findTotalQuestions()">
</p>
</td>
</tr>
</table>
Controller:
$scope.getProposedSkills = function() {
return $http.get("/api/topics?SubjectId=" + $scope.newWorkbook.SubjectId).then(function(skills) {
return $scope.newWorkbookskills = _.map(skills.data, function(skill) {
var obj;
obj = {
TopicId: skill.id,
quantity: 0,
level: null,
topicText: skill.topicText,
startLevel: skill.startLevel,
endLevel: skill.endLevel
};
return obj;
});
});
};
$scope.findTotalQuestions = function() {
$scope.totalQuestions = 0;
$scope.totalQuestions = $scope.totalQuestions + $scope.skill.quantity;
return console.log($scope.totalQuestions);
};
Move your getProposedSkills() code in a service then in the controller set the $scope.skill like below
app.service('service',function($http){
return {getProposedSkills:$http.get('your api url')};
})
Your controller should look like below
app.controller('ctrl',function($scope,$http,service){
$scope.getProposedSkills = service.getProposedSkills().then(function(res){
$scope.skill=res;
})
$scope.findTotalQuestions = function() {
$scope.totalQuestions = 0;
$scope.totalQuestions = $scope.totalQuestions + $scope.skill.quantity;
return console.log($scope.totalQuestions);
}
})
And other question i can't find the place where you set $scope.skill in your controller,
if it's a paremeter to be used in your funcion, you should receive it as a paremter of your function and pass it form the view
if it's a variable on your controller it should be initialized or at least check if it exists.
By the way, In the html you have a div with ng-model, this is not correct ng-model should be place inside form inputs (select, input, textarea)
I want to add a row in the table on click of "Add" button.I am trying to accomplish this using "ng-show" directive but that row is displaying even without clicking "Add" button. Please help me with this. Here is the code -
home.html -
<table class="table">
<thead>
<tr>
<th>SNo.</th>
<th>UserId</th>
<th>Name</th>
<th>Share</th>
<th>Paid</th>
</tr>
</thead>
<tbody ng-repeat="member in members">
<tr>
<td>{{member.sNo}}</td>
<td>{{member.id}}</td>
<td>{{member.name}}</td>
<td>{{member.share}}</td>
<td>{{member.paid}}</td>
</tr>
<tr ng-show="show" class="ng-hide"> //the row which is to be added
<td><span>{{counter}}</span></td>
<td><input type="text" required ng-model="new.id"></td>
<td><input type="text" required ng-model="new.name"></td>
<td><input type="text" required ng-model="new.share"></td>
<td><input type="text" required ng-model="new.paid"></td>
</tr>
</tbody>
</table>
<div><input type="button" value="Add" ng-click="addMember()"/></div>
controller.js -
expmodule.controller('expenseForm', function($scope, sharedProperties) {
var id, expenses = {};
$scope.show = false;
$scope.members = [{
sNo: "1",
id: "abc#gmail.com",
name: "Neha",
share: 200,
paid: 400,
}, {
sNo: "2",
id: "xyz#gmail.com",
name: "Sneha",
share: 200,
paid: 400,
}];
$scope.counter = $scope.members.length++;
$scope.addMember = function () {
$scope.show = true;
return $scope.newRow = true;
}
});
Are you looking for something like this? http://plnkr.co/edit/jxXX5sWhgmYrANV7ieKM?p=preview
There are few things which are not correct in the code provided in the question.
The add row is inside ng-repeat. So a new add row will be added for each member.
Also as the show variable is inside ng-repeat it will be a child of parent scope and will be always false as per the current logic.
$scope.counter = $scope.members.length++ This will increase the array length by 1 and the new object in the array will be undefined. I guess you wanted to display the counter in the new add row. It can be simple done like this - {{members.length + 1}} in the add row rather than creating a new scope variable for this. Also this will always have the latest value when ever a new member is added.
The return statement in the addMember function has a assignment operation.
It seems to work fine (see demo below).
Of course, you should place the ng-repeat="member in members" to the <tr> not <tbody> (unless you want to have multiple tbodies).
And you should change $scope.counter = $scope.members.length++; to $scope.counter = $scope.members.length + 1;
See, also, this short demo.