依賴注入Dependency Injection(一)
Let's say we have a CustomerEditScreen
that
needs to load a Customer
entity
by ID from a web service. We wouldn't want to place all the details of our AJAX implementation inside our CustomerEditScreen
class.
Instead, we would want to factor that into a CustomerService
class
that our CustomerEditScreen
Customer
.
Aurelia's dependency injection container lets you accomplish this by declaring that the CustomerEditScreen
needs
to have a CustomerService
injected
at creation time.
我們不想把Ajax請求寫在CustomerEditScreen裡面,我們把Ajax請求放在CustomerService裡面,這樣任何class都可以利用這個檔案請求。Aurelia's
dependency injection container 通過在建立時候宣告CustomerEditScreen需要CustomerService
Typically,
you would use Decorators, an ES Next feature supported by both Babel and TypeScript. Here's what it looks like to declare that the CustomerEditScreen
needs
a CustomerService
用修飾符,ES6特性,Babel和TS都支援。下面是個demo。
import {CustomerService} from 'backend/customer-service'; import {inject} from 'aurelia-framework'; @inject(CustomerService) export class CustomerEditScreen { constructor(private customerService: CustomerService) { this.customer = null; } activate(params) { return this.customerService.getCustomerById(params.customerId) .then(customer => this.customer = customer); } }
Notice
that we use the inject
decorator
and that the constructor signature matches the list of dependencies in the inject
decorator.
This tells the DI that any time it wants to create an instance of CustomerEditScreen
it
must first obtain an instance of CustomerService
which
it can inject into
the constructor of CustomerEditScreen
during
instantiation. You can have as many injected dependencies as you need. Simply ensure that the inject
decorator
and the constructor match one another.
我們用inject修飾符,建構函式匹配inject裡面列出的依賴。注意,建構函式裡面的依賴必須要一一對應inject修飾符裡裡面的。這個告訴DI,它任何時候想要建立CustomerEditScreen例項,必須首先獲得CustomerService例項,這個要獲得的例項可以在CustomerEditScreen例項化的時候被注入到CustomerEditScreen的建構函式中。
多個依賴的注入需要建構函式裡面的依賴和inject裡面的依賴一一對應。
import {CustomerService} from 'backend/customer-service';
import {CommonDialogs} from 'resources/dialogs/common-dialogs';
import {EventAggregator} from 'aurelia-event-aggregator';
import {inject} from 'aurelia-framework';
@inject(CustomerService, CommonDialogs, EventAggregator)
export class CustomerEditScreen {
constructor(private customerService: CustomerService, private dialogs: CommonDialogs, private ea: EventAggregator) {
this.customer = null;
}
activate(params) {
return this.customerService.getCustomerById(params.customerId)
.then(customer => this.customer = customer)
.then(customer => this.ea.publish('edit', customer));
}
}
If you are using TypeScript, you can take advantage of an experimental
feature of the language to have the TypeScript transpiler automatically provide Type information to Aurelia's DI. You can do this by configuring the TypeScript compiler with the "emitDecoratorMetadata":
true
option in the compilerOptions
section
of your tsconfig.json
file.
If you do this, you don't need to duplicate the type information with inject
,
instead, as long as your constructor definition contains its paramaters' types, you can use Aurelia's autoinject
decorator
like this
如果用的是ts,那麼只需要進行上述的配置就可以自動注入依賴。inject換成了autoinject。
Interestingly,
you don't need to use our autoinject
decorator
at all to get the above to work. The TypeScript compiler will emit the type metadata if any decorator
is added to the class. Aurelia can read this metadata regardless of what decorator triggers TypeScript to add it. We simply provide the autoinject
decorator
for consistency and clarity.
其實如果用ts,連autoinject都不需要新增,之所以新增,只是為了讓程式碼更清晰。
import {CustomerService} from 'backend/customer-service';
import {CommonDialogs} from 'resources/dialogs/common-dialogs';
import {EventAggregator} from 'aurelia-event-aggregator';
import {autoinject} from 'aurelia-framework';
@autoinject
export class CustomerEditScreen {
constructor(private customerService: CustomerService, private dialogs: CommonDialogs, private ea: EventAggregator) {
this.customer = null;
}
activate(params) {
return this.customerService.getCustomerById(params.customerId)
.then(customer => this.customer = customer)
.then(customer => this.ea.publish('edit', customer));
}
}
If
you aren't using Babel's or TypeScript's decorator support (or don't want to), you can easily provide inject
metadata
using a simple static method or property on your class:
如果babel和ts都不用,那麼就這樣新增依賴。用一個inject靜態方法。
import {CustomerService} from 'backend/customer-service';
import {CommonDialogs} from 'resources/dialogs/common-dialogs';
import {EventAggregator} from 'aurelia-event-aggregator';
export class CustomerEditScreen {
static inject() { return [CustomerService, CommonDialogs, EventAggregator]; }
constructor(customerService, dialogs, ea) {
this.customerService = customerService;
this.dialogs = dialogs;
this.ea = ea;
this.customer = null;
}
activate(params) {
return this.customerService.getCustomerById(params.customerId)
.then(customer => this.customer = customer)
.then(customer => this.ea.publish('edit:begin', customer));
}
}
因為我們class檔案裡面的方法都是public或者private,我們想要使用必須對class進行例項化,只有static可以直接使用,
所以需要注入依賴例項化後才能呼叫。