Best practices for global/external variables used in a module

I wanted to know what others think is the best approach when making a shiny module that uses some functions/variables that in a non-module app would belong in global.R

Do you still keep them in global.R? To me that seems to break the isolation and independence of modules, since a module should be explicit about all its inputs and outputs.

As a concrete example: in the following app, the module UI and module server use a variable defined outside of it. Here's one way to write it:

foodata <- mtcars
complex_model_calculation <- function(data) {
  mean(data)
}

fooUI <- function(id) {
  ns <- NS(id)
  
  tagList(
    selectInput(ns("var"), "Variable", names(foodata)), 
    "Result:", textOutput(ns("result"))
  )
}

foo <- function(input, output, session) {
  output$result <- renderText({
    complex_model_calculation(foodata[[input$var]])
  })
}

ui <- fluidPage(
  fooUI("foo")
)

server <- function(input, output, session) {
  callModule(foo, "foo")
}

shinyApp(ui, server)

If this was a non-modularized app, I would place the data definition and the calculation function in a global.R file. In a modular app, I can still place the data variable and the function in global.R, or I can put them in a "foo_module_helpers.R" file and source that file from the module code.

In terms of how to get these objects into the module, I can either do what's currently done (assume the objects are defined, and use them in the module, but that goes against the idea that a module should be self-contained), or I can add them as parameters to the module UI and module server.

If I choose to add parameters to the module UI+server, then again I have two options: either make the main app ui/server know about the variable and function and the main app will pass these objects to the module, or I can use default parameters in the module.

I hope I explained the different situations clear enough - I would love to see some opinions on which is the best approach

5 Likes

Hi,

I may have similar setup. I have a large Shiny app with quite a few modules. All the modules use global setup information (like database connection info and other settings). I have put all these global setup info into a separate module (along with UI and server logic for modification). I store the info as reactive values in the module, and I return a reactive from the module to access the values. The returned reactive is then passed as a parameter to each "client" module that needs the data.

In the following minimal example I probably use more reactives than necessary (but I always hit my head on a missing-reactive-context, so the more the better :-)), and of course all global data can be put into one structure, but I hope you get the idea.

Cheers
Steen

# in app.R

source("globaldata.R")
source("module1.R")

server <- function(input, output, session) {
...
  GlobalData = callModule(GlobalModule, "globals")
  callModule(Module1, "mod1", GlobalData
}

# in globaldata.R

GlobalModule <- function(input, output, session) {
  ...
  stash = reactiveValues()
  stash$GlobalVal1 = ...
 ...
  return(list(GetGlobalVal1 = reactive(stash$GlobalVal1))
}

# in module1.R

Module1 <- function(input, output, session, globals) {
  ...
  #somewhere in a reactive context
  val1 = globals$GetGlobalVal1()
}
4 Likes

Thanks for adding to the discussion, that's an interesting solution creating a "globals module"

What about the case where there are no shared variables across different modules - each module just has its own set of functions/variables that it needs, and I don't want to hardcode them inside the module. Would you still say that creating a globals module that defines specific variables for each module makes sense?

If the functions/variables are truly local to the module (as in your example) I would put them in the module source R file along with the UI function(s) and the module (server) function. I that way the source file is self-contained. The module function probably won't work without the UI function anyway, so these two will have to go together. I don't see an issue with putting the helper functions and variables that the UI and module functions depend on into the source file as well. If it's a question of having too much code in one file I would put them in a foo_modulee_helpers.R file and source that in the module file.

Just my $0.02 :slight_smile:

So that still leaves another topic unanswered: would you add them as parameters to the module ui+server so that the module is truly self contained, or would you just assume in the ui+server that these variables exist, which kind of goes against the idea that a module should not touch objects outside of it. And if you do use parameters, would you pass them in or use default arguments?

These are subtle differences, but I come across this often enough that I wonder what others think is cleanest

Well, the server function (in the module) and the UI function depends on each other anyway, so they both have external dependencies. I don't see the difference between them two needing each other and a third object being needed by both. I would put them in the same source file, so the source file is self-contained. I think passing it as parameters is over-engineering :slight_smile:

If you use your module in different projects or contexts, and if the external objects vary between these projects/contexts, it may make sense to pass them as parameters.

So I guess it depends on how volatile the external objects are?

When you're writing a regular function, and you have some information you need to pass to it, do you 1) define a global variable that the function will go look at, or 2) pass the information in as a parameter? However you answer that question, the same (more or less) applies for modules. I hope that most of the time you do number 2, and especially if the function (or module function) is defined in a different file than it's called from.

7 Likes

I know this is late to this discussion, but have you updated your position on the topic?

From the discussion, I believe what works is something like:

mymod_global <- function(...) {
  ...
  return(list(something_meaningful)) # or an env or something useful
}
mymod_ui <- function(id) {
  ns <- shiny::NS(id)
  htmltools::tagList(...)
}
mymod_server <- function(input, output, session, global, ...) {
  ...
}

And used as:

mymod_obj <- mymod_global(...)
ui <- shiny::shinyUI(
  ...,
  mymod_ui("quux"),
  ...
)
server <- function(input, output, server) {
  quux <- shiny::callModule(mymod_server, "quux", glob)
  ...
}

My understanding of this is that the quux object is global in the sense of shiny scoping, and the quux object is per-session.

Hi,

You could also consider session$userData . Have a look here:

Shiny session object

I am using this to store objects and variables (including reactive ones) that should be visible across all modules within your application.

Hope this helps

Cheers
Skodman

1 Like

I'm trying to emulate your approach to global data and have several (possibly naive) questions.
In the GlobalModule function, should there be something like

ns = NS(id = "globals")

I understand how to add data frames to the stash reactive but I don't understand why the return is only of GetGlobalVal1 instead of all of stash.
Could the line of code be

return(stash)
Then in Module 1, to get the reactive df out, something like
dfOut <- globals$dfname

And should Module1 have a line of code like

ns = NS(id = "mod1")

Hi,

It's been a while since I worked with Shiny, so I may be a bit rusty. For the discussion of global variables you don't need namespaces, but you will need them for any UI identifiers your modules create/use.

I don't think there's any reason not to be able to return the stash (please keep in mind that "stash" is just a name here that I gave my reactiveValues structure). You just have to return it as a reactive. Maybe it's sufficient to return it as you do as it is already a reactiveValues, or maybe you would have to wrap it in a reactive() statement as this:

GlobalModule <- function(input, output, session) {
  ...
  stash = reactiveValues()
 ...
  return(list(GetStash = reactive(stash)) // or maybe just return(list(GetStash=stash))
}

Module1 <- function(input, output, session, globals) {
  ...
  #somewhere in a reactive context
 globalstash = globals$GetStash()
}

You can experiment a bit and see what works. You may get the dreaded "no reactive context" error, though :-).

Cheers
Steen

Hi all,
I've been trying to come up with a solution for what seems like a common use case related to this discussion for a couple of weeks now with no luck. This conversation comes the closest to explaining how global variables might be created and used across Shiny apps - but I'm having difficulty understanding the nuances of the code provided.

The Global Profile Problem

We have an app that walks a user through a "wizard setup"
intro that allows them to configure certain parameters that impact the functioning of individual Shiny application modules in a multiple app flexdashboard. These need to be stored globally, passed into the sub applications, and in the "profile" app need to be modifiable at the global level such that changes made to the global profile modify the functioning of the other modules accordingly.

I'm attempting to make sense of the code provided in this post and adapt it to this use case but thus far I've been unable to determine how to do so. Any help would be greatly appreciated!

---
title: "Global Profile Reprex"
output: 
  flexdashboard::flex_dashboard:
    orientation: columns
    vertical_layout: fill
runtime: shiny
---

```{r setup, include=FALSE}
library(flexdashboard)
library(shiny)
library(magrittr)

Global <- function(input, output, session) {
  profile  <- reactiveValues(goal = 5)
  return(profile)
}
Module1 <- function(input, output, session, globals) {
  goal = globals$profile
}

```

Column {data-width=500} 
-----------------------------------------------------------------------

### Access
```{r 'Access'}
# This is just intented to access the global reactive values
ui <- renderUI(fluidPage(
    shiny::htmlOutput("viewProfile")
))



server <- function(input, output, session) {
  globalProfile <- callModule(Global, "global")
    output$viewProfile <- shiny::renderText({
        paste0("Goal:",globalProfile$profile$goal)
    })
}

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

Column {data-width=500} 
-----------------------------------------------------------------------

### Update
```{r 'Update'}
# This is intended to allow a user to update the global reactive values.
ui <- renderUI(fluidPage(
    
    fluidRow(width = 12,
             column(12,
                    textInput(inputId = "goal",
                              label = "Goals (time per week)",
                              placeholder = "ex 4h42m 4:42"))),
    fluidRow(width = 12,
             column(5), column(2, submitButton("Save Changes", icon("save", lib = "font-awesome"))), column(5)) , 
    shiny::htmlOutput("test.text")
))



# Define server logic required to draw a histogram
server <- function(input, output, session) {
  globalProfile <- callModule(Global, "global")
  
    reactive({
        print("profile Saved")
      mod1 <- callModule(Module1, "mod1", globalProfile)
        mod1$profile$goal <- input$goal
    })
    output$test.text <- shiny::renderText({
        c("Goal",globalProfile$profile$goal) %>% paste0(collapse = " ")
             
    })
}

# Run the application 
shinyApp(ui = ui, server = server)
```
R version 3.5.3 (2019-03-11)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 10 x64 (build 18362)

Matrix products: default

locale:
[1] LC_COLLATE=English_United States.1252 
[2] LC_CTYPE=English_United States.1252   
[3] LC_MONETARY=English_United States.1252
[4] LC_NUMERIC=C                          
[5] LC_TIME=English_United States.1252    

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] magrittr_1.5         shiny_1.4.0.9000     RevoUtils_11.0.3    
[4] RevoUtilsMath_11.0.0

loaded via a namespace (and not attached):
 [1] Rcpp_1.0.3.1             pillar_1.4.2            
 [3] compiler_3.5.3           later_1.0.0             
 [5] plyr_1.8.4               tools_3.5.3             
 [7] digest_0.6.22.3          packrat_0.4.9-3         
 [9] lubridate_1.7.4.9000     jsonlite_1.6.9000       
[11] evaluate_0.14            tibble_2.1.3            
[13] gtable_0.2.0             pkgconfig_2.0.3         
[15] rlang_0.4.1              rstudioapi_0.10.0-9000  
[17] yaml_2.2.0               xfun_0.11.1             
[19] fastmap_1.0.1            withr_2.1.2             
[21] dplyr_0.8.3              httr_1.4.1.9000         
[23] knitr_1.26               grid_3.5.3              
[25] tidyselect_0.2.5         flexdashboard_0.5.1.9000
[27] glue_1.3.1.9000          R6_2.4.1                
[29] rmarkdown_1.17           ggplot2_3.1.0           
[31] purrr_0.3.3.9000         promises_1.1.0.9000     
[33] scales_1.0.0.9000        htmltools_0.4.0.9000    
[35] rsconnect_0.8.13         assertthat_0.2.1        
[37] xtable_1.8-4             mime_0.7                
[39] colorspace_1.4-0         httpuv_1.5.2.9000       
[41] lazyeval_0.2.1           munsell_0.5.0           
[43] crayon_1.3.4 

Update

The access function works with this simplified code, but I am still unable to figure out how to update the global reactiveValues from inside the Update app.

---
title: "Global Profile Reprex"
output: 
  flexdashboard::flex_dashboard:
    orientation: columns
    vertical_layout: fill
runtime: shiny
---

```{r setup, include=FALSE}
library(flexdashboard)
library(shiny)
library(magrittr)


  profile  <- reactiveValues(goal = 5)

Module1 <- function(input, output, session, globals) {
  goal = globals$profile
}

```

Column {data-width=500} 
-----------------------------------------------------------------------

### Access
```{r 'Access'}
# This is just intented to access the global reactive values
ui <- renderUI(fluidPage(
    shiny::htmlOutput("viewProfile")
))



server <- function(input, output, session) {
  
    output$viewProfile <- shiny::renderText({
        paste0("Goal:", profile$goal)
    })
}

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

Column {data-width=500} 
-----------------------------------------------------------------------

### Update
```{r 'Update'}
# This is intended to allow a user to update the global reactive values.
ui <- renderUI(fluidPage(
    
    fluidRow(width = 12,
             column(12,
                    textInput(inputId = "goal",
                              label = "Goals (time per week)",
                              placeholder = "ex 4h42m 4:42"))),
    fluidRow(width = 12,
             column(5), column(2, submitButton("Save Changes", icon("save", lib = "font-awesome"))), column(5)) , 
    shiny::htmlOutput("test.text")
))



# Define server logic required to draw a histogram
server <- function(input, output, session) {
  
    reactive({
        print("profile Saved")
      profile$goal <<- input$goal
    })
    output$test.text <- shiny::renderText({
        c("Goal: ",profile$goal) %>% paste0(collapse = " ")
             
    })
}

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

Update 2019-11-15 1657

Alright, solved this after some tinkering! Use of observeEvent and the save actionButton correctly updates the reactive profile values.

    ---
    title: "Global Profile Reprex"
    output: 
      flexdashboard::flex_dashboard:
        orientation: columns
        vertical_layout: fill
    runtime: shiny
    ---
    
    ```{r setup, include=FALSE}
    library(flexdashboard)
    library(shiny)
    library(magrittr)
    
    
      profile  <- reactiveValues(goal = 5)
    
    Module1 <- function(input, output, session, globals) {
      goal = globals$profile
    }
    
    ```
    
    Column {data-width=500} 
    -----------------------------------------------------------------------
    
    ### Access
    ```{r 'Access'}
    # This is just intented to access the global reactive values
    ui <- renderUI(fluidPage(
        shiny::htmlOutput("viewProfile")
    ))
    
    
    
    server <- function(input, output, session) {
      
        output$viewProfile <- shiny::renderText({
            paste0("Goal:", profile$goal)
        })
    }
    
    # Run the application 
    shinyApp(ui = ui, server = server)
    ```
    
    Column {data-width=500} 
    -----------------------------------------------------------------------
    
    ### Update
    ```{r 'Update'}
    # This is intended to allow a user to update the global reactive values.
    profile  <- reactiveValues(goal = 5)
    uiUpdate <- fluidPage(
        
        fluidRow(width = 12,
                 column(12,
                        textInput(inputId = "goal",
                                  label = "Goals (time per week)",
                                  placeholder = "ex 4h42m 4:42")), actionButton("save","Save Changes", icon("save", lib = "font-awesome"))), 
      shiny::htmlOutput("test.text")
    )
    
    
    
    # Define server logic required to draw a histogram
    serverUpdate <- function(input, output, session) {
      
        observeEvent(input$save,{
           # print("profile Saved")
          message("Profile Saved")
          profile$goal <<-  input$goal
        })
        output$test.text <- shiny::renderText({
            c("Goal: ",profile$goal) %>% paste0(collapse = " ")
                 
        })
    }
    
    # Run the application 
    shinyApp(ui = uiUpdate, server = serverUpdate)
    ```