Skip to content

Instantly share code, notes, and snippets.

@skyjur
Last active November 11, 2017 03:24
Show Gist options
  • Select an option

  • Save skyjur/20410b8327696490f9ca89e86571570e to your computer and use it in GitHub Desktop.

Select an option

Save skyjur/20410b8327696490f9ca89e86571570e to your computer and use it in GitHub Desktop.
Primitive dependency injection with React
import { Component, Children } from "react";
const containerKey = 'container';
interface ServiceResolver {
get(serviceIdentifier: string): any;
}
export class Container implements ServiceResolver {
private factories: {[serviceIdentifier: string]: ()=>any} = {};
constant(serviceIdentifier: string, value: any) {
this.factories[serviceIdentifier] = () => value;
}
factory(serviceIdentifier: string, factory: () => any) {
this.factories[serviceIdentifier] = factory;
}
singleton(serviceIdentifier: string, factory: () => any) {
this.factories[serviceIdentifier] = () => {
let val = factory();
this.factories[serviceIdentifier] = () => val;
return val;
}
}
get(serviceIdentifier: string): any {
if(!this.factories[serviceIdentifier]) { throw new Error(`Service "${serviceIdentifier}" is not registered`); }
return this.factories[serviceIdentifier].apply(this);
}
}
export function inject(serviceIdentifier: string) {
return function(target: any, propertyKey?: string, descriptor?: PropertyDescriptor) {
if(!target.constructor.contextTypes) {
target.constructor.contextTypes = {}
}
if(!target.constructor.contextTypes[containerKey]) {
target.constructor.contextTypes[containerKey] = (): null => null;
}
Object.defineProperty(target, propertyKey, {
get() {
if(!this.context[containerKey] || !this.context[containerKey].get) {
throw new Error(`Context should have dependency container in key "${containerKey}"`);
}
return this.context[containerKey].get(serviceIdentifier);
}
});
}
}
export class Provider extends Component<{container: ServiceResolver}> {
static childContextTypes = {
container: (): null => null
}
getChildContext() {
return {container: this.props[containerKey]};
}
render() {
return Children.only(this.props.children);
}
}
import * as React from "react";
import * as TestRenderer from "react-test-renderer";
import { Container, inject, Provider } from "./di";
import { expect } from "chai";
describe('Dependency injection with React', () => {
class Child extends React.Component {
@inject('x') val: number;
render() { return 'x is: ' + this.val; }
}
it.only('Should inject value', () => {
let container = new Container;
container.constant('x', 1);
let r = TestRenderer.create(React.createElement(Provider, {container}, React.createElement(Child, {})));
expect(r.toJSON()).to.eq('x is: 1');
});
it.only('Should throw error about missing context', () => {
let r = () => TestRenderer.create(React.createElement(Child, {}));
expect(r).to.throw('Context should have dependency container in key "container"');
});
it.only('Should throw error about missing service', () => {
let container = new Container;
let r = () => TestRenderer.create(React.createElement(Provider, {container}, React.createElement(Child, {})));
expect(r).to.throw('Service "x" is not registered');
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment