0

I'm new to angular animations and was following a guide on how to animate an item. This is the template

    <div 
        #header        
        [@nav-header-animate] = "{
            value: isLoggedIn(),
            params: {
                startHeight: header.clientHeight
            }
        }"
        class="nav-header">
        @switch(isLoggedIn()){
            @case(true){
                <h1 class="title-logged-in">{{ title }}</h1>
            }
            @case(false){
                <h1 class="title-not-logged-in">{{ title }}</h1>
            }
        }
    </div>

This is the animation

  animations: [
    trigger('nav-header-animate', [
      transition('* => *', [
        query(':self', [
          style({height: '{{startHeight}}px'})
        ]),
        query(':enter', [
          style({opacity: 0, transform: 'scale(0.9)'})
        ]),
        query(':leave', [
          style({opacity: 1, transform: 'scale(1)'}),
          animate('150ms ease-in', style({opacity: 0, transform: 'scale(0.9)'}))
        ]),
        group([
          query(':self', [
            animate('150ms ease-in', style({height: '*'}))
          ]),
          query(':enter', [
            animate('150ms ease-in', style({opacity: 1, transform: 'scale(1)'}))
          ])
        ])
      ], {params : {startHeight: 0}})
    ])
  ]

and these are the relevant scss classes

.nav-header{
    display: flex;
    padding: 0;
    padding-left: 1rem;
}
.title-not-logged-in{
    font-size: 5rem;
    margin-top: 30%;
}
.title-logged-in{
    font-size: 1.5rem;
}

and this is how isLoggedIn is calculated

tokens = this.store.selectSignal(selectTokens);
isLoggedIn: Signal<boolean> = computed(() => this.tokens().accessToken !== null);

for the purpose of testing this out I am using a toggle to switch the access token between null and a string.

the height of the nav-header is animating fine. But the animations for opacity and scale are not applied on the h1 tags at all. Once the animation starts it displays both the h1 tags, and when it ends the initial h1 tag is removed. I am using angular 20. Any help is greatly appreciated.

2
  • Hmm, what happens if you replace that switch with a pair of if's? Commented Jul 21 at 7:51
  • @Zlatko same issue persists Commented Jul 22 at 1:34

2 Answers 2

0

There are multiple issues in the above snippet.
1. @switch Directive Doesn't Trigger upon Re-entry

2. optional: true should be set for animation queries

3. Trigger Runs Too Early Before DOM Is Stable

@switch Directive Doesn't Trigger DOM Re-entry
As a result:

  1. query(':enter') and query(':leave') don't detect new/removed DOM elements

  2. Your <h1> elements may never be considered "entering" or "leaving"

Template:

<ng-container [ngSwitch]="isLoggedIn()">
  <h1 *ngSwitchCase="true" class="title-logged-in">{{ title }}</h1>
  <h1 *ngSwitchCase="false" class="title-not-logged-in">{{ title }}</h1>
</ng-container>

Also,
Animation Queries Need optional: true

If a query like query(':enter') or query(':self') doesn't find any matching elements, Angular throws an error unless optional: true is set.

query(':enter', [
  style({ opacity: 0, transform: 'scale(0.9)' })
], { optional: true }),

query(':leave', [
  style({ opacity: 1, transform: 'scale(1)' }),
  animate('150ms ease-in', style({ opacity: 0, transform: 'scale(0.9)' }))
], { optional: true }),

query(':self', [
  style({ height: '{{startHeight}}px' })
], { optional: true })

Apart from this:
the last issue that, Trigger runs too early before DOM Is stable
The use of header.clientHeight inside params may evaluate before Angular renders children, especially during isLoggedIn() switch. So your startHeight is likely 0 or stale.
It can be delayed using setTimeout hack or use afterViewInit hook

@ViewChild('header') headerRef!: ElementRef;
ngAfterViewInit() {
  const height = this.headerRef.nativeElement.clientHeight;
}
Sign up to request clarification or add additional context in comments.

1 Comment

I tried your fix, it still does not work for me. But I do have a few questions about it. Isn't "@"switch and "@"if just the new way of writing ngswitch and ngif, what is the difference. Also the "optional: True", is it required? because I expect there to be something in the child to be animatable. I commented out the height params and :self queries cause that was not the main focus. To got rid of the clientHeight issue and focus on the h1 issue.
0

Solved the issue. The parent of this component had an animation on it, and as I found out while going through to doc, animations on parent block animations on child unless explicitly disabled. I tried that still did not work in my case. So I moved a couple of things around and merged both animations into one. Started working great.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.