DEV Community

Akash Kava for Web Atoms

Posted on • Edited on

Dependency Injection in Web Atoms

Web Atoms Dependency Injection

Why did we create new one?

Well most dependency injection frameworks available on JavaScript weren't supporting runtime dependency injection. For example, you can load dependency just by giving full module url with package name. And it should be loaded before all dependent modules are loaded in the system.

Why do we need runtime dependency injection?

  1. Mocking - To resolve mocked services on design time
  2. Language Resources - Loading language based string resources without worrying about deployment
  3. Being able to load dependency from public cdn such as jsdelivr, unpkg etc.

Registration

To register dependency can decorate class with following attributes.

  • DISingleton
  • DIScoped
  • DITransient

Each attribute lets you declare mock and inject option for remote path.

@DISingleton()
export default class TaskService extends BaseService {

   @Get
   public list(
      @Query("search") search?: string
   ): Promise<ITaskModel[]> { return null; }

}

This will register this class itself for its own dependency.

Mock

@DISingleton( { mock: "./mocks/MockTaskService" } )
export default class TaskService extends BaseService {

   @Get
   public list(
      @Query("search") search?: string
   ): Promise<ITaskModel[]> { return null; }

}

This is interesting, while in design time, TaskService dependency will be resolved against MockTaskService sitting in mocks folder which is relative to current module. And Web Atoms' own module loader takes care of loading this dependency at runtime before current module is fully loaded.

export default class MockTaskService extends TaskService {

   public list(
      search?: string
   ): Promise<ITaskModel[]> { 
      return this.sendResult([ /* mock result */... {} ]); 
   }

}

Abstract class

If you have different dependencies for different platforms, they can be injected as follow, please note {platform} will be web for browser and xf for Xamarin.Forms

@DISingleton( {
   inject: "./{platform}/LocalStorageService",
   mock: "./mock/MockStorageService"
})
export default abstract class StorageService {

   public abstract store(key: string, value: any): Promise<void>;

   public abstract read<T>(key: string): Promise<T>;

}

Now this is interesting, while in design time, MockStorageService will be resolved for StorageService irrespective of the platform you are testing. And on runtime, for Web, it will resolve ./web/LocalStorageService and for Xamarin.Forms it will resolve ./xf/LocalStorageService.

Implementation in different module

@DISingleton({
   mock: "./mocks/MockAuthService",
   inject: "@private-packages/user/dist/{platform}/UserAuthService"
})
export default abstract class AuthService {
}

In this case, your implementation can be in different module altogether.

Language Resources

Making your application support multiple languages is a prime requirement now days. Web Atoms makes it very easy with Dependency Injection.

In the following example, we want to inject class based on the current selected language by user.

@DISingleton({ inject: "./{lang}/StringResource" })
export abstract class BaseStringResource {
    public abstract get username(): string;
}

/en-us/StringResource class

export default class StringResource extends BaseStringResource {
    public username = "Username";
} 

/hi/StringResource class

export default class StringResource extends BaseStringResource {
    public username = "यूज़र नेम";
} 

Injection

Constructor Injection

export default class TaskListViewModel extends AtomViewModel {

    // constructor injection..
    constructor(
        @Inject app: App,
        @Inject private taskService: TaskService,
        @Inject private stringResource: StringResource)
    {
        super(app);
    }
}

Though rewriting constructor on every view model becomes little tedious so we also support property injection, but injected properties are not available in constructor. So we created init method which will be called after view model has been constructed and all injected properties are available.

Property Injection

export default class TaskListViewModel extends AtomViewModel {

    @Inject
    public taskService: TaskService;

    @Inject
    public stringResource: StringResource;

}

App

App class is derived from ServiceProvider and it acts as a service container. Every control and every view model must receive app in construction injection for web atoms to function correctly.

Top comments (0)