# 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)