The Angular 5 animations enter transition successfully performs (void => state) but encounters issues with the leave transition (state => void)

Currently tackling a challenge in Angular 5 where I am trying to build a custom carousel image browser from scratch. The goal is to allow users to navigate through images by clicking or swiping to view the next or previous image.

The animations for the :enter transitions are working flawlessly, as indicated in my code with "void => next" and "void => prev". However, the transitions for "next => void" and "prev => void" are not functioning as expected.

Every solution I have found online revolves around child components, setting the element style to display: block, and triggering detectChanges() after changing the state. Despite implementing these steps and even including "display: block" in the animation style, the issue persists. I have double-checked that detectChanges() is called immediately after the state change, but none of these tactics have resolved the problem.

I came across a comment suggesting that detectChanges() may no longer suffice for :leaving animations. The proposed workaround involved enclosing the code responsible for removing the element from the DOM within a setTimeout() callback. Regrettably, even this approach did not yield the desired outcome.

Desperate for a solution, I resorted to copying and pasting the entire code block from a GitHub repository, with minor modifications to variable names. Surprisingly, this also failed to resolve the issue!

This problem is causing me a great deal of frustration. Any assistance would be greatly appreciated.

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');

...

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>

Answer №1

Summary: Opt for using *ngIf over *ngFor and incorporate a setTimeout callback for updating the selectedImage.

During my debugging process, I stumbled upon a solution while exploring an alternative approach. As I delved into debugging, I noticed that the leave animation worked when I placed a breakpoint on the line resetting the array. Referencing the GitHub entry I shared (https://github.com/born2net/Angular-kitchen-sink/blob/master/src/comps/app2/notes/AnimateCards.ts) provided most of the guidance, indicating that newer versions of Angular required a slightly different handling of the animation.

Instead of utilizing an *ngFor directive within the "image-container" div, I have opted for an ngIf linked to a class member named "showIt" on the component. I first update the orientation and invoke detectChanges(). Following this, I set "showIt" to FALSE, then call detectChanges() again. This process may seem repetitive, but omitting the incremental detectChanges() calls seemed to leave DOM changes pending in a "queue" that would execute on subsequent detectChanges() calls. Subsequently, I store the target index for the images array in a local variable and wrap the consecutive calls to update the selectedImage within a setTimeout callback, timed to align with the transition animation.

If you find yourself thinking, "ugh, it's inelegant to resort to using a setTimeout callback," I wholeheartedly concur. However, it appears to be the only method to ensure both :enter and :leave animations occur smoothly. The underlying issue of my original problem stemmed from the rapid DOM updates upon setting the selectedImage array to empty, causing the animation to be overridden before the leave animation could be perceptible to 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 original post remained accurate. Only the click/swipe method underwent alterations.

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);
    }
  }

Answer №2

Rather than utilizing 'state => void', you have the option to utilize :leave as demonstrated below, which will activate this transition when the element is eliminated from the DOM (this typically occurs when using a structural directive such as *ngIf).

transition(':leave', //add your styling here)

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Is it possible to generate a table without any grid lines present?

Currently, I am in the process of designing a table that will function as a popup when a link is clicked within my imported SQL table. An example of this table is included with this message for reference. After conducting thorough research, I attempted to ...

Utilizing Angular2 with Firebase for efficient denormalized data queries

I am currently working on crafting a query for a denormalized database. Drawing inspiration from the example showcased in Firebase's blog post, my objective is to: Retrieve the array of forms associated with the current user and return references to ...

Setup a Single Page Application on Google Cloud Storage with Load Balancer and Content Delivery Network

I am currently exploring how to deploy an Angular or React web application on Google Cloud using GCS, Load Balancer, and CDN. I've configured the LB and the GCS using the urlRewrite feature. However, due to the LB's limitation on full URL rewrit ...

Have the validation state classes (such as .has-error) been removed from Bootstrap 5?

I've noticed that the validation state classes (.has-success, .has-warning, etc) seem to have been removed in bootstrap 5 as they are not working anymore and I can't find them in the bootstrap.css file. Before, I could easily use these classes w ...

What is the best way to utilize the async pipe along with @defer for efficiently loading and declaring variables in the template within Angular 17

One way I can accomplish this is by using @if. An example of this is: @if(items$ | async; as items), where I can assign the array of items to a variable named 'items' using the 'as' keyword in the template. Is there a similar approach ...

Custom Bootstrap design layout where the right column wraps under the left column

Need assistance with my bootstrap layout. Prior to the 980px breakpoint, the right column wraps under the left column. I want it to remain in its position without wrapping. The challenge is ensuring the left column has a fixed width while allowing the ri ...

Troubleshooting Problem with Bootstrap CSS Menu Box Format

I'm having trouble creating a simple menu for my Bootstrap site. What I want to achieve is something like this: https://i.sstatic.net/abZXC.png This is what I have accomplished so far: https://i.sstatic.net/JFVC2.png I've attempted to write th ...

The embedded Twitter widget in the Angular 2+ app is visible only upon initial page load

After implementing the built-in function from Twitter docs into ngAfterViewInit function, my app works flawlessly. However, I encountered an issue where the widget disappears when switching routes. Here is the code that only functions on the initial page ...

Managing the dispatch of a successful action in a component

In my component, I have a form that adds items to a list. Once an item is successfully added, I want to reset the form using form.resetForm();. However, I am struggling to determine when the action of adding the item has been successful. I attempted to sub ...

What advantages can be gained by opting for more precise module imports?

As an illustration, consider a scenario where I have an Angular 6 application and need to bring in MatIconModule from the @angular/material library. Two options could be: import { MatIconModule } from '@angular/material/icon'; Or import { Mat ...

External style sheet link is not operational

Inside base.php <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8> <title>Base</title> <style> body { background: blue; } </style> </head> <body> &l ...

When using an iPhone 6+ in landscape mode with tabs open on Mobile Safari running iOS 8, the Bootstrap navbar-fixed-top may experience difficulty in

Encountered an issue with Bootstraps navbar-fixed-top on iPhone 6+ in landscape mode on iOS 8 when multiple tabs are open. To replicate the bug: 1) Visit http://getbootstrap.com/examples/navbar-fixed-top/ on iPhone 6+ in landscape mode with another tab ...

Creating assets from typescript plugins in Angular 6: A comprehensive guide

Situation I am currently in the process of migrating from Angular 4 and Angular Seed to Angular 6 and Angular CLI. Challenge One issue I am facing is with dynamic loading of plugins within a component using SystemJS. SystemJS.import("client/plugins/" + ...

The font-face feature is only functional in Chrome, not in Firefox or Opera

Hey there, I'm having an issue with the font-face element. It seems to work perfectly fine in Chrome, but doesn't seem to be working in any other browser. Any ideas on what could be causing this? Thanks in advance! Check out the demo here -> ...

Unlocking the Power of Transition: Effortlessly Submitting a Form Post

After the modal finishes fading out, I want my form to be submitted and sent to the email file "refreshform.php". However, currently after the modal fades out, the form does not submit or post anything to the PHP file for sending the email. It simply fades ...

Align Text Center inside a Magical H1 Heading Tag

I'm having a bit of trouble with something that should be simple. I want to center the text inside my h1 tag on a wizard, and I've added this CSS to my stylesheet.css: .h1textalign { text-align:center; } Here's how I'm trying to apply ...

Attach a click event to the button with a defined class using Angular

In order to meet the requirement, I need to track user click events on all buttons with a specific class. To do this, I have to bind the click event to all buttons and ensure that the same function is triggered in all components. Any ideas on how I can ac ...

Issue with Angular filtering when utilizing pipe and mapping the response

Code snippet from shop.service.ts getProducts(brandId?: number, typeId?: number) { let params = new HttpParams(); if (brandId){ params = params.append('brandId', brandId.toString()); } if (typeId){ params = params.append('typeId', ...

Issue with inner span not inheriting max-width set on outer <td> element

Whenever I hover over a table cell, the Angular Bootstrap popover should appear next to the text. However, the 'span' element remains at its full width. <td style="max-width: 50px; text-align: left; white-space:nowrap; overflow:hidden; text- ...

Unable to resolve every parameter

I am facing an issue while trying to inject a service into my component, resulting in the following error: https://i.stack.imgur.com/zA3QB.png news.component.ts import { Component,OnInit } from '@angular/core'; import { NewsService } from &apo ...