Skip to content

React Hooks for Angular

React Hooks are great and I really miss them in Angular. What if Angular had hooks? We could build hooks for state management, subscription handling and even reactive state. So let's build Angular Hooks!

Published
Jan 24, 2022
Updated
Jan 24, 2022
Reading time
6 min read

I'm a fan of React Hooks and I'm recently thought about what if Angular had hooks? Sure, we don't have function components in Angular but hooks are much more than "state for function components".

We'll see why React Hooks are so great and how we can implement something similar in Angular.

About React Hooks

Hooks let you use state (and other features) in React function components. Previously, you had to use classes to build stateful components.

Let's take a simple counter hook as an example. The hook uses useState to persist the counter value. It provides the counter value and two methods increment() and decrement(). Updating the state will re-render the component automatically.

/counter-hook.ts
function useCounter(initialValue = 0) {
  const [count, setCount] = useState(initialValue);

  const increment = () => setCount((count) => count + 1);
  const decrement = () => setCount((count) => count - 1);

  return { count, increment, decrement };
}
/counter.tsx
function Counter() {
  // Use the hook in the component
  const { count, increment, decrement } = useCounter();

  return (
    <div>
      <div>Value: {count}</div>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </div>
  );
}

Does Angular need hooks?

Angular does not have function components. Do we even need hooks then? Yes, because hooks not just let us create stateful function components, but also allow us to encapsulate a piece of code into a function that is reusable and can have state and effects. This makes hooks so powerful!

In Angular we currently have two ways to reuse code across components:

Singleton Services
Logic can be extracted into a singleton service. That's definitely useful but services are not tight to the component lifecycle. They can have state but not state that updates the component when it changes.

Directives / Pipes
We can use directives and pipes (like *ngIf and the async pipe) to encapsulate logic and reuse it across components. This is pretty flexible but they are part of the component template and not of the component class.

Now let's build hooks in Angular.

Building hooks

We could take the Hooks API from React and try to implement it in Angular but this would not work. Hook functions are designed to be called initially and whenever the component is re-rendered but Angular does not have function components or a render method.

Instead, we need to apply the Angular patterns to hooks. A hook should be a class instead of a function. And instead of importing them directly we use the dependency injection system to inject them (together with the @Injectable() decorator). As our components are stateful by design, we don't need useState and useRef.

You can already see that our hooks API will look quiet different than React Hooks. That's not a bad thing because we want to implement hook "the Angular way". They should feel like everything else in Angular and not like something that is out of place.

Now to be actual useful, Angular Hooks need three things:

  1. Access to the ChangeDetectorRef of the component to trigger re-rendering when state has changed.
  2. Access to providers (services, injection tokens, ...).
  3. Access to lifecycle events of the component, at least OnDestroy to clean up itself.

Let's first create a class for our hook. We add the Injectable() decorator to make it injectable. We can then use dependency injection to get the ChangeDetectorRef and any other dependencies we need. In addition, we can use ngOnDestroy to clean up any resources we created. Other lifecycle events like ngOnInit are only supported for components.

/example.hook.ts
@Injectable()
export class ExampleHook implements OnDestroy {
  // Get access to the ChangeDetectorRef of the component
  constructor(private cdr: ChangeDetectorRef) {}

  use() {
    // The actual hook implementation goes here...
  }

  ngOnDestroy() {
    // Clean up any resources we created...
  }
}

We then use the hook in our component:

/example.component.ts
@Component({
  // 1. Provide the hook
  providers: [ExampleHook],
})
export class ExampleComponent {
  // 2. Inject the hook
  constructor(private exampleHook: ExampleHook) {}

  // 3. Use the hook
  example = this.exampleHook.use();
}

The hook is a provider of the component. This will create a new instance of the hook for each component instance. The hook can inject the ChangeDetectorRef of the component and gets destroyed when the component is destroyed.

We call the use() method on the hook instance for the actual functionality (whatever the hook does). We can use the same hook multiple times by calling the use method multiple times.

That's the theory. Now let's see how it works in practice.

Hooks in action

You know how the hooks API looks like.

Rx Hook

The Rx Hook handles Observables in the component class (as an better alternative to the async pipe ). It will subscribe to the observable, provide you the value and status and automatically unsubscribe when the component is destroyed.

Read More: Handling Observables with Angular Rx Hook.

State Hook

The State Hook handles states in your component by updating automatically when the state changes. It uses Immer behind the scene to make object manipulation easier.

Read More: Handling States with Angular State Hook


These are just two basic examples. What about a memo hook similar to useMemo in React to have computed properties that are cached until a dependency changes? Or a hook to use a global state management library like Valtio?

Demo

Try a simple demo of both hooks:
angular-hooks.blog.sandroroth.com

The full source code is available on GitHub:
@rothsandro/angular-hooks

What if Angular REALLY had hooks?

We implemented hooks with the current features supported by Angular. But what if Angular itself would support hooks? This could make the API even more powerful and easier to use.

Angular could provide a @Hook() decorator to build hooks. They could support more lifecycle events (like ngOnInit) and other features like @ViewChild. Hooks could be injected directly without adding it as a component provider and could be injected multiple times. Each injection would create a new instance (no need for the use method).

I'm just dreaming...

Summary

I just discovered that pattern and did not (yet) use it in production. I don't know if this is already used by other developers or not. Or if it's a good or a really bad idea. So let me know.