如何在 golem Shiny 应用程序中包含 R6 对象以跨模块共享数据

How to include R6 objects to share data across modules in golem Shiny app

我第一次尝试使用 golem 创建一个 Shiny 应用程序。 golem 使用模块构建 Shiny 应用程序以帮助保持大型 Shiny 应用程序模块化。但是,默认情况下,模块之间不会相互通信。我想跨模块共享数据。根据 golem 文档,R6 objects are a useful way to share data across modules.

但是,在 example provided in the golem documentation, it is unclear where to put the R6 generator. According to Appsilon 中,R6 生成器进入一个单独的 .R 文件(例如,logger_manager.R),并在 [=27] 中调用=] 从 class 构造一个新对象:logger_manager = LoggerManager$new()。但是,基于 golemShiny 应用程序中没有 global.R 文件。

下面是我的基于 golemShiny 应用程序的最小示例。我试图遵循 example provided in the golem documentation 中的结构,但它似乎并没有跨模块共享数据:

app_ui.R:

app_ui <- function(request) {
  tagList(
    # Leave this function for adding external resources
    golem_add_external_resources(),
    
    # List the first level UI elements here 
    fluidPage(
      mod_a_ui("a_ui_1"),
      mod_b_ui("b_ui_1")
    )
  )
}

golem_add_external_resources <- function(){
  
  add_resource_path(
    'www', app_sys('app/www')
  )
 
  tags$head(
    favicon(),
    bundle_resources(
      path = app_sys('app/www'),
      app_title = 'Test'
    ),
    # Add here other external resources
    # for example, you can add shinyalert::useShinyalert()
    shinyjs::useShinyjs()
  )
}

app_server.R:

app_server <- function( input, output, session ) {
  
  # Generate R6 Class
  QuestionnaireResponses <- R6Class(
    classname = "QuestionnaireResponses",
    public = list(
      resp_id = NULL,
      timezone = NULL,
      timestamp = NULL,
      gender = NULL,
    )
  )
  
  # Create new object to share data across modules using the R6 Class
  questionnaire_responses <- QuestionnaireResponses$new()
  
  # List the first level callModules here
  callModule(mod_a_server, "a_ui_1", questionnaire_responses)
  callModule(mod_b_server, "b_ui_1", questionnaire_responses)
}

mod_a.R:

mod_a_ui <- function(id){
  ns <- NS(id)
  
  tagList(
    radioButtons(inputId = "gender",
                 label = "What is your sex?",
                 choices = c("Male" = 1,
                             "Female" = 2),
                 selected = character(0))
)
}

mod_a_server <- function(input, output, session, questionnaire_responses){
  ns <- session$ns
  
  # Add time start to the output vector
  timestamp <- format(Sys.time(), "%Y-%m-%d %H:%M:%OS6")
  timezone <- Sys.timezone()
  
  # Generate a survey-specific ID number
  resp_id <- paste0(sample(c(letters, LETTERS, 0:9), 10), collapse = "")
  
  # Assign values to R6 object
  questionnaire_responses$resp_id <- resp_id
  questionnaire_responses$timezone <- timezone
  questionnaire_responses$timestamp <- timestamp
  questionnaire_responses$gender <- input.gender
}

mod_b.R:

mod_b_ui <- function(id){
  ns <- NS(id)
  tagList(
    print("questionnaire_responses$resp_id")
  )
}

mod_b_server <- function(input, output, session, questionnaire_responses){
  ns <- session$ns
  }

但是,数据不能跨模块共享,因为当我尝试在模块 B(在模块 A 中生成)中打印 resp_id 时,我收到以下错误:

An error has occurred!
object 'questionnaire_responses' not found

我对你的代码做了一些更正(一些错字)

tl;dr:您无法在 UI.

中访问 questionnaire_responses

app_ui.R:

same as you

app_server.R:

#' @import shiny
#' @import R6
#' @noRd
app_server <- function( input, output, session ) {
  
  # Generate R6 Class
  QuestionnaireResponses <- R6Class(
    classname = "QuestionnaireResponses",
    public = list(
      resp_id = NULL,
      timezone = NULL,
      timestamp = NULL,
      gender = NULL
    )
  )
  
  # Create new object to share data across modules using the R6 Class
  questionnaire_responses <- QuestionnaireResponses$new()
  
  # List the first level callModules here
  callModule(mod_a_server, "a_ui_1", questionnaire_responses)
  callModule(mod_b_server, "b_ui_1", questionnaire_responses)
}

mod.R:

mod_a_ui <- function(id){
  ns <- NS(id)
  
  tagList(
    radioButtons(inputId = ns("gender"),
                 label = "What is your sex?",
                 choices = c("Male" = 1,
                             "Female" = 2),
                 selected = character(0))
  )
}

mod_a_server <- function(input, output, session, questionnaire_responses){
  ns <- session$ns
  
  # Add time start to the output vector
  timestamp <- format(Sys.time(), "%Y-%m-%d %H:%M:%OS6")
  timezone <- Sys.timezone()
  
  # Generate a survey-specific ID number
  resp_id <- paste0(sample(c(letters, LETTERS, 0:9), 10), collapse = "")
  
  # Assign values to R6 object
  questionnaire_responses$resp_id <- resp_id
  questionnaire_responses$timezone <- timezone
  questionnaire_responses$timestamp <- timestamp
  questionnaire_responses$gender <- "input$gender"
}


mod_b_ui <- function(id){
  ns <- NS(id)
  tagList(
    print("questionnaire_responses$resp_id") # you can't access questionnaire_responses in the UI
  )
}

mod_b_server <- function(input, output, session, questionnaire_responses){
  ns <- session$ns
  print(questionnaire_responses$resp_id) # but here you can
}

所以这里的问题是,当您的 R6 对象被传递 到服务器时,您试图从 UI 函数 打印对象 函数.

换句话说,当 R6 对象被传递给 mod_b_server 函数时,您正试图在 mod_b_ui 函数中进行打印。

对于您的原始问题,您可以在标准文件中创建 R6 class,如 https://github.com/ColinFay/golemexamples/blob/master/golemR6/R/R6.R

请注意,尽管您的数据对象 不是反应性的 并且在您更改模块 A 时不会重新打印 — 例如,您可以在此处使用 {gargoyle}

请参阅 https://github.com/ColinFay/golemexamples/tree/master/golemR6 以获取完整的工作示例。

PS:您的模块中缺少 ns():)

科林