R Shiny Modules: How to Create Your First R Shiny Module

[This article was first published on Tag: r - Appsilon | Enterprise R Shiny Dashboards, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Let’s be honest – all of your R Shiny app logic is contained in a single app.R file, and it’s probably several hundreds of lines long. No judgment – we’ve all been there. But there’s a better way, and today you’ll learn all about it.

R Shiny modules provide you with the ability to separate reusable logic into small and manageable R scripts. You can then plug different modules into your app.R and end up with a professional-looking code base. Think of R Shiny modules as components in React.

Are you new to R Shiny? This article will explain how to make a career out of it.

Table of contents:


What are R Shiny Modules and Why Should You Care?

People often think of R Shiny modules as an advanced topic, but that doesn’t have to be the case. Sure, modules can sometimes make even an experienced R programmer uncomfortable, but there are plenty of easy topics to explore.

In a nutshell, if you understand R Shiny basics and know how to write R functions, you have everything needed to start learning about R Shiny modules. Yup, it’s that simple!

As we mentioned earlier, most beginners tend to use only one file – app.R when structuring Shiny apps, which, needless to say, results in a big chunk of messy code.

Shiny modules act like functions by wrapping up sets of UI and server elements. They will ensure a modular structure with reusable components and no code duplication.

If you understand the DRY (Don’t Repeat Yourself) concept of programming, you’ll understand why modularizing an application might be a good thing to do.

You can also think of R Shiny modules as many small Shiny applications that are composed later in a single application file.

Okay, so now you know the basics. Up next, you’ll see how to create your first module.

Introduction to Modules in R Shiny – Create Your First Module

In this section, you’ll learn how to make a module responsible for plotting a bar chart. It’s a simple one, sure, but will enable you to see how everything connects.

We recommend you create a new directory and two files inside it – app.R and mod-chart.R. The latter will contain all of the module logic, while the prior will use the module in a Shiny application.

Writing the Code for a Custom Chart Module

Let’s first discuss mod-chart.R. You will need to write two functions, one for the module UI and the other for the module server.

The UI function can use your standard Shiny functions, just like you would typically do with a single file app structure. In our case, we’ll use the fluidRow() and plotOutput() function to indicate the UI element will take up the entire row and will render a chart.

The other important thing about the UI is the NS() function. Always use it with custom modules, as it will ensure a unique variable name for your input/output elements (IDs).

On the other hand, the server function can take up as many arguments as you want. Make it as customizable as you need. We decided to make the plot have variables X, Y, and title values.

In the server function, you’ll need to use the moduleServer() function to specify the module ID and its function. This inner function basically does what your regular R Shiny server() function does – deals with reactive values and renders elements.

Anyhow, here’s the full code snippet for the mod-chart.R file:

library(ggplot2)
library(shiny)


chartUI <- function(id) {
  # Create unique variable name
  ns <- NS(id)
  fluidRow(
    plotOutput(outputId = ns("chart"))
  )
}


chartServer <- function(id, x, y, title) {
  moduleServer(
    id = id,
    module = function(input, output, session) {
      # Convert input data to data.frame
      df <- reactive({
        data.frame(
          x = x,
          y = y
        )
      })

      # Render a plot
      output$chart <- renderPlot({
        ggplot(df(), aes(x = x, y = y)) +
          geom_col() +
          labs(
            title = title
          )
      })
    }
  )
}

Sure, there are some module-specific functions you need to learn about, but everything else should look familiar. Let’s now use this file in app.R

Using the Custom Chart Module in R Shiny

Let’s now switch to app.R and import our newly created module by calling source("mod-chart.R") function.

From there, it’s your everyday R Shiny application. We can leverage the chartUI() and chartServer() functions to render the contents of our module, and the code logic looks exactly the same as if you were to use a set of built-in Shiny functions.

Just note the parameters required for chartServer() – These are the ones declared as arguments to the function in the module file.

Here’s the full code snippet:

library(shiny)
source("mod-chart.R")


ui <- fluidPage(
  chartUI(id = "chart1")
)


server <- function(input, output, session) {
  chartServer(
    id = "chart1",
    x = c("Q1", "Q2", "Q3", "Q4"),
    y = c(505.21, 397.18, 591.44, 674.90),
    title = "Sales in 000 for 2023"
  )
}


shinyApp(ui = ui, server = server)

You can now run the app to see the chart:

Image 1 - Modularized R Shiny chart

Image 1 – Modularized R Shiny chart

That was easy, wasn’t it? Let’s give the chart a bit of a visual overhaul next.

Additional Module Tweaking

This section is optional to follow, but will somewhat improve the aesthetics of our chart. The module logic inside mod-chart.R remains the same – we’re just adding a couple more ggplot2 functions to the chart to make it look nicer.

Take a look for yourself:

library(ggplot2)
library(shiny)


chartUI <- function(id) {
  # Create unique variable name
  ns <- NS(id)
  fluidRow(
    plotOutput(outputId = ns("chart"))
  )
}


chartServer <- function(id, x, y, title) {
  moduleServer(
    id = id,
    module = function(input, output, session) {
      # Convert input data to data.frame
      df <- reactive({
        data.frame(
          x = x,
          y = y
        )
      })

      # Render a plot
      output$chart <- renderPlot({
        ggplot(df(), aes(x = x, y = y)) +
          geom_col(fill = "#0099f9") +
          geom_text(aes(label = y), vjust = 2, size = 6, color = "#ffffff") +
          labs(
            title = title
          ) +
          theme_classic() +
          theme(
            plot.title = element_text(hjust = 0.5, size = 20, face = "bold"),
            axis.title.x = element_text(size = 15),
            axis.title.y = element_text(size = 15),
            axis.text.x = element_text(size = 12),
            axis.text.y = element_text(size = 12)
          )
      })
    }
  )
}

In a nutshell, we’ve changed the bar color, given text labels inside the bars, tweaked the overall theme, centered the title, and increased font sizes for axis labels and ticks.

You can restart the app now – here’s what you will see:

Image 2 - Styled ggplot2 chart

Image 2 – Styled ggplot2 chart

Let’s now discuss what really matters with R Shiny modules – reusability.

Want to learn more about bar charts in R? Read our complete guide to gpplot2.

Reusing the Module in R Shiny

Since the UI and server logic of our module are basically R functions, you can call them however many times you want in your R Shiny app.

The example below creates a second chart with our module and shows you how effortless this task is now. No need to repeat the logic – simply call the functions and you’re good to go:

library(shiny)
source("mod-chart.R")


ui <- fluidPage(
  chartUI(id = "chart1"),
  chartUI(id = "chart2")
)


server <- function(input, output, session) {
  chartServer(
    id = "chart1",
    x = c("Q1", "Q2", "Q3", "Q4"),
    y = c(505.21, 397.18, 591.44, 674.90),
    title = "Sales in 000 for 2023"
  )
  chartServer(
    id = "chart2",
    x = c("IT", "Sales", "Marketing", "HR"),
    y = c(15, 23, 19, 5),
    title = "Number of employees per department"
  )
}


shinyApp(ui = ui, server = server)

Here’s what you’ll see when you restart the app:

Image 3 – Two stacked modularized charts

You now know how to create R Shiny modules, so next we’ll kick things up a notch by combining multiple modules in a single Shiny application.

Combining Multiple R Shiny Modules – Let’s Build an App

If you’ve followed through with the previous section, this one will feel like a walk in the park.

Essentially, we’ll build an R Shiny application based on the Gapminder dataset. The app will allow the user to select a continent, and the contents of the app will automatically update to show:

  • Year, average life expectancy, and average GDP per capita as a data table.
  • Average life expectancy over time as a bar chart.
  • Average GDP per capita as a line chart.

To achieve this, we’ll write another new module and update the existing one to accommodate more chart types. Let’s dig in!

Writing the Custom Table Module

First things first, let’s discuss the new data table module. Create a new file named  mod-table.R, and declare two functions for taking care of UI and server – tableUI() and tableServer().

The UI function will use DT to create a new table, and the server function will render the table based on the passed data.frame, column names, and table captions.

Want to learn more about R packages for visualizing table data? These packages are everything you’ll ever need.

Here’s the entire code snippet for mod-table.R:

library(shiny)
library(DT)


tableUI <- function(id) {
  # Unique variable name
  ns <- NS(id)
  fluidRow(
    DTOutput(outputId = ns("table"))
  )
}


tableServer <- function(id, df, colnames, caption) {
  moduleServer(
    id = id,
    module = function(input, output, session) {
      # Render a table
      output$table <- renderDT({
        datatable(
          data = df(),
          colnames = colnames,
          caption = caption,
          filter = "top"
        )
      })
    }
  )
}

Up next, let’s tweak the chart module.

Writing the Custom Chart Module

Our mod-chart.R file needs a bit of tweaking. We’ll now allow the user to specify the chart type (only bar and line). In addition, the entire data.frame will now be passed alongside string names for the X and Y columns. This is only to keep congruent with the logic inside mod-table.R.

Both chart types will be themed the same, so it makes sense to extract the theming logic to reduce code duplication. The line chart will also be colored differently, just so we can end up with a more lively application.

This is the updated code for mod-chart.R file:

library(shiny)
library(ggplot2)


chartUI <- function(id) {
  # Unique variable name
  ns <- NS(id)
  fluidRow(
    plotOutput(outputId = ns("chart"))
  )
}


chartServer <- function(id, type, df, x_col_name, y_col_name, title) {
  moduleServer(
    id = id,
    module = function(input, output, session) {
      # Chart logic
      # Extract a common property
      chart_theme <- ggplot2::theme(
        plot.title = element_text(hjust = 0.5, size = 20, face = "bold"),
        axis.title.x = element_text(size = 15),
        axis.title.y = element_text(size = 15),
        axis.text.x = element_text(size = 12),
        axis.text.y = element_text(size = 12)
      )

      # Line chart
      if (type == "line") {
        output$chart <- renderPlot({
          ggplot(df(), aes_string(x = x_col_name, y = y_col_name)) +
            geom_line(color = "#f96000", size = 2) +
            geom_point(color = "#f96000", size = 5) +
            geom_label(
              aes_string(label = y_col_name),
              nudge_x = 0.25,
              nudge_y = 0.25
            ) +
            labs(title = title) +
            theme_classic() +
            chart_theme
        })

      # Bar chart
      } else {
        output$chart <- renderPlot({
          ggplot(df(), aes_string(x = x_col_name, y = y_col_name)) +
            geom_col(fill = "#0099f9") +
            geom_text(aes_string(label = y_col_name), vjust = 2, size = 6, color = "#ffffff") +
            labs(title = title) +
            theme_classic() +
            chart_theme
        })
      }
    }
  )
}

Let’s now use both of these in app.R.

Looking to learn more about line charts? This article has you covered.

Tying it All Together

First things first – don’t forget to load the table module code at the top of the R file.

Our application will use the sidebar layout to separate user controls from the app contents. The user will have access to a dropdown menu from which to continent can be changed. It will be set to Europe by default.

The main panel will call dedicated module functions – once for the table and twice for the chart.

As for the server() function, we’ll filter and summarize the Gapminder dataset and hold it as a reactive value. Then, we’ll call the module server functions to render dynamic data and pass in the required parameters:

library(shiny)
library(gapminder)
source("mod-chart.R")
source("mod-table.R")


ui <- fluidPage(
  sidebarLayout(
    sidebarPanel(
      tags$h3("Shiny Module Showcase"),
      tags$hr(),
      selectInput(inputId = "continent", label = "Continent:", choices = unique(gapminder$continent), selected = "Europe")
    ),
    mainPanel(
      tableUI(id = "table-data"),
      chartUI(id = "chart-bar"),
      chartUI(id = "chart-line")
    )
  )
)


server <- function(input, output, session) {
  # Filter the dataset first
  data <- reactive({
    gapminder %>%
      filter(continent == input$continent) %>%
      group_by(year) %>%
      summarise(
        avg_life_exp = round(mean(lifeExp), digits = 0),
        avg_gdp_percap = round(mean(gdpPercap), digits = 2)
      )
  })
  
  # Data table
  tableServer(
    id = "table-data",
    df = data,
    colnames = c("Year", "Average life expectancy", "Average GDP per capita"),
    caption = "Gapminder datasets stats by year and continent"
  )

  # Bar chart
  chartServer(
    id = "chart-bar",
    type = "bar",
    df = data,
    x_col_name = "year",
    y_col_name = "avg_life_exp",
    title = "Average life expectancy over time"
  )

  # Line chart
  chartServer(
    id = "chart-line",
    type = "line",
    df = data,
    x_col_name = "year",
    y_col_name = "avg_gdp_percap",
    title = "Average GDP per capita over time"
  )
}


shinyApp(ui = ui, server = server)

And that’s it – nice and tidy! Let’s launch the app to see if everything works as expected:

Image 4 - Finalized R Shiny modules app

Image 4 – Finalized R Shiny modules app

As you can see, combining multiple R Shiny modules is as easy as using only one of them. The code base now looks extra clean and doesn’t suffer from code duplication.


Summing up R Shiny Modules

And there you have it – your first modularized R Shiny application. You’ve seen how easy it is to extract programming logic from the main application file and how good of a job this does when creating multiple objects of the same type.

For the homework assignment, we strongly recommend you try adding a third type of chart and tweaking the visuals of the data table. This will further increase the customizability of the app and will give you time to practice.

What’s your approach when using R Shiny modules? Are they a de facto standard even for small apps? Let us know in the comment section below.

R and R Shiny can help you improve business workflows – Here are 5 examples how.

The post appeared first on appsilon.com/blog/.

To leave a comment for the author, please follow the link and comment on their blog: Tag: r - Appsilon | Enterprise R Shiny Dashboards.

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)