Fork me on GitHub
Dax Borges18:09:48

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)

Dax Borges19:09:28

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?

Dax Borges19:09:03

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?

Dax Borges19:09:23

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 😄

Dax Borges19:09:14

Appreciate it!

Dax Borges19:09:26

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

Dax Borges19:09:12

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...)))

Dax Borges19:09:52

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)

Dax Borges19:09:42

no it's the reg-fx that's synchronous

Dax Borges19:09:59

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?

Dax Borges19:09:14

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 {}})

Dax Borges19:09:41

this part :comp/c {} is wrong


So you have multiple configurations?

Dax Borges19:09:39

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

Dax Borges19:09:05

does that make sense?

Dax Borges19:09:25

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

Dax Borges19:09:50

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/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 😅

Dax Borges19:09:53

hey, I appreciate any ideas you have!


(ns app
   [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)

(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


Just copy paste this and try it out in your repl


(Assuming that I met your requirements 😅 )

Dax Borges20:09:05

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 :thinking_face:

Dax Borges20:09:09

Music to my ears

Dax Borges20:09:00

I think the one downside to this (though not a huge problem) is needing to define the dep tree all up front

Dax Borges20:09:23

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

Dax Borges20:09:37

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 😅

Dax Borges20:09:22

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!

Dax Borges20:09:23

Thanks! I'll update you when I learn more!