Angular 5 animations enter (void => state) works but leave (state => void) not working
Working in Angular 5. Trying to create a carousel type image browser from scratch. User can click/swipe to view next or previous image.
The :enter
type animations are working perfectly (shown in my code as "void => next"
and "void => prev"
). However, the "next => void"
and "prev => void"
animation transitions are not happening.
Every answer on the web seems to revolve around child components, making style for the element display: block
, and calling detectChanges()
after changing state. This isn't a child component scenario, I've already assigned "display: block"
css to the elements even going so far as to include it in the animation style, and I've made sure to add detectChanges()
right after the state change. None of this has worked.
I read on a comment somewhere that detectChanges() doesn't do the trick for :leaving animations anymore. Their workaround was to wrap the code that removes the element from the DOM in a setTimeout() callback. Even this did not work.
It got to the point where I simply copy/pasted the entire code block from https://github.com/born2net/Angular-kitchen-sink/blob/master/src/comps/app2/notes/AnimateCards.ts only changing the variable names. But even that didn't work!
This is making me lose hair. Please help me.
Component (Angular 5 in typescript)
import { Component, OnInit, ChangeDetectorRef, ElementRef, ViewChild } from '@angular/core';
import { trigger, transition, style, animate, keyframes } from '@angular/animations';
type Orientation = ('prev' | 'next' | 'none');
@Component({
selector: 'app-album-browser',
templateUrl: './album-browser.component.html',
styleUrls: ['./album-browser.component.scss'],
animations: [
trigger('animateCarousel',
[
transition('prev => void', // ------> leaving ------->
[
animate('500ms ease-in-out', keyframes([
style({ opacity: 1.0, left: 0 }),
style({ opacity: 0.0, left: 200 })
]))
]),
transition('void => prev', // ----> entering ---->
[
animate('500ms ease-in-out', keyframes([
style({ opacity: 0.0, left: -200, zIndex: 2 }),
style({ opacity: 1.0, left: 0, zIndex: 2 })
]))
]),
transition('next => void', // <------- leaving <-------
[
animate('500ms ease-in-out', keyframes([
style({ opacity: 1.0, right: 0 }),
style({ opacity: 0.0, right: 200 })
]))
]),
transition('void => next', // <------- entering <--------
[
animate('500ms ease-in-out', keyframes([
style({ opacity: 0.0, right: -200, zIndex: 2 }),
style({ opacity: 1.0, right: 0, zIndex: 2 })
]))
])
])
]
})
export class AlbumBrowserComponent implements OnInit, AfterViewInit {
private readonly SWIPE_ACTION = { LEFT: 'swipeleft', RIGHT: 'swiperight'};
public readonly LEFT = 'LEFT';
public readonly RIGHT = 'RIGHT';
public orientation: Orientation = 'none';
public images: string = ;
public selectedImage: string = ;
public changeDetectorRef: ChangeDetectorRef;
constructor(changeDetectorRef: ChangeDetectorRef) {
this.changeDetectorRef = changeDetectorRef;
this.images.push('../../../assets/images/1.jpg');
this.images.push('../../../assets/images/2.jpg');
this.images.push('../../../assets/images/3.jpg');
this.selectedImage.push(this.images[0]);
}
ngOnInit() {
}
public click(direction: string) {
if (direction === this.LEFT) {
this.swipe(this.SWIPE_ACTION.LEFT);
}
if (direction === this.RIGHT) {
this.swipe(this.SWIPE_ACTION.RIGHT);
}
}
public swipe(action = this.SWIPE_ACTION.RIGHT) {
let res: string;
const index = this.images.indexOf(this.selectedImage[0]);
if (action === this.SWIPE_ACTION.LEFT) {
this.orientation = 'next';
this.selectedImage = ;
this.changeDetectorRef.detectChanges();
res = !!this.images[index + 1] ?
this.images[index + 1] :
this.images[0];
}
if (action === this.SWIPE_ACTION.RIGHT) {
this.orientation = 'prev';
this.selectedImage = ;
this.changeDetectorRef.detectChanges();
res = !!this.images[index - 1] ?
this.images[index - 1] :
this.images[this.images.length - 1];
}
this.selectedImage.push(res);
}
}
Template
<div class="album-browser-container">
<div class="left arrow small-glow" (click)="click(LEFT)"></div>
<div class="viewport-frame glow">
<div class="viewport">
<div class="image-slider"
(swipeleft)="swipe($event.type)"
(swiperight)="swipe($event.type)">
<div class="carousel"
*ngFor="let image of selectedImage">
<div class="image-container"
[@animateCarousel]="orientation">
<img [src]="image" class="album-image">
</div>
</div>
</div>
</div>
</div>
<div class="right arrow small-glow" (click)="click(RIGHT)"></div>
</div>
css angular
add a comment |
Working in Angular 5. Trying to create a carousel type image browser from scratch. User can click/swipe to view next or previous image.
The :enter
type animations are working perfectly (shown in my code as "void => next"
and "void => prev"
). However, the "next => void"
and "prev => void"
animation transitions are not happening.
Every answer on the web seems to revolve around child components, making style for the element display: block
, and calling detectChanges()
after changing state. This isn't a child component scenario, I've already assigned "display: block"
css to the elements even going so far as to include it in the animation style, and I've made sure to add detectChanges()
right after the state change. None of this has worked.
I read on a comment somewhere that detectChanges() doesn't do the trick for :leaving animations anymore. Their workaround was to wrap the code that removes the element from the DOM in a setTimeout() callback. Even this did not work.
It got to the point where I simply copy/pasted the entire code block from https://github.com/born2net/Angular-kitchen-sink/blob/master/src/comps/app2/notes/AnimateCards.ts only changing the variable names. But even that didn't work!
This is making me lose hair. Please help me.
Component (Angular 5 in typescript)
import { Component, OnInit, ChangeDetectorRef, ElementRef, ViewChild } from '@angular/core';
import { trigger, transition, style, animate, keyframes } from '@angular/animations';
type Orientation = ('prev' | 'next' | 'none');
@Component({
selector: 'app-album-browser',
templateUrl: './album-browser.component.html',
styleUrls: ['./album-browser.component.scss'],
animations: [
trigger('animateCarousel',
[
transition('prev => void', // ------> leaving ------->
[
animate('500ms ease-in-out', keyframes([
style({ opacity: 1.0, left: 0 }),
style({ opacity: 0.0, left: 200 })
]))
]),
transition('void => prev', // ----> entering ---->
[
animate('500ms ease-in-out', keyframes([
style({ opacity: 0.0, left: -200, zIndex: 2 }),
style({ opacity: 1.0, left: 0, zIndex: 2 })
]))
]),
transition('next => void', // <------- leaving <-------
[
animate('500ms ease-in-out', keyframes([
style({ opacity: 1.0, right: 0 }),
style({ opacity: 0.0, right: 200 })
]))
]),
transition('void => next', // <------- entering <--------
[
animate('500ms ease-in-out', keyframes([
style({ opacity: 0.0, right: -200, zIndex: 2 }),
style({ opacity: 1.0, right: 0, zIndex: 2 })
]))
])
])
]
})
export class AlbumBrowserComponent implements OnInit, AfterViewInit {
private readonly SWIPE_ACTION = { LEFT: 'swipeleft', RIGHT: 'swiperight'};
public readonly LEFT = 'LEFT';
public readonly RIGHT = 'RIGHT';
public orientation: Orientation = 'none';
public images: string = ;
public selectedImage: string = ;
public changeDetectorRef: ChangeDetectorRef;
constructor(changeDetectorRef: ChangeDetectorRef) {
this.changeDetectorRef = changeDetectorRef;
this.images.push('../../../assets/images/1.jpg');
this.images.push('../../../assets/images/2.jpg');
this.images.push('../../../assets/images/3.jpg');
this.selectedImage.push(this.images[0]);
}
ngOnInit() {
}
public click(direction: string) {
if (direction === this.LEFT) {
this.swipe(this.SWIPE_ACTION.LEFT);
}
if (direction === this.RIGHT) {
this.swipe(this.SWIPE_ACTION.RIGHT);
}
}
public swipe(action = this.SWIPE_ACTION.RIGHT) {
let res: string;
const index = this.images.indexOf(this.selectedImage[0]);
if (action === this.SWIPE_ACTION.LEFT) {
this.orientation = 'next';
this.selectedImage = ;
this.changeDetectorRef.detectChanges();
res = !!this.images[index + 1] ?
this.images[index + 1] :
this.images[0];
}
if (action === this.SWIPE_ACTION.RIGHT) {
this.orientation = 'prev';
this.selectedImage = ;
this.changeDetectorRef.detectChanges();
res = !!this.images[index - 1] ?
this.images[index - 1] :
this.images[this.images.length - 1];
}
this.selectedImage.push(res);
}
}
Template
<div class="album-browser-container">
<div class="left arrow small-glow" (click)="click(LEFT)"></div>
<div class="viewport-frame glow">
<div class="viewport">
<div class="image-slider"
(swipeleft)="swipe($event.type)"
(swiperight)="swipe($event.type)">
<div class="carousel"
*ngFor="let image of selectedImage">
<div class="image-container"
[@animateCarousel]="orientation">
<img [src]="image" class="album-image">
</div>
</div>
</div>
</div>
</div>
<div class="right arrow small-glow" (click)="click(RIGHT)"></div>
</div>
css angular
add a comment |
Working in Angular 5. Trying to create a carousel type image browser from scratch. User can click/swipe to view next or previous image.
The :enter
type animations are working perfectly (shown in my code as "void => next"
and "void => prev"
). However, the "next => void"
and "prev => void"
animation transitions are not happening.
Every answer on the web seems to revolve around child components, making style for the element display: block
, and calling detectChanges()
after changing state. This isn't a child component scenario, I've already assigned "display: block"
css to the elements even going so far as to include it in the animation style, and I've made sure to add detectChanges()
right after the state change. None of this has worked.
I read on a comment somewhere that detectChanges() doesn't do the trick for :leaving animations anymore. Their workaround was to wrap the code that removes the element from the DOM in a setTimeout() callback. Even this did not work.
It got to the point where I simply copy/pasted the entire code block from https://github.com/born2net/Angular-kitchen-sink/blob/master/src/comps/app2/notes/AnimateCards.ts only changing the variable names. But even that didn't work!
This is making me lose hair. Please help me.
Component (Angular 5 in typescript)
import { Component, OnInit, ChangeDetectorRef, ElementRef, ViewChild } from '@angular/core';
import { trigger, transition, style, animate, keyframes } from '@angular/animations';
type Orientation = ('prev' | 'next' | 'none');
@Component({
selector: 'app-album-browser',
templateUrl: './album-browser.component.html',
styleUrls: ['./album-browser.component.scss'],
animations: [
trigger('animateCarousel',
[
transition('prev => void', // ------> leaving ------->
[
animate('500ms ease-in-out', keyframes([
style({ opacity: 1.0, left: 0 }),
style({ opacity: 0.0, left: 200 })
]))
]),
transition('void => prev', // ----> entering ---->
[
animate('500ms ease-in-out', keyframes([
style({ opacity: 0.0, left: -200, zIndex: 2 }),
style({ opacity: 1.0, left: 0, zIndex: 2 })
]))
]),
transition('next => void', // <------- leaving <-------
[
animate('500ms ease-in-out', keyframes([
style({ opacity: 1.0, right: 0 }),
style({ opacity: 0.0, right: 200 })
]))
]),
transition('void => next', // <------- entering <--------
[
animate('500ms ease-in-out', keyframes([
style({ opacity: 0.0, right: -200, zIndex: 2 }),
style({ opacity: 1.0, right: 0, zIndex: 2 })
]))
])
])
]
})
export class AlbumBrowserComponent implements OnInit, AfterViewInit {
private readonly SWIPE_ACTION = { LEFT: 'swipeleft', RIGHT: 'swiperight'};
public readonly LEFT = 'LEFT';
public readonly RIGHT = 'RIGHT';
public orientation: Orientation = 'none';
public images: string = ;
public selectedImage: string = ;
public changeDetectorRef: ChangeDetectorRef;
constructor(changeDetectorRef: ChangeDetectorRef) {
this.changeDetectorRef = changeDetectorRef;
this.images.push('../../../assets/images/1.jpg');
this.images.push('../../../assets/images/2.jpg');
this.images.push('../../../assets/images/3.jpg');
this.selectedImage.push(this.images[0]);
}
ngOnInit() {
}
public click(direction: string) {
if (direction === this.LEFT) {
this.swipe(this.SWIPE_ACTION.LEFT);
}
if (direction === this.RIGHT) {
this.swipe(this.SWIPE_ACTION.RIGHT);
}
}
public swipe(action = this.SWIPE_ACTION.RIGHT) {
let res: string;
const index = this.images.indexOf(this.selectedImage[0]);
if (action === this.SWIPE_ACTION.LEFT) {
this.orientation = 'next';
this.selectedImage = ;
this.changeDetectorRef.detectChanges();
res = !!this.images[index + 1] ?
this.images[index + 1] :
this.images[0];
}
if (action === this.SWIPE_ACTION.RIGHT) {
this.orientation = 'prev';
this.selectedImage = ;
this.changeDetectorRef.detectChanges();
res = !!this.images[index - 1] ?
this.images[index - 1] :
this.images[this.images.length - 1];
}
this.selectedImage.push(res);
}
}
Template
<div class="album-browser-container">
<div class="left arrow small-glow" (click)="click(LEFT)"></div>
<div class="viewport-frame glow">
<div class="viewport">
<div class="image-slider"
(swipeleft)="swipe($event.type)"
(swiperight)="swipe($event.type)">
<div class="carousel"
*ngFor="let image of selectedImage">
<div class="image-container"
[@animateCarousel]="orientation">
<img [src]="image" class="album-image">
</div>
</div>
</div>
</div>
</div>
<div class="right arrow small-glow" (click)="click(RIGHT)"></div>
</div>
css angular
Working in Angular 5. Trying to create a carousel type image browser from scratch. User can click/swipe to view next or previous image.
The :enter
type animations are working perfectly (shown in my code as "void => next"
and "void => prev"
). However, the "next => void"
and "prev => void"
animation transitions are not happening.
Every answer on the web seems to revolve around child components, making style for the element display: block
, and calling detectChanges()
after changing state. This isn't a child component scenario, I've already assigned "display: block"
css to the elements even going so far as to include it in the animation style, and I've made sure to add detectChanges()
right after the state change. None of this has worked.
I read on a comment somewhere that detectChanges() doesn't do the trick for :leaving animations anymore. Their workaround was to wrap the code that removes the element from the DOM in a setTimeout() callback. Even this did not work.
It got to the point where I simply copy/pasted the entire code block from https://github.com/born2net/Angular-kitchen-sink/blob/master/src/comps/app2/notes/AnimateCards.ts only changing the variable names. But even that didn't work!
This is making me lose hair. Please help me.
Component (Angular 5 in typescript)
import { Component, OnInit, ChangeDetectorRef, ElementRef, ViewChild } from '@angular/core';
import { trigger, transition, style, animate, keyframes } from '@angular/animations';
type Orientation = ('prev' | 'next' | 'none');
@Component({
selector: 'app-album-browser',
templateUrl: './album-browser.component.html',
styleUrls: ['./album-browser.component.scss'],
animations: [
trigger('animateCarousel',
[
transition('prev => void', // ------> leaving ------->
[
animate('500ms ease-in-out', keyframes([
style({ opacity: 1.0, left: 0 }),
style({ opacity: 0.0, left: 200 })
]))
]),
transition('void => prev', // ----> entering ---->
[
animate('500ms ease-in-out', keyframes([
style({ opacity: 0.0, left: -200, zIndex: 2 }),
style({ opacity: 1.0, left: 0, zIndex: 2 })
]))
]),
transition('next => void', // <------- leaving <-------
[
animate('500ms ease-in-out', keyframes([
style({ opacity: 1.0, right: 0 }),
style({ opacity: 0.0, right: 200 })
]))
]),
transition('void => next', // <------- entering <--------
[
animate('500ms ease-in-out', keyframes([
style({ opacity: 0.0, right: -200, zIndex: 2 }),
style({ opacity: 1.0, right: 0, zIndex: 2 })
]))
])
])
]
})
export class AlbumBrowserComponent implements OnInit, AfterViewInit {
private readonly SWIPE_ACTION = { LEFT: 'swipeleft', RIGHT: 'swiperight'};
public readonly LEFT = 'LEFT';
public readonly RIGHT = 'RIGHT';
public orientation: Orientation = 'none';
public images: string = ;
public selectedImage: string = ;
public changeDetectorRef: ChangeDetectorRef;
constructor(changeDetectorRef: ChangeDetectorRef) {
this.changeDetectorRef = changeDetectorRef;
this.images.push('../../../assets/images/1.jpg');
this.images.push('../../../assets/images/2.jpg');
this.images.push('../../../assets/images/3.jpg');
this.selectedImage.push(this.images[0]);
}
ngOnInit() {
}
public click(direction: string) {
if (direction === this.LEFT) {
this.swipe(this.SWIPE_ACTION.LEFT);
}
if (direction === this.RIGHT) {
this.swipe(this.SWIPE_ACTION.RIGHT);
}
}
public swipe(action = this.SWIPE_ACTION.RIGHT) {
let res: string;
const index = this.images.indexOf(this.selectedImage[0]);
if (action === this.SWIPE_ACTION.LEFT) {
this.orientation = 'next';
this.selectedImage = ;
this.changeDetectorRef.detectChanges();
res = !!this.images[index + 1] ?
this.images[index + 1] :
this.images[0];
}
if (action === this.SWIPE_ACTION.RIGHT) {
this.orientation = 'prev';
this.selectedImage = ;
this.changeDetectorRef.detectChanges();
res = !!this.images[index - 1] ?
this.images[index - 1] :
this.images[this.images.length - 1];
}
this.selectedImage.push(res);
}
}
Template
<div class="album-browser-container">
<div class="left arrow small-glow" (click)="click(LEFT)"></div>
<div class="viewport-frame glow">
<div class="viewport">
<div class="image-slider"
(swipeleft)="swipe($event.type)"
(swiperight)="swipe($event.type)">
<div class="carousel"
*ngFor="let image of selectedImage">
<div class="image-container"
[@animateCarousel]="orientation">
<img [src]="image" class="album-image">
</div>
</div>
</div>
</div>
</div>
<div class="right arrow small-glow" (click)="click(RIGHT)"></div>
</div>
css angular
css angular
edited Nov 24 '18 at 3:10
Christian-Capo
asked Nov 24 '18 at 0:14
Christian-CapoChristian-Capo
11
11
add a comment |
add a comment |
1 Answer
1
active
oldest
votes
TLDR; use *ngIf instead of *ngFor and wrap the updating of the selectedImage in a setTimeout callback.
I came across the solution while debugging an alternative I came up with. As I debugged, I saw the leave animation working when I put a breakpoint on the array reset line. The GitHub entry I posted above (https://github.com/born2net/Angular-kitchen-sink/blob/master/src/comps/app2/notes/AnimateCards.ts) got me most of the way there, it appears that later releases of Angular created the need to handle the animation in a slightly different way.
Instead of using an *ngFor
directive in the "image-container" div, I'm using an ngIf
bound to a class member called "showIt" on the component. I update the orientation and detectChanges() first. Then update the "showIt" to FALSE, followed by another call detectChanges(). This may seem redundant but when I leave out the incremental calls to detectChanges(), it almost seems like there are DOM changes left in a "queue" (for lack of a better term) which end up getting executed on subsequent calls to detectChanges(). Then, I store the target index for the images array in a local variable and finally wrap the subsequent calls to update the selectedImage in a setTimeout callback with the timeout set to the same timing as the transition animation.
If you're thinking "ugh how inelegant to have to wrap code in a setTimeout callback", I couldn't agree more. But it just seems to be the only way to ensure both :enter
and :leave
animations are able to transpire. The root cause of my original problem was that when the selectedImage array was set to empty, the DOM was getting updated so quickly that the animation was getting overridden before the leave animation could even be detected by the human eye.
Final template
<div class="album-browser-container">
<div class="left arrow small-glow" (click)="click(LEFT)"></div>
<div class="viewport-frame glow">
<div class="viewport">
<div class="image-slider"
(swipeleft)="swipe($event.type)"
(swiperight)="swipe($event.type)">
<div class="carousel">
<div class="image-container"
*ngIf="showIt"
[@animateCarousel]="orientation">
<img [src]="selectedImage[0]"
class="album-image"
(swipeleft)="swipe($event.type)"
(swiperight)="swipe($event.type)">
</div>
</div>
</div>
</div>
</div>
<div class="right arrow small-glow" (click)="click(RIGHT)"></div>
</div>
The animations from my OP were actually accurate. Only the click / swipe method changed.
Component (truncated)
public swipe(action = this.SWIPE_ACTION.RIGHT) {
let res: string;
if (action === this.SWIPE_ACTION.LEFT) {
this.orientation = 'next';
this.changeDetectorRef.detectChanges();
this.showIt = false;
this.changeDetectorRef.detectChanges();
const index = this.images.indexOf(this.selectedImage[0]);
res = !!this.images[index + 1] ?
this.images[index + 1] :
this.images[0];
setTimeout(() => {
this.selectedImage = ;
this.selectedImage.push(res);
this.showIt = true;
this.changeDetectorRef.detectChanges();
}, 300);
}
if (action === this.SWIPE_ACTION.RIGHT) {
this.orientation = 'prev';
this.changeDetectorRef.detectChanges();
this.showIt = false;
this.changeDetectorRef.detectChanges();
const index = this.images.indexOf(this.selectedImage[0]);
res = !!this.images[index - 1] ?
this.images[index - 1] :
this.images[this.images.length - 1];
setTimeout(() => {
this.selectedImage = ;
this.selectedImage.push(res);
this.showIt = true;
this.changeDetectorRef.detectChanges();
}, 300);
}
}
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53454123%2fangular-5-animations-enter-void-state-works-but-leave-state-void-not-w%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
TLDR; use *ngIf instead of *ngFor and wrap the updating of the selectedImage in a setTimeout callback.
I came across the solution while debugging an alternative I came up with. As I debugged, I saw the leave animation working when I put a breakpoint on the array reset line. The GitHub entry I posted above (https://github.com/born2net/Angular-kitchen-sink/blob/master/src/comps/app2/notes/AnimateCards.ts) got me most of the way there, it appears that later releases of Angular created the need to handle the animation in a slightly different way.
Instead of using an *ngFor
directive in the "image-container" div, I'm using an ngIf
bound to a class member called "showIt" on the component. I update the orientation and detectChanges() first. Then update the "showIt" to FALSE, followed by another call detectChanges(). This may seem redundant but when I leave out the incremental calls to detectChanges(), it almost seems like there are DOM changes left in a "queue" (for lack of a better term) which end up getting executed on subsequent calls to detectChanges(). Then, I store the target index for the images array in a local variable and finally wrap the subsequent calls to update the selectedImage in a setTimeout callback with the timeout set to the same timing as the transition animation.
If you're thinking "ugh how inelegant to have to wrap code in a setTimeout callback", I couldn't agree more. But it just seems to be the only way to ensure both :enter
and :leave
animations are able to transpire. The root cause of my original problem was that when the selectedImage array was set to empty, the DOM was getting updated so quickly that the animation was getting overridden before the leave animation could even be detected by the human eye.
Final template
<div class="album-browser-container">
<div class="left arrow small-glow" (click)="click(LEFT)"></div>
<div class="viewport-frame glow">
<div class="viewport">
<div class="image-slider"
(swipeleft)="swipe($event.type)"
(swiperight)="swipe($event.type)">
<div class="carousel">
<div class="image-container"
*ngIf="showIt"
[@animateCarousel]="orientation">
<img [src]="selectedImage[0]"
class="album-image"
(swipeleft)="swipe($event.type)"
(swiperight)="swipe($event.type)">
</div>
</div>
</div>
</div>
</div>
<div class="right arrow small-glow" (click)="click(RIGHT)"></div>
</div>
The animations from my OP were actually accurate. Only the click / swipe method changed.
Component (truncated)
public swipe(action = this.SWIPE_ACTION.RIGHT) {
let res: string;
if (action === this.SWIPE_ACTION.LEFT) {
this.orientation = 'next';
this.changeDetectorRef.detectChanges();
this.showIt = false;
this.changeDetectorRef.detectChanges();
const index = this.images.indexOf(this.selectedImage[0]);
res = !!this.images[index + 1] ?
this.images[index + 1] :
this.images[0];
setTimeout(() => {
this.selectedImage = ;
this.selectedImage.push(res);
this.showIt = true;
this.changeDetectorRef.detectChanges();
}, 300);
}
if (action === this.SWIPE_ACTION.RIGHT) {
this.orientation = 'prev';
this.changeDetectorRef.detectChanges();
this.showIt = false;
this.changeDetectorRef.detectChanges();
const index = this.images.indexOf(this.selectedImage[0]);
res = !!this.images[index - 1] ?
this.images[index - 1] :
this.images[this.images.length - 1];
setTimeout(() => {
this.selectedImage = ;
this.selectedImage.push(res);
this.showIt = true;
this.changeDetectorRef.detectChanges();
}, 300);
}
}
add a comment |
TLDR; use *ngIf instead of *ngFor and wrap the updating of the selectedImage in a setTimeout callback.
I came across the solution while debugging an alternative I came up with. As I debugged, I saw the leave animation working when I put a breakpoint on the array reset line. The GitHub entry I posted above (https://github.com/born2net/Angular-kitchen-sink/blob/master/src/comps/app2/notes/AnimateCards.ts) got me most of the way there, it appears that later releases of Angular created the need to handle the animation in a slightly different way.
Instead of using an *ngFor
directive in the "image-container" div, I'm using an ngIf
bound to a class member called "showIt" on the component. I update the orientation and detectChanges() first. Then update the "showIt" to FALSE, followed by another call detectChanges(). This may seem redundant but when I leave out the incremental calls to detectChanges(), it almost seems like there are DOM changes left in a "queue" (for lack of a better term) which end up getting executed on subsequent calls to detectChanges(). Then, I store the target index for the images array in a local variable and finally wrap the subsequent calls to update the selectedImage in a setTimeout callback with the timeout set to the same timing as the transition animation.
If you're thinking "ugh how inelegant to have to wrap code in a setTimeout callback", I couldn't agree more. But it just seems to be the only way to ensure both :enter
and :leave
animations are able to transpire. The root cause of my original problem was that when the selectedImage array was set to empty, the DOM was getting updated so quickly that the animation was getting overridden before the leave animation could even be detected by the human eye.
Final template
<div class="album-browser-container">
<div class="left arrow small-glow" (click)="click(LEFT)"></div>
<div class="viewport-frame glow">
<div class="viewport">
<div class="image-slider"
(swipeleft)="swipe($event.type)"
(swiperight)="swipe($event.type)">
<div class="carousel">
<div class="image-container"
*ngIf="showIt"
[@animateCarousel]="orientation">
<img [src]="selectedImage[0]"
class="album-image"
(swipeleft)="swipe($event.type)"
(swiperight)="swipe($event.type)">
</div>
</div>
</div>
</div>
</div>
<div class="right arrow small-glow" (click)="click(RIGHT)"></div>
</div>
The animations from my OP were actually accurate. Only the click / swipe method changed.
Component (truncated)
public swipe(action = this.SWIPE_ACTION.RIGHT) {
let res: string;
if (action === this.SWIPE_ACTION.LEFT) {
this.orientation = 'next';
this.changeDetectorRef.detectChanges();
this.showIt = false;
this.changeDetectorRef.detectChanges();
const index = this.images.indexOf(this.selectedImage[0]);
res = !!this.images[index + 1] ?
this.images[index + 1] :
this.images[0];
setTimeout(() => {
this.selectedImage = ;
this.selectedImage.push(res);
this.showIt = true;
this.changeDetectorRef.detectChanges();
}, 300);
}
if (action === this.SWIPE_ACTION.RIGHT) {
this.orientation = 'prev';
this.changeDetectorRef.detectChanges();
this.showIt = false;
this.changeDetectorRef.detectChanges();
const index = this.images.indexOf(this.selectedImage[0]);
res = !!this.images[index - 1] ?
this.images[index - 1] :
this.images[this.images.length - 1];
setTimeout(() => {
this.selectedImage = ;
this.selectedImage.push(res);
this.showIt = true;
this.changeDetectorRef.detectChanges();
}, 300);
}
}
add a comment |
TLDR; use *ngIf instead of *ngFor and wrap the updating of the selectedImage in a setTimeout callback.
I came across the solution while debugging an alternative I came up with. As I debugged, I saw the leave animation working when I put a breakpoint on the array reset line. The GitHub entry I posted above (https://github.com/born2net/Angular-kitchen-sink/blob/master/src/comps/app2/notes/AnimateCards.ts) got me most of the way there, it appears that later releases of Angular created the need to handle the animation in a slightly different way.
Instead of using an *ngFor
directive in the "image-container" div, I'm using an ngIf
bound to a class member called "showIt" on the component. I update the orientation and detectChanges() first. Then update the "showIt" to FALSE, followed by another call detectChanges(). This may seem redundant but when I leave out the incremental calls to detectChanges(), it almost seems like there are DOM changes left in a "queue" (for lack of a better term) which end up getting executed on subsequent calls to detectChanges(). Then, I store the target index for the images array in a local variable and finally wrap the subsequent calls to update the selectedImage in a setTimeout callback with the timeout set to the same timing as the transition animation.
If you're thinking "ugh how inelegant to have to wrap code in a setTimeout callback", I couldn't agree more. But it just seems to be the only way to ensure both :enter
and :leave
animations are able to transpire. The root cause of my original problem was that when the selectedImage array was set to empty, the DOM was getting updated so quickly that the animation was getting overridden before the leave animation could even be detected by the human eye.
Final template
<div class="album-browser-container">
<div class="left arrow small-glow" (click)="click(LEFT)"></div>
<div class="viewport-frame glow">
<div class="viewport">
<div class="image-slider"
(swipeleft)="swipe($event.type)"
(swiperight)="swipe($event.type)">
<div class="carousel">
<div class="image-container"
*ngIf="showIt"
[@animateCarousel]="orientation">
<img [src]="selectedImage[0]"
class="album-image"
(swipeleft)="swipe($event.type)"
(swiperight)="swipe($event.type)">
</div>
</div>
</div>
</div>
</div>
<div class="right arrow small-glow" (click)="click(RIGHT)"></div>
</div>
The animations from my OP were actually accurate. Only the click / swipe method changed.
Component (truncated)
public swipe(action = this.SWIPE_ACTION.RIGHT) {
let res: string;
if (action === this.SWIPE_ACTION.LEFT) {
this.orientation = 'next';
this.changeDetectorRef.detectChanges();
this.showIt = false;
this.changeDetectorRef.detectChanges();
const index = this.images.indexOf(this.selectedImage[0]);
res = !!this.images[index + 1] ?
this.images[index + 1] :
this.images[0];
setTimeout(() => {
this.selectedImage = ;
this.selectedImage.push(res);
this.showIt = true;
this.changeDetectorRef.detectChanges();
}, 300);
}
if (action === this.SWIPE_ACTION.RIGHT) {
this.orientation = 'prev';
this.changeDetectorRef.detectChanges();
this.showIt = false;
this.changeDetectorRef.detectChanges();
const index = this.images.indexOf(this.selectedImage[0]);
res = !!this.images[index - 1] ?
this.images[index - 1] :
this.images[this.images.length - 1];
setTimeout(() => {
this.selectedImage = ;
this.selectedImage.push(res);
this.showIt = true;
this.changeDetectorRef.detectChanges();
}, 300);
}
}
TLDR; use *ngIf instead of *ngFor and wrap the updating of the selectedImage in a setTimeout callback.
I came across the solution while debugging an alternative I came up with. As I debugged, I saw the leave animation working when I put a breakpoint on the array reset line. The GitHub entry I posted above (https://github.com/born2net/Angular-kitchen-sink/blob/master/src/comps/app2/notes/AnimateCards.ts) got me most of the way there, it appears that later releases of Angular created the need to handle the animation in a slightly different way.
Instead of using an *ngFor
directive in the "image-container" div, I'm using an ngIf
bound to a class member called "showIt" on the component. I update the orientation and detectChanges() first. Then update the "showIt" to FALSE, followed by another call detectChanges(). This may seem redundant but when I leave out the incremental calls to detectChanges(), it almost seems like there are DOM changes left in a "queue" (for lack of a better term) which end up getting executed on subsequent calls to detectChanges(). Then, I store the target index for the images array in a local variable and finally wrap the subsequent calls to update the selectedImage in a setTimeout callback with the timeout set to the same timing as the transition animation.
If you're thinking "ugh how inelegant to have to wrap code in a setTimeout callback", I couldn't agree more. But it just seems to be the only way to ensure both :enter
and :leave
animations are able to transpire. The root cause of my original problem was that when the selectedImage array was set to empty, the DOM was getting updated so quickly that the animation was getting overridden before the leave animation could even be detected by the human eye.
Final template
<div class="album-browser-container">
<div class="left arrow small-glow" (click)="click(LEFT)"></div>
<div class="viewport-frame glow">
<div class="viewport">
<div class="image-slider"
(swipeleft)="swipe($event.type)"
(swiperight)="swipe($event.type)">
<div class="carousel">
<div class="image-container"
*ngIf="showIt"
[@animateCarousel]="orientation">
<img [src]="selectedImage[0]"
class="album-image"
(swipeleft)="swipe($event.type)"
(swiperight)="swipe($event.type)">
</div>
</div>
</div>
</div>
</div>
<div class="right arrow small-glow" (click)="click(RIGHT)"></div>
</div>
The animations from my OP were actually accurate. Only the click / swipe method changed.
Component (truncated)
public swipe(action = this.SWIPE_ACTION.RIGHT) {
let res: string;
if (action === this.SWIPE_ACTION.LEFT) {
this.orientation = 'next';
this.changeDetectorRef.detectChanges();
this.showIt = false;
this.changeDetectorRef.detectChanges();
const index = this.images.indexOf(this.selectedImage[0]);
res = !!this.images[index + 1] ?
this.images[index + 1] :
this.images[0];
setTimeout(() => {
this.selectedImage = ;
this.selectedImage.push(res);
this.showIt = true;
this.changeDetectorRef.detectChanges();
}, 300);
}
if (action === this.SWIPE_ACTION.RIGHT) {
this.orientation = 'prev';
this.changeDetectorRef.detectChanges();
this.showIt = false;
this.changeDetectorRef.detectChanges();
const index = this.images.indexOf(this.selectedImage[0]);
res = !!this.images[index - 1] ?
this.images[index - 1] :
this.images[this.images.length - 1];
setTimeout(() => {
this.selectedImage = ;
this.selectedImage.push(res);
this.showIt = true;
this.changeDetectorRef.detectChanges();
}, 300);
}
}
edited Nov 24 '18 at 6:58
answered Nov 24 '18 at 6:33
Christian-CapoChristian-Capo
11
11
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53454123%2fangular-5-animations-enter-void-state-works-but-leave-state-void-not-w%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown