Dependency Injection

Automatic discovery and creation of services, view and components

Overview

Dynamics Mobile SDK eliminates the need for the developer to create class instance for certain classes. The framework automatically discovers if specific class instance must be created and automatically creates it for the developer. The developer in this case needs to only declare the need for an instance of a specific class and then just use it.

Such a concept of class instance discovery and creation is used widely in the software industry today and known as Dependency Injection, which is a part of a more broad concept called Inversion of Control

Services, Views, Components

You might noticed already that above we talk about automatic discovery and creation of certain classes. It means that in order for the framework to be able to discover and inject instance of specific class, the class must be "marked" as injectable.

Dynamics Mobile SDK defines 3 types of injectable classes:

The automatic discovery and injection feature is based on the decorators feature of TypeScript

View

A view is a class, which has a number of well-defined methods with fixed signatures, which controls the rendering and behavior of the UI presented to the user. When the developer wants to display certain screen to the user, the developer needs to implement View class.

See more details about Views and UI here

Let's see how we can define a view class:

//file-name: customer-list-view.ts
import { View } from '@dms';
@View()
export class CustomerList {
}

Actually in order for us to declare a view class, we need to place a decorator for our class called @View(). This instructs the framework that this class is a View ( e.g. it will be responsible to render UI ) and that it will be able to eventually take advantage of the automatic injection of other class instances.

Service

A service class is such class, which has a decorator @Service and provides non UI related functionalities. Such a class may fetch list of customers from the local on-device database or from a remote source and then filter them based on their revenues and returning the top 10 by revenue. We can declare a service class like this:

//filename: customer-service.ts
import { Service } from '@dms';
import { Customer } from '@dms-bo';
@Service()
export class CustomersService {
public async getTopByRevenue(): Promise<Customer>{
return [
{no:'C001', name:'Customer 1', revenue: 10000},
{no:'C002', name:'Customer 2', revenue: 5000},
]
}
}

Components

A component class is a class, which has @Component() decorator and is meant to render and present UI to the user. The components are quite similar to Views, but they are meant to be reused across different views. A good example for a component is a Toolbar rendered on top of each view where we place the common buttons for our app. We can implement the Toolbar as a component and reuse it in every View class. We will not talk anymore about components in this document.

See more about Components here

Using the View and Service - full example

We have declared our View and our Service. Let's now extend our View so that it can use the CustomerService class.

One possible and straightforward approach would be to just create an instance of the CustomerService class in our view , like this:

//filename: customer-list-view.ts
import { View } from '@dms';
import { CustomerService } from './customer-service';
@View()
export class CustomerList {
CustomerService: CustomerService;
constructor(){
//manually create our service instance
this.CustomerService = new CustomerService();
}
async load(): Promise<void>{
//call the service method to return the top customers by revenue
let topCustomers = await this.CustomerService.getTopByRevenue()
}
}

Well this approach, where we manually create our service instance will work quite well, but it's not scalable. What if our CustomerService depends on another Service class? We need to first create this other Service class instance and then pass it to the constructor of the CustomerService class. The other issue that we might have is that manual instance creation approach is not easily testable. If we want to test our View class, it will be hard to replace the CustomerService with another implementation during our unit testings.

Here comes the Dependency Injection approach in, making our work easier. Instead of creating manually CustomerService instance we only can declare that we need an instance of the CustomerService class and wait for the framework to automatically create one.

//filename: customer-list-view.ts
import { View } from '@dms';
import { CustomerService } from './customer-service';
@View()
export class CustomerList {
//we do not need this
//CustomerService: CustomerService;
constructor(
//declare that we need automatic dependency injection here
private CustomerService: CustomerService
){
//we dont need this
//this.CustomerService = new CustomerService();
}
async load(): Promise<void>{
//call the service method to return the top customers by revenue
let topCustomers = await this.CustomerService.getTopByRevenue()
}
}

What we can do instead is to declare the CustomerService as a member in the constructor of the View on line #13. This statement is a TypeScript way to declare private or public member of the class - our View in this case. However, when the view is rendered, our framework will realize that the CustomerService member is a Service class (e.g. class with @Service decorator) and will automatically create and pass the new instance to the constructor. The only thing remaining to be done is to use the CustomerService instance in the load method of the view.

Built-in Services.

The Dynamics Mobile SDK comes with a set of built-in classes, which provide various useful methods. Such classes are UserInterfaceService, DeviceService, FileService and others. You can tell from the name that these classes are actually services and they can be declared as members of the View ( or other Service) classes in their constructors. The framework will automatically inject new instances of these services on demand.

You can recognize if any of the built-in SDK classes is a Service, if their class name has the Service word at the end.

Also, the built-in services classes have the following hint in their respective documentation:

SERVICE This class is a "service" and it is automatically injected if declared as a member in the constructor of the consuming class. - See the constructor section below for details - See the Automatic Instance Injection page for details on the topic

Wish you happy dependency injecting. 😀