reitit/doc/frontend/controllers.md

128 lines
4.6 KiB
Markdown
Raw Permalink Normal View History

2018-09-07 21:05:59 +00:00
# Controllers
2018-08-23 07:41:13 +00:00
2018-08-22 18:58:20 +00:00
* https://github.com/metosin/reitit/tree/master/examples/frontend-controllers
2018-08-23 07:41:13 +00:00
Controllers run code when a route is entered and left. This can be useful to:
- Load resources
- Update application state
2018-11-30 16:07:41 +00:00
## How controllers work
2018-12-04 13:02:16 +00:00
A controller map can contain these properties:
2018-11-30 16:07:41 +00:00
2018-12-04 13:02:16 +00:00
* `identity` function which takes a Match and returns an arbitrary value,
* or `parameters` value, which declares which parameters should affect
controller identity
* `start` & `stop` functions, which are called with controller identity
2018-11-30 16:07:41 +00:00
2018-12-04 13:02:16 +00:00
When you navigate to a route that has a controller, controller identity
is first resolved by using `parameters` declaration, or by calling `identity` function,
or if neither is set, the identity is `nil`. Next, the controller
is initialized by calling `start` with the controller identity value.
When you exit that route, `stop` is called with the last controller identity value.
2018-11-30 16:07:41 +00:00
2018-12-04 13:02:16 +00:00
If you navigate to the same route with different match, identity gets
resolved again. If the identity changes from the previous value, controller
is reinitialized: `stop` and `start` get called again.
2018-11-30 16:07:41 +00:00
You can add controllers to a route by adding them to the route data in the
`:controllers` vector. For example:
2018-12-04 08:51:17 +00:00
```cljs
2018-11-30 16:07:41 +00:00
["/item/:id"
2018-12-04 13:02:16 +00:00
{:controllers [{:parameters {:path [:id]}
:start (fn [parameters] (js/console.log :start (-> parameters :path :id)))
:stop (fn [parameters] (js/console.log :stop (-> parameters :path :id)))}]}]
2018-11-30 16:07:41 +00:00
```
2018-12-04 13:02:16 +00:00
You can leave out `start` or `stop` if you do not need both of them.
2018-11-30 16:07:41 +00:00
## Enabling controllers
You need to
call
[`reitit.frontend.controllers/apply-controllers`](https://cljdoc.org/d/metosin/reitit-frontend/CURRENT/api/reitit.frontend.controllers#apply-controllers) whenever
the URL changes. You can call it from the `on-navigate` callback of
`reitit.frontend.easy`:
2018-12-04 08:51:17 +00:00
```cljs
2018-11-30 16:07:41 +00:00
(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](https://github.com/metosin/reitit/tree/master/examples/frontend-controllers).
## Nested controllers
2018-12-04 08:51:17 +00:00
When you nest routes in the route tree, the controllers get concatenated when
route data is merged. Consider this route tree:
2018-11-30 16:07:41 +00:00
2018-12-04 08:51:17 +00:00
```cljs
2018-11-30 16:07:41 +00:00
["/" {:controllers [{:start (fn [_] (js/console.log "root start"))}]}
["/item/:id"
2020-10-14 07:08:20 +00:00
{:controllers [{:parameters {:path [:id]}
:start (fn [parameters] (js/console.log "item start" (-> parameters :path :id)))
:stop (fn [parameters] (js/console.log "item stop" (-> parameters :path :id)))}]}]]
2018-11-30 16:07:41 +00:00
```
* 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.
2018-12-04 08:51:17 +00:00
## Tips
### Authentication
2018-08-23 07:41:13 +00:00
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.
2018-12-04 08:51:17 +00:00
#### Run controllers and check authentication
2018-08-23 07:41:13 +00:00
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).
2018-12-04 08:51:17 +00:00
#### Disable controllers until user is authenticated
2018-08-23 07:41:13 +00:00
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
2018-08-23 07:42:43 +00:00
even GraphQL query) per route, and then have code automatically load
2018-08-23 07:41:13 +00:00
missing resources.
2018-11-30 16:07:41 +00:00
## Controllers elsewhere
* [Controllers in Keechma](https://keechma.com/guides/controllers/)