npm package: @alexlafroscia/service-locator
Description: Serice locator pattern for ES6 classes
Latest version: 1.2.0

# @alexlafroscia/service-locator

A JS implementation of the [Service Locator pattern][service locator pattern] for ES6 classes.

## Features

- Store commonly-needed values in a single place, allowing easy access to shared state
- Inject registry access into any ES6 class through a mixin, or extend the provided ES6 class
- Easily provide stubs for objects in the registry to simplify testing

## A quick example

Imagine we have a component that should check if the user is logged in and either prompt for login, or greet the user. We have two parts: fetching the user data, and the component itself.

If you were to use this package, here is how the code might be set up (note that I'm using a fake Component implementation that is something similar to React or Preact):

```javascript
import { registry, RegistryAccess } from '@alexlafroscia/service-locator';

class UserService {
  fetchUserInfo() {
    return fetch('...').then(res => res.json());
  }

  async isLoggedIn() {
    const { isLoggedIn } = await this.fetch('...');

    return isLoggedin;
  }
};

registry.register('userService', new UserService());

export default class LoggedInComponent extends RegistryAccess.Mixin(Component) {
  async getInitialState() {
    const isLoggedIn = this.userService.isLoggedIn();
    this.setState({ isLoggedIn });
  }

  render({ isLoggedIn }) {
    return isLoggedIn ? 'Hello!' : 'Please log in!';
  }
}
```

What's the benefit here? For one, we've separated out the thing that fetches the data from the thing that displays it, which is always a good idea. Additionally, we made it easy to stub the data fetching in a test:

```javascript
test('shows the right thing when the user is logged in', () => {
  registry.stub('userService', {
    async fetchUserInfo() {
      return { isLoggedIn: true };
    }
  });

  const result = render(<Component />);
  expect(result).to.contain('Hello!');
});

test('shows the right thing when the user is not logged in', () => {
  registry.stub('userService', {
    async fetch() {
      return { isLoggedIn: false };
    }
  });

  const result = render(<Component />);
  expect(result).to.contain('Please log in!');
});
```

## Could you make this easier?

In fact, you can. I _love_ the syntax that Ember provides for injecting services, and wanted to emulate that as closely as possible. Through the power of [decorators][decorators] I was able to do it. The downside is that you need a [Babel transform for it][decorators-babel-transform], but with that in place, we can clean up the implementation a whole lot. Rather than repeating an extended example, check out the examples in the tests.


## Why?

I've struggled with making a complex set of components using [Skate.js][skate] where all components needed access to some state. I didn't want to have pass the properties to every component explicitly, and wished I had an easy way of using service injection the way that I'm used to with [Ember.js][ember]. So, I started messing around with this.

## Should I use this?

Maybe. In reality, it's not that different than just accessing everything off the `window`, and we all know that's a bad idea (although this works in a Node environment where `window` isn't available, and is built for use with JavaScript modules where there isn't a shared global context).

## Compatibility Notes

This package is meant to be consumed in an ES6 environment. It relies on [Proxy][proxy] and is built with ES6 classes in mind. Make sure your environment can handle this.

## Prior Art

- [Ember.js service injection][ember service injection]

[service locator pattern]: https://martinfowler.com/articles/injection.html
[skate]: https://github.com/skatejs/skatejs
[ember]: https://www.emberjs.com/api/ember/2.17/classes/@ember%2Fservice/methods/inject?anchor=inject
[ember service injection]: https://www.emberjs.com/api/ember/2.17/classes/@ember%2Fservice/methods/inject?anchor=inject
[proxy]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
[decorators]: https://github.com/tc39/proposal-decorators
[decorators-babel-transform]: https://www.npmjs.com/package/babel-plugin-transform-decorators-legacy