AnimationFactory: how Angular creates animation renderers
Tags: angular, webdev, typescript
Published: 2022-12-20 11:08:14

This article is intended to be the beginning of a short series about Angular transition animations mechanism source code, a niche in Angular codebase often considered confusing and not enough documented.

---

## Angular transition animations
For _"Transition Animations"_ in Angular we mean adding transitioning effects between elements' state changes.
Normally this is done by binding a _trigger_ to a property that will get the values of the _states_ whose transition we want to animate.
They share only last part of their flow with _"Timeline Animations_", the ones executed by explicitly operating _Animation Players_, and that will not be a topic of these articles.

## Quick recap on Angular Renderers
To manipulate the DOM without directly accessing its nodes Angular leverages _Renderers_, tools designed to be a level of abstraction on native DOM operations.
They're shaped upon abstract class [**Renderer2**](https://angular.io/api/core/Renderer2), and are fundamental in transition animations context because some of their flavors get used to start animation flow.
They get not instanciated manually, but generated by a [**RendererFactory2**](https://angular.io/api/core/RendererFactory2).


## Animations module
First step to start using animations is importing [**BrowserAnimationsModule**](https://angular.io/api/platform-browser/animations/BrowserAnimationsModule).
This module, among other things, will provide some concrete implementations of aforementioned abstract classes, adding animation capabilities to component creation logic.
Above all, it will ship [**AnimationRendererFactory**](https://github.com/angular/angular/blob/main/packages/platform-browser/animations/src/animation_renderer.ts) that, together with some **Renderer** implementations it's capable to return, will supersede the ones provided by **BrowserModule**.
(For the sake of simplicty this series will take in account only the Browser platform, used to build webapps). 
```ts
export function instantiateRendererFactory(
    renderer: DomRendererFactory2, engine: AnimationEngine, zone: NgZone) {
  return new AnimationRendererFactory(renderer, engine, zone);
}

const SHARED_ANIMATION_PROVIDERS: Provider[] = [
  {provide: AnimationBuilder, useClass: BrowserAnimationBuilder},
  {provide: AnimationStyleNormalizer, useFactory: instantiateDefaultStyleNormalizer},
  {provide: AnimationEngine, useClass: InjectableAnimationEngine}, {
    provide: RendererFactory2,
    useFactory: instantiateRendererFactory,
    deps: [DomRendererFactory2, AnimationEngine, NgZone]
  }
];
```

## AnimationRendererFactory creating animation renderer
The gist of this factory is that it gets injected with an [**AnimationEngine**](https://github.com/angular/angular/blob/f58ad86e51817f83ff18db790a347528262b850b/packages/animations/browser/src/render/animation_engine_next.ts#L22) used by its generated renderers to enrich DOM operations with animated effects. 
```ts
@Injectable()
export class AnimationRendererFactory implements RendererFactory2 {
  ...
  constructor(
      private delegate: RendererFactory2, private engine: AnimationEngine, private _zone: NgZone)
```
Another important dependency passed to its constructor, is the property called **delegate** typed as **RendererFactory2**, populated with an instance of [**DomRendererFactory2**](https://github.com/angular/angular/blob/main/packages/platform-browser/src/dom/dom_renderer.ts#L70).
This is an implementation of [**Delegation Pattern**](https://en.wikipedia.org/wiki/Delegation_pattern) used to compose an enhanced _RendererFactory_.
Normal non-animated DOM operations will be delegated to the original **DefaultDomRenderer2** instances generated by this delegate factory, while the new _delegator_ animated renderers will take care of animation tasks only.

Being this class a factory of renderers, its main functionalities reside in [**createRenderer**](https://github.com/angular/angular/blob/main/packages/platform-browser/animations/src/animation_renderer.ts#L44) method.
It accepts two arguments:

- the **hostElement** being the "first ancestor" of all the elements included in our template (think of the element identified by your component's selector)
- a **type** typed as [**RendererType2**](https://angular.io/api/core/RendererType2) (I know... sorry), an object storing some rendering information, mostly retrieved from your `@Component` decorator's metadata.

Looking at its early lines we can understand how a basic animation renderer is created for components **not registering any animation**.
```ts
createRenderer(hostElement: any, type: RendererType2): Renderer2 {
  const EMPTY_NAMESPACE_ID = '';

  // cache the delegates to find out which cached delegate can
  // be used by which cached renderer
  const delegate = this.delegate.createRenderer(hostElement, type);
  if (!hostElement || !type || !type.data || !type.data['animation']) {
    let renderer: BaseAnimationRenderer|undefined = this._rendererCache.get(delegate);
    if (!renderer) {
      // Ensure that the renderer is removed from the cache on destroy
      // since it may contain references to detached DOM nodes.
      const onRendererDestroy = () => this._rendererCache.delete(delegate);
      renderer =
          new BaseAnimationRenderer(EMPTY_NAMESPACE_ID, delegate, this.engine, onRendererDestroy);
      // only cache this result when the base renderer is used
      this._rendererCache.set(delegate, renderer);
    }
    return renderer;
  }
```

1. first it uses the factory delegate to generate a pure "animation-unaware" _DOMRenderer_ and store it as a local `delegate` variable  
(don't get confused: global delegate `this.delegate` refers to **factory delegate**, while (unfortunately) homonymous local `const delegate` point to the newly generated **renderer delegate**)
2. it checks for any trigger listed inside **animations** property of _@Component_'s decorator
(after verifying the creation request is not relative to an _hostRenderer_, but this detail is beyond the scope of this article), and the branch entered when it can't find anyone is the one we're investigating
3. looks inside factory cache for an _AnimationRenderer_ bound to the _DOMRenderer_ just created (delegate _DomRendererFactory2_ is capable of returning cached _DOMRenderer_ too, so the "created" could be a reference to an already existing one, thus giving a match in strict equality lookup used by JS for _Map.prototype_)
4. if not found, creates a new one (injected with freshly created **renderer delegate**, and with a cache cleanup callback to be issued on renderer destruction), adds that to cache and returns it

## Little work for BaseAnimationRenderer
Looking at [**BaseAnimationRenderer**](https://github.com/angular/angular/blob/main/packages/platform-browser/animations/src/animation_renderer.ts#L140) code we notice how for most of its implementations it merely calls the corresponding _delegated renderer_ methods, aside from elements insertions and deletions related ones.
```ts
export class BaseAnimationRenderer implements Renderer2 {
  constructor(
      protected namespaceId: string, public delegate: Renderer2, public engine: AnimationEngine,
      private _onDestroy?: () => void)

  ...

  appendChild(parent: any, newChild: any): void {
    this.delegate.appendChild(parent, newChild);
    this.engine.onInsert(this.namespaceId, newChild, parent, false);
  }

  insertBefore(parent: any, newChild: any, refChild: any, isMove: boolean = true): void {
    this.delegate.insertBefore(parent, newChild, refChild);
    // If `isMove` true than we should animate this insert.
    this.engine.onInsert(this.namespaceId, newChild, parent, isMove);
  }

  removeChild(parent: any, oldChild: any, isHostElement: boolean): void {
    this.engine.onRemove(this.namespaceId, oldChild, this.delegate, isHostElement);
  }
```
This seems to be needed by deep layers of animation module to mark the elements as removed or inserted even in absence of a visible transition, mainly for move operations.
More interesting are the overrides of its child class **AnimationRenderer**, instanciated by the factory when the component that's gonna be created declares some animation triggers.
Both trigger registrations and added functionalities will be covered in following articles.

Thanks for reading, and feel free to get in touch in comments for every question, correction, clarification or opinion.