Good way to create a "reactive-aware" R6 class

One more version, with a few improvements:

  • The $reactive() method only creates a reactive expression object once; then it stores the reactive expression so if $reactive() is called again, it returns the same object. (Note that this is good for most use cases, but not all.)
  • The reactiveVal and reactive are created only if needed (if someone calls the $reactive() method).
  • Removed internal use of isolate(). This reduces overhead, and makes it easier to use the class without Shiny if desired.
Person <- R6::R6Class(
  "Person",
  private = list(
    name = "",
    reactiveDep = NULL,
    reactiveExpr = NULL,
    invalidate = function() {
      private$count <- private$count + 1
      private$reactiveDep(private$count)
      invisible()
    },
    count = 0
  ),
  public = list(
    initialize = function(name) {
      # Until someone calls $reactive(), private$reactiveDep() is a no-op. Need
      # to set it here because if it's set in the definition of private above, it will
      # be locked and can't be changed.
      private$reactiveDep <- function(x) NULL
      private$name <- name
    },
    reactive = function() {
      # Ensure the reactive stuff is initialized.
      if (is.null(private$reactiveExpr)) {
        private$reactiveDep <- reactiveVal(0)
        private$reactiveExpr <- reactive({
          private$reactiveDep()
          self
        })
      }
      private$reactiveExpr
    },
    print = function() {
      cat("Person:", private$name)
    },
    changeName = function(newName) {
      private$name <- newName
      private$invalidate()
    },
    getName = function() {
      private$name
    }
  )
)

pr <- Person$new("Dean")$reactive()

# The observer accesses the reactive expression
o <- observe({
  message("Person changed. Name: ", pr()$getName())  
})
shiny:::flushReact()
#> Person changed. Name: Dean

# Note that this is in isolate only because we're running at the console; in 
# typical Shiny code, the isolate() wouldn't be necessary.
isolate(pr()$changeName("Newname"))
shiny:::flushReact()
#> Person changed. Name: Newname
1 Like