Closed powerbuoy closed 5 years ago
I have fixed this issue by instructing Repeat
to ignore mutation while processing instance change (main changes can be found at https://github.com/bigopon/templating-resources/blob/d4ff656a44fd2c2fc4d7b5770f6c036723325b60/src/repeat.js#L138-L142). Code:
this.ignoreMutation = true;
this.strategy.instanceChanged(this, items);
this.observerLocator.taskQueue.queueMicroTask(() => {
this.ignoreMutation = false;
});
With the above changes, the issue in @powerbuoy 's demo is fixed, demo at https://gist.run/?id=56d85dc0c87214d1f79b5b284c76308a . Though I haven't been able to create a test for it, I built the code and created a branch at https://github.com/bigopon/templating-resources/tree/fix-repeat so folks who need it can use it to workaround if needed.
@EisenbergEffect @jdanyow @fkleuver / all Would love to have some help on the test here (it always got the number of elements right even without the changes)
Edit 1:
Work around the issue temporarily by pasting this in app entry:
import { Repeat } from 'aurelia-templating-resources';
Repeat.prototype.itemsChanged = function() {
this._unsubscribeCollection();
if (!this.scope) {
return;
}
var items = this.items;
this.strategy = this.strategyLocator.getStrategy(items);
if (!this.strategy) {
throw new Error('Value for \'' + this.sourceExpression + '\' is non-repeatable');
}
if (!this.isOneTime && !this._observeInnerCollection()) {
this._observeCollection();
}
this.ignoreMutation = true;
this.strategy.instanceChanged(this, items);
this.observerLocator.taskQueue.queueMicroTask(() => {
this.ignoreMutation = false;
})
};
Repeat.prototype.handleCollectionMutated = function(collection, changes) {
if (!this.collectionObserver) {
return;
}
if (this.ignoreMutation) {
return;
}
this.strategy.instanceMutated(this, collection, changes);
};
@bigopon Thanks for looking into this. @jdanyow Can you do a review? This is a critical issue in repeat. If the fix looks good, I'd like to get it out ASAP. Please let me know!
@bigopon could it also improve the rendering perf for patial updates?
@Alexander-Taran That's a nice suggestion. Not sure if ignoring mutation while processing mutation will cause any issue. Will need to investigate a bit
@Alexander-Taran To answer the main question: I don't think it would have any impact on that.
@bigopon this thread: https://github.com/krausest/js-framework-benchmark/pull/376#issuecomment-388396221 last 4-5 comments. Sometimes partial update benchmark produces way more than 1-2 paints.. and the resultant timing is off charts.
I just took a look at this, and the problem is not what it seems.
These two things happen within a single CallScope.evaluate
call:
if.bind
set to true
-> SetterObserver
pushed on the task queueModifyArrayObserver
pushed on the task queueThe task queue is flushed, then:
SetterObserver
for the if.bind
. This causes the whole lifecycle to be invoked and yields a fully rendered page that is doneModifyArrayObserver
saying "hey we've got a change! add this one item", and it's added. Now we've got two rendered items for one array item.Any changes that happen after setting an if.bind
to true
will be applied twice for this reason - the if
renders its children with the latest scope values (that inherently include the changes), then the changes are applied in the next task.
// two text boxes
this.editing = !this.editing;
if (this.editing) {
this.items.push({});
}
// one text box
const editing = !this.editing;
if (editing) {
this.items.push({});
}
this.editing = editing;
The same principle applies (although in different forms) when you're doing different operations on the array within the same evaluation cycle.
The changes are queued onto different tasks when instead they should be either on the same task or consolidated into a single changeset.
I would say this is neither a problem in repeat
nor in if
, but rather a problem in "abusing" the TaskQueue for preventing infinite loops between change handlers. It solves the problem over here and then causes the same problem later over there. Before long, there would be multiple nested calls to the TaskQueue and the problems get increasingly more difficult to solve.
I think the only proper solution is to find a way to eliminate usages of queueMicroTask
in the Aurelia framework, rather than adding more of them. This could be simple, but will be tedious - it means an extra parameter needs to be passed around between every observer and change handler that keeps track of the sources and targets of changes. On the flip side, it would likely be a nice performance improvement..
I just started using Aurelia using aspnetcore, aurelia-cli and the todo tutorial:
Each time i add a todo to the array it renders another (empty) <li>
element after the 'correct' elements of the array.
When i log the amount elements in the array, it shows the correct amount.
Initially i added aurrelia to an existing aspnetcore project but later i reproduced thesame with a simple emtpy aspnetcore project using Visual Studio and then used aurelia-cli using the following properties:
I have no experience with webpack and only little experience with SPA's (used angular1 a while back and tried vuejs) so much of the files created by the cli is like a black box to me. If any info is required, i'll be happy to supply it.
EDIT: I've followed thesame steps on my work workstation and everything works fine, i'm gonna try to run the project of my laptop on my workstation and see if any file in the project is the culprit.
Managed to fix it by updating nodejs to the latest version.
After that removing the node_modules
folder and manually executing npm install
.
The same repeat.for issue (duplicating empty elements) is happening in my project even with all the latest aurelia packages (as of 2018-07-10) including the 'ignoreMutation' change. Code had been working fine for many months before updating the binding/templating packages. I'm guessing it is similar to the "Duplicate of aurelia-binding..." issue mentioned above because I'm using aurelia-breeeze and that has references in package.json to aurelia-binding "^1.0.0-rc.1.0.0" (and several other rc1 references). Thus giving me 2 differing copies of aurelia-binding in my webpack bundle. Reassigning the array rather than using .push gets around the issue.
@shunty-gh Have you tried a fresh npm install
? Remove node_modules and package-lock.json and then a fresh npm install with the new package versions from your updated package.json. I had the same issue but a clean npm install solved the problem.
...and I can confirm that using a local (non npm) copy of aurelia-breeze and, thus, ignoring the outdated dependencies allows the Array.push approach to work fine. And makes my bundle a little smaller too :-)
@mikeesouth Thanks for that. I had tried deleting node_modules and re-running npm install
but I hadn't tried removing package-lock.json too.
However, I have now tried and it gives the same erroneous results with repeat.for so I'll stick with the local copy of aurelia-breeze for now.
This seems to remain unsolved (EDIT: Issue is solved. Had an old version of aurelia-templating-resources.), so here is my workaround in case someone else is stuck here and need a quick fix.
View Model:
<your-element if.bind="!updatingArray" repeat.for="item of items"></your-element>
Data Model:
this.updatingArray = true;
setTimeout(() => {
this.items.push(item);
this.updatingArray = false;
},1);
As stated above - it seems like it works as long as the array is re-rendered in its entirety, as it appears to be mutations of the array that triggers the issue.
@ljtn could you share your package-lock.json in a gist?
Sorry, I was using an old version of aurelia-templating-resources (version 1.6.0.) It works now that I updated to 1.7.1.
What is the status on this? I am using aurelia-templating-resources@1.7.1 and the issue still persists for a simple view like:
<ul>
<li repeat.for="element of elements">
test
</li>
</ul>
<button class="btn btn-ordami-green" click.delegate="add()">Add</button>
export class MyView {
elements = [];
add() {
this.elements.push("test");
}
}
When I click the "Add" button once, 2 list items are produced instead of just one. No if
involved here.
EDIT: Turns out that the combination of
is bad. Using aurelia-binding@1.7.3 my issue is resolved.
@Mobe91 you should also see the warning about duplicated attempt patching Array prototype if it was causing you issue. If you are using webpack, please see https://github.com/aurelia/cli/pull/906
@EisenbergEffect I think this can be closed
Definitely can be closed
I have a strange issue where when I try to push an empty object to an array that is rendered in my view - the newly pushed element is rendered twice.
The duplicate element is in fact the exact same element as the previous one (if I change the first the second also changes).
I'm unable to reproduce this in a GistRun (perhaps version differences) - but I have managed to reproduce it in a completely new Aurelia CLI project with the following code:
app.js
And this view:
app.html
My
package.json
looks like this (for version info):package.json
Maybe it's something obvious I've missed? But it seems like a genuine bug to me.
I'm attaching my example app as well which exhibits the bug and can be run in Firefox directly. Just click "Edit" and two input fields will be shown instead of one - changing one actually changes both.
autest.zip