Mvc – What other approaches are there to break circular dependency in MVC

circular-dependencydependency-injectionfront-endmvcreactjs

I'm attempting to build a web front-end based on the MVC pattern, as opposed to based on the libraries involved (e.g. React.js).

I'm using constructor-based dependency injection and interfaces to de-couple the implementations of each element (of MVC), but this still leaves a circular dependency.

  • Controller depends on Model (to respond to end-user events).
  • Model depends on View (to re-render updated model).
  • View depends on React Component (for implementation).
  • React Component depends on Controller methods (to allow for end-user input).

I've broken this circular dependency by using mutator-based dependency injection with the View, like so:

view = new View()
model = new Model(view)
controller = new Controller(model)

component = new Component(controller.onClick)
view.component = component

…which works, but it leaves me with a problem.

As the Component dependency in the View is mutator-injected, it won't be available (for use) until the injection actually happens.

Yet my Model is doing some initialization before the mutator-injection occurs and because the View doesn't have a Component to operate, it just does nothing (until it does have a Component).

I'm thinking I can solve this by storing pending View updates (within the View implementation) and then replaying them in order when the Component becomes available.

Is there a better approach to this?

Edit 1

Thanks to John for pointing out that I have my Model <–> View dependencies the wrong way around.

Yet if I fix them to get this:

model = new Model()
controller = new Controller(model)
component = new Component(controller.onClick)
view = new View(model, component)

…that does allow me to use constructor-based dependency injection all around, but my original problem still persists.

I.e. If my Model does some initialization before the View subscribes to its update broadcasts, then those initial broadcasts will not make it to the View (and hence the end-user).

I'm thinking that I'll just have to try and delay these Model initializations until after the View has subscribed to the Model updates.

Am I missing another approach?

Best Answer

I think your problem is this:

React Component depends on Controller (to allow for end-user input).

The controller should inject callbacks into your view. For example if you want to call a controller method on click, you would expose a onClick property on your React component and the controller would set the property to call the method on itself.

The view should not know anything about the controller. It should only have knowledge of the model.

// model
function todo(title) {
    return {
        title: title,
    }
}

// view
function View({
    todos,
    onAddTodo
}) {
    return (
        <button onClick={onAddTodo}>Add todo</button>
        {
            todos.map(todo => (
                <p>{todo.title}</p>
            ))
        }
    )
}

// controller
function controller() {
    const todos = [todo('foo'), todo('bar')]

    const addTodo = todos.push(todo('new'))

    return (<View todos={todos} onAddTodo={addTodo}/>);
}

In this example:

controller -> model
controller -> view
view -> model

There are no circular dependencies. You may want to see my answer on how to use DI with react because components are usually deeply nested in React, and DI helps solve the problem of passing dependencies to deeply nested components.

Edit 1:

Your snippet from Edit 1 is how it should be designed. But you say

If my Model does some initialization before the View subscribes to its update broadcasts, then those initial broadcasts will not make it to the View

The view shouldn't subscribe directly to the model. In React, changes to the model should be pushed through properties by the controller into the view. The view should be a pure visual representation of the model--it shouldn't be involved with subscribing and updating itself.