rshinyr-leafletshinymodules

Extract an input created in server function with leaflet


My goal is to create a clickable map module with leaflet that would store the latitude and longitude in an input that I could re-use in other modules. Currently the module works by creating in the ui a leafletOutput (id="mymap") and using in the server function a observeEvent function that reacts to click on the map. The click event generates an input vector of the longitude and latitude (input$input$mymap_click$lat[1] & input$mymap_click$lng[1])which is used to place a marker on the map. But I struggle to extract those to values to use it externally by other modules or render* functions. (it works without the "module method" but the code is a bit messy)

For the sake of clarity in my example I try to use the latitude and longitude in a textOutput with renderText instead of in a module.

# clickable leaflet module ----------------------------------------------------------

## loads leaflet library

library(leaflet)

##ui function

clicMapOutput <- function(id) {
  ns <- NS(id)
  
  tagList(leafletOutput(ns("mymap")),
          textOutput(ns("text")))
}

## serverfunction

clicMapServer <- function(id) {
  moduleServer(id,
               function(input, output, session) {
                 # outputs a map
                 output$mymap <-
                   leaflet::renderLeaflet({
                     leaflet() %>% addTiles() %>% setView(lat = 0,
                                                          lng = 0,
                                                          zoom = 2)
                   })
                 
                 # makes map clickable to obtain a marker and a longitude + latitude vector
                 observeEvent(input$mymap_click, {
                   output$mymap <-
                     leaflet::renderLeaflet({
                       leaflet() %>% addTiles() %>% addMarkers(lat = input$mymap_click$lat[1],
                                                               lng = input$mymap_click$lng[1])
                     })
                 })
                 
               })
}



# Calling modules ---------------------------------------------------------

library(shiny)

ui<-fluidPage(
   clicMapOutput("map"),
textOutput("lng")
)

server<-function(input,output,session){
  
  clicMapServer("map")
  
  output$lng<-renderText({
    input$mymap_click$lng[1]
  })
  

}

shinyApp(ui=ui,server=server)


Solution

  • the ususal way of doing this is to define a return value in the server part of the module and then using it in the module consumer

    ## module server
    clicMapServer <- function(id) {
      moduleServer(id, function(input, output, session) {
        ## ...
        return(reactive(input$mymap_click$lng[1])))
      }
    }
    
    ## consumer server
    server <- function(input, output, session) {
      lng <- clicMapServer("map")
      output$lng <- renderText({ lng() })
    }
    

    You should always make sure to wrap the return values with reactives and use the return value like a function. If you want to return more than one variable, see my answer to this question for details.

    library(leaflet)
    library(shiny)
    
    ## module ui
    clicMapOutput <- function(id) {
      ns <- NS(id)
      leafletOutput(ns("mymap"))
    }
    
    ## module server
    clicMapServer <- function(id) {
      moduleServer(id, function(input, output, session) {
        output$mymap <- renderLeaflet({
            leaflet() %>% addTiles() %>% setView(
               lat = 0, lng = 0, zoom = 2)
        })
        
        # handle click events
        observeEvent(input$mymap_click, {
          output$mymap <- renderLeaflet({
            leaflet() %>% addTiles() %>% addMarkers(
              lat = input$mymap_click$lat[1],
              lng = input$mymap_click$lng[1])
          })
        })
    
        return(reactive(input$mymap_click$lng[1]))
      })
    }
    
    # main ui
    ui <- fluidPage(
      clicMapOutput("map"),
      textOutput("lng")
    )
    
    # main server
    server <- function(input, output, session) {
      lng <- clicMapServer("map")
      output$lng <- renderText({ lng() })
    }
    
    shinyApp(ui = ui, server = server)
    

    Another thing I noticed is that you are updating the leaflet widget by overwriting output$mymap. It would be better to use leaflet::leafletProxy() instead. Generally speaking, outputs should not be assigned inside observe() or observeEvent()