⚠️ This article is based on an early version of the library. Click here for the most recent version.
Angular Composition API is a lightweight (3kb), experimental library for writing functional Angular applications.
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.
@Directive()
class Props {
@Input() userId: string
}
Next define a state function. It will receive props and return an object containing the todos Value
.
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:
- We create a
userId
value that will update when theuserId
prop changes. - We
Inject
theLoadTodosByUserId
token, which returns an array containing aValue
and anEmitter
. - We set up todos to be loaded whenever a new
userId
is emitted. - We return the todos
Value
, which will be automatically subscribed in the template. Change detection is scheduled whenever a returnedValue
changes.
@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.
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.
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, 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.
Top comments (1)
It looks very useful. I will try. Thank you