This is a great question. Some resources and thoughts:
- Creating standalone helper functions / "engine functions" in separate R files are certainly a great trick. One great way to use these throughout your app is to wrap them up in a package (R's favorite way to share functions), add unit tests with
testthat, and load that package when necessary.
- Creating standalone modules, as you mentioned. These can then be put into packages or files, and tested / developed independently using
testthat or shinytest.
- following the practices of those that have gone before you
- reading more about Shiny modules
Ultimately, I am hopeful of a function like the following eventually making its way into the shiny package for easier use. The idea is to run a module independently of its parent application. I'm not 100% satisfied with the API, yet, but it's a start. Feel free to use it!
runModule <- function(
id,
ui,
server,
ui_param = list(),
server_param = list()
){
actualUI <- do.call(ui, c(id = id, ui_param))
actualServer <- function(input, output, session) {
do.call(callModule,
c(module = server
, id = id
, server_param
)
)
}
shinyApp(actualUI, actualServer)
}
Call it with something like:
runModule("my_module", ui = sliderUI, server = slider)
I recommend an approach like this one to the testSlider function that you wrote, because it follows functional programming best practices that make it easier to maintain / reason about. All UI / server parameters would be passed to the ui_params and server_params variables. The only difficulty at present is if you wanted to pass a reactive value, that would be difficult in this context.