Skip to content

Commit c69fff1

Browse files
vicbjasonaden
authored andcommitted
fix(core): fix re-insertions in the iterable differ (angular#17891)
fixes angular#17852
1 parent dd7c113 commit c69fff1

File tree

5 files changed

+64
-64
lines changed

5 files changed

+64
-64
lines changed

packages/core/src/change_detection/differs/default_iterable_differ.ts

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,6 @@ export class DefaultIterableDiffer<V> implements IterableDiffer<V>, IterableChan
172172

173173
onDestroy() {}
174174

175-
// todo(vicb): optim for UnmodifiableListView (frozen arrays)
176175
check(collection: NgIterable<V>): boolean {
177176
this._reset();
178177

@@ -281,12 +280,12 @@ export class DefaultIterableDiffer<V> implements IterableDiffer<V>, IterableChan
281280
_mismatch(record: IterableChangeRecord_<V>|null, item: V, itemTrackBy: any, index: number):
282281
IterableChangeRecord_<V> {
283282
// The previous record after which we will append the current one.
284-
let previousRecord: IterableChangeRecord_<V>;
283+
let previousRecord: IterableChangeRecord_<V>|null;
285284

286285
if (record === null) {
287-
previousRecord = this._itTail !;
286+
previousRecord = this._itTail;
288287
} else {
289-
previousRecord = record._prev !;
288+
previousRecord = record._prev;
290289
// Remove the record from the collection since we know it does not match the item.
291290
this._remove(record);
292291
}
@@ -394,7 +393,7 @@ export class DefaultIterableDiffer<V> implements IterableDiffer<V>, IterableChan
394393

395394
/** @internal */
396395
_reinsertAfter(
397-
record: IterableChangeRecord_<V>, prevRecord: IterableChangeRecord_<V>,
396+
record: IterableChangeRecord_<V>, prevRecord: IterableChangeRecord_<V>|null,
398397
index: number): IterableChangeRecord_<V> {
399398
if (this._unlinkedRecords !== null) {
400399
this._unlinkedRecords.remove(record);
@@ -419,17 +418,19 @@ export class DefaultIterableDiffer<V> implements IterableDiffer<V>, IterableChan
419418
}
420419

421420
/** @internal */
422-
_moveAfter(record: IterableChangeRecord_<V>, prevRecord: IterableChangeRecord_<V>, index: number):
423-
IterableChangeRecord_<V> {
421+
_moveAfter(
422+
record: IterableChangeRecord_<V>, prevRecord: IterableChangeRecord_<V>|null,
423+
index: number): IterableChangeRecord_<V> {
424424
this._unlink(record);
425425
this._insertAfter(record, prevRecord, index);
426426
this._addToMoves(record, index);
427427
return record;
428428
}
429429

430430
/** @internal */
431-
_addAfter(record: IterableChangeRecord_<V>, prevRecord: IterableChangeRecord_<V>, index: number):
432-
IterableChangeRecord_<V> {
431+
_addAfter(
432+
record: IterableChangeRecord_<V>, prevRecord: IterableChangeRecord_<V>|null,
433+
index: number): IterableChangeRecord_<V> {
433434
this._insertAfter(record, prevRecord, index);
434435

435436
if (this._additionsTail === null) {
@@ -447,7 +448,7 @@ export class DefaultIterableDiffer<V> implements IterableDiffer<V>, IterableChan
447448

448449
/** @internal */
449450
_insertAfter(
450-
record: IterableChangeRecord_<V>, prevRecord: IterableChangeRecord_<V>,
451+
record: IterableChangeRecord_<V>, prevRecord: IterableChangeRecord_<V>|null,
451452
index: number): IterableChangeRecord_<V> {
452453
// todo(vicb)
453454
// assert(record != prevRecord);
@@ -665,11 +666,11 @@ class _DuplicateItemRecordList<V> {
665666
}
666667

667668
// Returns a IterableChangeRecord_ having IterableChangeRecord_.trackById == trackById and
668-
// IterableChangeRecord_.currentIndex >= afterIndex
669-
get(trackById: any, afterIndex: number|null): IterableChangeRecord_<V>|null {
669+
// IterableChangeRecord_.currentIndex >= atOrAfterIndex
670+
get(trackById: any, atOrAfterIndex: number|null): IterableChangeRecord_<V>|null {
670671
let record: IterableChangeRecord_<V>|null;
671672
for (record = this._head; record !== null; record = record._nextDup) {
672-
if ((afterIndex === null || afterIndex < record.currentIndex !) &&
673+
if ((atOrAfterIndex === null || atOrAfterIndex <= record.currentIndex !) &&
673674
looseIdentical(record.trackById, trackById)) {
674675
return record;
675676
}
@@ -724,15 +725,15 @@ class _DuplicateMap<V> {
724725

725726
/**
726727
* Retrieve the `value` using key. Because the IterableChangeRecord_ value may be one which we
727-
* have already iterated over, we use the afterIndex to pretend it is not there.
728+
* have already iterated over, we use the `atOrAfterIndex` to pretend it is not there.
728729
*
729730
* Use case: `[a, b, c, a, a]` if we are at index `3` which is the second `a` then asking if we
730-
* have any more `a`s needs to return the last `a` not the first or second.
731+
* have any more `a`s needs to return the second `a`.
731732
*/
732-
get(trackById: any, afterIndex: number|null): IterableChangeRecord_<V>|null {
733+
get(trackById: any, atOrAfterIndex: number|null): IterableChangeRecord_<V>|null {
733734
const key = trackById;
734735
const recordList = this.map.get(key);
735-
return recordList ? recordList.get(trackById, afterIndex) : null;
736+
return recordList ? recordList.get(trackById, atOrAfterIndex) : null;
736737
}
737738

738739
/**

packages/core/src/change_detection/differs/iterable_differs.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {Optional, Provider, SkipSelf} from '../../di';
1010
import {ChangeDetectorRef} from '../change_detector_ref';
1111

1212
/**
13-
* A type describing supported interable types.
13+
* A type describing supported iterable types.
1414
*
1515
* @stable
1616
*/

packages/core/test/change_detection/change_detector_util_spec.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
*/
88

99
import {devModeEqual} from '@angular/core/src/change_detection/change_detection_util';
10-
import {describe, expect, it} from '@angular/core/testing/src/testing_internal';
1110

1211
export function main() {
1312
describe('ChangeDetectionUtil', () => {

packages/core/test/change_detection/differs/default_iterable_differ_spec.ts

Lines changed: 41 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
*/
88

99
import {DefaultIterableDiffer, DefaultIterableDifferFactory} from '@angular/core/src/change_detection/differs/default_iterable_differ';
10-
import {beforeEach, describe, expect, it} from '@angular/core/testing/src/testing_internal';
1110

1211
import {TestIterable} from '../../change_detection/iterable';
1312
import {iterableChangesAsString} from '../../change_detection/util';
@@ -28,7 +27,7 @@ class ComplexItem {
2827
export function main() {
2928
describe('iterable differ', function() {
3029
describe('DefaultIterableDiffer', function() {
31-
let differ: any /** TODO #9100 */;
30+
let differ: DefaultIterableDiffer<any>;
3231

3332
beforeEach(() => { differ = new DefaultIterableDiffer(); });
3433

@@ -41,7 +40,7 @@ export function main() {
4140
});
4241

4342
it('should support iterables', () => {
44-
const l = new TestIterable();
43+
const l: any = new TestIterable();
4544

4645
differ.check(l);
4746
expect(differ.toString()).toEqual(iterableChangesAsString({collection: []}));
@@ -64,7 +63,7 @@ export function main() {
6463
});
6564

6665
it('should detect additions', () => {
67-
const l: any[] /** TODO #9100 */ = [];
66+
const l: any[] = [];
6867
differ.check(l);
6968
expect(differ.toString()).toEqual(iterableChangesAsString({collection: []}));
7069

@@ -144,7 +143,7 @@ export function main() {
144143
});
145144

146145
it('should detect changes in list', () => {
147-
const l: any[] /** TODO #9100 */ = [];
146+
const l: any[] = [];
148147
differ.check(l);
149148

150149
l.push('a');
@@ -192,19 +191,7 @@ export function main() {
192191
}));
193192
});
194193

195-
it('should test string by value rather than by reference (Dart)', () => {
196-
const l = ['a', 'boo'];
197-
differ.check(l);
198-
199-
const b = 'b';
200-
const oo = 'oo';
201-
l[1] = b + oo;
202-
differ.check(l);
203-
expect(differ.toString())
204-
.toEqual(iterableChangesAsString({collection: ['a', 'boo'], previous: ['a', 'boo']}));
205-
});
206-
207-
it('should ignore [NaN] != [NaN] (JS)', () => {
194+
it('should ignore [NaN] != [NaN]', () => {
208195
const l = [NaN];
209196
differ.check(l);
210197
differ.check(l);
@@ -294,6 +281,22 @@ export function main() {
294281
}));
295282
});
296283

284+
// https://github.com/angular/angular/issues/17852
285+
it('support re-insertion', () => {
286+
const l = ['a', '*', '*', 'd', '-', '-', '-', 'e'];
287+
differ.check(l);
288+
l[1] = 'b';
289+
l[5] = 'c';
290+
differ.check(l);
291+
expect(differ.toString()).toEqual(iterableChangesAsString({
292+
collection: ['a', 'b[null->1]', '*[1->2]', 'd', '-', 'c[null->5]', '-[5->6]', 'e'],
293+
previous: ['a', '*[1->2]', '*[2->null]', 'd', '-', '-[5->6]', '-[6->null]', 'e'],
294+
additions: ['b[null->1]', 'c[null->5]'],
295+
moves: ['*[1->2]', '-[5->6]'],
296+
removals: ['*[2->null]', '-[6->null]'],
297+
}));
298+
});
299+
297300
describe('forEachOperation', () => {
298301
function stringifyItemChange(record: any, p: number, c: number, originalIndex: number) {
299302
const suffix = originalIndex == null ? '' : ' [o=' + originalIndex + ']';
@@ -329,8 +332,8 @@ export function main() {
329332
const startData = [0, 1, 2, 3, 4, 5];
330333
const endData = [6, 2, 7, 0, 4, 8];
331334

332-
differ = differ.diff(startData);
333-
differ = differ.diff(endData);
335+
differ = differ.diff(startData) !;
336+
differ = differ.diff(endData) !;
334337

335338
const operations: string[] = [];
336339
differ.forEachOperation((item: any, prev: number, next: number) => {
@@ -352,12 +355,12 @@ export function main() {
352355
const startData = [0, 1, 2, 3];
353356
const endData = [2, 1];
354357

355-
differ = differ.diff(startData);
356-
differ = differ.diff(endData);
358+
differ = differ.diff(startData) !;
359+
differ = differ.diff(endData) !;
357360

358361
const operations: string[] = [];
359362
differ.forEachOperation((item: any, prev: number, next: number) => {
360-
const value = modifyArrayUsingOperation(startData, endData, prev, next);
363+
modifyArrayUsingOperation(startData, endData, prev, next);
361364
operations.push(stringifyItemChange(item, prev, next, item.previousIndex));
362365
});
363366

@@ -372,12 +375,12 @@ export function main() {
372375
const startData = [1, 2, 3, 4, 5, 6];
373376
const endData = [3, 6, 4, 9, 1, 2];
374377

375-
differ = differ.diff(startData);
376-
differ = differ.diff(endData);
378+
differ = differ.diff(startData) !;
379+
differ = differ.diff(endData) !;
377380

378381
const operations: string[] = [];
379382
differ.forEachOperation((item: any, prev: number, next: number) => {
380-
const value = modifyArrayUsingOperation(startData, endData, prev, next);
383+
modifyArrayUsingOperation(startData, endData, prev, next);
381384
operations.push(stringifyItemChange(item, prev, next, item.previousIndex));
382385
});
383386

@@ -393,12 +396,12 @@ export function main() {
393396
const startData = [0, 1, 2, 3, 4];
394397
const endData = [4, 1, 2, 3, 0, 5];
395398

396-
differ = differ.diff(startData);
397-
differ = differ.diff(endData);
399+
differ = differ.diff(startData) !;
400+
differ = differ.diff(endData) !;
398401

399402
const operations: string[] = [];
400403
differ.forEachOperation((item: any, prev: number, next: number) => {
401-
const value = modifyArrayUsingOperation(startData, endData, prev, next);
404+
modifyArrayUsingOperation(startData, endData, prev, next);
402405
operations.push(stringifyItemChange(item, prev, next, item.previousIndex));
403406
});
404407

@@ -414,12 +417,12 @@ export function main() {
414417
const startData = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
415418
const endData = [10, 11, 1, 5, 7, 8, 0, 5, 3, 6];
416419

417-
differ = differ.diff(startData);
418-
differ = differ.diff(endData);
420+
differ = differ.diff(startData) !;
421+
differ = differ.diff(endData) !;
419422

420423
const operations: string[] = [];
421424
differ.forEachOperation((item: any, prev: number, next: number) => {
422-
const value = modifyArrayUsingOperation(startData, endData, prev, next);
425+
modifyArrayUsingOperation(startData, endData, prev, next);
423426
operations.push(stringifyItemChange(item, prev, next, item.previousIndex));
424427
});
425428

@@ -440,8 +443,8 @@ export function main() {
440443
const startData = [1, 2, 3, 4];
441444
const endData = [5, 6, 7, 8];
442445

443-
differ = differ.diff(startData);
444-
differ = differ.diff(endData);
446+
differ = differ.diff(startData) !;
447+
differ = differ.diff(endData) !;
445448

446449
const operations: string[] = [];
447450
differ.forEachOperation((item: any, prev: number, next: number) => {
@@ -465,7 +468,7 @@ export function main() {
465468

466469
it('should treat null as an empty list', () => {
467470
differ.diff(['a', 'b']);
468-
expect(differ.diff(null).toString()).toEqual(iterableChangesAsString({
471+
expect(differ.diff(null !) !.toString()).toEqual(iterableChangesAsString({
469472
previous: ['a[0->null]', 'b[1->null]'],
470473
removals: ['a[0->null]', 'b[1->null]']
471474
}));
@@ -478,7 +481,7 @@ export function main() {
478481
});
479482

480483
describe('trackBy function by id', function() {
481-
let differ: any /** TODO #9100 */;
484+
let differ: any;
482485

483486
const trackByItemId = (index: number, item: any): any => item.id;
484487

@@ -565,8 +568,9 @@ export function main() {
565568
}));
566569
});
567570
});
571+
568572
describe('trackBy function by index', function() {
569-
let differ: any /** TODO #9100 */;
573+
let differ: DefaultIterableDiffer<string>;
570574

571575
const trackByIndex = (index: number, item: any): number => index;
572576

@@ -584,9 +588,6 @@ export function main() {
584588
identityChanges: ['h']
585589
}));
586590
});
587-
588591
});
589-
590-
591592
});
592593
}

packages/core/test/change_detection/differs/iterable_differs_spec.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,14 @@
88

99
import {ReflectiveInjector} from '@angular/core';
1010
import {IterableDiffers} from '@angular/core/src/change_detection/differs/iterable_differs';
11-
import {beforeEach, describe, expect, it} from '@angular/core/testing/src/testing_internal';
1211

1312
import {SpyIterableDifferFactory} from '../../spies';
1413

1514
export function main() {
1615
describe('IterableDiffers', function() {
17-
let factory1: any /** TODO #9100 */;
18-
let factory2: any /** TODO #9100 */;
19-
let factory3: any /** TODO #9100 */;
16+
let factory1: any;
17+
let factory2: any;
18+
let factory3: any;
2019

2120
beforeEach(() => {
2221
factory1 = new SpyIterableDifferFactory();
@@ -57,7 +56,7 @@ export function main() {
5756
.toThrowError(/Cannot extend IterableDiffers without a parent injector/);
5857
});
5958

60-
it('should extend di-inherited diffesr', () => {
59+
it('should extend di-inherited differs', () => {
6160
const parent = new IterableDiffers([factory1]);
6261
const injector =
6362
ReflectiveInjector.resolveAndCreate([{provide: IterableDiffers, useValue: parent}]);

0 commit comments

Comments
 (0)