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?
1 Answer 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– DanielM09/26/2017 11:57:20Commented Sep 26, 2017 at 11:57
-
A different question -- how would you handle the dependency of the
h()
function (and those using it for conciseness, such asbutton()
) indocument
? I find it a tricky case, because they feel like (are?) pure functions, so using them directly seems harmless, but at the same timedocument
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– DanielM09/26/2017 12:14:15Commented Sep 26, 2017 at 12:14 -
It's worked great for us. I don't follow your other questions.Samuel– Samuel09/26/2017 19:14:28Commented Sep 26, 2017 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 usesdocument.createElement()
for that. And also functions such asbutton()
, which are partially applied versions ofh()
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– DanielM09/26/2017 20:06:30Commented Sep 26, 2017 at 20:06 -
document
is a global. I think you shouldn't use it directly. You should have something likefunction h(createElement, ...)
and pass indocument.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 passdocument
as a parameter instead of referencing the global.Samuel– Samuel09/26/2017 20:19:50Commented Sep 26, 2017 at 20:19
Explore related questions
See similar questions with these tags.
location
a component.