Author: NEST CoreDev

# Data as shiny Module

#### NEST CoreDev

## Introduction

Proper functioning of any `teal` application requires the presence of a `teal_data` object. Typically, a `teal_data` object created in the global environment will be passed to the `data` argument in `init`. This `teal_data` object should contain all elements necessary for successful execution of the application’s modules.

In some scenarios, however, application developers may opt to postpone some data operations until the application runtime. This can be done by passing a special `shiny` module to the `data` argument. The `teal_data_module` function is used to build such a module from the following components:

- a UI function; accepts only one argument, `id`; defines user interface elements for the data module
- a server function: accepts only one argument, `id`; defines server logic for the data module, including data creation; must return a reactive expression containing a `teal_data` object

`teal` will run this module when the application starts and the resulting `teal_data` object that will be used throughout all `teal` (analytic) modules.

## Creating data in-app

One case for postponing data operations is datasets that are dynamic, frequently updated. Such data cannot be created once and kept in the global environment. Using `teal_data_module` enables creating a dataset from scratch every time the user starts the application.

See `?qenv` for a detailed explanation of how to use the `within` method.

    library(teal)

    data_module <- teal_data_module(
      ui = function(id) tags$div(),
      server = function(id) {
        moduleServer(id, function(input, output, session) {
          reactive({
            data <- within(
              teal_data(),
              {
                dataset1 <- iris
                dataset2 <- mtcars
              }
            )
            data
          })
        })
      }
    )

    app <- init(
      data = data_module,
      modules = example_module()
    )

    if (interactive()) {
      shinyApp(app$ui, app$server)
    }

## Modification of data in-app

Another reason to postpone data operations is to involve the application user in the preprocessing stage. An initial, constant form of the data can be created in the global environment and then modified once the app starts.

The following example illustrates how `teal_data_module` can be utilized to subset data based on the user inputs:

    data <- within(teal_data(), {
      dataset1 <- iris
      dataset2 <- mtcars
    })

    data_module <- teal_data_module(
      ui = function(id) {
        ns <- NS(id)
        tags$div(
          selectInput(ns("species"), "Select species to keep",
            choices = unique(iris$Species), multiple = TRUE
          ),
          actionButton(ns("submit"), "Submit")
        )
      },
      server = function(id) {
        moduleServer(id, function(input, output, session) {
          eventReactive(input$submit, {
            data_modified <- within(
              data,
              dataset1 <- subset(dataset1, Species %in% selected),
              selected = input$species
            )
            data_modified
          })
        })
      }
    )

    app <- init(
      data = data_module,
      modules = example_module()
    )

    if (interactive()) {
      shiny::shinyApp(ui = app$ui, server = app$server)
    }

Note that running preprocessing code in a module as opposed to the global environment will increase app loading times. It is recommended to keep the constant code in the global environment and to move only the dynamic parts to a data module.

###### WARNING

When using `teal_data_module` to modify a pre-existing `teal_data` object, it is crucial that the server function and the data object are defined in the same environment, otherwise the server function will not be able to access the data object. This means server functions defined in packages cannot be used.

### Extending existing `teal_data_modules`

The server logic of a `teal_data_module` can be modified before it is used in an app, using the `within` function. This allows the `teal_data` object that is created in the `teal_data_module` to be processed further.

In the previous example, `data_module` takes a predefined `teal_data` object and allows the app user to select a subset. The following example modifies `data_module` so that new columns are added once the data is retrieved.

    data_module_2 <- within(
      data_module,
      {
        # Create new column with Ratio of Sepal.Width and Petal.Width
        dataset1$Ratio.Sepal.Petal.Width <- round(dataset1$Sepal.Width / dataset1$Petal.Width, digits = 2L)
        # Create new column that converts Miles per Galon to Liter per 100 Km
        dataset2$lp100km <- round(dataset2$mpg * 0.42514371, digits = 2L)
      }
    )

    app <- init(
      data = data_module_2,
      modules = example_module()
    )

    if (interactive()) {
      shiny::shinyApp(ui = app$ui, server = app$server)
    }