3

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?

5
  • 1
    In MVC, a model should not depend on any view unless you are doing something pretty strange. The view depends on the model.
    – John Wu
    Aug 15, 2017 at 2:28
  • 2
    Regarding the edit: The initial state of the View should not depend on broadcasts from the Model, but the View should explicitly ask the Model for the information it initially needs. That way, the missed broadcasts at Model initialization don't form a problem. Those broadcasts are then only used to indicate changes after the Model and View are connected to each other. Aug 15, 2017 at 7:11
  • 2
    "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)." - You can do either of these: (1) have the view pull the data it needs once the model is passed in, or (2) have the model push the data to to an observer/listener when it subscribes. Aug 15, 2017 at 7:24
  • Can you clarify what exactly it is that needs initialization in your model and which information about it is to be sent to the View?
    – larsbe
    Aug 15, 2017 at 8:02
  • @larsbe E.g. Some data fetched async from a back-end. Aug 15, 2017 at 9:34

2 Answers 2

1

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.

6
  • If I understand you correctly, then that is exactly what I'm doing. My React Component has an onClick prop (let's say) and I pass controller.onClick in as value for that prop. React Component depends on Controller, but the View interface does not depend on Controller. Have I misinterpreted you? Aug 15, 2017 at 2:43
  • Who passes controller.onClick as a value for that prop? Nothing in the view should know anything about the controller at all.
    – Samuel
    Aug 15, 2017 at 2:50
  • A factory passes it in. I think I understand how I may have misled you, so I'll update my question. Aug 15, 2017 at 2:51
  • I've changed the code a little, does that make it clearer? The code up there would be in my "factory". So the View interface doesn't depend on the Controller interface, but the Component does depend on functions, which the Controller interface provide. Aug 15, 2017 at 2:55
  • Your Edit #1 snippet is how it should be done. I've updated my answer too.
    – Samuel
    Aug 15, 2017 at 2:59
0

Building on the feedback provided by Bart and Filip (in the question comments)...

This dependency design works well:

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

As for overcoming the issue where the Model updates before the View "attaches" itself to the Model, a nice approach would be for the View to query the Model (for what it needs) when it gets constructed and then subscribe to the Model (for updates).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.