Skip to content

Commit fabe12c

Browse files
author
mcibique
committed
Added introduction to DI
1 parent fbc12ba commit fabe12c

File tree

1 file changed

+171
-0
lines changed

1 file changed

+171
-0
lines changed

README.md

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,3 +214,174 @@ class Nav {
214214
}
215215
}
216216
```
217+
218+
# Dependency Injection
219+
220+
Mocking dependencies and imports in tests might be really tedious. Using [inject-loader](https://github.com/plasticine/inject-loader) can help a lot but requires lots of ugly coding. Another option is to involve dependency injection, such as [inversify](https://github.com/inversify/InversifyJS). They are primarily focused on Typescript but they support [vanilla JS](https://github.com/inversify/inversify-vanillajs-helpers) too. In scenarios where you don't have control over instantiating components and services, there is another very handy [toolbox](https://github.com/inversify/inversify-inject-decorators) which gives you a set of decorators usable in Vue components.
221+
222+
## Setting up DI in VUE
223+
224+
We need to create a container instance for holding all registered injections. We need only one instance per application:
225+
```js
226+
import { Container } from 'inversify';
227+
let container = new Container();
228+
export default container;
229+
```
230+
and then just execute it in the bootstrap phase of the application:
231+
```js
232+
import './di';
233+
```
234+
Let's create a service:
235+
```js
236+
export default class AuthService {
237+
login (username, password) {
238+
// do something
239+
}
240+
}
241+
```
242+
... register it in `di.js`:
243+
```js
244+
import { Container } from 'inversify';
245+
import AuthService from 'services/auth';
246+
let container = new Container();
247+
container.bind('authService').to(AuthService);
248+
export default container;
249+
```
250+
... and then use it in VUE component:
251+
```js
252+
import container from './di';
253+
254+
class LoginView extends Vue {
255+
constructor() {
256+
this.authService = container.get('authService');
257+
}
258+
login (username, password) {
259+
return this.authService.login(username, password);
260+
}
261+
}
262+
```
263+
There are a couple of issues with this approach:
264+
1. If we register all our services in `di.js`, then we nullified code splitting because everything is required during the application bootstrap. To solve this issue, let's register service only when is required for the first time:
265+
```js
266+
import container from './di';
267+
268+
export default class AuthService {
269+
login (username, password) {
270+
// do something
271+
}
272+
}
273+
274+
container.bind('authService').to(AuthService);
275+
```
276+
2. We have lots of [magic strings](http://deviq.com/magic-strings/) everywhere (e.g. what we would do if `authService` changes the name? How we prevent naming collisions). Well, ES6 introduced [symbols](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol) that we can use. Don't forget to export the Symbol:
277+
```js
278+
import container from './di';
279+
280+
export const AUTH_SERVICE_ID = Symbol('authService');
281+
282+
export default class AuthService {
283+
login (username, password) {
284+
// do something
285+
}
286+
}
287+
288+
container.bind(AUTH_SERVICE_ID).to(AuthService);
289+
```
290+
... and use it in VUE component:
291+
```js
292+
import container from './di';
293+
import { AUTH_SERVICE_ID } from './services/auth';
294+
295+
class LoginView extends Vue {
296+
constructor() {
297+
this.authService = container.get(AUTH_SERVICE_ID);
298+
}
299+
login (username, password) {
300+
return this.authService.login(username, password);
301+
}
302+
}
303+
```
304+
305+
## Using decorators
306+
Decorators can help us to eliminate lots of code repetition and make our code cleaner.
307+
308+
### @Register decorator
309+
`inversify-vanillajs-helpers` can create Register helper which can be used as a decorator anywhere in our code. Let's add this code to `di.js`:
310+
```js
311+
import { helpers } from 'inversify-vanillajs-helpers';
312+
313+
let container = new Container();
314+
let register = helpers.register(container);
315+
316+
export { register as Register } // we are exporting decorator with capital R because other decorators we are already using (e.g. for Vuex) also have a capital letter
317+
```
318+
319+
... and then use it:
320+
```js
321+
import { Register } from '@di';
322+
323+
export const AUTH_SERVICE_ID = Symbol('authService');
324+
325+
@Register(AUTH_SERVICE_ID)
326+
export default class AuthService {
327+
login (username, password) {
328+
// do something
329+
}
330+
}
331+
332+
```
333+
334+
If your service depends on other services, you can pass IDs as a second parameters:
335+
```js
336+
import { Register } from '@di';
337+
import { OTHER_SERVICE_ID } from './other-service';
338+
339+
export const AUTH_SERVICE_ID = Symbol('authService');
340+
341+
@Register(AUTH_SERVICE_ID, [ OTHER_SERVICE_ID ])
342+
export default class AuthService {
343+
constructor(otherService) {
344+
this.otherService = otherService;
345+
}
346+
347+
login (username, password) {
348+
username = otherService.doSomething(username);
349+
// do something else
350+
}
351+
}
352+
```
353+
Check out documentation for [inversify-vanillajs-helpers](https://github.com/inversify/inversify-vanillajs-helpers#usage) to see all possibilities
354+
355+
### @LazyInject decorator
356+
We can improve injection in our VUE components too by using LazyInject decorators from [inversify-inject-decorators](https://github.com/inversify/inversify-inject-decorators). Let's create it and export it in `di.js` first:
357+
```js
358+
import getDecorators from 'inversify-inject-decorators';
359+
360+
let container = new Container();
361+
let { lazyInject } = getDecorators(container);
362+
363+
export { lazyInject as LazyInject }
364+
```
365+
Now we can adjust code in VUE component:
366+
```js
367+
import { LazyInject } from '@di';
368+
import { AUTH_SERVICE_ID } from './services/auth';
369+
370+
class LoginView extends Vue {
371+
@LazyInject(AUTH_SERVICE_ID) authService;
372+
373+
login (username, password) {
374+
return this.authService.login(username, password);
375+
}
376+
}
377+
```
378+
`LazyInject` caches the instance of `authService` until the component is destroyed. Check out the documentation for [inversify-inject-decorators](https://github.com/inversify/inversify-inject-decorators#basic-property-lazy-injection-with-lazyinject) to see more options and other decorators.
379+
380+
381+
## Testing using Dependency Injection
382+
383+
TODO
384+
* mock service in component tests
385+
* mock the other service in service tests
386+
* mock window.location
387+
* mock store in the router

0 commit comments

Comments
 (0)