Hi I'm looking to use integrant on the frontend where I'm lazy loading in modules made up of components. The components need to be initialized in. a specific order. I'm trying to figure out how to use integrant for the following: I have three components comp-a,`comp-b`, and`comp-c` . Both comp-a and comp-b depend on comp-c and need it to be initialized be for they are. I have two modules, mod-a and mod-b which contain comp-a and comp-b, respectively. If I can't control the order of when the modules load is there a way to initialize comp-c in whichever module loads first and then use its reference in the other module (without initializing it a second time). For example, if mod-a loads first, comp-c and comp-a are initialized , then mod-b loads and only comp-b is initialized and the reference of comp-c from mod-a is used?
> For example, if `mod-a` loads first, `comp-c` and `comp-a` are initialized , then `mod-b` loads and only `comp-b` is initialized and the reference of `comp-c` from `mod-a` is used? Yes, both comp-a and comp-b will use the same value returned by comp-c init-key. Meaning that comp-c only gets evaluated (initialized) once
It seems to me that the order is valid though. So I'm not sure what the problem in your scenario is
What are you using to render your components? (we use reagent with integrant at my work)
As I understand it we need to create two systems by calling ig/init in both mod-a and mod-b. If I don't control which system starts first how can share comp-c? Is there a way to merge systems?
We use re-frame with reagent, what I'm trying to do is to make sure events and subscriptions are loaded before they're used.
All right, if two components share 1 ref (in this case, comp-c) does comp-c need to emit an event twice?
no, I want to register the event handler once and in comp-c and then from comp-a and comp-b I want to be able to dispatch the event knowing that the handler exists
Writing up some pseudo code to see if I understand, one moment 😄
Appreciate it!
let me try to get some put together
To me it sounds likes you're doing something like this?:
(defmethod ig/init-key :comp/a [_ {:keys [comp-c]}]
(fn []
[:div "From A - Comp C: " comp-c]))
(defmethod ig/init-key :comp/b [_ {:keys [comp-c]}]
(fn []
[:div "From B - Comp C: " comp-c]))
(defmethod ig/init-key :comp/c [_ opts]
(re-frame/dispatch [:initialize/comp-c opts])
(let [comp-c (re-frame/subscribe [:db/comp-c]) ]
(fn []
[:div "Comp C: " @comp-c])))So you have 3 (reagent) components. a and b reference component c. component C dispatches an event at initialization (once) and returns a reagent component (fn) with its own sibscriber
{:comp/a {:comp-c #ig/ref :comp/c}
:comp/b {:comp-c #ig/ref :comp/c}
:comp/c {}}And this would be the config
(At my job we sometimes use ig/prep-key to dispatch events instead of ig/init-key )
But either should work. The events are only dispatched once and comp-c has access to the subscription within its fn context
more this
(defmethod ig/init-key :components/comp-a [_ {:keys [post-alert-event] :as opts}]
(rf/dispatch [post-alert-event "comp-a alert"]))
(defmethod ig/init-key :components/comp-b [_ {:keys [post-alert-event] :as opts}]
(rf/dispatch [post-alert-event "comp-b alert"]))
(defmethod ig/init-key :components/comp-c [_ _]
(rf/reg-fx :post-alert (fn [_ [_ title]] ...trigger alert...)))adding more to the example .. one min
Yeah I don't think this is reliable. re-frame/dispatch is not synchronous.
So the order of execution won't be set in stone by integrant (I think)
no it's the reg-fx that's synchronous
do I don't want dispatch to be called until reg-fx is
Right, but since dispatch is asynchronous I don't think it will wait
but I haven't tried this before
You've tested this and it doesn't work?
assuming comp-c init is called first that example will work fine, but I think we're getting to far into implementation details. Backing up the real issue I'm trying to figure out is how to control when comp-c is initalized if I want something like this
(ig/init {:comp/a {:comp-c #ig/ref :comp/c}
:comp/c {}})
(ig/init {:comp/b {:comp-c #ig/ref :comp/c}
:comp/c {}})this part :comp/c {} is wrong
So you have multiple configurations?
yes, the real case is I have my app which calls ig/init and then the user navigates to some section the requires lazy loading a module , I then need to ig/init all components in that module but they often have deps that are already init originally by the app
does that make sense?
Really appreciate your help by the way! Trying to be concise and clear but also forming the picture in my head at the same time
I think what you're saying is that you want to initialize part of your config, and reuse the initialized parts later
yes, that's a better way of putting it
Yeah that's tough
You're going to have to write some wrapper code to make this work
I don't think integrant has any way of handling this situation. Integrant was created to build your entire system at initialization, not with lazyloading in mind
If you try to initialize a specific component, it will drop any other components that aren't referenced
(ig/init {:comp/a {:comp-c ( ig/ref :comp/c)}
:comp/b {:comp-c ( ig/ref :comp/c)}
:comp/c {}}
[:comp/a])
;; => {:comp/c :c
;; => :comp/a [:a :c]}But even if the keys (`comp/b` in this case) were still available, the next ig/init would fail because it would initialize :comp/c again instead of returning the value
A quick hack you could use is something like this...
Ok bear with me on this one 😅
hey, I appreciate any ideas you have!
(ns app
(:require
[integrant.core :as ig]
[integrant-tools.core :as it]))
(set! *print-namespace-maps* false)
(defmethod ig/init-key :comp/a [_ opts]
[:a (:comp-c opts)])
(defmethod ig/init-key :comp/b [_ opts]
[:b (:comp-c opts)])
(defmethod ig/init-key :comp/c [_ opts]
;; This is only called once
(println :initialize/comp-c)
:c)
(defn call-subset [config ks]
(let [config-subset (ig/init config ks)]
(doseq [k (keys config-subset)] (remove-method ig/init-key k))
(it/derive-unknown config-subset ig/init-key :it/const)
(merge config config-subset)))
(let [config {:comp/a {:comp-c (ig/ref :comp/c)}
:comp/b {:comp-c (ig/ref :comp/c)}
:comp/c {}}]
(-> config
(call-subset [:comp/a])
(call-subset [:comp/b])))
;; Result:
;; => {:comp/a [:a :c]
;; => :comp/b [:b :c]
;; => :comp/c :c}So in this scenario we have the same config / init-keys as before (but now with a println to check if :comp/c gets called once or twice). We create a new function call-subset which only calls the required keys. Afterwards to removes all ig/init-key multimethods of the results keys, and derive those keys from :it/const (same as :duct/const, just return the opts). Finally we merge the new subset config into the original config.
Uhm sorry, I think I made a mistake somehwere. the result isn't really correct 😕
Ok I just typo'd my result
Link to integrant-tools: https://github.com/kwrooijen/integrant-tools
Just copy paste this and try it out in your repl
(Assuming that I met your requirements 😅 )
looks to be right, now I just need to grok it
🤯
Yeah this really goes into the deep end 🤷
This would actually be a good addition to integrant-tools 🤔
Music to my ears
Possibly
I think the one downside to this (though not a huge problem) is needing to define the dep tree all up front
Hmm
config {:comp/a {:comp-c (ig/ref :comp/c)}
:comp/c {}}That wouldn't be the case?
You could also merge your "new" config into the old config
(-> config
(call-subset [:comp/a])
(merge {:comp/b {:comp-c (ig/ref :comp/c)}})
(call-subset [:comp/b]))You just need to keep track of your config state, with an atom or something
(Which makes sense since you want to re-use components)
Makes sense to me at least
Seems like I need to play with it more. It's actually not me but another member of my team who's offline, I've now spent more time on this than I should have 😬. Really appreciate all the help @kevin.van.rooijen!! We'll test it out and I'll let you know how it goes. Hope you don't mind if we return with more questions 😅
Generally it looks like it should work, just wondering if I'm missing anything between toy code and PoC implementation with our app
You're welcome, good luck!
Thanks! I'll update you when I learn more!