Part 3: Shiny Modules

Ted Laderas

Fred Hutch Cancer Center

You have LinkedIn to Blame

Modules

  • Why?
  • Motivating example
  • How to code

Why?

  • A new way to thinking about apps
  • Modularize code into smaller apps
  • makes it easier to develop with others
  • makes it easier to test code

Use Shiny Modules if:

  • You want to reuse your code, especially in the same app
  • You are tired of thinking up unique ids for all the elements in your app (namespacing)
  • You need to break up a monolithic shiny application into pieces
    • multiple Shiny developers

Maybe not:

  • Your app is a one and done - single version
  • It doesn’t have that many controls that need to be reused

Motivating Example

  • An ui that lets you pass in a dataset
  • Lets you select columns from that dataset
  • Want multiple plots in the same app

Using Shiny modules

ui

ui <- fluidPage(   
    select_hist_ui(
      id = "mtcars_module", 
      var_choices = mtcars_var_choices)
    )
  • note that select_hist_ui() is a function that takes an id argument
  • uses an id called mtcars_module

Using Shiny modules

ui

ui <- fluidPage(   
    select_hist_ui(
      id = "mtcars_module", 
      var_choices = mtcars_var_choices)
    )

server

server <- function(input, output) {
    select_hist_server(
      id="mtcars_module", 
      data=mtcars)
}
  • Note that we have a function select_hist_server()
  • Also uses the id "mtcars_module"

Exercise: Try it out

runApp("modules/module_demo_01/")
countdown::countdown(minutes = 3)
03:00

Reusing code

ui

ui <- fluidPage(   
    select_hist_ui(id = "mtcars_module", 
                   var_choices = mtcars_var_choices),
    select_hist_ui(id = "penguin_module",
                   var_choices = penguin_choices)
    )
  • note we have two select_hist_ui() functions
  • the second one has the id of penguin_module

Reusing code

ui

ui <- fluidPage(   
    select_hist_ui(id = "mtcars_module", 
                   var_choices = mtcars_var_choices),
    select_hist_ui(id = "penguin_module",
                   var_choices = penguin_choices)
    )

server

server <- function(input, output) {
    select_hist_server(id="mtcars_module", 
                       data=mtcars)
    select_hist_server(id="penguin_module", 
                       data=penguins)
}
  • Note that we have two instances of select_hist_server()
  • The second one use the id "penguin_module"
  • We can have two different plots/interfaces!

Two modules, two datasets

  • dataset is mtcars

  • dataset is penguins

Making Your Own Modules

  • ui module
  • server module
  • demo app

Make it easy on yourself

  • Put your code in a package (usethis::create_package())
  • Use <module_name>_server and <module_name>_ui for your module names
  • Have a demo app <module_name>_demo (makes it easier for testing)

ui module

select_hist_ui <- function(id, var_choices){
  ns <- NS(id)
  tagList(
    selectInput(inputId = ns("x_var"), 
                label="Select X Variable", 
                choices=var_choices, 
                selected = var_choices[1]),
    plotOutput(ns("hist_plot"))
  )
}
  • Initialize the namespace for id using NS() function
  • Need to refer to any controls/plots with ids wrapped in ns()
    • such as ns("x_var") or ns("hist_plot")

Server Module

select_hist_server <- function(id, data) {
  moduleServer(id, function(input, output, session) {

  })
  
}
  • define a function that takes an id as input
  • moduleServer() encapsulates your server code

Server Module

select_hist_server <- function(id, data) {
  moduleServer(id, function(input, output, session) {

    output$hist_plot <- 
      renderPlot({
        x_var <- input$x_var
        data %>% ggplot(aes(x=.data[[x_var]])) +
          geom_histogram()
    })
    
  })
  
}
  • Put your server code in moduleServer()
  • be really careful with closing curly brackets and parentheses

Write a demo app function

## modules.R
select_hist_demo <- function(data=mtcars){
  var_choices <- names(data)
  ui <- fluidPage(
    select_hist_ui(id = "module", 
                   var_choices = var_choices)  
  )
  server <- function(input,output,session){
    select_hist_server(id="module", data=data)
  }
  shinyApp(ui, server)
}
  • write an <module_name>_demo() function
  • useful for documentation and testing

Exercise: Use a Shiny Module

  • In RStudio, open up "modules/module_demo_02/app.R"
  • Wire in a new dataset to one of the plots
  • If you’re feeling adventurous, add a new plot
countdown::countdown(minutes = 5)
05:00

Module Design

Module Design

Careful design

  • What does the module do?
  • What is it trying to accomplish?
  • What is the module’s name?

Inputs and Return Values

  • Static or Reactive Inputs?
  • Complexity of return values
  • Which outputs serve as inputs for other modules?

Eric Nantz: Effective Use of Shiny modules in App development :::

Communicating between modules

  # execute plot variable selection modules
  plot1vars <- varselect_mod_server("plot1_vars") #returns a reactive
  plot2vars <- varselect_mod_server("plot2_vars") #returns a reactive
  
  # execute scatterplot module
  res <- scatterplot_mod_server(
                    "plots",
                    dataset = ames,
                    plot1vars = plot1vars,
                    plot2vars = plot2vars)

scatterplot_mod_server

scatterplot_mod_server <- function(input, 
                                   output, 
                                   session, 
                                   dataset, 
                                   plot1vars, 
                                   plot2vars) {
  
  plot1_obj <- reactive({
    p <- scatter_sales(dataset, xvar = plot1vars$xvar(), yvar = plot1vars$yvar())
    return(p)
  })
  
  plot2_obj <- reactive({
    p <- scatter_sales(dataset, xvar = plot2vars$xvar(), yvar = plot2vars$yvar())
    return(p)
  })
  
  output$plot1 <- renderPlot({
    plot1_obj()
  })

Go Further