DEV Community

Cover image for Dependency Injection, Inversion of Control & Jails 5
Eduardo Ottaviani Aragão
Eduardo Ottaviani Aragão

Posted on

Dependency Injection, Inversion of Control & Jails 5

Cover from: https://www.linkedin.com/pulse/understanding-dependency-injection-alex-merced

In software engineering, dependency injection is a programming technique in which an object or function receives other objects or functions that it requires, as opposed to creating them internally. ( Wikipedia )

Alright, here's the deal: Dependency Injection is like the secret sauce for untangling your system. It's a straightforward method to loosen up the connections between components. With this approach, you're basically flipping the switch on who's calling the shots in a component or module. And trust me, it can totally revolutionize how you structure your code.

Inversion of Control

You know, Inversion of Control (IoC) is baked right into the core of JavaScript. It's like second nature for us JavaScript devs, and honestly, it's a breeze to pull off in this language, especially with callback functions.
IoC lets us split up generic behavior from the nitty-gritty specifics, which gives us some awesome reusability perks too.

Example - Find a user by id from a list and remove it from the list.

Without Dependency Injection and Inversion of Control

const findUserAndRemove = (id, list) => {
  let newlist = []
  list.forEach( user => {
    if( user.id !== id )
      newlist.push( user )
  })
  return newlist
}
Enter fullscreen mode Exit fullscreen mode

There's nothing completely wrong with the code above, it's easy to understand and it does its job. However, we might encounter scenarios where we want to locate a user based on another property, or where we desire to do more than simply remove the user from a list; we may wish to modify or enhance the user with additional properties, for instance. In such cases, not only will the name of our function lose its relevance, but it will also expand with increasingly complex behaviors, potentially rendering it difficult to manage.

There are at least two different behaviors on that code, the first is the iteration over a list, the second one is by identifying a case where we are looking for and do something with it. The findUserAndRemove function has 100% of control of what is going on on that task.

So how can we decouple those two behaviors and make it more reusable?

The solution is already inherent in the language. The filter array method is an implementation that achieves Inversion of Control by incorporating a callback function.

Therefore, the filter function's purpose is to iterate over each element of the list and return a new list containing only elements that satisfy a given condition.

In the initial code snippet, the findUserAndRemove function possesses complete control over the user selection process.

However, the filter method reverses this control dynamic, empowering you to determine the criteria for selecting the user:

With Dependency Injection and Inversion of Control

const newlist = list.filter( user => user.id !== id )
Enter fullscreen mode Exit fullscreen mode

With a single line we did exactly the same as we did before and filter can be reused for many other cenarios.
If we want to find a particular user without changing the size of items of a particular list, we can use the map method for it:

const newlist = list.map( user => {
  if( user.id == id )
    user.role = 'admin'
  return user 
})
Enter fullscreen mode Exit fullscreen mode

DI & IC in Frameworks

Those concepts are so deeply ingrained in the language that they shouldn't become more complex when transitioning into Frameworks.

Indeed, React lacks a dedicated Dependency Injection (DI) mechanism; it is inherently built into the framework due to the functional nature of its components. Consequently, all that's required is to anticipate a callback function within your generic component, and there you have it.

In contrast, Angular doesn't offer such simplicity. To inject dependencies, you must invest time in understanding its mechanisms, and familiarize yourself with concepts like Services, Decorators, Injectable, Providers, if you haven't already done so.

With that said, it's clearer, at least to me, that the more a framework or library aligns with the language's native features and functionality, the easier it becomes to accomplish tasks.


DI & IC in Jails

Jails is all about simplicity, so naturally, those things should be straightforward in Jails, right? Yeah… and they are:

// at home.js
import jails from 'jails-js'
import router from 'jails.pandora/router'
import store from 'jails.pandora/store'

import * as myComponent from './components/my-component'

jails.register('my-component', myComponent, { router, store, ... })
//...other components registering
Enter fullscreen mode Exit fullscreen mode

Component usage

export default function myComponent ({ main, dependencies }) {

  const { router, store } = dependencies 

  main( _ => {
    router.get('/', log)
  })

  const log = () => console.log('Hello World!')
}
Enter fullscreen mode Exit fullscreen mode

The injection occurs during the component registration phase, and the purpose of Dependency Injection (DI) and Inversion of Control (IC) in Jails aligns precisely with what we discussed earlier in this article.

It's all about decoupling generic behavior from specific logic. Moreover, it involves sharing capabilities and instances between components, as illustrated in the example above.


A Real World Use Case

Form Validation poses an ongoing challenge in the Front End realm. Eventually, every framework and library will offer a general abstraction to handle this task, and Jails is no exception.

Recently, I developed a validation component using Jails and shared it in a repository. While I don't anticipate creating numerous abstractions with Jails, as the philosophy of this application library prioritizes full interoperability with the Vanilla JavaScript ecosystem, there's an exception for Form Validation.

The challenge in crafting such an abstraction lies in ensuring its generic nature, enabling anyone worldwide to utilize it irrespective of regional specificities. To achieve this, we must discern between what aspects should be generic and what should remain specific.

The generic aspect entails attaching event handlers to validate functions, updating the view with error feedback when necessary, and removing errors upon validation. This segment manages events and applies classes to DOM elements for programmers to utilize in handling UI errors.

Conversely, since validations can vary based on regions, business rules, etc., all validations should be externally provided to the component, adhering to a defined contract to ensure correct behavior of the generic abstraction.

Additionally, dynamic configurations must be accessible to specify the validations a field requires.


Introducing - Form Validation Component

So, to invert the control of the Form Validation component and ensure its generic usability across various scenarios, the Form Validation Component leverages the Jails Dependency Injection system to inject specific validation rules. Additionally, it retrieves configuration data from HTMLFormElement.


So that's it… That's all I got for you guys.

Checkout the Demo hosted on StackBlitz so you can learn more about that.

See you!

Top comments (0)