最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

signals - Difference between changing input of Angular component inside and outside a component (+animations) - Stack Overflow

programmeradmin10浏览0评论

I made an accordion component (angular18) with lazy loading of it's body content and smooth animation of opening and closing. So it works pretty well when i toggle it from inside (by clicking on header), but when i toggle it from outside - it doesn't open smoothly (but closes smoothly).

Here's the stackblitz:

What's the point of this behavior? i'm quite new to Angular animations..

tried to replace model with input - same thing. Css animations with maxheight are not good enough because of different content sizes.

upd. added squares to ensure than animations works there - they do.

I made an accordion component (angular18) with lazy loading of it's body content and smooth animation of opening and closing. So it works pretty well when i toggle it from inside (by clicking on header), but when i toggle it from outside - it doesn't open smoothly (but closes smoothly).

Here's the stackblitz: https://stackblitz/edit/stackblitz-starters-rjsmkv

What's the point of this behavior? i'm quite new to Angular animations..

tried to replace model with input - same thing. Css animations with maxheight are not good enough because of different content sizes.

upd. added squares to ensure than animations works there - they do.

Share Improve this question edited Nov 16, 2024 at 17:45 argentum4k asked Nov 16, 2024 at 14:35 argentum4kargentum4k 331 silver badge5 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 1

I think the problem is due to mixing of signal and observable updates. Where the signal is updated first followed by the observable being updated later.

The scenario is there is no content inside the accordion and the signal is updated, then the animation has already started. At a later point of time the observable delayedIsOpen is updated by that time the animation is midway or even over. This causes the jerk.

I have noticed that using both the sources as observables, solves the issue.

So we can create another observable isOpenDelayed = toObservable(this.isOpen); just to fix the timing issue, then use this to trigger the animation.

<div
  class="main-content"
  [@content]="
    (isOpenDelayed | async)
      ? { value: 'visible', params: { transitionParams } }
      : { value: 'hidden', params: { transitionParams } }
  "
>
  @if (delayedIsOpen | async) {
  <ng-container *ngTemplateOutlet="accordionBodyRef" />
  }
</div>

Full Code:

HTML:

<div
  class="test"
  [@sqare]="
    isOpen()
      ? { value: 'red', params: { transitionParams } }
      : { value: 'blue', params: { transitionParams } }
  "
>
  isOpen
</div>
<p></p>
<div
  class="test"
  [@sqare]="
    (delayedIsOpen | async)
      ? { value: 'red', params: { transitionParams } }
      : { value: 'blue', params: { transitionParams } }
  "
>
  delayedIsOpen
</div>

<p></p>

<div
  class="accordion"
  [ngStyle]="{ '--transiton-time': transitionTime + 'ms' }"
>
  <div class="accordion-head">
    <div class="top-side top-side_left" (click)="toggle()">
      <ng-content select="[topLeft]" />
    </div>
  </div>
  <div
    class="main-content"
    [@content]="
      (isOpenDelayed | async)
        ? { value: 'visible', params: { transitionParams } }
        : { value: 'hidden', params: { transitionParams } }
    "
  >
    @if (delayedIsOpen | async) {
    <ng-container *ngTemplateOutlet="accordionBodyRef" />
    }
  </div>
</div>

TS:

import {
  animate,
  state,
  style,
  transition,
  trigger,
} from '@angular/animations';
import {
  ChangeDetectionStrategy,
  Component,
  input,
  model,
  ContentChild,
  TemplateRef,
} from '@angular/core';
import { toObservable } from '@angular/core/rxjs-interop';
import { delay, of, switchMap } from 'rxjs';

@Component({
  selector: 'accordion',
  templateUrl: './accordionponent.html',
  styleUrl: './accordionponent.scss',
  changeDetection: ChangeDetectionStrategy.OnPush,
  animations: [
    trigger('content', [
      state(
        'hidden',
        style({
          height: '0',
          visibility: 'hidden',
        })
      ),
      state(
        'visible',
        style({
          height: '*',
          visibility: 'visible',
        })
      ),
      transition('visible <=> hidden', [animate('{{transitionParams}}')]),
      transition('void => *', animate(0)),
    ]),
    trigger('sqare', [
      state(
        'red',
        style({
          background: 'red',
        })
      ),
      state(
        'blue',
        style({
          background: 'blue',
        })
      ),
      transition('red <=> blue', [animate('{{transitionParams}}')]),
      transition('void => *', animate(0)),
    ]),
  ],
})
export class AccordionComponent {
  isOpen = model<boolean>(false);

  transitionTime = 500;
  transitionParams = `${this.transitionTime}ms linear`;

  delayedIsOpen = toObservable(this.isOpen).pipe(
    switchMap((open) =>
      open ? of(open) : of(open).pipe(delay(this.transitionTime))
    )
  );

  isOpenDelayed = toObservable(this.isOpen);

  @ContentChild('accordionBody', { read: TemplateRef })
  accordionBodyRef: TemplateRef<unknown> | null = null;

  toggle(): void {
    this.isOpen.update((value) => !value);
  }
}

Stackblitz Demo

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论