Skip to main content
Version: 2.x

Dependency Injection

Introduction

Dependency injection is a flexible & modular design pattern for passing functionality to the parts of an app that need it. If you need your project to be more maintainable and testable, this might be a good solution for you to use. The @microsoft/fast-element package has dependency injection utilities for regular use in JavaScript, and for injecting dependencies into web components.

Basic Example

The first step to dependency injection is the creation of a container, this is where dependencies will be injected and resolved. There are two available ways to create a container, you can either use createContainer() or getOrCreateDOMContainer(). You can have as many containers as you like, however they will only resolve dependencies registered within their container.

import { DI } from "@microsoft/fast-element/di.js";

const container = DI.createContainer();

Now that your container is created, let's create a few dependencies to host from it.

First let's define an interface for the dependency which we'll call MyServiceConfig, and create a ContextDecorator of the same name which will take the interface as it's generic type:

import { DI } from "@microsoft/fast-element/di.js";

export interface MyServiceConfig {
get: () => Promise<Response>;
}

export const MyServiceConfig = DI.createContext();

Next let's define our implementation of the service:

export class MyService implements MyServiceConfig {
private serviceUrl: string = "http://localhost:7776";

async get(): Promise<Response> {
return await fetch(this.serviceUrl, { method: "GET" });
}
}

Now that we have our service defined, let's add it as a dependency to our application App class. Note the use of the ContextDecorator injected into the constructor as @MyServiceConfig. This will attach it to the instance with the same name.

export class App implements AppConfig {
constructor(
@MyServiceConfig private readonly myService: MyServiceConfig
) {
return;
}

public async getMyServiceStatus(): Promise<number> {
return (await this.myService.get()).status;
}
}

Finally, let's update our container by registering MyService with the dependency injection container. We will use the MyServiceConfig as the key, this will allow App to resolve the dependency. Note the use of transient, this is a utility for creating an instance each time the service is fetched. Other utilities are available, refer to the di API documentation for details.

import { DI, Registration } from "@microsoft/fast-element/di.js";

const { transient } = Registration;

const container = DI.createContainer();

container.register(transient(MyServiceConfig, MyService));

const responseStatus = await container.get(App).getMyServiceStatus();

console.log("Server Status:", responseStatus);

Using Dependency Injection with Web Components

Register Dependencies Before Defining Web Components

When using dependency injection with web components, ensure you define your container before you define your web components. Otherwise your web components may be initialized before the dependencies can be resolved.

Use getOrCreateDOMContainer()

The getOrCreateDOMContainer() must be used for web components to resolve dependencies. Pass in a node to create the container for, in this example we will use document.body.

import { DI, Registration } from "@microsoft/fast-element/di.js";

const { transient } = Registration;

const container = DI.getOrCreateDOMContainer(document.body);

container.register(transient(MyServiceConfig, MyService));

Access Dependencies in the connectedCallback

Once the web component has reached the connectedCallback hook, you will be able to access any injected dependencies.

import { FASTElement, html, observable } from "@microsoft/fast-element";

export class MyComponent extends FASTElement {
@MyServiceConfig myService!: MyServiceConfig;

@observable
status: number;

connectedCallback() {
super.connectedCallback();

this.myService.get().then((value: Response) => {
this.status = value.status;
});
}
}

MyComponent.define({
name: "my-component",
template: html`<div>${x => x.status}</div>`
});