integrant

Dax Borges 2021-09-27T18:54:48.021900Z

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?

2021-09-27T19:05:11.023200Z

> 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

2021-09-27T19:06:04.023900Z

It seems to me that the order is valid though. So I'm not sure what the problem in your scenario is

2021-09-27T19:06:48.024600Z

What are you using to render your components? (we use reagent with integrant at my work)

Dax Borges 2021-09-27T19:11:28.027500Z

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 Borges 2021-09-27T19:12:03.028300Z

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.

2021-09-27T19:13:11.030100Z

All right, if two components share 1 ref (in this case, comp-c) does comp-c need to emit an event twice?

Dax Borges 2021-09-27T19:14:23.031100Z

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

2021-09-27T19:16:00.032Z

Writing up some pseudo code to see if I understand, one moment 😄

Dax Borges 2021-09-27T19:16:14.032200Z

Appreciate it!

Dax Borges 2021-09-27T19:16:26.032500Z

let me try to get some put together

2021-09-27T19:17:20.033Z

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

2021-09-27T19:18:22.034200Z

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

2021-09-27T19:18:56.034500Z

{:comp/a {:comp-c #ig/ref :comp/c}
 :comp/b {:comp-c #ig/ref :comp/c}
 :comp/c {}}

2021-09-27T19:19:01.034700Z

And this would be the config

2021-09-27T19:20:32.035100Z

(At my job we sometimes use ig/prep-key to dispatch events instead of ig/init-key )

2021-09-27T19:21:23.036100Z

But either should work. The events are only dispatched once and comp-c has access to the subscription within its fn context

Dax Borges 2021-09-27T19:22:12.036500Z

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 Borges 2021-09-27T19:22:52.037500Z

adding more to the example .. one min

2021-09-27T19:23:15.037900Z

Yeah I don't think this is reliable. re-frame/dispatch is not synchronous.

2021-09-27T19:23:34.038500Z

So the order of execution won't be set in stone by integrant (I think)

Dax Borges 2021-09-27T19:23:42.038700Z

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

Dax Borges 2021-09-27T19:23:59.039100Z

do I don't want dispatch to be called until reg-fx is

2021-09-27T19:24:40.039600Z

Right, but since dispatch is asynchronous I don't think it will wait

2021-09-27T19:24:49.040Z

but I haven't tried this before

2021-09-27T19:24:59.040200Z

You've tested this and it doesn't work?

Dax Borges 2021-09-27T19:27:14.042100Z

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 Borges 2021-09-27T19:27:41.042800Z

this part :comp/c {} is wrong

2021-09-27T19:28:03.043200Z

So you have multiple configurations?

Dax Borges 2021-09-27T19:29:39.044900Z

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 Borges 2021-09-27T19:30:05.045200Z

does that make sense?

Dax Borges 2021-09-27T19:31:25.046800Z

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

2021-09-27T19:31:25.046900Z

I think what you're saying is that you want to initialize part of your config, and reuse the initialized parts later

Dax Borges 2021-09-27T19:31:50.047200Z

yes, that's a better way of putting it

2021-09-27T19:34:28.047800Z

Yeah that's tough

2021-09-27T19:34:50.048200Z

You're going to have to write some wrapper code to make this work

2021-09-27T19:35:18.048800Z

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

2021-09-27T19:36:04.049800Z

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]}

2021-09-27T19:37:15.051Z

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

2021-09-27T19:38:20.051400Z

A quick hack you could use is something like this...

2021-09-27T19:41:37.051900Z

Ok bear with me on this one 😅

Dax Borges 2021-09-27T19:41:53.052200Z

hey, I appreciate any ideas you have!

2021-09-27T19:48:04.052600Z

(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}

2021-09-27T19:50:00.054700Z

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.

2021-09-27T19:50:38.055300Z

Uhm sorry, I think I made a mistake somehwere. the result isn't really correct 😕

2021-09-27T19:53:46.055800Z

Ok I just typo'd my result

2021-09-27T19:54:10.056100Z

Link to integrant-tools: https://github.com/kwrooijen/integrant-tools

2021-09-27T19:54:33.056600Z

Just copy paste this and try it out in your repl

2021-09-27T19:55:03.056900Z

(Assuming that I met your requirements 😅 )

Dax Borges 2021-09-27T20:03:05.057200Z

looks to be right, now I just need to grok it

Dax Borges 2021-09-27T20:03:30.057400Z

🤯

2021-09-27T20:03:52.057800Z

Yeah this really goes into the deep end 🤷

2021-09-27T20:06:48.058200Z

This would actually be a good addition to integrant-tools 🤔

Dax Borges 2021-09-27T20:07:09.058500Z

Music to my ears

2021-09-27T20:07:31.059Z

Possibly

Dax Borges 2021-09-27T20:08:00.059500Z

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

2021-09-27T20:08:23.059800Z

Hmm

Dax Borges 2021-09-27T20:08:23.060100Z

config {:comp/a {:comp-c (ig/ref :comp/c)}
              :comp/c {}}

2021-09-27T20:08:27.060300Z

That wouldn't be the case?

2021-09-27T20:08:51.060600Z

You could also merge your "new" config into the old config

2021-09-27T20:09:25.060900Z

(-> config
    (call-subset [:comp/a])
    (merge {:comp/b {:comp-c (ig/ref :comp/c)}})
    (call-subset [:comp/b]))

2021-09-27T20:09:59.061300Z

You just need to keep track of your config state, with an atom or something

2021-09-27T20:10:11.061600Z

(Which makes sense since you want to re-use components)

2021-09-27T20:10:31.061800Z

Makes sense to me at least

Dax Borges 2021-09-27T20:16:37.063600Z

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 Borges 2021-09-27T20:17:22.064300Z

Generally it looks like it should work, just wondering if I'm missing anything between toy code and PoC implementation with our app

2021-09-27T20:18:02.064500Z

You're welcome, good luck!

Dax Borges 2021-09-27T20:18:23.064800Z

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