Communicating Between Shiny Modules – A Simple Example

R Shiny is a great tool to create an interactive web application. The web framework was designed to be able to build very complex apps and the more code there is, the more complicated it is to debug the application.

There are some ways that can help organize your application. One way is to write functions for certain UI elements and server elements. Another more robust way is to write R Shiny modules. However, modules are a bit complicated to get used to at first and there is a learning curve. In this tutorial, we are building a simple R Shiny application that will get you up to speed with Shiny modules. Specifically, we will show you how your Shiny modules can communicate with each other.  We will also be discussing how you can return one single value and multiple values from a Shiny module.

Let’s get started and see how Shiny modules can communicate!

First, here is the application we will be building.

R Shiny Application Set-Up

The application consists of three modules.

  • The first R Shiny module creates the slider and the action button in the UI and then returns the slider value and the action button in the server module.
  • The second R Shiny module creates the radio buttons with normal and exponential distribution. Depending on the choice, this module uses the action button from module one and returns the specified distribution.
  • The third module takes the slider value, the action button from module one, and the distribution from module two and then plots a histogram.

R Shiny Module One: Slider Input + Action Button

slider_input_ui <- function(id) {
  
  ns <- shiny::NS(id)
  
  shiny::tagList(
    
    shiny::sliderInput(inputId = ns("bins"),
                       label   = "Number of bins:",
                       min     = 1,
                       max     = 50,
                       value   = 30),
    
    br(),
    
    shiny::actionButton(inputId = ns("click"),
                        label   = "Click Me"),
    
    br()
    
  )
  
}

slider_input_server <- function(id) {
  
  shiny::moduleServer(
    id,
    
    function(input, output, session) {
      
      shinyjs::click(id = "click")
      
      return(
        list(
          bins       = shiny::reactive(input$bins),
          action_btn = shiny::reactive(input$click)
        )
      )
      
    }
    
  )
  
}

The UI is pretty straight forward. We are creating a slider input and also an action button. We are wrapping the ids into the ns object for the namespace.

For the server-side, we are using the shinyjs package click the action button when the Shiny app is loading. We are also returning the slider value and the action button. The crucial part about that is to wrap the return values with the reactive function for it to work!

R Shiny Module Two: Radio Buttons + Distribution

dist_ui <- function(id) {
  
  ns <- shiny::NS(id)
  
  shiny::tagList(
    
    br(),
    shiny::radioButtons(inputId = ns("dist"),
                        label   = "Choose Distribution",
                        choices = c("Normal", "Exponential")
    )
    
  )
  
}

dist_server <- function(id, action_button) {
  
  shiny::moduleServer(
    id,
    
    function(input, output, session) {
      
      distribution <- shiny::eventReactive(action_button(), {
        
        if(input$dist == "Normal") {
          
          dist <- rnorm(n = 10000, mean = 0, sd = 1)
          
        } else {
          
          dist <- rexp(n = 10000, rate = .2)
          
        }
        
        return(dist)
        
      })
      
      return(distribution)
      
    }
    
  )
  
}

In the UI, we are going to create radio buttons. Very basic, very simple.

The server is a bit more complicated because we are using the action button from module one. To do so, we need to specify the action_button as a function argument. Inside the server module, we need to access the action button with action_button(). The brackets are very important and needed because the action button is a reactive value.

R Shiny Module 3: Histogram

The last module is drawing the histogram. Nothing is going to be returned with this module, however, we will be using return values from module one and module 2.

histogram_ui <- function(id) {
  
  ns <- shiny::NS(id)
  
  shiny::tagList(
    
    shiny::plotOutput(ns("plot"))
    
    
  )
  
}

histogram_server <- function(id, df, slider_input_bins, action_button) {
  
  shiny::moduleServer(
    id,
    
    function(input, output, session) {
      
      bins <- shiny::eventReactive(action_button(), {
        
        bins <- seq(min(df()), max(df()), length.out = slider_input_bins() + 1)
        
      })
      
      output$plot <- shiny::renderPlot({
        
        # draw the histogram with the specified number of bins
        hist(df(), breaks = bins(), col = 'darkgray', border = 'white')
        
      })
      
    }
    
  )
  
}

Again, the UI is very easy to understand. The server function is again a bit harder. The df argument specifies the vector of values returned from module two. It will either be a random normal distribution or a random exponential distribution. We are also using the slider input value from module one to draw the right amount of bins the user specified.

The most important thing is the brackets. We need to use df(), slider_input_bins(), and action_button(). This is because all these values are reactive.

Communicating Between Shiny Modules: Final App

Now, the last step is to create the final application and make the modules communicate with each other.

library(shiny)
library(tidyverse)
library(shinyjs)

list.files("modules") %>%
    purrr::map(~ source(paste0("modules/", .)))

ui <- shiny::fluidPage(
    
    shinyjs::useShinyjs(),
    
    # Application title
    titlePanel("Modules Tutorial"),
    
    sidebarLayout(
        sidebarPanel(
            
            slider_input_ui("slider_btn"),
            dist_ui("dist")
            
        ),
        
        mainPanel(
            
            histogram_ui("hist")
            
        )
    )
)

server <- function(input, output, session) {
    
    slider_btn_vals <- slider_input_server(id = "slider_btn")
    
    dist_values <- dist_server(id = "dist",
                               action_button = slider_btn_vals$action_btn)

    histogram_server(id = "hist",
                     df = dist_values,
                     slider_input_bins = slider_btn_vals$bins,
                     action_button = slider_btn_vals$action_btn)
    
}

# Run the application 
shinyApp(ui = ui, server = server)

The slider_input_server function Shiny module returns a list with the action button and the slider value. To use these two values in the second Shiny module, we have to save the output and then reference the values with the dollar sign, $, in the second Shiny module function.

We do almost the same with the second Shiny module function. We are saving the vector of either the normal or exponential distribution in dist_values and then using dist_values as an argument in the histogram_server function. Here, no referencing with the dollar sign is needed because we are not returning a list, as we did in module one.

There is no need to use brackets, (), or any reactivity in the server function in the actual application.

Also, I am reading in the modules with:

list.files("modules") %>% 
    purrr::map(~ source(paste0("modules/", .)))

I am listing all the modules in the folder and source every file that is in that folder. I like this way of sourcing files since I do not have to source a file when I am creating a new module in that folder.

I hope you have enjoyed this quick tutorial about the communication between Shiny modules.

If you are interested in other Shiny tricks, here is a tutorial that shows you how you can use DataTable and a proxy, to replace data rather than rendering the table every time your data changes.

If you have any suggestions, please let me know in the comments below. Thanks!

 

Post your comment