CreateInjectable

Defines a typed factory function for creating injectable dependencies that return a tuple of injection utilities. Offers an alternative to class-based Angular services with functional, composable dependency factories.

Usage

angular-ts
import { Component, computed, signal } from '@angular/core';
import { createInjectable } from '@signality/core';

export const [injectCounter, provideCounter] = createInjectable( 
  'Counter',
  (initialValue = 0) => {
    const count = signal(initialValue);
    const doubled = computed(() => count() * 2);

    function increment() {
      count.update(v => v + 1);
    }

    return { count: count.asReadonly(), doubled, increment };
  }
);

@Component({
  selector: 'app-counter',
  providers: [provideCounter(10)], 
  template: `
    <p>Count: {{ counter.count() }}</p>
    <p>Doubled: {{ counter.doubled() }}</p>
    <button (click)="counter.increment()">Increment</button>
  `,
})
export class CounterComponent {
  readonly counter = injectCounter(); 
}

createInjectable.root

The root variant creates an injectable that is provided in the application root by default.

angular-ts
import { computed, inject } from '@angular/core';
import { createInjectable } from '@signality/core';
import { AuthGateway } from './auth-gateway';

export const [injectAuth] = createInjectable.root('Auth', () => { 
  const authGateway = inject(AuthGateway);

  const me = rxResource({ stream: () => authGateway.getUserMe() });

  const isAdmin = computed(() => {
    return me().hasValue() && me.value().roles.includes('admin');
  });

  return { userMe: me.asReadonly(), isAdmin };
});

Parameters

ParameterTypeDescription
descriptionstringA descriptive label for the underlying InjectionToken (useful for debugging).
factoryFunctionA factory function that defines the injectable's logic.

Return Value

Returns a tuple containing:

IndexNameDescription
0injectFnA function to retrieve the instance (equivalent to inject).
1provideFnA function to provide the injectable in a providers array.
2injectionTokenThe underlying Angular InjectionToken.

Examples

Manual providing of root injectables

Even if an injectable is defined with .root, you can still provide it manually to override its value in a specific component tree (e.g., for testing or scoped configuration).

angular-ts
const [injectConfig, provideConfig] = createInjectable.root( 
  'Config',
  (apiUrl = 'https://api.example.com') => ({ apiUrl }) //
);

@Component({
  providers: [
    provideConfig('https://staging-api.example.com') 
  ],
})
export class StagingComponent {
  readonly config = injectConfig(); // Returns staging API URL
}

Using with custom Injector

The injectFn supports passing an explicit Injector if you need to inject the value outside of the typical injection context.

angular-ts
import { inject, Injector } from '@angular/core';

export class ManualInjection {
  private injector = inject(Injector); 

  someMethod() {
    const counter = injectCounter({ injector: this.injector }); 
  }
}

Type Definitions

ts
type CreateInjectableRef<Arguments extends any[], InjectReturn> = Readonly<
  [
    injectFn: InjectFn<InjectReturn>,
    provideFn: ProvideFn<Arguments>,
    injectionToken: InjectionToken<InjectReturn>
  ]
>;

interface CreateInjectableFn {
  <Arguments extends any[], Return>(
    description: string,
    factory: Factory<Arguments, Return>,
  ): CreateInjectableRef<Arguments, Return>;

  root: <Arguments extends any[], Return>(
    description: string,
    factory: (...args: OptionalArgs<Arguments>) => Return,
  ) => CreateInjectableRef<OptionalArgs<Arguments>, Return>;
}
Edit this page on GitHub Last updated: Apr 27, 2026, 21:07:43