# NgRx Component Store w/ Effect + Clean Up ability - When we need to interact with an external API (DOM, Canvas, etc...) that is imperative and the interaction needs to have: initialization, clean up, and final disposal. - We need to react to `Observable` emissions. With `ngrx/component-store`, we have `effect()` to setup an effect that can __react__ to `Observable` emissions. Combined with `tap()`, we then have a somewhat robust solution, but there's still something missing. Considering the following use-case: - I have a stream of a HTML element `{ width, height }`, eg: `size$`. - Based on `size$`, I want to `registerAnimationFrame` to start some animations. - On `size$` changes, I want to clean up the previously registered animation with `cancelAnimationFrame()` based on some comparison logic between the prevous `size` versus the new `size` - On Component destroyed, I want to clean up the animation frame with `cancelAnimationFrame()` as well as run some additional clean up logic. The first snippet is **MY** (it can be simplified, lemme know how you simplify it, I'm curious) implementation by using `ngrx/component-store` `effect()` and `tap()` operator. ```ts @Injectable() export class AnimationStore extends ComponentStore<any> { // we can have a property to keep track of it private isSizeChangeAnimationRegistered = false; private sizeChangeAnimationId?: ReturnType<typeof registerAnimationFrame>; private previousWidth = 0; private currentWidth = 0; readonly onSizeChange = this.effect<{width: number, height: number}>( tap({ next: ({ width, height }) => { // height can also be tracked if needed this.currentWidth = width; // setup registerAnimationFrame etc... // however, this runs everytime width or height changes // so we need to clean up the animation before setting it up again if (this.isSizeChangeAnimationRegistered) { // - subsequent emissions // clean up logic this.cleanUpSizeChangeAnimation(this.previousWidth, this.currentWidth, false) // then reassign previous value this.previousWidth = this.currentWidth; // then re-register this.sizeChangeAnimationId = registerAnimationFrame(/*...*/); } else { // - initial emission // assign previous value this.previousWidth = width; // register animationFrame this.sizeChangeAnimationId = registerAnimationFrame(/*...*/); // set the flag this.isSizeChangeAnimationRegistered = true; } // but we still need to run clean up logic on destroyed }, unsubscribe: () => { // clean up logic this.cleanUpSizeChangeAnimation(this.previousWidth, this.currentWidth, true); }, complete: () => { // clean up logic this.cleanUpSizeChangeAnimation(this.previousWidth, this.currentWidth, true); } // and sometimes, we might need to know // the previous emitted values before executing clean up logic // so we might need to keep track of the width and height here. }) ) private cleanUpSizeChangeAnimation(previousWidth: number, currentWidth: number, isDestroyed: boolean) { // might have comparison between previousWidth and currentWidth // clear the animationFrame if (this.sizeChangeAnimationId) { cancelAnimationFrame(this.sizeChangeAnimationId); } if (isDestroyed) { // run additional destroyed logic } } } ``` If we had an `effect()` but with `cleanUpFn()` built-in, this would have been easier. Let's assume that `effect()` is named `useEffect()` ```ts export class AnimationStore extends ComponentStore<any> { readonly onSizeChange = this.useEffect<{width: number, height: number}>( ({width, height}) => { const animationId = registerAnimationFrame(/* ... */); // return cleanUpFn that runs every emissions AND one last time on destroyed return ({previous, current, isDestroyed}) => { // previous is {width, height} // might have comparison between previousWidth and currentWidth cancelAnimationFrame(animationId); if (isDestroyed) { // run additional destroyed logic } } } ) } ``` This is just an example of `requestAnimationFrame` but you can extend this use-case to ANY kind of DOM APIs that need to "register" then "cancel". Angular Three has its own `tapEffect()` operator to achieve this [NgtComponentStore](https://angular-three.netlify.app/docs/core/component-store#handling-side-effects)