Fork me on GitHub

hey @tony.kay 🙂 say i wanted to get started with Untangled, using everything according to the latest understanding. is there perhaps a lein template that brings it all together? particularly interested in the UI stuff


@robert-stuttaford I'm not sure if there is a lein template to generate a project but there is this template project which is pretty great to clone and modify for your use case


i see this one doesn’t have the support-viewer or the new ui stuff, i’m guessing it predates those


Ahh yup, admittedly I haven't used the UI components much yet or the support viewer. The form support is very interesting though.


@robert-stuttaford if you're just getting started with and Untangled I highly recommend the Untangled video series. It was pretty crucial to my understanding of and the boundaries Untangled hooks into.


yeah, i’m looking into those now


i have mucked around with omn before, but found the parser stuff to be quite heavy. keen to see Unt’s take


my need is that we need to build a lot of db admin stuff pretty quickly and i’m really not interested in reinventing anything. done enough of that 🙂


just looking for something with batteries to get stuff built


Yup building the parser emitters in pure Om.Next on the client side was rather cumbersome, I quite like Untangled's approach. Some things that bit us early on were not having a great idea of what our entities were in the system (data backed by an ident), and having too many different ident backed data structures. We've since taken a different approach where we use normalization/idents only where it's necessary for performance or deduplication. Keeping the count of different ident types in your application low helps reasoning tremendously.


happily we understand our data tremendously well; it’s 4 years old 🙂


we’ve lots of fixed-scope tools to build


which each focus on a small part of the whole


our tooling has lagged behind our primary feature dev and operation quite a lot. so we know our data, and what we want to do to manage it, we just need a sensible, scalable toolkit to get it done


literally things as simple as capturing SSO integration configs, support tools like reviewing a user’s profile, emails we’ve sent them, stuff like that


Ah yup, that sounds like the sweet spot for Om.Next/Untangled. Developing both your UI and data-model at the same time is more difficult than other frameworks because you need to be fairly precise with queries describing your component's data dependencies. Large changes take longer because you need to not only change the UI logic but also the query layout among several components.


We've found that once the data model solidifies into something that makes sense, change becomes much easier and more reliable.


I’m interested @gardnervickers: do you choose not to use idents on things that need to update in the UI? Or are you saying that with nested data that comes from the server for pure display you don’t bother?


@robert-stuttaford The untangled-ui project needs a little polish, but it is usable as-is. Just add it as a dependency and use the stuff outlined in the devcards. The forms stuff doesn’t have any server adapters yet, but the “diff protocol” is sends is really easy to reason about.


I’d say the very biggest problem I’ve seen with new users is not leveraging union queries to keep the query low-overhead. The new defrouter macro facilitates that. If you just make one big query without that your UI performance will suck


and the components guide


the former is pure css, the latter is more wrappers that emit dom +css


cool. i’m going to work through all your videos, and read through all the devcards, and then try building something. i will have questions 🙂


i guess it doesn’t matter (because it’s easy for me to do differently), but why did you stick with the verbose om.dom rendering code?


note that the Untangled in the Large videos mention making defrouter…defrouter is now a formal part of Untangled, with a slightly improved API than shown there


server-side rendering


not adding more deps for ppl


the current syntax doesn’t bother me 🙂


so, basically what you said: it is easy for you to do differently


ah 🙂 it bothers me, heh. i’m all for concision in naturally verbose code. html is just one of those.


yep. happy to deal with it my own way!


overhead as well. didn’t want to add possible performance problems


really nice work btw. i can see a huge effort here. whether we end up using it or not, kudos!


@tony.kay For example, if our UI tree looks like

{:users {:user/id 1 :user/phone {:phone/type :work :phone/number "4014770248"}}}
We would have a table of :user/by-id but we wouldn’t, for example have a table of :phone.number/by-number.


That might be a bad example, but essentially we were finding that we were using idents/normalization where it didn’t benefit us all that much.


Not every level of map nesting needs to be normalized


kinda like only using :avet in datomic when you genuinely need to


I see. The normalization really helps with things like “forms support”


e.g. I could not easily write something like that without normalization


it would be a lot more complex (you’d have to be able to declare “type of” nesting, I think)


So that only works on flat maps with no nesting?


the forms support lets you wrap normalized entities, because I can easily write the mutate/read code “for you”. If you arbitrarily denormalize, it would be a lot more complicated


In general, if it is editable, I’d recommend normalizing it.


and for new users, I’d recommend making something with defrouter first…learn how to do that immediately. I’ve seen too many people write an app that queries the “whole UI” for every frame, and that leads to unusable UIs


I need to make a new stater tutorial and video series that stresses that. I think it is the most common place where early failure will happen with new users


is defrouter covered in any of the docs or videos?


the Untangled in the Large shows what it does (I made that while developing it). The API is a bit improved, adn the devguide covers it


that last one is the in-the-large video on ui routing


@tony.kay I’ll try and get a devcard together this weekend showcasing how we using an approach similar to initial-state to compose url->app-state and app-state->url serialization/deserialization for routing. We even hook into :tempid migration to automatically place the correct resource URL when the server returns the generated ID.


Basically, a Union query is one in which only a single part of the UI (and query) is visible at once…like a switching mechanism (it can also be used for hetero lists). Only one branch is eval’d at once, so it is a way to ensure your UI only queries for the part on-screen.


if you don’t use them, you end up querying everything that could possibly be on screen every frame…which can be horribly slow.


BTW, I’m combining all the recipes into a devcards project. I could give you access to that if you’d like to put it there. It is private while I’m working on it


OH, devcards won’t work for that. You cannot change the URI or devcards pukes


Oh interesting. Let me get a gist up first, not sure if I’ll have time to refine it much since I’m with the family for Easter this weekend. I’ll keep you in the loop.


@robert-stuttaford So, if you can get a handle on the app database graph format (via the devguide) and queries, then most of the rest falls into place. Perhaps watching the;t=1s&amp;list=PLVi9lDx-4C_T_gsmBQ_2gztvk6h_Usw6R&amp;index=8 video about the data lifecycle will tie things together as well.


i’m sure that it will, thank you! i have that video queued


There really are not very many core concepts. I think it gets ultra-simple once you’ve got those bits down.


i have lots of om.previous and rum experience, so i understand dealing with the normalised database vs ui tree graph mismatch quite well


just need to become familiar with the idioms and overall organisation, and build that first something


yeah, I’m really stoked with the result. Every time I go to do something I’m amazed at how easily it falls out, and how little (incidental) complexity there is.


@gardnervickers the other bit about normalization: you can’t easily relocate a UI component that isn’t normalized, because the mutations become non-portable.


what i’m hoping for is that we get one or two tools built, establishing some patterns, and then we can tear through the backlog from there. like i said earlier, we know our data well (and it’s all in Datomic 😎)


Oh, also make sure you know about defmutation. Much nicer IDE support for the mutations. That is another one I show in-the-large that is now part of the lib.


@tony.kay Just to clarify, we do use normalization quite a bit. We just don’t normalize things that are not shared but still nested.


got it. Yeah, I just wanted to point out the important aspects of normalization…more for whoever might be lurking and not speaking 😉


it feels like overkill for the toy app (root + list + item), but i can see the value for non-trivial apps


@tony.kay The first two video links after the routing guide link where not public links - will you share those again?


@robert-stuttaford it does feel like overkill for small things…but nothing stays small 🙂


nothing useful 🙂


help me out…which ones in particular?


oh, I see…


that’s the whole playlist


Great - thanks.


that’s the routing one


those work?


and for anyone reading the stuff above…the defrouter thing is important, but you aren’t going to understand it until you get queries/ident/db format. Those foundational pieces are necessary to build anything. But then make sure you get defrouter.


hello! watched a few talks about untangled today, thanks a lot for sharing the knowledge and the code! a question here.. any one knows of an example using a library such as martinklepsch/derivatives (used by rum) with untangled? I'm considering using untangled for a next project, but trying to see a solution for managing derived values in more declarative ways (i.e. instead of having to call functions on various mutations) - looking into re-frame`s reaction abstraction as well


i think for Om Next it would be just an impl. detail of a mutation.. but was curious if there is already such a pattern in the untangled ecosystem or someone working on a reference integration 🙂


@pedroteixeira In Om.Next, the read parsers would be what generate your “derivatives”. Untangled provides a default read parser that is backed by the app db, so you need to generate your “derivatives” using a mutation.


hm, ok! starting to read some of the code, thanks for the pointer. I was curious if there were examples of expressing derived values across the app state. For ex: instead of having to call update functions in various mutations, hook up something more generic in Om.Next to listen to changes in data and apply futher changes according to rules but in the same "transact".


Right, derivatives are just represented in the graph as data. Everything goes in mutations. Of course, if it is a simple derivation, you could calculate it as part of the view.


The basic idea is that you have to make it somewhere…if you do it in a parser, then you add the complexity of figuring out when (and caching for performance), and mix that into a parser abstraction with graph navigation. In Untangled, the idea is that since you’re mostly going to want to cache the result of your computation, why not just make that the “way”, and elide the need for more complex mechanisms. Lifecycles of views are pretty easy to manage, in that you have mount/unmount from React, and UI navigation events (e.g. clicking on a tab to go to X).


Generate the “initial view” where it makes sense (e.g. on load or view routing), update it where it makes sense. It is your data, and limiting the ways in which you have to think about it keeps things naturally simple for the most part


It's probably just me, and tony.kay has helped elucidate quite a bit already, but I find untangled a bit daunting to use for an application where everything in the database is editable all the time and editable from multiple places in the app at the same time. It kind of breaks normalization as soon as one edit component has state in the app-db.


On the other hand, the app is also liable to need caching at some point, so perhaps I might as well bite the bullet now.


I'm thinking that perhaps I can compute everything on the fly if I use virtualization like


@urbank I really don’t know what you mean “It kind of breaks normalization”…you mean you might have to have more than one graph edge that points to the same thing? That is still normalized…but it does mean you have to manage more than one link, but the fact is that you can still locally reason about those graph edges from that component’s viewpoint. It is true that a list of idents might need an update in multiple places, but again that is a composeable task: compose together the list refreshed into the data lifecycle mutation of the mutations that post-mutate of the load


OR use a root (link) query and just keep shared lists in the root node


e.g. [ [:my-list '_] ] in a query anywhere in the app pulls in the edge from the root node


so, if it is true that you need to use the same “collection” or “edge” from multiple places you can just do that….or even join on some specific entity’s edge [{[:table id] (om/get-query Table)}]


to get the entity, then pull out the edge from those props…all sorts of possibilities


@tony.kay Yeah, sorry - loose choice of words. Mostly talking about components with opaque state in the app-db (like calendar) that edits some other part of the app-db via a callback, but internally updates its "local state". If the entity it targets is also edited by another component, similar to the calendar, the calendar has to be updated whenever that other component updates the entity, else it displays stale data.


Sill, your post gives me ideas. I'll continue playing around with it