Modules
Misc
- Resources
- RStudio
- Mastering Shiny, Ch. 19
- Engineering Production-Grade Shiny Apps - {golem} book
- Simple app packaged, modules, etc.
- Packages
- Differences between monolith apps and using modules
- Functions: the
ui
andserver
are both written as functions. - Id: id is the first argument in the module function. The unique id tells the server function call which ui function call.
- Namespace: Input IDs from different modules can have the same name, because they’re associated with the unique id for their specific module. This can be useful if multiple people are creating modules for the app. (The has to do with usage of
NS
)
- Functions: the
- When to use modules (source)
- Calling the same component creating function multiple times.
- Creating a list of
id
values and iterating over and calling a function that makes a component. - Creating at least 2 lists that track the
id
and some other input for the component.- For example a separate list for the
id
orlabel
, but can also include things like a column name of a dataframe.
- For example a separate list for the
- Iterating across lists(s) to ensure inputs are captured together
- Especially if you find your self using the
zip()
function
- Especially if you find your self using the
Workflow
Decompose Requirements to help determine modules
- Pick a subset of the data
- The metrics of interest are:
- e.g. average departure delay, average arrival delay, proportion of flights with an arrival delay > 5min
- For each metric of interest, users should:
- Example:
- See a time series plot of the average daily value of the metric
- Be able to click a download button to download a PNG of the plot
- Read a text summary that reports the number of days with breaches
- Example:
Demo function for testing modules
<- function () { module_demo # define test data <- data.frame(x = 1:30, y = 1:30) df # call module components <- fluidPage(module_ui("x")) ui <- function(input, output, session) { server module_server("x", reactive({df})) }shinyApp(ui, server) }
- When testing modules, use
source("./modules/my-module.R")
to load the module.
- When testing modules, use
Example:
Handle one project requirement at a time
Build the module for text summary of a metric
<- function(id) { text_ui fluidRow(textOutput(NS(id, "text"))) } <- function(id, df, vbl, threshhold) { text_server moduleServer(id, function(input, output, session) { <- reactive({sum(df()[[vbl]] > threshhold)}) n $text <- renderText({ outputpaste("In this month", vbl,"exceeded the average daily threshhold of", threshhold,"a total of", n(), "days") }) }) } <- function() { text_demo <- data.frame(day = 1:30, arr_delay = 1:30) df <- fluidPage(text_ui("x")) ui <- function(input, output, session) { server text_server("x", reactive({df}), "arr_delay", 15) }shinyApp(ui, server) }
Build the module for plotting
<- function(id) { plot_ui fluidRow( column(11, plotOutput(NS(id, "plot"))), column( 1, downloadButton(NS(id, "dnld"), label = "")) ) } <- function(id, df, vbl, threshhold = NULL) { plot_server moduleServer(id, function(input, output, session) { <- reactive({viz_monthly(df(), vbl, threshhold)}) plot $plot <- renderPlot({plot()}) output$dnld <- downloadHandler( outputfilename = function() {paste0(vbl, '.png')}, content = function(file) {ggsave(file, plot())} ) }) } <- function() { plot_demo <- data.frame(day = 1:30, arr_delay = 1:30) df <- fluidPage(plot_ui("x")) ui <- function(input, output, session) { server plot_server("x", reactive({df}), "arr_delay") }shinyApp(ui, server) }
Compose both modules
<- function(id) { metric_ui fluidRow( text_ui(NS(id, "metric")), plot_ui(NS(id, "metric")) ) } <- function(id, df, vbl, threshhold) { metric_server moduleServer(id, function(input, output, session) { text_server("metric", df, vbl, threshhold) plot_server("metric", df, vbl, threshhold) }) } <- function() { metric_demo <- data.frame(day = 1:30, arr_delay = 1:30) df <- fluidPage(metric_ui("x")) ui <- function(input, output, session) { server metric_server("x", reactive({df}), "arr_delay", 15) }shinyApp(ui, server) }
Build-out app
<- fluidPage( ui titlePanel("Flight Delay Report"), sidebarLayout( sidebarPanel = sidebarPanel( selectInput("month", "Month", choices = setNames(1:12, month.abb), selected = 1 ) ),mainPanel = mainPanel( h2(textOutput("title")), h3("Average Departure Delay"), metric_ui("dep_delay"), h3("Average Arrival Delay"), metric_ui("arr_delay"), h3("Proportion Flights with >5 Min Arrival Delay"), metric_ui("ind_arr_delay") ) )<- function(input, output, session) { server $title <- renderText({paste(month.abb[as.integer(input$month)], "Report")}) output<- reactive({filter(ua_data, month == input$month)}) df_month metric_server("dep_delay", df_month, vbl = "dep_delay", threshhold = 10) metric_server("arr_delay", df_month, vbl = "arr_delay", threshhold = 10) metric_server("ind_arr_delay", df_month, vbl = "ind_arr_delay", threshhold = 0.5) }
Best Practices
There shouldn’t be any deeply nested bracketing in your code
Pass reactive variable to modules. Don’t call modules inside of some other reactive statement like observe()
Example: Bad module calling
observe({ # Process data before sending it into the module if (input$filterTo != "special") { myModuleServer( %>% data filter(val == input$filterTo) )else { } # Handle special case myModuleServer( %>% data ... ) } })
Example: Good module calling
<- function(input, output, session server # initialise the app state... <- reactiveValues(. app_state .# add server logic mod_commute_mode("mode", app_state) mod_commute_map("map", app_state) mod_commute_table("table", app_state) mod_commute_filter("filter", app_state) }
Liberally use reactive variables and your code becomes much cleaner, faster, and more maintainable
<- reactive({ datasheet_df %>% sample_data filter(site %in% input$selectSiteDatasheets) %>% ... })# Download button $download_datasheet <- downloadHandler( outputfilename = function() { paste("spreadsheet_", input$selectSiteDatasheets, ".csv", sep = "") },content = function(file) { write.csv(datasheet_df(), file, row.names = FALSE) } )
- Here datasheet_df is a reactive variable that Shiny will always keep up to date. Therefore the download button only needs to describe that it uses whatever the current value of that reactive is.
- This separation keeps the code easy to reason about and allows easy use of datasheet_df in other contexts than just the download button.
- Here datasheet_df is a reactive variable that Shiny will always keep up to date. Therefore the download button only needs to describe that it uses whatever the current value of that reactive is.