diff --git a/packages/animations/browser/src/render/transition_animation_engine.ts b/packages/animations/browser/src/render/transition_animation_engine.ts
index ec69059ef2..192c747e87 100644
--- a/packages/animations/browser/src/render/transition_animation_engine.ts
+++ b/packages/animations/browser/src/render/transition_animation_engine.ts
@@ -28,13 +28,15 @@ const STAR_SELECTOR = '.ng-star-inserted';
const EMPTY_PLAYER_ARRAY: TransitionAnimationPlayer[] = [];
const NULL_REMOVAL_STATE: ElementAnimationState = {
namespaceId: '',
- setForRemoval: null,
+ setForRemoval: false,
+ setForMove: false,
hasAnimation: false,
removedBeforeQueried: false
};
const NULL_REMOVED_QUERIED_STATE: ElementAnimationState = {
namespaceId: '',
- setForRemoval: null,
+ setForMove: false,
+ setForRemoval: false,
hasAnimation: false,
removedBeforeQueried: true
};
@@ -58,7 +60,8 @@ export interface QueueInstruction {
export const REMOVAL_FLAG = '__ng_removed';
export interface ElementAnimationState {
- setForRemoval: any;
+ setForRemoval: boolean;
+ setForMove: boolean;
hasAnimation: boolean;
namespaceId: string;
removedBeforeQueried: boolean;
@@ -660,6 +663,11 @@ export class TransitionAnimationEngine {
const details = element[REMOVAL_FLAG] as ElementAnimationState;
if (details && details.setForRemoval) {
details.setForRemoval = false;
+ details.setForMove = true;
+ const index = this.collectedLeaveElements.indexOf(element);
+ if (index >= 0) {
+ this.collectedLeaveElements.splice(index, 1);
+ }
}
// in the event that the namespaceId is blank then the caller
@@ -946,9 +954,18 @@ export class TransitionAnimationEngine {
const ns = this._namespaceList[i];
ns.drainQueuedTransitions(microtaskId).forEach(entry => {
const player = entry.player;
+ const element = entry.element;
allPlayers.push(player);
- const element = entry.element;
+ if (this.collectedEnterElements.length) {
+ const details = element[REMOVAL_FLAG] as ElementAnimationState;
+ // move animations are currently not supported...
+ if (details && details.setForMove) {
+ player.destroy();
+ return;
+ }
+ }
+
if (!bodyNode || !this.driver.containsElement(bodyNode, element)) {
player.destroy();
return;
diff --git a/packages/core/test/animation/animation_integration_spec.ts b/packages/core/test/animation/animation_integration_spec.ts
index cc8b9556f1..4980722a36 100644
--- a/packages/core/test/animation/animation_integration_spec.ts
+++ b/packages/core/test/animation/animation_integration_spec.ts
@@ -1453,6 +1453,117 @@ const DEFAULT_COMPONENT_ID = '1';
.toBeTruthy();
});
+ it('should retain state styles when the underlying DOM structure changes even if there are no insert/remove animations',
+ () => {
+ @Component({
+ selector: 'ani-cmp',
+ template: `
+
+ {{ item }}
+
+ `,
+ animations: [trigger('color', [state('green', style({backgroundColor: 'green'}))])]
+ })
+ class Cmp {
+ public colorExp = 'green';
+ public items = [0, 1, 2, 3];
+
+ reorder() {
+ const temp = this.items[0];
+ this.items[0] = this.items[1];
+ this.items[1] = temp;
+ }
+ }
+
+ TestBed.configureTestingModule({declarations: [Cmp]});
+
+ const fixture = TestBed.createComponent(Cmp);
+ const cmp = fixture.componentInstance;
+ fixture.detectChanges();
+
+ let elements: HTMLElement[] = fixture.nativeElement.querySelectorAll('.item');
+ assertBackgroundColor(elements[0], 'green');
+ assertBackgroundColor(elements[1], 'green');
+ assertBackgroundColor(elements[2], 'green');
+ assertBackgroundColor(elements[3], 'green');
+
+ elements[0].title = '0a';
+ elements[1].title = '1a';
+
+ cmp.reorder();
+ fixture.detectChanges();
+
+ elements = fixture.nativeElement.querySelectorAll('.item');
+ assertBackgroundColor(elements[0], 'green');
+ assertBackgroundColor(elements[1], 'green');
+ assertBackgroundColor(elements[2], 'green');
+ assertBackgroundColor(elements[3], 'green');
+
+ function assertBackgroundColor(element: HTMLElement, color: string) {
+ expect(element.style.getPropertyValue('background-color')).toEqual(color);
+ }
+ });
+
+ it('should retain state styles when the underlying DOM structure changes even if there are insert/remove animations',
+ () => {
+ @Component({
+ selector: 'ani-cmp',
+ template: `
+
+ {{ item }}
+
+ `,
+ animations: [trigger(
+ 'color',
+ [
+ transition('* => *', animate(500)),
+ state('green', style({backgroundColor: 'green'}))
+ ])]
+ })
+ class Cmp {
+ public colorExp = 'green';
+ public items = [0, 1, 2, 3];
+
+ reorder() {
+ const temp = this.items[0];
+ this.items[0] = this.items[1];
+ this.items[1] = temp;
+ }
+ }
+
+ TestBed.configureTestingModule({declarations: [Cmp]});
+
+ const fixture = TestBed.createComponent(Cmp);
+ const cmp = fixture.componentInstance;
+ fixture.detectChanges();
+
+ getLog().forEach(p => p.finish());
+
+ let elements: HTMLElement[] = fixture.nativeElement.querySelectorAll('.item');
+ assertBackgroundColor(elements[0], 'green');
+ assertBackgroundColor(elements[1], 'green');
+ assertBackgroundColor(elements[2], 'green');
+ assertBackgroundColor(elements[3], 'green');
+
+ elements[0].title = '0a';
+ elements[1].title = '1a';
+
+ cmp.reorder();
+ fixture.detectChanges();
+
+ getLog().forEach(p => p.finish());
+
+ elements = fixture.nativeElement.querySelectorAll('.item');
+ assertBackgroundColor(elements[0], 'green');
+ assertBackgroundColor(elements[1], 'green');
+ assertBackgroundColor(elements[2], 'green');
+ assertBackgroundColor(elements[3], 'green');
+
+ function assertBackgroundColor(element: HTMLElement, color: string) {
+ expect(element.style.getPropertyValue('background-color')).toEqual(color);
+ }
+ });
+
it('should animate removals of nodes to the `void` state for each animation trigger, but treat all auto styles as pre styles',
() => {
@Component({