Search code examples
shinyshinydashboardshiny-servershiny-reactivityshinymodules

Name-spacing is breaking when creating UI elements within nested moduleServers


I am developing a Shiny application with nested shiny modules, when I define variable UI elements within a nested module server the parent module name space is not inherited correctly. For example, if you had the following

  1. Parent module -> ns = parent
  2. Child module -> ns = child

The UI when inspecting the application would display the name-spacing as 'parent-child-...' however when a UI element is defined from the child servers it is now only 'child-...'. To account for this I tried a hacky solution and it worked by pasting 'parent' in front of the 'child' id when creating the element.

I've created an example to capture this issue.

library(shiny)

# Base UI and server elements  -------------------------------------------------
histogramUI <- function(id) {
  ns <- NS(id)
  tagList(
    selectInput(ns("var"), "Variable", choices = names(mtcars)),
    numericInput(ns("bins"), "bins", value = 10, min = 1),
    plotOutput(ns("hist"))
  )
}

histogramServer <- function(id) {
  moduleServer(id, function(input, output, session) {
    data <- reactive(mtcars[[input$var]])
    output$hist <- renderPlot({
      hist(data(), breaks = input$bins, main = input$var)
    }, res = 96)
  })
}

# Button UI and server elements ------------------------------------------------
buttonUI <- function(id) {
  ns <- NS(id)
  uiOutput(ns("new_btn"))
}

# Server created button
buttonServer <- function(id) {
  moduleServer(id, function(input, output, session) {
    
    observe({
      req(input$var == "cyl")
      output$new_btn <- 
        renderUI({
          div(
            actionButton(
              # Does work \/\/\/
              NS(paste0('test-', id), 'action_button'),
              # Doesn't work \/\/\/
              # NS(id, 'action_button')
              label = "Button test")
          )
        })
    })
    
    observeEvent(input$action_button, {
      # Printing the session id and selected var
      print(id)
      print(input$var)
    })
  })
}

# Master UI elements
major_piece_of_func_ui <- function(id){
  ns <- NS(id)
  div(
    histogramUI(ns("hist_test_1")),
    buttonUI   (ns("hist_test_1"))
  )
}

major_piece_of_func_serv <- 
  function(id) {
    moduleServer(id, function(input, output, session) {
      histogramServer("hist_test_1")
      buttonServer   ("hist_test_1")
    })
  }

# Ui and server construction
ui <- fluidPage(
  major_piece_of_func_ui('test')
)

server <- function(input, output, session) {
  major_piece_of_func_serv('test')
}

shinyApp(ui, server)

I am very open to the fact that I may be going about this in the completely wrong way and am open to alternative solutions that at a minimum hold the following constraints:

Constraints:

  1. Withhold the structure of nested modules
  2. Withhold the ability to create UI elements within child module servers

Cheers, Aidan


Solution

  • For a non-nested module, NS(id) is equivalent to session$ns in the server part. But not for a nested module. For a nested module, use session$ns, it returns the namespacing function with the composed namespace.