1

I'm spending some time lately reading and thinking about alternative ways to build web applications. One of the approaches I'm experimenting with is heavily based on OO, using TypeScript. The general idea is to divide the application into ever-smaller components, similar to the way Angular (and so many others) does it.

One important aspect of it is that components define their APIs (such as callbacks to be executed after some event) as part of their constructor. This is a nice way to establish communication between components.

Another facet worth highlighting is that each component is responsible for the bit of HTML it is associated with, which is created programatically (the idea behind this particularity is to provide type-safer HTML) by the component itself.

As a proof of concept, I created a small to-do application, available at https://jsbin.com/sopener/edit?js,output.

My question is: now that components' constructors have already a good purpose, what options do I have to manage dependencies of those components, such as services responsible for server communication, browser-specific objects such as location or document, etc.? Hard-coding those dependencies would make testing very difficult, and expecting them as part of the constructor, mixed with the rest, would make things unclear, and it would make difficult to implement some kind of Spring-like dependency injection facility (e.g. an @Autowired/@Inject decorator).

I'm also interested in any other insights about this architectural concept. For instance, do you think it is a good idea to use classes, or simple JavaScript-style objects would suffice or even fit better? Are responsibilities well divided with it, or would you structure things differently?

  • Why is server communication or location/document not a component? – Telastyn Sep 25 '17 at 2:21
  • Your question confuses me. Are you proposing that they should be a component? If so, what would a component be for you? In this case, I was using the word "component" to refer to classes representing bits of UI, whose only purpose is to render HTML elements and control user interaction. In this definition, it wouldn't make sense to perform server communication from a component, nor to call native global objects such as location a component. – DanielM Sep 26 '17 at 11:35
1

My approach with React is a solution to a very similar problem as what you're describing. With React components the class constructor is used by the framework to pass element properties from the parent component. To pass additional non-property dependencies I introduce a "factory" function.

export default function(client, location, document) {
    return class LoginComponent extends Component {
        // ...
    }
}

Then at the composition root, you invoke the factory above to get the component class which will be injected anywhere that LoginComponent is used.

  • It's a very interesting approach, a clean way to separate both kinds of arguments. Could you comment on your personal experience so far using it? – DanielM Sep 26 '17 at 11:57
  • A different question -- how would you handle the dependency of the h() function (and those using it for conciseness, such as button()) in document? I find it a tricky case, because they feel like (are?) pure functions, so using them directly seems harmless, but at the same time document will not be available during unit tests, unless run in the browser. And since they are used quite a lot, and there are many of them, including them as part of the factory is quite a pain in the ass. – DanielM Sep 26 '17 at 12:14
  • It's worked great for us. I don't follow your other questions. – Samuel Sep 26 '17 at 19:14
  • Sorry, I must have clarified that I was referring to the JS Bin code that I linked in the original question. There you can find a function h() that takes in a tag name, a map of HTML properties, and an array of children, and returns an HTML element -- and uses document.createElement() for that. And also functions such as button(), which are partially applied versions of h() with a fixed first argument, like "button". My idea included having one of such functions per HTML element, in order to have type-safe(r) HTML, constructed by the components themselves. – DanielM Sep 26 '17 at 20:06
  • document is a global. I think you shouldn't use it directly. You should have something like function h(createElement, ...) and pass in document.createElement in your composition root. As you said, using the global directly will be troublesome for unit tests. Passing in the function is more flexible. If you find you need to access more of the document, IMO you should still pass document as a parameter instead of referencing the global. – Samuel Sep 26 '17 at 20:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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