Angular: ChangeDetection Interferes with HostBinding

346 Views Asked by At

I'm trying to implement an Animate On Scroll library/helper based on this idea Wizdm Genesys

I have a service that emits an IntersectionInfo (IsIntersecting, Direction, IsEntering, etc. ) object when an element intersects with the viewport.

I subscribe to it and if the element is entering the viewport, I trigger the enter animation.

It works fine unless I use ChangeDetection.OnPush on any of the components containing the element or any of it's parents. detectChanges() doesn't fix it.

The weird thing is that variable is changeing and I can see it in the developer-tools but it is not triggering the animation.

Component using the animate on scroll library (HelloComponent.html):

<button mat-button color="primary" (click)="_replay = !_replay" >Primary</button>


<ng-container *ngFor="let item of items; let idx = index">

  <div class="item-container" myAos [animate]="_replay">
    <div class="item">
      {{item}}
    </div>
  </div>

</ng-container>

In AosComponnent I use selector: '[myAos]' so it can be used like a directive.

Here's the AosComponent

@Component({
  selector: '[myAos]',
  template: `
    <!-- H5 is just for testing info -->
    <h5>{{ trigger | json }}</h5>
    <ng-content></ng-content>
  `,
  styleUrls: ['./aos.component.scss'],
  animations: [
    trigger('aosAnimate', [
      state('idle-bumpIn', style({ opacity: 0 })),

      transition(
        '* => bumpIn',
        [
          style({ transform: 'scale(0.5)', opacity: 0 }),

          animate(
            '{{timing}} {{delay}} cubic-bezier(.8, -0.6, 0.2, 1.5)',

            style({ transform: 'scale(1)', opacity: 1 })
          )
        ],
        { params: { timing: '500ms', delay: '' } }
      ),
      // None
      state('none', style('*')),
      state('idle-none', style('*'))
    ])
  ]
})
export class AosComponent implements OnInit {
  @Input('debounce') _debounce = 0;
  @Input('rootMargin') _rootMargin = '0px';
  @Input('root') _root: HTMLElement | undefined = undefined;
  @Input('threshold') _threshold: number = 0;
  @Input('delaySecs') _delay: number = 0.25;
  @Input('durationSecs') _duration: number = 0.5;
  private destroy$ = new Subject();

  @HostBinding('@aosAnimate')
  public trigger: any = 'none';


  @Input()
  set animate(trgr: boolean) {
    this.trigger = !trgr
      ? 'idle-bumpIn'
      : {
          value: 'bumpIn',
          params: {
            timing: `${this._duration}s`,
            delay: `${this._delay}s`
          }
        };
    this._cd.detectChanges();

    console.log('animate Input', trgr, this.trigger);
  }

  //-----------------------------------------------------//

  constructor(
    private _element: ElementRef,
    private renderer: Renderer2,
    private _scroll: IntersectionService,
    private _cd: ChangeDetectorRef
  ) {} //ctor

  //-----------------------------------------------------//

  ngOnInit(): void {
    // const me = this

    this._scroll
      .observeIntersection(
        this._element,
        this._threshold,
        this._root,
        this._rootMargin
      )
      .pipe(takeUntil(this.destroy$))
      .subscribe(info => {
        this.animate = info.isEntering;
        console.log('observeIntersection', info.isEntering);
        this._cd.detectChanges();
      });
  } //ngOnInit

  //-----------------------------------------------------//

  ngOnDestroy() {
    this.destroy$.next();
  } //ngOnDestroy

  //-----------------------------------------------------//
  
} //Cls

If I set @Input() animate to true from inside HelloComponent it always works. If I set @Input() animate to true from subscribe it only works when I remove changeDetection: ChangeDetectionStrategy.OnPush from HelloComponent

HelloComponent.ts

@Component({
  selector: 'hello',
  templateUrl: './hello.component.html',
  styleUrls: ['./hello.component.scss']
  // changeDetection: ChangeDetectionStrategy.OnPush  <--- this is the problem
})
export class HelloComponent {
  _replay = false;
  items = ['item 1', 'item 2', 'item 3', 'item 4', 'item 5'];
}

StackBlitz here: My Stackblitz

Any ideas???

0

There are 0 best solutions below