Search code examples
angularbootstrap-modalangular-animations

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?


Solution

  • 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.