Pieterjan
Pieterjan

Reputation: 3531

angular: pass data to animation

I've created a bootstrap modal component for angular, but I'd like to choose from which direction the modal enters the window. I've created a StackBlitz too with only the bare minimum.

Bootstrap modal for angular

At the moment my EnterLeaveAnimation looks like this:

export const EnterLeaveAnimation =
  trigger('enterLeave', [
    transition(
      ':enter', [
        style({ top: '-50%' }), // <-- I should be able to change this dynamically, eg: left: '-50%'
        animate('{{ duration }} ease-in-out', style({ top: 0 })),
      ], {
        params: {
          duration: '500ms'
        }
      }
    ),
    transition(
      ':leave', [
        animate('{{ duration }} ease-in-out', style({ top: '-50%' }))
      ], {
        params: {
          duration: '500ms'
        }
      }
    ),
  ]);

ATM this animation is applied to the div like this:

<div class="modal d-block" [@fadeInOut] [@enterLeave] *ngIf="isOpen">
  <!-- [fromDirection]="fromDirection" -->
  ...
</div>

<div [@fadeInOut] *ngIf="isOpen">
  <div class="modal-backdrop fade" [class.show]="isOpen"></div>
</div>

But would eventually end up looking like this:

<div class="modal d-block" [@fadeInOut] [@enterLeave]="{ value: isOpen ? ':enter' : ':leave', params: { fromDirection: fromDirection } }" *ngIf="isOpen">
  ...
</div>

<div [@fadeInOut] *ngIf="isOpen">
  <div class="modal-backdrop fade" [class.show]="isOpen"></div>
</div>

The problem I have now, is that I can't figure out how I can modify my EnterLeaveAnimation to switch on the fromDirection and apply different style rules accordingly:

style({ top: '-50%' })
OR
style({ bottom: '-50%' })
OR
style({ left: '-50%' })
OR
style({ right: '-50%' })

As you can see, I'm already passing my duration parameter on to the animation, but how do I switch on a parameter and use the style function accordingly?

Upvotes: 0

Views: 236

Answers (1)

Eliseo
Eliseo

Reputation: 57939

I'am afraid that you should make the animation "manually"

Don't worry, it's easy, see this SO for a simple e.g.

With this idea we can defined in your host component some like

  animate(element: any, state: string, background: boolean) {
    if (element) {
      let customstyle =
        state == 'enter'
          ? background
            ? { opacity: 0 }
            : { top: '-50%', left: '0', opacity: 0 }
          : background
          ? { opacity: 1 }
          : { top: '-100%', left: '0', opacity: 1 };
      if (!background) {
        switch (this.fromDirection) {
          case 'bottom':
            customstyle =
              state == 'enter'
                ? { top: '50%', left: '0', opacity: 0 }
                : { top: '100%', left: '0', opacity: 0 };
            break;
          case 'left':
            customstyle =
              state == 'enter'
                ? { top: '0', left: '-100%', opacity: 0 }
                : { top: '0', left: '-100%', opacity: 0 };
            break;

          case 'right':
            customstyle =
              state == 'enter'
                ? { top: '0', left: '100%', opacity: 0 }
                : { top: '0', left: '100%', opacity: 0 };
            break;
        }
      }
      const myAnimation =
        state == 'enter'
          ? this.builder.build([
              style(customstyle),
              animate(this.timing, style({ top: 0, left: 0, opacity: 1 })),
            ])
          : this.builder.build([animate(this.timing, style(customstyle))]);
      this.player = myAnimation.create(element);
      if (state == 'leave') {
        this.player.onDone(() => {
          this.componentInstance.instance.isOpen = false;
        });
      }
      this.player.play();
    }
  }

You also define in your host component a function close

  close() {
    this.isOpen = false;
    this.animate(
      (this.componentInstance.instance as any).modal.nativeElement,
      'leave',
      false
    );
    this.animate(
      (this.componentInstance.instance as any).modalBackground.nativeElement,
      'leave',
      true
    );
  }

And use the getter to call to animate

@Input() set isOpen(value: boolean) {
    this._isOpen = value;
    if (this.componentInstance) {
      if (value) {
        this.componentInstance.instance.isOpen = value;
        this.animate(
          (this.componentInstance.instance as any).modal.nativeElement,
          'enter',
          false
        );
        this.animate(
          (this.componentInstance.instance as any).modalBackground
            .nativeElement,
          'enter',
          true
        );
      } else {
        this.animate(
          (this.componentInstance.instance as any).template.nativeElement,
          'leave',
          false
        );
        this.animate(
          (this.componentInstance.instance as any).modalBackground
            .nativeElement,
          'leave',
          true
        );
      }
    }
    this.isOpenChange.emit(value);
  }

As you see the animate "reemplace" your animations, but we can not use *ngIf to show/hide the modal else [ngClass] in the way

<!---modal.component.html-->
<div #modal [ngClass]="isOpen?'modal d-block':'modal d-none'" class='modal d-block'>
  <div class="modal-dialog">
    <div class="modal-content">
      <ng-container *ngTemplateOutlet="template"></ng-container>
    </div>
  </div>
</div>
<div #modalBackground [ngClass]="isOpen?'d-block':'d-none'">
  <div class="modal-backdrop fade" [class.show]="isOpen"></div>
</div>

See how you use template reference variable to get the background and the content, so we need use ViewChild

  @ViewChild('modal') modal:ElementRef
  @ViewChild('modalBackground') modalBackground:ElementRef

NOTE: In the stackblitz I "hardcode the "timing", you can use an @Input or another way.

Upvotes: 1

Related Questions