Skip to content

Commit 65211f4

Browse files
matskoIgorMinar
authored andcommitted
fix(animations): retain state styling for nodes that are moved around (#23534)
PR Close #23534
1 parent da9ff25 commit 65211f4

File tree

2 files changed

+132
-4
lines changed

2 files changed

+132
-4
lines changed

packages/animations/browser/src/render/transition_animation_engine.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,15 @@ const STAR_SELECTOR = '.ng-star-inserted';
2828
const EMPTY_PLAYER_ARRAY: TransitionAnimationPlayer[] = [];
2929
const NULL_REMOVAL_STATE: ElementAnimationState = {
3030
namespaceId: '',
31-
setForRemoval: null,
31+
setForRemoval: false,
32+
setForMove: false,
3233
hasAnimation: false,
3334
removedBeforeQueried: false
3435
};
3536
const NULL_REMOVED_QUERIED_STATE: ElementAnimationState = {
3637
namespaceId: '',
37-
setForRemoval: null,
38+
setForMove: false,
39+
setForRemoval: false,
3840
hasAnimation: false,
3941
removedBeforeQueried: true
4042
};
@@ -58,7 +60,8 @@ export interface QueueInstruction {
5860
export const REMOVAL_FLAG = '__ng_removed';
5961

6062
export interface ElementAnimationState {
61-
setForRemoval: any;
63+
setForRemoval: boolean;
64+
setForMove: boolean;
6265
hasAnimation: boolean;
6366
namespaceId: string;
6467
removedBeforeQueried: boolean;
@@ -660,6 +663,11 @@ export class TransitionAnimationEngine {
660663
const details = element[REMOVAL_FLAG] as ElementAnimationState;
661664
if (details && details.setForRemoval) {
662665
details.setForRemoval = false;
666+
details.setForMove = true;
667+
const index = this.collectedLeaveElements.indexOf(element);
668+
if (index >= 0) {
669+
this.collectedLeaveElements.splice(index, 1);
670+
}
663671
}
664672

665673
// in the event that the namespaceId is blank then the caller
@@ -946,9 +954,18 @@ export class TransitionAnimationEngine {
946954
const ns = this._namespaceList[i];
947955
ns.drainQueuedTransitions(microtaskId).forEach(entry => {
948956
const player = entry.player;
957+
const element = entry.element;
949958
allPlayers.push(player);
950959

951-
const element = entry.element;
960+
if (this.collectedEnterElements.length) {
961+
const details = element[REMOVAL_FLAG] as ElementAnimationState;
962+
// move animations are currently not supported...
963+
if (details && details.setForMove) {
964+
player.destroy();
965+
return;
966+
}
967+
}
968+
952969
if (!bodyNode || !this.driver.containsElement(bodyNode, element)) {
953970
player.destroy();
954971
return;

packages/core/test/animation/animation_integration_spec.ts

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1453,6 +1453,117 @@ const DEFAULT_COMPONENT_ID = '1';
14531453
.toBeTruthy();
14541454
});
14551455

1456+
it('should retain state styles when the underlying DOM structure changes even if there are no insert/remove animations',
1457+
() => {
1458+
@Component({
1459+
selector: 'ani-cmp',
1460+
template: `
1461+
<div class="item" *ngFor="let item of items" [@color]="colorExp">
1462+
{{ item }}
1463+
</div>
1464+
`,
1465+
animations: [trigger('color', [state('green', style({backgroundColor: 'green'}))])]
1466+
})
1467+
class Cmp {
1468+
public colorExp = 'green';
1469+
public items = [0, 1, 2, 3];
1470+
1471+
reorder() {
1472+
const temp = this.items[0];
1473+
this.items[0] = this.items[1];
1474+
this.items[1] = temp;
1475+
}
1476+
}
1477+
1478+
TestBed.configureTestingModule({declarations: [Cmp]});
1479+
1480+
const fixture = TestBed.createComponent(Cmp);
1481+
const cmp = fixture.componentInstance;
1482+
fixture.detectChanges();
1483+
1484+
let elements: HTMLElement[] = fixture.nativeElement.querySelectorAll('.item');
1485+
assertBackgroundColor(elements[0], 'green');
1486+
assertBackgroundColor(elements[1], 'green');
1487+
assertBackgroundColor(elements[2], 'green');
1488+
assertBackgroundColor(elements[3], 'green');
1489+
1490+
elements[0].title = '0a';
1491+
elements[1].title = '1a';
1492+
1493+
cmp.reorder();
1494+
fixture.detectChanges();
1495+
1496+
elements = fixture.nativeElement.querySelectorAll('.item');
1497+
assertBackgroundColor(elements[0], 'green');
1498+
assertBackgroundColor(elements[1], 'green');
1499+
assertBackgroundColor(elements[2], 'green');
1500+
assertBackgroundColor(elements[3], 'green');
1501+
1502+
function assertBackgroundColor(element: HTMLElement, color: string) {
1503+
expect(element.style.getPropertyValue('background-color')).toEqual(color);
1504+
}
1505+
});
1506+
1507+
it('should retain state styles when the underlying DOM structure changes even if there are insert/remove animations',
1508+
() => {
1509+
@Component({
1510+
selector: 'ani-cmp',
1511+
template: `
1512+
<div class="item" *ngFor="let item of items" [@color]="colorExp">
1513+
{{ item }}
1514+
</div>
1515+
`,
1516+
animations: [trigger(
1517+
'color',
1518+
[
1519+
transition('* => *', animate(500)),
1520+
state('green', style({backgroundColor: 'green'}))
1521+
])]
1522+
})
1523+
class Cmp {
1524+
public colorExp = 'green';
1525+
public items = [0, 1, 2, 3];
1526+
1527+
reorder() {
1528+
const temp = this.items[0];
1529+
this.items[0] = this.items[1];
1530+
this.items[1] = temp;
1531+
}
1532+
}
1533+
1534+
TestBed.configureTestingModule({declarations: [Cmp]});
1535+
1536+
const fixture = TestBed.createComponent(Cmp);
1537+
const cmp = fixture.componentInstance;
1538+
fixture.detectChanges();
1539+
1540+
getLog().forEach(p => p.finish());
1541+
1542+
let elements: HTMLElement[] = fixture.nativeElement.querySelectorAll('.item');
1543+
assertBackgroundColor(elements[0], 'green');
1544+
assertBackgroundColor(elements[1], 'green');
1545+
assertBackgroundColor(elements[2], 'green');
1546+
assertBackgroundColor(elements[3], 'green');
1547+
1548+
elements[0].title = '0a';
1549+
elements[1].title = '1a';
1550+
1551+
cmp.reorder();
1552+
fixture.detectChanges();
1553+
1554+
getLog().forEach(p => p.finish());
1555+
1556+
elements = fixture.nativeElement.querySelectorAll('.item');
1557+
assertBackgroundColor(elements[0], 'green');
1558+
assertBackgroundColor(elements[1], 'green');
1559+
assertBackgroundColor(elements[2], 'green');
1560+
assertBackgroundColor(elements[3], 'green');
1561+
1562+
function assertBackgroundColor(element: HTMLElement, color: string) {
1563+
expect(element.style.getPropertyValue('background-color')).toEqual(color);
1564+
}
1565+
});
1566+
14561567
it('should animate removals of nodes to the `void` state for each animation trigger, but treat all auto styles as pre styles',
14571568
() => {
14581569
@Component({

0 commit comments

Comments
 (0)