Directives as a Service
Published: 2022-09-09 08:03:01

One of the unique features of [Angular State Library](https://github.com/antischematic/angular-state-library) is that every store is a `@Directive`.

> Disclaimer: This project is an experiment, it is not production ready.

Unlike services, directives have access to inputs, outputs, host bindings/listeners, lifecycle hooks, templates, and queries. Directives are also instantiated eagerly, while services remain dormant until something else injects it. Directives have full control over the DOM.

Using directives as a service isn't a new concept. It's common practice to inject directives and components from other components in the injector tree. But we don't always think of directives as services, or that we might want services to be like a directive.

```ts
@Store()
@Component()
export class UIButton {
  @Input() disabled = false

  @Action()
  @HostListener("click")
  animateClick() {
    const { nativeElement } = inject(ElementRef)
    if (!this.disabled) {
      return dispatch(animateRipple(nativeElement), {
        complete() {
          console.log("animation done")
        }
      })
    }
  }
}

function animateRipple(element) {
  // use your imagination
}
```

<center><small>Can your redux store do this?</small></center><br/>

In Angular State Library, a `@Store` is both a directive and a service. By using directives we can eliminate most of the boilerplate that comes with state management.

## Template Providers

Since directives have inputs, we can make stores configurable. The store can then be re-used in different ways without sub-classing. This can be used to create a context API similar to React.

```ts
@Directive({
  standalone: true,
  selector: "ui-theme",
})
export class UITheme extends TemplateProvider {
  color = "red"
}
```

The theme provider can then be configured from a parent component.

```html
<ui-theme [value]="blueTheme">
  <ui-button>Blue button</ui-button>
</ui-theme>
<ui-theme [value]="greenTheme">
  <ui-button>Green button</ui-button>
</ui-theme>
```
```tsx
@Component({
  standalone: true,
  imports: [UITheme],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class UIApp {
  blueTheme = {
    color: "blue"
  }
  greenTheme = {
    color: "green"
  }
}
```

And consumed from a descendant component.

```html
<div [style.color]="theme.color">
  <ng-content></ng-content>
</div>
```
```tsx
@Component({
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class UIButton {
  theme = select(UITheme)
}
```

We now have a configurable and reactive store that can be composed anywhere in our application.

{% embed https://stackblitz.com/edit/angular-ivy-twrmdy?ctl=1&embed=1&file=src%2Fapp%2Fui-descendent.ts,src%2Fapp%2Fui-counter.ts,src%2Fapp%2Fui-theme.ts,src%2Fapp%2Fapp.component.html&hideExplorer=1&hideNavigation=1 %}

## Host Directives

Host Directives are the perfect use case for directives as a service. In Angular 15 (pending) we will be able to add directives to a host component without adding them to the template. This means we can attach root stores directly to a root `@Component` or Angular Element, which currently isn't possible.

```ts
@Component({
  standalone: true,
  selector: "ui-root",
  hostDirectives: [
    AuthStore, 
    ProfileStore, 
    NotificationStore
  ]
})
export class UIRoot {}
```

We will have to wait and see what the final solution looks like once it's released, but for now we can work around this by attaching the host directives to the component template instead.

```html
<ng-container authStore profileStore notificationStore>
  <router-outlet></router-outlet>
</ng-container>
```

<center><small>Good enough for now</small></center><br/>

## When to Use a Directive Instead of a Service

This is just an opinion, but you might want to use a directive as a service if:

**The service is stateful**

A directive has the advantage of lifecycle hooks to properly manage long-lived subscriptions, react to input changes, and run change detection. It's also much easier to compose in templates.

**The service depends on DOM elements or DOM events**

While services can access the host element if it is provided in a directive, a directive can also use `@Content` and `@View` queries. It also has access to `@HostListener` and `@HostBinding` to make things easier.

**The service needs to be initialized before it is injected**

Directives are like `ENVIRONMENT_INITIALIZER`, except it works at component scope instead of application scope. Directives are always eager.

---

Angular State Library is a yes on all fronts. You can also start using your own directives as a service today.

## Help Wanted

This project is currently a proof of concept. It's no where near production ready. If you are interested in contributing or dogfooding feel free to [open a line on Github discussions](https://github.com/antischematic/angular-state-library/discussions/1), or leave a comment with your thoughts below.

Thanks for reading!