Stéphane Laurent
Stéphane Laurent

Reputation: 84529

Separate the `renderUI` in a module server

In some complex and large shiny apps, the UI parts are often rendered with renderUI and uiOutput. Consider this small app for illustration:

library(shiny)

modUI <- function(id) {
  ns <- NS(id)
  uiOutput(ns("theUI"))
}

modServer <- function(id) {
  moduleServer(
    id, 
    function(input, output, session) {
      
      output[["plot"]] <- renderPlot({
        n <- input[["numb"]]
        plot(rnorm(n), rnorm(n), pch = 19L)
      })
      
      ns <- session$ns
      output[["theUI"]] <- renderUI({
        tagList(
          sliderInput(ns("numb"), "N", 10, 100, 50),
          plotOutput(ns("plot"))
        )
      })
    }
  )
}

ui <- basicPage(
  br(),
  modUI("myapp")
)

server <- function(input, output, session) {
  modServer("myapp")
}

shinyApp(ui, server)

Here modUI is ridiculously small. But in modServer, the renderUI could be large and there could be many other output components. Therefore it is desirable to split modServer:

renderPlotServer <- function(id) {
  moduleServer(
    id, 
    function(input, output, session) {
      
      output[["plot"]] <- renderPlot({
        n <- input[["numb"]]
        plot(rnorm(n), rnorm(n), pch = 19L)
      })
      
    }
  )
}

renderUIServer <- function(id) {
  moduleServer(
    id, 
    function(input, output, session) {
      
      ns <- session$ns
      output[["theUI"]] <- renderUI({
        tagList(
          sliderInput(ns("numb"), "N", 10, 100, 50),
          plotOutput(ns("plot"))
        )
      })
      
    }
  )
}

and to put them together one can use the same id:

modServer <- function(id) {
  moduleServer(
    id, 
    function(input, output, session) {
      renderPlotServer("mysubapp")
      renderUIServer("mysubapp")
    }
  )
}

but then we need a nested namespace in the UI part of the module:

modUI <- function(id) {
  ns <- NS(NS(id)("mysubapp"))
  uiOutput(ns("theUI"))
}

This is not convenient.

A more convenient solution consists in using a NULL namespace, to avoid the nested namespace in the UI part:

modServer <- function(id) {
  moduleServer(
    id, 
    function(input, output, session) {
      renderPlotServer(NULL)
      renderUIServer(NULL)
    }
  )
}

But this solution has a problem: it prevent to use the module multiple times.

So, what would be a convenient solution avoiding this problem?

I think I have one: one can nest the "main" id with itself:

modServer <- function(id) {
  moduleServer(
    id, 
    function(input, output, session) {
      renderPlotServer(id)
      renderUIServer(id)
    }
  )
}

modUI <- function(id) {
  ns <- NS(NS(id)(id))
  uiOutput(ns("theUI"))
}

Do you have another nice solution?

Upvotes: 2

Views: 97

Answers (1)

thothal
thothal

Reputation: 20329

So maybe I am overlooking some subtleties of your approach, but why would you need to use moduleServer in render(Plot|UI)Server in the first place? Rather you can go for a plain function and all the namespacing seems to work without any further ado?

library(shiny)

modUI <- function(id) {
   ns <- NS(id)
   uiOutput(ns("theUI"))
}

renderPlotServer <- function(input, output, session) {
   output[["plot"]] <- renderPlot({
      n <- input[["numb"]]
      plot(rnorm(n), rnorm(n), pch = 19L)
   })
}

renderUIServer <- function(input, output, session) {
   ns <- session$ns
   output[["theUI"]] <- renderUI({
      tagList(
         sliderInput(ns("numb"), "N", 10, 100, 50),
         plotOutput(ns("plot"))
      )
   })
   
}

modServer <- function(id) {
   moduleServer(
      id, 
      function(input, output, session) {
         renderPlotServer(input, output, session)
         renderUIServer(input, output, session)
      }
   )
}

ui <- basicPage(
   br(),
   modUI("myapp"),
   br(),
   modUI("myapp2") ## a second module works like a charm
)

server <- function(input, output, session) {
   modServer("myapp")
   modServer("myapp2")
}

shinyApp(ui, server)

Upvotes: 1

Related Questions