Skip to content

Commit

Permalink
refactor: simplify components with new features (#174)
Browse files Browse the repository at this point in the history
  • Loading branch information
arturovt committed Jan 25, 2024
1 parent 8f1393b commit afb0222
Show file tree
Hide file tree
Showing 10 changed files with 71 additions and 135 deletions.
3 changes: 0 additions & 3 deletions libs/ngx-lottie/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
export { LottieModule } from './lib/lottie.module';
export { LottieCacheModule } from './lib/cacheable-animation-loader/lottie-cache.module';

export { AnimationLoader } from './lib/animation-loader';

export { provideLottieOptions, provideCacheableAnimationLoader } from './lib/providers';
Expand Down
45 changes: 25 additions & 20 deletions libs/ngx-lottie/src/lib/animation-loader.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,40 @@
import { Injectable, NgZone, Inject } from '@angular/core';
import { Injectable, NgZone, inject } from '@angular/core';

import { Observable, from, of, animationFrameScheduler } from 'rxjs';
import { map, observeOn, shareReplay, tap } from 'rxjs/operators';
import { Observable, from, of } from 'rxjs';
import { map, mergeMap, shareReplay, tap } from 'rxjs/operators';

import {
LOTTIE_OPTIONS,
LottiePlayer,
LottieOptions,
AnimationItem,
AnimationOptions,
AnimationConfigWithData,
AnimationConfigWithPath,
LottiePlayerFactoryOrLoader,
} from './symbols';

function convertPlayerOrLoaderToObservable(
player: LottiePlayerFactoryOrLoader,
useWebWorker?: boolean,
): Observable<LottiePlayer> {
const playerOrLoader = player();
function convertPlayerOrLoaderToObservable(): Observable<LottiePlayer> {
const ngZone = inject(NgZone);
const { player, useWebWorker } = inject(LOTTIE_OPTIONS);
const playerOrLoader = ngZone.runOutsideAngular(() => player());
const player$ =
playerOrLoader instanceof Promise
? from(playerOrLoader).pipe(map(module => module.default || module))
: of(playerOrLoader);

return player$.pipe(
tap(player =>
(player as unknown as { useWebWorker: (useWebWorker?: boolean) => void }).useWebWorker(
useWebWorker,
),
),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
tap(player => (player as any).useWebWorker?.(useWebWorker)),
shareReplay({ bufferSize: 1, refCount: true }),
);
}

@Injectable({ providedIn: 'root' })
export class AnimationLoader {
protected player$ = convertPlayerOrLoaderToObservable(
this.options.player,
this.options.useWebWorker,
).pipe(observeOn(animationFrameScheduler));
protected player$ = convertPlayerOrLoaderToObservable().pipe(
mergeMap(player => raf$(this.ngZone).pipe(map(() => player))),
);

constructor(private ngZone: NgZone, @Inject(LOTTIE_OPTIONS) private options: LottieOptions) {}
private ngZone = inject(NgZone);

loadAnimation(
options: AnimationConfigWithData | AnimationConfigWithPath,
Expand Down Expand Up @@ -71,3 +64,15 @@ export class AnimationLoader {
return this.ngZone.runOutsideAngular(() => player.loadAnimation(options));
}
}

function raf$(ngZone: NgZone) {
return new Observable<void>(subscriber => {
const requestId = ngZone.runOutsideAngular(() =>
requestAnimationFrame(() => {
subscriber.next();
subscriber.complete();
}),
);
return () => cancelAnimationFrame(requestId);
});
}
49 changes: 24 additions & 25 deletions libs/ngx-lottie/src/lib/base.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@ import {
Directive,
Input,
Output,
Inject,
PLATFORM_ID,
OnDestroy,
SimpleChanges,
NgZone,
inject,
OnDestroy,
} from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';

import { Subject, BehaviorSubject, Observable, defer } from 'rxjs';
import { filter, switchMap, takeUntil } from 'rxjs/operators';
import { filter, switchMap } from 'rxjs/operators';

import {
AnimationOptions,
Expand All @@ -38,83 +39,81 @@ export class BaseDirective implements OnDestroy {
/**
* `animationCreated` is dispatched after calling `loadAnimation`.
*/
@Output() animationCreated = this.getAnimationItem();
@Output() readonly animationCreated = this.getAnimationItem();

/**
* `complete` is dispatched after completing the last frame.
*/
@Output() complete = this.awaitAnimationItemAndStartListening<BMCompleteEvent>('complete');
@Output() readonly complete =
this.awaitAnimationItemAndStartListening<BMCompleteEvent>('complete');

/**
* `loopComplete` is dispatched after completing the frame loop.
*/
@Output() loopComplete =
@Output() readonly loopComplete =
this.awaitAnimationItemAndStartListening<BMCompleteLoopEvent>('loopComplete');

/**
* `enterFrame` is dispatched after entering the new frame.
*/
@Output() enterFrame = this.awaitAnimationItemAndStartListening<BMEnterFrameEvent>('enterFrame');
@Output() readonly enterFrame =
this.awaitAnimationItemAndStartListening<BMEnterFrameEvent>('enterFrame');

/**
* `segmentStart` is dispatched when the new segment is adjusted.
*/
@Output() segmentStart =
@Output() readonly segmentStart =
this.awaitAnimationItemAndStartListening<BMSegmentStartEvent>('segmentStart');

/**
* Original event name is `config_ready`. `config_ready` is dispatched
* after the needed renderer is configured.
*/
@Output() configReady = this.awaitAnimationItemAndStartListening<void>('config_ready');
@Output() readonly configReady = this.awaitAnimationItemAndStartListening<void>('config_ready');

/**
* Original event name is `data_ready`. `data_ready` is dispatched
* when all parts of the animation have been loaded.
*/
@Output() dataReady = this.awaitAnimationItemAndStartListening<void>('data_ready');
@Output() readonly dataReady = this.awaitAnimationItemAndStartListening<void>('data_ready');

/**
* Original event name is `DOMLoaded`. `DOMLoaded` is dispatched
* when elements have been added to the DOM.
*/
@Output() domLoaded = this.awaitAnimationItemAndStartListening<void>('DOMLoaded');
@Output() readonly domLoaded = this.awaitAnimationItemAndStartListening<void>('DOMLoaded');

/**
* `destroy` will be dispatched when the component gets destroyed,
* it's handy for releasing resources.
*/
@Output() destroy = this.awaitAnimationItemAndStartListening<BMDestroyEvent>('destroy');
@Output() readonly destroy = this.awaitAnimationItemAndStartListening<BMDestroyEvent>('destroy');

/**
* `error` will be dispatched if the Lottie player could not render
* some frame or parse config.
*/
@Output() error = this.awaitAnimationItemAndStartListening<
@Output() readonly error = this.awaitAnimationItemAndStartListening<
BMRenderFrameErrorEvent | BMConfigErrorEvent
>('error');

private destroy$ = new Subject<void>();
private ngZone = inject(NgZone);
private isBrowser = isPlatformBrowser(inject(PLATFORM_ID));

private animationLoader = inject(AnimationLoader);

private loadAnimation$ = new Subject<[SimpleChanges, HTMLElement]>();
private animationItem$ = new BehaviorSubject<AnimationItem | null>(null);

constructor(
private ngZone: NgZone,
@Inject(PLATFORM_ID) private platformId: string,
private animationLoader: AnimationLoader,
) {
constructor() {
this.setupLoadAnimationListener();
}

ngOnDestroy(): void {
this.destroy$.next();
this.destroyAnimation();
}

protected loadAnimation(changes: SimpleChanges, container: HTMLElement): void {
// The `loadAnimation` may load `lottie-web` asynchronously and also pipes the player
// with `animationFrameScheduler`, which schedules an animation task and triggers change
// detection. We'll trigger change detection only once when the animation item is created.
this.ngZone.runOutsideAngular(() => this.loadAnimation$.next([changes, container]));
}

Expand Down Expand Up @@ -150,7 +149,7 @@ export class BaseDirective implements OnDestroy {

private setupLoadAnimationListener(): void {
const loadAnimation$ = this.loadAnimation$.pipe(
filter(([changes]) => isPlatformBrowser(this.platformId) && changes.options !== undefined),
filter(([changes]) => this.isBrowser && changes.options !== undefined),
);

loadAnimation$
Expand All @@ -161,7 +160,7 @@ export class BaseDirective implements OnDestroy {
this.animationLoader.resolveOptions(changes.options.currentValue, container),
);
}),
takeUntil(this.destroy$),
takeUntilDestroyed(),
)
.subscribe(animationItem => {
this.ngZone.run(() => this.animationItem$.next(animationItem));
Expand Down

This file was deleted.

16 changes: 2 additions & 14 deletions libs/ngx-lottie/src/lib/lottie.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,14 @@ import {
Component,
ChangeDetectionStrategy,
Input,
Inject,
ElementRef,
ViewChild,
PLATFORM_ID,
OnChanges,
SimpleChanges,
NgZone,
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { NgClass, NgStyle } from '@angular/common';

import { BaseDirective } from './base.directive';
import { AnimationLoader } from './animation-loader';

@Component({
selector: 'ng-lottie',
Expand All @@ -28,22 +24,14 @@ import { AnimationLoader } from './animation-loader';
`,
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [CommonModule],
imports: [NgStyle, NgClass],
})
export class LottieComponent extends BaseDirective implements OnChanges {
@Input() width: string | null = null;
@Input() height: string | null = null;

@ViewChild('container', { static: true }) container: ElementRef<HTMLElement> = null!;

constructor(
ngZone: NgZone,
@Inject(PLATFORM_ID) platformId: string,
animationLoader: AnimationLoader,
) {
super(ngZone, platformId, animationLoader);
}

ngOnChanges(changes: SimpleChanges): void {
super.loadAnimation(changes, this.container.nativeElement);
}
Expand Down
21 changes: 2 additions & 19 deletions libs/ngx-lottie/src/lib/lottie.directive.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,10 @@
import {
Directive,
Inject,
Self,
ElementRef,
PLATFORM_ID,
OnChanges,
SimpleChanges,
NgZone,
} from '@angular/core';
import { Directive, ElementRef, OnChanges, SimpleChanges, inject } from '@angular/core';

import { BaseDirective } from './base.directive';
import { AnimationLoader } from './animation-loader';

@Directive({ selector: '[lottie]', standalone: true })
export class LottieDirective extends BaseDirective implements OnChanges {
constructor(
ngZone: NgZone,
@Inject(PLATFORM_ID) platformId: string,
@Self() private host: ElementRef<HTMLElement>,
animationLoader: AnimationLoader,
) {
super(ngZone, platformId, animationLoader);
}
private host = inject(ElementRef);

ngOnChanges(changes: SimpleChanges): void {
super.loadAnimation(changes, this.host.nativeElement);
Expand Down
23 changes: 0 additions & 23 deletions libs/ngx-lottie/src/lib/lottie.module.ts

This file was deleted.

0 comments on commit afb0222

Please sign in to comment.