Liking cljdoc? Tell your friends :D

Controllers

Controllers run code when a route is entered and left. This can be useful to:

  • Load resources
  • Update application state

How controllers work

A controller consists of three functions:

  • params which takes a Match and returns an arbitrary value.
  • start which takes the result of params and whose return value is discarded.
  • stop which takes the result of params and whose return value is discarded.

When you navigate to a route that has a controller, params gets called first and then start is called with its return value. When you exit that route, stop is called with the return value of params.

If you navigate to the same route with different parameters, params gets called again. If the return value changes from the previous return value, stop and start get called again.

You can add controllers to a route by adding them to the route data in the :controllers vector. For example:

["/item/:id"
 {:controllers [{:params (fn [match] (get-in match [:path-params :id]))
                 :start  (fn [item-id] (js/console.log :start item-id))
                 :stop   (fn [item-id] (js/console.log :stop item-id))}]}]

If you leave out params, start and stop get called with nil. You can leave out start or stop if you do not need both of them.

Enabling controllers

You need to call reitit.frontend.controllers/apply-controllers whenever the URL changes. You can call it from the on-navigate callback of reitit.frontend.easy:


(ns frontend.core
  (:require [reitit.frontend.easy :as rfe]
            [reitit.frontend.controllers :as rfc]))

(defonce match-a (atom nil))

(def routes
  ["/" ...])

(defn init! []
  (rfe/start!
    routes
    (fn [new-match]
      (swap! match-a
        (fn [old-match]
          (when new-match
            (assoc new-match
              :controllers (rfc/apply-controllers (:controllers old-match) new-match))))))))

See also the full example.

Nested controllers

When you nest routes in the route tree, the controllers get nested as well. Consider this route tree:

["/" {:controllers [{:start (fn [_] (js/console.log "root start"))}]}
 ["/item/:id"
  {:controllers [{:params (fn [match] (get-in match [:path-params :id]))
                  :start  (fn [item-id] (js/console.log "item start" item-id))
                  :stop   (fn [item-id] (js/console.log "item stop" item-id))}]}]]

  • When you navigate to any route at all, the root controller gets started.
  • If you navigate to /item/something, the root controller gets started first and then the item controller gets started.
  • If you then navigate from /item/something to /item/something-else, first the item controller gets stopped with parameter something and then it gets started with the parameter something-else. The root controller stays on the whole time since its parameters do not change.

Authentication

Controllers can be used to load resources from a server. If and when your API requires authentication you will need to implement logic to prevent controllers trying to do requests if user isn't authenticated yet.

Run controllers and check authentication

If you have both unauthenticated and authenticated resources, you can run the controllers always and then check the authentication status on controller code, or on the code called from controllers (e.g. re-frame event handler).

Disable controllers until user is authenticated

If all your resources require authentication an easy way to prevent bad requests is to enable controllers only after authentication is done. To do this you can check authentication status and call apply-controllers only after authentication is done (also remember to manually call apply-controllers with current match when authentication is done). Or if no navigation is possible before authentication is done, you can start the router only after authentication is done.

Alternatives

Similar solution could be used to describe required resources as data (maybe even GraphQL query) per route, and then have code automatically load missing resources.

Controllers elsewhere

Can you improve this documentation? These fine people already did:
Tommi Reiman, Juho Teperi & Miikka Koskinen
Edit on GitHub

cljdoc is a website building & hosting documentation for Clojure/Script libraries

× close