Angular Composition API
Published: 2021-05-31 16:10:06

> ⚠️ This article is based on an early version of the library. [Click here](https://mmuscat.github.io/angular-composition-api/docs/intro) for the most recent version.

---

[Angular Composition API](https://github.com/mmuscat/angular-composition-api) is a lightweight (3kb), **experimental** library for writing functional Angular applications.

```ts
function State(props: Props) {
  Subscribe(() => {
    console.log("Hello World!")
  })
}
```

# Concepts

This library introduces an **execution context** that removes a lot of the ceremony needed to wire and orchestrate Angular components. It provides a layer of abstraction on top of existing Angular constructs, such as lifecycle hooks, change detection, queries, host bindings and host listeners. It embraces the power of RxJS with composable subscriptions. Angular Composition API is designed to feel native to the Angular developer.

There are two core APIs: `View` and `Service`.

## View

The `View` API is a mixin that extends an Angular component or directive. It takes a `State` factory function and optional `Props` argument. The function will run in an execution context that allows other context-dependant APIs to be called.

## Service

The `Service` API is a mixin that creates a tree-shakable service from a factory function. The function will run in an execution context that allows other context-dependant APIs to be called.

## Definitions

When this library refers to `Value`, it means `BehaviorSubject`, and when it refers to an `Emitter`, it means `EventEmitter`. 

# Example

To give you an idea of what application development with this library looks like, let's write a component to display some todos from a service.

First define the props interface. The component will inherit its metadata.

```ts
@Directive()
class Props {
  @Input() userId: string
}
```

Next define a state function. It will receive props and return an object containing the todos `Value`.

```ts
function State(props: Props) {
  const userId = DoCheck(() => props.userId) // <1>
  const [todos, loadTodosByUserId] = Inject(LoadTodosByUserId) // <2>

  Subscribe(userId, loadTodosByUserId) // <3>

  return {
    todos // <4>
  }
}
```

A few things to observe:
1. We create a `userId` value that will update when the `userId` prop changes.
2. We `Inject` the `LoadTodosByUserId` token, which returns an array containing a `Value` and an `Emitter`. 
3. We set up todos to be loaded whenever a new `userId` is emitted.
4. We return the todos `Value`, which will be automatically subscribed in the template. Change detection is scheduled whenever a returned `Value` changes.

```ts
@Component({
  selector: "todo-list",
  template: `
    <todo *ngFor="let todo of todos"></todo>
  `
})
export class TodoList extends View(Props, State) {}
```

Lastly connect the `Props` and `State` to the component with the `View` mixin.

# Service

What about `LoadTodosByUserId`? This is implemented using a `Service`. The example below is provided without comment.

```ts
function loadTodosByUserId() {
    const http = Inject(HttpClient)
    const emitter = Emitter()
    const value = Value()

    Subscribe(emitter, (userId) => {
        const source = http.get(`//example.com/api/v1/todo?userId=${userId}`)
        Subscribe(source, set(value))
    })

    return [value, emitter]
}

export const LoadTodosByUserId = Service(loadTodosByUserId, {
    providedIn: "root"
})
```

# Subscribe

Effects are performed using `Subscribe`. It is similar to the subscribe method in `RxJS`, except you can return teardown logic from the observer. The teardown logic will be executed each time a new value is received by the observer, or when the context is destroyed. It can also be called with just an observer, which is  called once when the view is mounted.

```ts
function State(props: Props) {
  Subscribe(() => {
    console.log("Hello World! I am only called once")
    return () => console.log("Goodbye World!")
  })
}
```

`Subscribe` can be used in both `View` and `Service` contexts.

# A Prelude

Perhaps when `NgModule` and `NgZone` opt out arrives from the [Angular roadmap](https://angular.io/guide/roadmap), we will gain access to more ergonomic, functional and type safe component APIs. Angular Composition API is a step in that direction.

That's it! Thanks for reading.

---

{% github mmuscat/angular-composition-api no-readme %}