72

What are the best practices to organize larger Shiny applications?
I think best R practices are also applicable to Shiny.
Best R practices are discussed here: How to organize large R programs
Link to Google's R Style Guide: Style Guide

But what are the unique tips and tricks in Shiny context which I can adopt to make my Shiny code look better (and more readable)? I am thinking of things like:

  • Exploiting object oriented programming in Shiny
  • In server.R which parts should be sourced?
  • File hierarchy of project containing markdown documents, pictures, xml and source files

For example if I am using navbarPage and tabsetPanel in every tabPanel my code is starting to look quite messy after addition of several UI elements.

Example code:

server <- function(input, output) {

 #Here functions and outputs..

}

ui <- shinyUI(navbarPage("My Application",
  tabPanel("Component 1",
             sidebarLayout(
                sidebarPanel(
                    # UI elements..
                ),
                mainPanel(
                    tabsetPanel(
                        tabPanel("Plot", plotOutput("plot")
                                 # More UI elements..
                                 ), 
                        tabPanel("Summary", verbatimTextOutput("summary")
                                 # And some more...
                                 ), 
                        tabPanel("Table", tableOutput("table")
                                 # And...
                                 )
                    )
                )
    )           
  ),
  tabPanel("Component 2"),
  tabPanel("Component 3")
))

shinyApp(ui = ui, server = server)

For organizing ui.R code I found quite nice solution from GitHub: radiant code
Solution is to use renderUI to render every tabPanel and in server.R tabs are sourced to different files.

server <- function(input, output) {

  # This part can be in different source file for example component1.R
  ###################################
  output$component1 <- renderUI({
        sidebarLayout(
                sidebarPanel(
                ),
                mainPanel(
                    tabsetPanel(
                        tabPanel("Plot", plotOutput("plot")), 
                        tabPanel("Summary", verbatimTextOutput("summary")), 
                        tabPanel("Table", tableOutput("table"))
                    )
                )
    )
  })
 #####################################  

}
ui <- shinyUI(navbarPage("My Application",
  tabPanel("Component 1", uiOutput("component1")),
  tabPanel("Component 2"),
  tabPanel("Component 3")
))

shinyApp(ui = ui, server = server)
Scarabee
  • 5,437
  • 5
  • 29
  • 55
Mikael Jumppanen
  • 2,436
  • 1
  • 17
  • 29

5 Answers5

31

After addition of modules to R shiny. Managing of complex structures in shiny applications has become a lot easier.

Detailed description of shiny modules:Here

Advantages of using modules:

  • Once created, they are easily reused
  • ID collisions is easier to avoid
  • Code organization based on inputs and output of modules

In tab based shiny app, one tab can be considered as one module which has inputs and outputs. Outputs of tabs can be then passed to other tabs as inputs.

Single-file app for tab-based structure which exploits modular thinking. App can be tested by using cars dataset. Parts of the code where copied from the Joe Cheng(first link). All comments are welcome.

# Tab module
# This module creates new tab which renders dataTable

dataTabUI <- function(id, input, output) {
  # Create a namespace function using the provided id
  ns <- NS(id)

  tagList(sidebarLayout(sidebarPanel(input),

                        mainPanel(dataTableOutput(output))))

}

# Tab module
# This module creates new tab which renders plot
plotTabUI <- function(id, input, output) {
  # Create a namespace function using the provided id
  ns <- NS(id)

  tagList(sidebarLayout(sidebarPanel(input),

                        mainPanel(plotOutput(output))))

}

dataTab <- function(input, output, session) {
  # do nothing...
  # Should there be some logic?


}

# File input module
# This module takes as input csv file and outputs dataframe
# Module UI function
csvFileInput <- function(id, label = "CSV file") {
  # Create a namespace function using the provided id
  ns <- NS(id)

  tagList(
    fileInput(ns("file"), label),
    checkboxInput(ns("heading"), "Has heading"),
    selectInput(
      ns("quote"),
      "Quote",
      c(
        "None" = "",
        "Double quote" = "\"",
        "Single quote" = "'"
      )
    )
  )
}

# Module server function
csvFile <- function(input, output, session, stringsAsFactors) {
  # The selected file, if any
  userFile <- reactive({
    # If no file is selected, don't do anything
    validate(need(input$file, message = FALSE))
    input$file
  })

  # The user's data, parsed into a data frame
  dataframe <- reactive({
    read.csv(
      userFile()$datapath,
      header = input$heading,
      quote = input$quote,
      stringsAsFactors = stringsAsFactors
    )
  })

  # We can run observers in here if we want to
  observe({
    msg <- sprintf("File %s was uploaded", userFile()$name)
    cat(msg, "\n")
  })

  # Return the reactive that yields the data frame
  return(dataframe)
}
basicPlotUI <- function(id) {
  ns <- NS(id)
  uiOutput(ns("controls"))

}
# Functionality for dataselection for plot
# SelectInput is rendered dynamically based on data

basicPlot <- function(input, output, session, data) {
  output$controls <- renderUI({
    ns <- session$ns
    selectInput(ns("col"), "Columns", names(data), multiple = TRUE)
  })
  return(reactive({
    validate(need(input$col, FALSE))
    data[, input$col]
  }))
}

##################################################################################
# Here starts main program. Lines above can be sourced: source("path-to-module.R")
##################################################################################

library(shiny)


ui <- shinyUI(navbarPage(
  "My Application",
  tabPanel("File upload", dataTabUI(
    "tab1",
    csvFileInput("datafile", "User data (.csv format)"),
    "table"
  )),
  tabPanel("Plot", plotTabUI(
    "tab2", basicPlotUI("plot1"), "plotOutput"
  ))

))


server <- function(input, output, session) {
  datafile <- callModule(csvFile, "datafile",
                         stringsAsFactors = FALSE)

  output$table <- renderDataTable({
    datafile()
  })

  plotData <- callModule(basicPlot, "plot1", datafile())

  output$plotOutput <- renderPlot({
    plot(plotData())
  })
}


shinyApp(ui, server)
Mikael Jumppanen
  • 2,436
  • 1
  • 17
  • 29
  • 1
    Nice, but the first few functions in your example (esp. `dataTabUI` & `plotTabUI`) are just normal functions that don't really demonstrate any **modules** functionality. In both cases, the call to `ns <- NS(id)` is completely superfluous. It serves no purpose because: (1) the resulting `ns` function is not called anywhere in the function and; (b) there are no corresponding functions used on the server side that takes referenced the namespaced objects that such calls to `ns` would produce. This example would be clearer if it did not include the functions `dataTabUI`, `plotTabUI`, and `dataTab`. – Josh O'Brien Apr 05 '19 at 19:22
  • @JoshO'Brien At least dataTabUI et al. follow the form of a Shiny app and are ready for a dynamic component in the future if needed. – Josiah Yoder Jun 28 '19 at 15:40
28

I really like how Matt Leonawicz organises his apps. I took his approach learning how to use Shiny, as we all know it can get quite scattered if not properly managed. Have a look at his structure, he gives an overview of the way he organises the apps in the app called run_alfresco

https://github.com/ua-snap/shiny-apps

Pork Chop
  • 28,528
  • 5
  • 63
  • 77
  • Thanks for this suggestion @Pork Chop - is there a good reason why Matt puts the io and reactives in *appSourceFiles* sub directory instead of leaving it in the externals directory? – micstr Jun 30 '16 at 10:05
  • @micstr, i guess he adopting structure from larger projects he has done in the past. I would suggest looking at the Visual studio project setup for developing and structuring apps (only for the insight). You mayalso adopt [MVC](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) model strucutre in your Rshiny and break it down by folders as he does – Pork Chop Jun 30 '16 at 11:20
  • 1
    @jangorecki As it was accepted by the user who asked the question I think It did the job, alternatively you can post something, so dont be salty – Pork Chop Aug 26 '18 at 09:00
  • 1
    Your post is not an answer but a reference to an answer, when the repo will be removed your post will be worthless. You should comment instead. This is how SO is designed to work. – jangorecki Aug 26 '18 at 10:20
  • true, however 4 years have passed and that github account is still there, you really shouldnt vote down legacy questions – Pork Chop Aug 26 '18 at 10:52
8

I wrote Radiant. I have not heard people say bad things about the code organization (yet) but I am sure it could be better. One option would be to separate the ui and logic as Joe Cheng does in shiny-partials.

https://github.com/jcheng5/shiny-partials

Another might be to try OO programming, e.g., using R6 http://rpubs.com/wch/17459

Vincent
  • 5,063
  • 3
  • 28
  • 39
  • 1
    A blog post regarding your organization method would be nice. Plus it would allow more conversation regarding shiny app architecture. Everyone benefits if more people are talking/thinking about it – MySchizoBuddy Jul 30 '15 at 00:47
  • @MySchizoBuddy I'll see what I can do. Probably won't be very soon however. Teaching two new classes this year. – Vincent Jul 30 '15 at 04:31
  • did you get any time to write about shiny app organization – MySchizoBuddy Nov 16 '16 at 13:38
  • Unfortunately no. I recommend taking a look at [shiny modules](http://shiny.rstudio.com/articles/modules.html). Another option is to create a shiny app as a set of R-packages. That is the approach I now use for [Radiant](https://github.com/radiant-rstats/radiant). – Vincent Nov 23 '16 at 00:52
4

Now there is also the golem package that provides a framework for organising shiny code. It mainly uses modules, but also provides a structure for how to organise e.g. helper functions and css/javascript files. There is also an accompanying book.

starja
  • 9,887
  • 1
  • 13
  • 28
1

It's been a while since I created this solution but I've finally made my work at Roche and Genentech Open Source. When we were working on a big shiny application we were not able to use shiny modules (If I remember correctly because modules did not allow us to share data), and I came up with a component-based architecture inspired mostly by AngularJS.

The repo for the project called Battery is available at Genentech organization on GitHub.

And here you can read a tutorial that explains how to use the framework.

Architecture for Non-Trivial R Shiny Applications

jcubic
  • 61,973
  • 54
  • 229
  • 402