Experimental class composition with Angular 14
Published: 2022-07-06 12:24:47

Do you sometimes wish that Angular had functional composition like other frameworks? 

![Angular webdev](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n8tmjivupp983hig8m93.jpg)

Well too bad, Angular is wedded to classes. And despite many attempts to fit square peg functions into round hole classes it just doesn't work well in practice. Angular does what it does for a reason and we shouldn't try to make it something it's not.

But what if you want it anyway and stumble across a way to make it happen **with just one line of code**?

```html
<p>Count: {{ counter.value }}</p>
```
```ts
@Auto()
@Component()
class MyComponent {
  // nothing unusual?
  counter = new Counter()

  ngOnInit() {
    console.log("so far so good")
  }
}

@Auto()
class Counter {
  value = 0
  interval

  increment(ctx) {
    ctx.value++
  }

  ngOnInit() {
    console.log("wait what?")
    this.interval = setInterval(this.increment, 1000, this)
  }

  ngOnDestroy() {
    console.log("this shouldn't be possible")
    clearInterval(this.interval)
  }
}
```

One of they key features of functional composition is the ability to extract and co-locate lifecycle hooks into a single unit of logic that can be reused across many components. In Angular this unit of logic is normally represented by services decorated with `Injectable`.

Services however, have some downsides compared to functional composition:

- Inability to pass parameters to a service from a component when it is created
- Leaky services. Some code further down the tree could inject and use it in unintended ways.
- Extra ceremony of having to add to providers array.
- Unless provided in a component, inability to update the view
- Accidentally injecting a parent instance because it wasn't provided correctly, or omitting `@Self`.
- No access to the lifecycle of a directive.

Angular only supports lifecycle hooks on decorated classes, but in the example above we have an arbitrary `Counter` object with lifecycle hooks. How does that work? Why now?

## Angular 14

In my recent article [Angular 14 dependency injection unlocked](https://dev.to/mmuscat/angular-14-dependency-injection-unlocked-3mi2) I explain how `inject` became a public API for all Angular decorated classes including components. This liberates us from constructors as the only means to instantiate our dependencies, making the following possible without any hacks at all:

```ts
@Component()
class MyComponent {
  resource = new Resource()
}

class Resource() {
  http = inject(HttpClient) // now works in Angular 14!
}
```

The ability to inject dependencies is another key piece of the composition puzzle. We just need some way to hook into the component lifecycle.

## Automatic Composition

{% embed https://github.com/mmuscat/angular-auto %}

With one line of code, just add `Auto` to your component, directive, service, etc. and it instantly composes with other `Auto` decorated objects.

Behind the scenes this decorator will cascade lifecycle hooks to any `Auto` object created inside a class field initializer or constructor. These are guarded so that component life cycles don't leak to services and vice versa.

Try it out!

{% embed https://stackblitz.com/edit/angular-ivy-lbu16w?file=src%2Fapp%2Fapp.component.ts %}

**But There's a Catch**

For now this is only possible by [mutating some private Angular APIs](https://github.com/mmuscat/angular-auto/blob/angular-auto-0.1.1/projects/auto/src/auto.ts#L74). So it's definitely not something you should try in production 🙇

## Angular Friendly?

If you flinch when seeing `useXXX` in other frameworks, rest assured that I am not advocating for this to become a thing in Angular.

In Angular we use `new XXX`.

Happy Coding!