npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@doughtnerd/wrangler-di

v1.2.3

Published

A React library for Angular-like dependency injection

Downloads

24

Readme

Wrangler DI

An opinionated React library for dependency injection via higher-order components.

NPM JavaScript Style Guide

Live Example

The example app is a very tiny example of this library being used to query anime character data from anime list.

https://doughtnerd.github.io/wrangler-di/#/anime-character-details

Live Example source code

Best way to see how this is used is looking at the Example App source code: Found here

Install

Npm:

npm install --save @doughtnerd/wrangler-di

Yarn:

npm add @doughtnerd/wrangler-di

Usage

Injecting

First, you need to use the withInjector function to provide the services that you will inject in child components down the component tree.

App.jsx

import React from 'react'

import { withInjector } from '@doughtnerd/wrangler-di'

import { HTTP_SERVICE, AxiosHTTP } from './services/axiosHttp'

import { AUTH_SERVICE, AuthService } from './services/authService'

const appProviders = [
  { provide: HTTP_SERVICE, useClass: AxiosHttp },
  { provide: AUTH_SERVICE, useFactory: (httpService) => new AuthService(httpService), deps: [HTTP_SERVICE] }
]

const App = () => {
  return (
    // The rest of your app
  )
}

export default withInjector(<App />, appProviders)

Accessing Injected Providers

You must use the withProviders function to wrap the component you wish to inject.

import React from 'react'

import { withProviders } from '@doughtnerd/wrangler-di'

import { AUTH_SERIVCE } from './services/authService'

/*
 * The component you use will have a `deps` array prop added. 
 * Each element in the array is the injected provider you define when you use `withProviders`
 */
const SignInPage = ({deps: [authService]}) => {

  return (
    <form onSubmit={(e) => {
        e.preventDefault()
        authService.login(e.target.username.value, e.target.password.value)
      }
    }>
      <input name="username" type="text"></input>
      <input name="password" type="password"></input>

      <button type="submit">Sign In</button>
    </form>
  )
}

// `AUTH_SERVICE` is the `Injection Token` of the thing you're trying to inject.
export default withProviders(SignInPage, [ AUTH_SERVICE ])

API

Injection Token

An injection token is a string that the injector uses to uniquely identify providers. This string can be anything you like but MUST be unique within a single injector.

A Provider object's provide key is an Injection Token.

It is recommended that this string get set to some constant in order to reduce mistyping or 'magic string' issues.

{ 
  provide: 'HttpService', // <- This is an injection token
  useClass: MyHttpService 
}

Provider

A provider is something that the injector will use to create the services that are consumed within an app.

There are three different types of providers.

Value Provider

Used to provide value objects or functions.

{ provide: 'FooBarProvider', useValue: { foo: 'bar' } }

Constructor Provider

Used to provide a class that the injector will create for you

{ provide: 'HttpService', useClass: MyHttpService }

Factory Provider

Used to provide a factory function that will control how the provider is created. Can be used with deps, which is an array of injection tokens of other providers.

{ provide: 'AuthService', useFactory: (httpService) => new AuthService(httpService), deps: ['HttpService'] }

If deps are used, the function assigned to useFactory will be called with deps in the same order as they are in the deps array.

{ 
  provide: 'AuthService', 
  useFactory: (httpService, graphqlService) => { // Notice httpService and graphqlService are in the same order as 'deps'
    return new ApiService(httpService, graphqlService)
  }, 
  deps: ['HttpService', 'GraphqlService'] 
}

withInjector

This function takes two parameters: The component you want to wrap with an injector and the array of providers you want to make available for injection.

const appProviders = [
  { provide: HTTP_SERVICE, useClass: AxiosHttp },
  { provide: AUTH_SERVICE, useFactory: (httpService) => new AuthService(httpService), deps: [HTTP_SERVICE] }
]

// This is also correct
withInjector(<App />, appProviders)

// So is this
withInjector(<App initialAppConfig={ {foo: "bar"} } />, appProviders)

// This is NOT correct
withInjector(App, appProviders)

You don't have to provide everything in one injector. If you have a service that only needs to be injected for one page, provide it at that page level. This library is smart enough to resolve hierarchical dependencies.

Example:

// App.jsx
import MyFavoritesPage from './MyFavoritesPage'

const appProviders = [
  { provide: HTTP_SERVICE, useClass: AxiosHttp }
]

const App = () => {
  return (
    <MyFavoritesPage />
  )
}
export default withInjector(<App />, appProviders)
// ------------ END OF FILE ----------------------

// MyFavoritesPage.jsx
import { FavoritesApi } from './favoritesApi'

const pageProviders = [ 
  { provide: 'FavoritesApi', useFactory: (httpService) => new FavoritesApi(httpService), deps: [HTTP_SERVICE] } 
]

const MyFavoritesPage = ({deps: [favoritesApi]}) => {
  return (
    <ul>
      {favoritesApi.getFavorites().map(favorite => <li key={favorite}>{favorite}</li>)}
    </ul>
  )
}

const MyFavoritesPageWithDeps = withProviders(MyFavoritesPage, [ 'FavoritesApi' ])

export default withInjector(<MyFavoritesPageWithDeps />, pageProviders)
// ------------ END OF FILE ----------------------

withProviders

This function takes two parameters: The component you wish to inject with dependencies, and an array of Injection Tokens. It returns a HOC that can then be passed any props that are on the original component minus the deps array.

/**
 * If props for the SignInPageBase component are:
 * { deps: [AuthService] }
 * 
 * This is all you need to do to inject (assuming you've used `withInjector` somewhere above in the component tree)
 */
const SignInPage = withProviders(SignInPageBase, [ 'AuthService' ])

// No props need to be provided, the HOC has taken care of the `deps` array
return <SignInPage />

/**
 * If you have more props than the `deps` array, for example:
 * { allowGoogleSignIn: boolean, deps: [AuthService] }
 * 
 * You are still able to set the other props like you would with a normal component
 */
const SignInPage = withProviders(SignInPageBase, [ 'AuthService' ])

// You can set the prop without having to set the `deps` prop. The HOC still handles setting that and passes any other props to the `SignInPageBase` component
return <SignInPage allowGoogleSignIn={false} />

Typescript Support

This library was built in TS and hopefully provides useful types you can use. The best way to see how to use Typescript here is to look at the example project found here

FAQ

Why are there no hooks or contexts that I can use?

First, because context is useful in many scenarios but is both over-used and used inappropriately. Often using context makes the component that uses it harder build, test, and re-use.

Second, because context and hooks defeat the purpose of using dependency injection in the first place - especially when those hooks/contexts involve side-effects that change the world outside of the component.

Warnings - Possible errors if you don't follow

  • Don't update the array used in withProviders at runtime
  • Don't update the array used in withInjector at runtime
  • Don't mutate Value Providers
  • Don't use deps with Constructor Providers

License

MIT © doughtnerd