Angular Animation not working with Input-Output Child component

374 Views Asked by At

I checked the Angular documentation and some tutorials but have not resolved my problem.

I have a child component accordeon used in many others. In accordeon, it receives a boolean value from the component using it, to know when it must be opened or closed. Accordeon is also divided between two ng-content, one getting the title given by the parent component, the other getting the content of the parent component.

Here the HTML of accordeon:

<div class="d-flex jc-between ai-center p-s" (click)="toggleAccordeon();" [ngClass]="specialAccordeon ? 'accordeon' : 'special-accordeon'">
  <ng-content select="[slot=title]"></ng-content>
  <span class="icon-chevron_bottom" [ngClass]="accordeonOpened ? 'rotation_180' : ''"></span>
</div>
<ng-content [@openCloseAccordeon]=animateAccordeon select="[slot=content]" [ngStyle]="accordeonOpened && {'display': 'none'}"></ng-content>

The .ts file of accordeon:

import { Component, OnInit, Output, EventEmitter, Input } from '@angular/core';
import { state, style, transition, animate, trigger } from '@angular/animations';

@Component({
  selector: 'srp-accordeon',
  templateUrl: './accordeon.component.html',
  styleUrls: ['./accordeon.component.scss'],
  animations: [
    trigger('openCloseAccordeon', [
      state('initial', style({
        display: 'none',
      })),
      state('final', style({
        display: 'block',
      })),
      transition('initial => final', [
        animate('1s')
      ]),
      transition('final => initial', [
        animate('2s')
      ]),
    ]),
  ]
})
export class AccordeonComponent implements OnInit {

  accordeonOpened = false;
  animateAccordeon = 'initial';
  @Output() open: EventEmitter<any> = new EventEmitter();
  @Output() close: EventEmitter<any> = new EventEmitter();
  @Output() openAccordeon: EventEmitter<any> = new EventEmitter();
  @Output() closeAccordeon: EventEmitter<any> = new EventEmitter();

  @Input() specialAccordeon;

  public toggleAccordeon(): void {
    this.animateAccordeon = this.animateAccordeon === 'initial' ? 'final' : 'initial';
    this.accordeonOpened = !this.accordeonOpened;
    if (this.accordeonOpened) {
      this.open.emit(true);
    } else {
      this.close.emit(false);
    }
    if (this.animateAccordeon === 'initial') {
      this.closeAccordeon.emit('initial');
    } else {
      this.openAccordeon.emit('final');
    }
  }

  constructor() { }

  ngOnInit() {
  }

}

The parent component using it, order-delivery, the interesting HTML part:

Currently I manage opening and closing with a ng-class, putting [@openCloseAccordeon]=animateAccordeon doesn't work.

<srp-accordeon [specialAccordeon]="true" (close)="accordeonOpened = false" (open)="accordeonOpened = true" (closeAccordeon)="animateAccordeon = 'initial'" (openAccordeon)="animateAccordeon = 'final'">
    <ng-container slot="title">{{currentOrder.statusLabel}} {{(!currentOrder.isCanceled ? currentOrder.isDeliveryAtHome ? 'order.atHome' : 'order.inWithdrawalPoint' : string.Empty) | translate}}</ng-container>
    <ng-container slot="content">
        <div class="order-delivery__details mb-s" [ngClass]="accordeonOpened ? 'accordeon-opened' : 'accordeon-closed'">

To sum up. I need to get the animation to trigger when the user click on the . I think I miss something, a data to pass to the accordeon component from order-delivery, but don't get what.

Thank you for your help.

1

There are 1 best solutions below

0
On

Eventually I find how to make it work.

accordeon.html:

<div class="d-flex jc-between ai-center p-s" (click)="toggleAccordeon(); toggleAnimation();" [ngClass]="specialAccordeon ? 'accordeon' : 'special-accordeon'">
  <ng-content select="[slot=title]"></ng-content>
  <span class="icon-chevron_bottom" [ngClass]="accordeonOpened ? 'rotation_180' : ''"></span>
</div>
<div [@openCloseAccordeon]=animateAccordeon>
  <ng-content select="[slot=content]"></ng-content>
</div>

accordeon.ts:

import { Component, OnInit, Output, EventEmitter, Input } from '@angular/core';
import { state, style, transition, animate, trigger } from '@angular/animations';

@Component({
  selector: 'srp-accordeon',
  templateUrl: './accordeon.component.html',
  styleUrls: ['./accordeon.component.scss'],
  animations: [
    trigger('openCloseAccordeon', [
      state('initial', style({
        opacity: '0',
      })),
      state('final', style({
        opacity: '1',
      })),
      transition('initial => final', [
        animate('1s')
      ]),
      transition('final => initial', [
        animate('2s')
      ]),
    ]),
  ]
})
export class AccordeonComponent implements OnInit {

  accordeonOpened = false;
  animateAccordeon = 'initial';

  @Output() open: EventEmitter<any> = new EventEmitter();
  @Output() close: EventEmitter<any> = new EventEmitter();

  @Input() specialAccordeon;

  public toggleAccordeon(): void {
    this.accordeonOpened = !this.accordeonOpened;
    if (this.accordeonOpened) {
      this.open.emit(true);
    } else {
      this.close.emit(false);
    }
  }

  public toggleAnimation() {
    this.animateAccordeon = this.animateAccordeon === 'initial' ? 'final' : 'initial';
  }

  constructor() { }

  ngOnInit() {
  }
}

No need to specify something in the parent component. One think to remember, the animation can't trigger on ng-content, this is why I added a div wrapping it.