[AU2] Trouble migrating commands using DI

Hello,

I finally decided to migrate to au2 but I’m having some issue with the DI part.

In au1 I have an implementation of the command pattern I would like to keep. I like it because I can just import my command and execute it. No need to specify it in the @inject()/resolve. It’s convenient.

command interface/factory

import { Container, Factory } from "aurelia-framework";

export type FactoryType<T> = () => T;

export function getFactory<T>(className): () => T {
  return Factory.of(className).get(Container.instance);
}

command sample

import { getFactory, ICommand } from "app/commands/ICommand";

export class ScrollToCommand implements ICommand {
  public execute(element: number): void {
    window.scrollTo(0, element);
  }
}

export const scrollTo = getFactory<ScrollToCommand>(ScrollToCommand);

usage sample

import { scrollTo } from "app/commands/ui/scrollTo";

class ScrollTopStep {
  public run(
    _routingContext: NavigationInstruction,
    next: Next
  ): Promise<void> {
    scrollTo().execute(0);

    return next();
  }
}

In au 2 what I could achieve so far

command interface/factory

export const rootContainer = DI.createContainer();

export function getFactory<T>(className): () => T {
   const factory = rootContainer.getFactory(className);
   return () => factory.construct(rootContainer);
}

command sample



export class ScrollToCommand implements ICommand {
   constructor(
      readonly httpClient: HttpClient = rootContainer.get(HttpClient),
   ) {
   }
   public execute(element: number): void {
      window.scrollTo(0, element);
   }
}

export const scrollTo = getFactory<ScrollToCommand>(ScrollToCommand);

The usage is the same. It’s kinda working but seems a bit hacky to use the rootContainer const and the arrow function that is required also. Is that the way to go ?

Thank a lot for your help.

You may create an injectable factory for that, if you want.

import { DI, resolve } from '@aurelia/kernel';

export const IMyFactory = DI.createInterface<IMyFactory>(
  'IMyFactory', 
  x => x.singleton(MyFactory)
);
export interface IMyFactory extends MyFactory {}

export class MyFactory {
  private readonly container: IContainer = resolve(IContainer);
  public create($class: Class<unknown>) { return this.container.invoke($class); }
}

Then, you can simply inject your factory.

class ScrollTopStep {
  private readonly factory: IMyFactroy = resolve(IMyFactroy);
  public run(
    _routingContext: NavigationInstruction,
    next: Next
  ): Promise<void> {
    this.factory.create(ScrollToCommand).execute(0);

    return next();
  }
}

Thanks for the suggestion !

I like the idea. However in my case I like the command to be callable directly from the import like in my sample. But maybe i can combine both. I mean exposing a const from inside the command definition so it can be called like this:

// no injection required for this to work
scrollTo().execute(0);