liveBook · Manning

Deep observability

As explained in Building User-Friendly DSLs

An observability property in which changes within an observable object’s nested contents are also observable.

Deep observability makes an object and the objects and arrays nested inside it observable as well. Changes anywhere in an observable AST can therefore notify subscribed observers. section 5.1.6 section 7.4

Apply deep observability to a reactive projection by:

  • Wrapping the rental AST with MobX observable before passing it to the projection. section 5.1.6
  • Marking the projection component as an observer so nested edits trigger updates. section 5.1.6
  • Using observable edit state and actions when updating nested values such as record and attribute names. section 5.1.6
import { observable } from "mobx"    #1

// ...other existing code...

createRoot(document.getElementById("root"))
    .render(
        <Projection
            astObject={observable(rental)}   #2
        />
    )

Wraps the rental AST in observable at the React render boundary. section 5.1.6

import { observer } from "mobx-react"

// ...other existing code...

const Projection = observer(({ astObject, parent }) => {
    // ...all other existing code...
})

Connects the projection component to observable changes through MobX observer. section 5.1.6

Deep conversion does not understand semantic reference relations. Applying observable directly to a circular AST can recurse until the call stack is exhausted. Construct the observable nodes first, then assign reference targets, or deserialize while explicitly resolving references. section 7.4

Listing 7.24 Making a circular AST observable—the right way
const { observable } = require("mobx") 

const observableAstObject = observable({      #1
    id: "IdOfFooObject", 
    concept: "Foo", 
    settings: { 
        "self-ref.": observable({})       #2
    } 
}) 

observableAstObject.settings["self-ref."].ref = observableAstObject   #3

Builds a circular observable AST safely by creating observable nodes before assigning the self-reference. section 7.4

Listing 7.25 Deserializing to an observable AST
const { observable } = require("mobx")        #1

const deserializeObservably = (serializedAst) => { 
    const id2AstObject = {} 
    const referencesToResolve = [] 

    const deserializeInternal = (value) => {       #2
        if (isAstObject(value)) { 
            const astObject = observable({       #3
                id: value.id, 
                concept: value.concept, 
                settings: {} 
            }) 
            for (const propertyName in value.settings) { 
                astObject.settings[propertyName] = 
                     deserializeInternal( 
                         value.settings[propertyName]) 
            } 
            id2AstObject[value.id] = astObject 
            return astObject 
        } 
        if (isSerializedAstReference(value)) { 
            const refObjectToFix = observable({})       #4
            referencesToResolve.push([ value.refId, refObjectToFix ]) 
            return refObjectToFix            #5
        } 
        if (Array.isArray(value)) { 
            return observable(                #6
                 value.map(deserializeInternal)) 
        } 
        return value 
    } 

    const deserializedAst = deserializeInternal(serializedAst) 

    referencesToResolve.forEach( 
             ([ refId, refObjectToFix ]) => { 
        refObjectToFix.ref = id2AstObject[refId]     #7
    }) 

    return deserializedAst 
} 

module.exports.deserializeObservably =     #8
     deserializeObservably

Deserializes AST objects into observable nodes while retaining deferred reference resolution. section 7.4

How Building User-Friendly DSLs covers it

How deep it goes
Explains automatic nested conversion and observer wiring, then stops short of solving circular references without explicit construction or deserialization.
Examples
Uses editable rental names, nested AST objects and arrays, and circular self-references as implementation examples.
What it assumes
Assumes JavaScript object structures, React components, and AST references while teaching nested MobX conversion from concrete examples.
What you can do after
Build it from scratch by wiring observable AST state, observer components, and explicit reference resolution.
How it connects
Connects observability with MobX, observer components, ASTs, deserialization, and reference relations.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Building User-Friendly DSLs. The section links point to the parts of the book it draws on.