Fork me on GitHub

that is what untangled is


the networking, parser, along with a simplification of the loading story


vanilla om next doesn’t do much without those


then you’ll find you need to augment the merge story, because the basic merge is too naive. Then you’ll find you want to augment merge behavior to handle return values from mutations…then you’ll have re-written Untangled


Untangled does make a specific trade-off that you might decide against: you don’t write any client-side parsing. It is all db->tree. I think that is a net win, but you lose the ability to customize the local read emitters to do things like handle parameters on query keywords and joins


My opinion is to just store those params (and the effects of them) in app state instead.


All told (client, server, forms, i18n) Untangled is about 1600 lines of code. It really isn’t that large. The docs are much much bigger 🙂


I’d love to hear what your resistance is to Untangled…do you think it is heavy? If so, why? Can you point to the place where it is heavy?


@tony.kay Well, mostly I was just curious in general if there was an example where you would opt against using untangled - for some reason that I hadn't thought of. So I suppose, more specifically, when, if ever, would the trade-off of of not using custom parsers not be worth it in your view? As to my resistance to Untangled. If I was in more of a hurry, I would probably have ran with it when I first found it, but I have the "luxury?" of meandering around, because a rewrite of our front-end isn't imminent. 🙂 Anyway, the main point is that, at least in theory, I really like the idea of components having arbitrary parsers to access the state of a component, and I also like the idea of QueryParams, because it seems to me to be a nice separate way of expressing modifiers for a component's query. Untangled, as far as I can see, asks you to put all these things into the mutation functions and essentially cache read computation by default.


@urbank You have understood it very well, it seems. I like the idea of the client parsing and query params, but in practice they just cost too much IMHO in terms of application complexity. Go through the use-cases in your UI of those params. Now consider that you can compute those things every time in a parser, which is nested somewhere in a recursive algorithm, or you can compute it on mutation, where it is a direct transform.


Yes, you have a cache invalidation problem now, but you also have a clear set of events (loads or user) that will logically go with that data. Say you’re paginating…the cached list of thing on the current page is trivial to keep correct. Next page, prior page, and list refresh.


I personally would rather think of it that way: next page…ok, where is the code to calculate that? In the mutation that I am looking at on the button that says “Next Page”. I don’t have to wade through the query, find that part of the parser, search for the function that handles that bit of the query, etc.


And if you’re using defmutation and IntelliJ, you get IDE jump-to for the mutation, so there is no searching at all.


Much smoother experience.


The Om Parser, as a generalization, is great. But as far as I’m concerned it should generally be used to “hook up” to a database format. E.g. it is trivial to hook a server-side query to Datomic. I haven’t had time yet, but an sql->tree is something I’d like to make (or see made) for this. You’ll still want to have top-level control of the parser on the server (security, etc), but for the most part queries that you send to the server are looking for a sub-graph that the db has.


Now, all that said: it would it be cool to add hooks to db->tree so you could escape into your own function for some part of a query. Then you’d have the best of both worlds (mostly automatic query interpretation, with param support when you want it).


Oh, and InitialAppState. That was one of the major tricks to getting the whole experience clean. That gives you easy refactoring: you just pick up components and re-compose them. The data automatically corrects itself. With a parser…well, have fun…your recursive tree just changed underneath you 😕


The Untangled in the Large youtube videos give some indications of this. There is one on developing components in isolation. Realize that with InitialAppState you can compose ANY component OR screen (with an ident, which they should all have) into a devcard (including server interactions and mutations), then move it into your application unchanged. No parsers required at any time. You don’t get that kind of rapid dev with stock Om Next unless you use a similar technique.


Use the tricks for mocking servers with javascript, and you can develop your full-stack components in devcards without needing the real server yet.


The point I’m trying to make (hopefully it is obvious) is that a recursive parser algorithm will get in your way a lot. You will find clever ways to make it better, but how “clever” do you really want to be in production code that random future engineers will need to work on and understand?


I added a simple join in my form:

{[ '_] (om/get-query entity-auto-complete/CompanyAutoComplete)}
Works like a charm


I only need to put the temporary value somewhere, and then I only add the company id in the form


temp value?


Works pretty good actually @tony.kay still figuring out where the put the temp value but probably just somewhere on the form under a :ui key


If you select a company with id 1, I save the number 1 in the form. But when I render the value I want to do a query on the selected value: [:company/name :company/website :db/id]


why not just add an attribute (not form field) to the entity itself to hold the name?


then your mutation just sets both (or all)


the form commit only commits declared fields…you can have other fields in your query and entity


no :ui prefixing necessary 🙂


What do you mean by entity the Form component or the thing?


Ah yeah that was my idea


right…then if you need to fill in the form (say you edit from server), you’d query for those things and pre-fill them as well (even though you only save the ID)


Might still prefix it with Ui to never accidentally send it to the server


you want to query from the server 🙂


Yes I was doing exactly that


and form commit will never commit anything that isn’t declared a field


That was what I saw, also not ding anything if there are no changes


technically, you can submit :ui prefixed fields if you declare them…that prefix is only known by read logic


glad to hear it is working for you 😄


It hurts a little bit now but we want to move over all forms to this. We now have some add-hoc stuff that I want to rid off


(This is the last part we need to port from re-frame to untangled)


yeah, NAVIS ran into the same thing. I had the forms support in my head from a long time back, but apps got written before the forms support existed…with some pain


Addhoc validation always gets messy


still more I want to do with it…like automatic form rendering if you don’t mind a stock look.


Ah thats awesome for prototyping


it is..and sometimes ok for production 😉


Yeah I created a atlas-crm.shared.forms namespace with our own ::text things and all other stuff. And thinking about just mapping over the form-spec keys to render the fields when the forms are simple enough


And I could do the same for queries, adding the right queries to form elements can be a little brittle


@tony.kay You make a convincing case for mutations being both more efficient and in many respects less complex than parses. The shape of my data is now much more clear then when I started researching what to use for the rewrite, and there's also a bit of a lull in development, so I should now have time to make a few POCs with a few different approaches. I will see where it leads me.


@urbank would love to hear what you decide and why.