Dependency Injection in Web Atoms
Akash Kava
Posted on June 25, 2019
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?
- Mocking - To resolve mocked services on design time
- Language Resources - Loading language based string resources without worrying about deployment
- 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.
Posted on June 25, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.