On the previous post we showed why modules are usefull to build Shiny applications. We also saw a first minimal “Hello-World” example.
It can get difficult to share reactive from/to modules. On this post we will see the 3 most common use cases of data workflow:
- Module → Application
- Application → Module
- Application ↔︎ Module
Want to run the examples ?
All code used in this post are available in Github ardata-fr/Shiny-Modules-Tutorials.
This repository is actually an R package containing all the modules. Applications are stored in the folder inst
.
To get apps locally, install the package and run applications:
# install.packages("remotes")
remotes::install_github("ardata-fr/Shiny-Modules-Tutorials")
library(shinyModulesTuto)
# List available applications
listEx()
# Run first application
runEx(listEx()[1])
Data from Module to Application
Online application here or get it locally with:
# Run Shiny application
runEx("module-vs-app")
In the module
The reactiveValues
returned is initialized as below:
toReturn <- reactiveValues(
variables = NULL,
variable_name = NULL,
trigger = 0
)
It will be updated as a standard reactiveValues
:
observeEvent(input$AB_load, {
toReturn$variable <- get(input$SI_dataset)[,input$SI_var]
toReturn$variable_name <- input$SI_var
toReturn$trigger <- toReturn$trigger + 1
})
Then the result is returned to application with:
return(toReturn)
In the application
Get results from module with:
# results is created as a reactiveValues with 3 slots (returned from module)
results <- callModule(module = load_data, id = "id1")
Used in application as a standard reactiveValues
:
output$PR_results_print <- renderPrint({
print(results$variable_name)
print(results$variable)
})
Data from Application to Module
Online application here or use command:
# Run Shiny application
runEx("app-vs-module")
In the application
Solution 1: Using a reactive
The reactive
given to module is created as below:
variable <- reactive({
iris[, input$SI_colname]
})
Pass the reactive
as a module parameter:
callModule(module = show_data, id = "id1",
variable = variable,
variable_name = reactive(input$SI_colname))
NB : As variable
is a reactive
, no need to use the function reactive()
.
Solution 2: Using reactiveValues
The reactiveValues
given to module is created as below:
rv <- reactiveValues(variable = NULL)
observe({
rv$variable <- iris[, input$SI_colname]
})
Pass the reactiveValues
as a module parameter:
callModule(module = show_data, id = "id2",
variable = reactive(rv$variable),
variable_name = reactive(input$SI_colname))
NB : As rv$variable
is a reactiveValues
, we must use the reactive()
function.
In the module
Solutions 1 & 2
For both solutions using a reactive
or a reactiveValues
in the application, you can get parameters from the module using:
output$PL_histogram_var <- renderPlot({
hist(variable(), main = variable_name(), xlab = NULL)
})
NB : We can use module parameters variable
& variable_name
as a standards reactive
(eg variable()
& variable_name()
).
Data from Application modified in Module
Online application here or use command:
# Run Shiny application
runEx("app-pong-module")
How can I get data in my application and let a module modify them ? The trick here is to use a second reactiveValues
.
We saw on the previous chapter how to deal with parameters inside modules. On this part, we will focus on the application.
Let’s consider the module apply_function
that takes as parameter a numeric vector on which the user can apply a function (ex : log(x)
). The module returns a reactiveValues
with 3 slots : the new numeric vector, the function name used and a “trigger” increasing when the user applies a function.
In the application we initialize the reactiveValues
as usual:
rv <- reactiveValues(variable = NULL, fun_historic = NULL)
observe({
rv$variable <- iris[, input$SI_colname]
rv$fun_historic <- NULL
})
Then pass it to the module apply_function
:
modified_data <- callModule(module = apply_function, id = "id1",
variable = reactive(rv$variable))
The output of module apply_function
is stored as a reactiveValues
.
It returns contains 3 slots:
- result (the result of the function applied)
- fun (name of the function applied)
- trigger (integer that increases when user applies a function)
In the application, to update our rv$variable
according to modified_data$result
, we set an observeEvent
on modified_data$trigger
:
observeEvent(modified_data$trigger, {
rv$variable <- modified_data$result
rv$fun_historic <- c(rv$fun_historic, modified_data$transformation)
})
The key here is to use a second reactiveValues
. So the real schema should be:
Application combining all previous examples
Online application here or use command:
# Run Shiny application
runEx("whole-app")
This application combines all the modules created so far. The module apply_scale
adds a second way of modifying the rv
“main” reactiveValues with the function scale
. The schema below shows that the reactiveValues rv
is the core of the application. It contains 2 slots, variable
which is the numeric vector and fun_history
containing the list of functions applied on our numeric vector.
Within the application, the reactiveValues rv
is created as below:
rv <- reactiveValues(variable = NULL, fun_history = NULL)
Then it’s updated by the 3 modules:
- By module load_data using the temporary reactiveValues
data_mod1
. - By module apply_function using the temporary reactiveValues
data_mod2
. - By module apply_scale using the temporary reactiveValues
data_mod3
.
To ease the process of initializing/updating the rv
reactiveValues, observeEvent
are set on the trigger
slot returned by modules. Thus we’re sure that the last user action is taken into account.
Trigger the load or reload from module load_data
:
# Call the module load_data
data_mod1 <- callModule(module = load_data, id = "mod1")
# When data_mod1$trigger changes, (re)initialization of rv$variable & rv$fun_history
observeEvent(data_mod1$trigger, {
req(data_mod1$trigger>0)
rv$variable <- data_mod1$variable
rv$fun_history <- c()
})
Trigger the modification through module apply_function
:
# Call the module apply_function with parameter rv$variable
data_mod2 <- callModule(module = apply_function, id = "mod2",
variable = reactive(rv$variable))
# When data_mod2$trigger changes, update of rv$variable & rv$fun_history
observeEvent(data_mod2$trigger, {
req(data_mod2$trigger>0)
rv$variable <- data_mod2$result
rv$fun_history <- c(rv$fun_history, data_mod2$fun)
})
Trigger the modification through module apply_scale
:
# Call the module apply_scale with parameter rv$variable
data_mod3 <- callModule(module = apply_scale, id = "mod3",
variable = reactive(rv$variable))
# When data_mod3$trigger changes, update of rv$variable & rv$fun_history
observeEvent(data_mod3$trigger, {
req(data_mod3$trigger>0)
rv$variable <- data_mod3$result
rv$fun_history <- c(rv$fun_history, "scale")
})
Conclusion
We described here (links to applications):
- How to get data from a module
- How to send data to a module
- How a reactiveValues in the application can be modified inside a module
- An example combining all of cases above
We highly recommand using modules when building complex/large Shiny applications.
But there’s even more if you want to go further:
- Nested modules (which is actually not complicated at all)
- Dynamic module calls
Before seing this concepts (let’s write a part 3 !!) I hope you will have great time developping your Shiny applications !
Follow us: - Recommanded sites: R-bloggers R weekly Twitter #rstats Jobs for R-users