Advanced Dependency Injection with AppTask

Currently in the new V2 docs there isn’t any specific section introducing AppTask, sure there are code snippets which contain it here and there but it’s never really explained what it’s for and that its related to Dependency Injection.

I would be really nice to have a section dedicated to some of the more advanced Registrations which use AppTask, plugin authors especially would find this useful.

1 Like

The AppTasks are somewhat unrelated with the dependency injection. There are the task slots which can be utilized to bootstrap something (example: some async initialization inside a plugin) before the app starts or to perform cleanup before the app stops.

@dwaynecharrington do you think we can have some docs on this?

The way I used to was to customise the instantiation of a class in the IoC container, similar to how you might want to use a factory lambda in C# when registering a dependency as it requires a special construction or special initialisation.

I hope that makes sense.

import { AppTask, HttpClient, IContainer, IRegistry } from 'aurelia';

export const FetchClientMiddleware: IRegistry = {
    register(container: IContainer): IContainer {
        container.register(
            AppTask.beforeCreate(
                HttpClient,
                client => FetchConfiguration(client)));

        return container;
    }
};

const FetchConfiguration = (http: HttpClient): void => {
    http.configure(config => {
        return config
            .useStandardConfiguration()
            .withDefaults({ headers: { 'Accept': 'application/json' } })
            .withBaseUrl('/api');
    });
};

I think what you need is a Registration.callback registration. That gives you the factory as you are used to in C#. However, that has nothing to do with AppTask.

did you see my example above, is that incorrect or is there another recommended approach to that?

It seems that the default configuration of the HTTP client is of concern to you. I think what you need rather is a singleton wrapper class that encapsulates the HttpClient and of course the relevant initialization code, and then you go on injecting that class everywhere.

import { HttpClient } from '@aurelia/fetch-client';
import { newInstanceOf, DI } from '@aurelia/kernel';

class HttpClientWrapper {
  public constructor(
    @newInstanceOf(IHttpClient) private readonly httpClient: IHttpClient
  ){ 
     /**
      * here configure the httpClient as shown here:
      * https://docs.aurelia.io/developer-guides/fetch-client#configuring-the-fetch-client 
      */
    }
}

interface IHttpClientWrapper extends HttpClientWrapper  {}
const IHttpClientWrapper = DI.createInterface<IHttpClientWrapper>('IHttpClientWrapper', x => x.singleton(HttpClientWrapper));

/* usage */
class MyVm {
  public constructor(
    @IHttpClientWrapper private readonly service: IHttpClientWrapper 
  ){}
}

If that’s all what you require, then you can forget about the AppTask. I mean that it is super useful but not applicable for your case.

Is it still possible to do something like this from V1? (Without the use of a wrapper).

const http = new HttpClient().configure(config => {
    config
        .withHeader("Content-Type", "application/json")
        .withInterceptor(
            new HttpInterceptor(
                new Notifier(),
                aurelia.container.get(EventManager)));
});

aurelia.container.registerInstance(HttpClient, http);

Maybe this?

import { HttpClient, IContainer, IRegistry, Registration } from 'aurelia';

export const FetchClientMiddleware: IRegistry = {
    register(container: IContainer): IContainer {
        const http = new HttpClient();
        http.configure(c => c
            .useStandardConfiguration()
            .withDefaults({ headers: { 'Accept': 'application/json' } })
            .withBaseUrl('/api'));

        container.register(Registration.instance(HttpClient, http));

        return container;
    }
};
1 Like

This seems to work fine.

I’m still not sure why the examples in the docs seem to use AppTask to register things? is it meant to be used for registering components and not services?

While @dwaynecharrington is working his magic on the AppTask documentation, let me give you a shorter version. The AppTasks are slots/hooks in your application lifecycle, those are outside your traditional view models; that is, those are applicable in the “application”-level. You can tap into those hooks to perform anything as you please. As you were asking about registering resources to DI, you can do that as well (please wait for the official doc for this) from a specific hook.

Another example I can give is of i18n. The initialization of i18n is an async process. For that reason, the associated promise is awaited by tapped into a AppTask hook. Take a look here: aurelia/configuration.ts at master · aurelia/aurelia · GitHub. Hope this somewhat answers your question.

frameworks typically have a hard time to let extensions decide when they want to register initialize or wait on them and thus delaying the bootstrapping as its a timing sensitive and typically complex workflow often resulting in negative TTI.

angular e.g. offers APP_INITIALIZER special tokens to register dependencies that need to be resolved upfront but the whole process is quite cumbersome with dynamic deps so you often resort to outside-angular fetches before the bootstrap phase begins alltogether (e.g. CASL based rules for a given JWT).

the intention with aurelia v2 is to offer a much broader set of entry points acting as hooks before/after specific steps. As @Sayan751 noted the i18n service requires an async setup (resource loading) as such it makes use of those Tasks. I’m also looking forward to @dwaynecharrington 's docs on said topic as the surface area of hooks is much larger than anything i’ve seen in any other framework so far

2 Likes