Dependency Injection in Web Atoms
Tags: dependencyinjection, mvvm, typescript, javascript
Published: 2019-06-25 06:01:16

---
title: Dependency Injection in Web Atoms
published: true
description: Web Atoms provides simple and runtime dependency injection with constructor and property injection.
tags: dependency injection, mvvm, typescript, javascript
---

# 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.

```typescript
@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

```typescript
@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.

```typescript
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

```typescript
@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

```typescript
@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.

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

```typescript
export default class StringResource extends BaseStringResource {
    public username = "Username";
} 
```
/hi/StringResource class

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

## Injection

### Constructor Injection

```typescript
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

```typescript
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.