(please forgive my inconsistent rendering of AngularJS/Angular/Angular.js/angular, I don’t know which one is right!)
In batman.js and Angular, there is a view scope hierarchy which mirrors the DOM. In this heirarchy, objects may:
- add values into the render scope
- access their own values
- belong to a parent object
- access values from their parents
- have child objects of their own
Consider a page like this:
HouseholdView has many child views. The
PersonViews belong to their parent,
HouseholdView. They may access values from
HouseholdView (such as the shared
Note: The batman.js view hierarchy includes a few other objects as well – see below.
Finding Values in the Hierarchy
To answer the question “how can child views access data from their parents”, Batman.js and Angular take different approaches.
$scope & Prototypal Inheritance
In Angular, data bindings are evaluated against a magical
$scope object. The scope object has key-value pairs which correspond to values in the data bindings. When
$scopes are created, Angular massages (tampers with?) the prototypal inheritance chain so that a child scope’s prototype is its parent scope.
(Usually, an object’s prototype is another “pristine” object of its same type. It’s generally treated as the “perfect instance” of the type. Other instances delegate to the prototype for properties that aren’t defined explicitly on themselves.)
In this case, a child
$scope’s prototype is not a “pristine instance”, but instead it’s the parent
When a parent
$scope has many children, all children have the same parent
$scope object as their prototype.
Batman.View & View::lookupKeypath
Batman.js builds a tree of
Batman.View objects. The root of the tree is called the
LayoutView and it is created automatically by batman.js. Each view keeps track of its children in its
subviews, which is a
Batman.Set containing views that are rendered inside it. Each view also keeps track of its
superview, which is its parent
To evaluate data bindings, batman.js uses
lookupKeypath on the view in question. This function climbs the “view hierarchy”, which actually includes a few extra objects:
Batman.Controllerinstance which rendered the view
Batman.currentApp, which is the
Batman.Appsubclass that you defined (the clas, not an instance)
Batman.container, which is usually
Here’s the whole view hierarchy from the previous example:
Batman.currentApp is in the view hierarchy, any
In batman.js and Angular, there are data bindings that create child scopes of their own. For example,
data-foreach both create a collection of child scopes with the same parent.
Angular does this by creating many child
$scopes with the same parent
$scope as their prototype.
Batman.js does this by automatically adding nodes to the view hierarchy. One downside of batman.js is that creating lots and lots of new views is CPU-intensive. I don’t know whether the same is true for creating
How Does It Know Which Scope to Bind To?
When I was reading about
$scope with the new value, not the parent
$scope, which is the child’s prototype.
That’s how prototypal inheritance works: It looks up missing values on the prototype, but it sets new values on the instance. Then, it stops “falling back” to the prototype for the property that was set on the instance.
To work around this, it’s recommended to “always use a
. in your
ng-model is a binding that creates a child scope.)
Batman.js doesn’t have this problem because, when uses
lookupKeypath, it remembers which
View object was the target for that keypath, then updates that object whenever the keypath changes.
However, Batman.js is prone to a different gotcha. If you leave an accessor unset (ie, returns
undefined), then set it after a view has rendered, it’s possible that
View::lookupKeypath won’t find it correctly. To avoid this, set defaults (or
null) before bindings are evaluated:
@renderin controller actions
- in the
constructorfor view instances
Or, make sure
null instead of
undefined as the signal that an object doesn’t have an accessor for a keypath, so be careful when setting keys
How Does It Know When to Update the DOM?
When these scope objects (
$scope) change, the framework must update the DOM accordingly.
Angular has a “digest cycle” where it checks for changes in the
$scope since last run, then updates the DOM if necessary. It automatically tracks any values that are put into templates. You can also watch other keys on
$scope.$watch. If you modify
$scope from outside Angular.js code, you must manually trigger the digest cycle with
Batman.View uses the
Batman.Property system to automatically track dependencies and changes. Any keypath that is passed to a
data- binding is automatically observed. DOM updates are triggered when:
- A keypath is updated with
set. Doesn’t matter whether it’s inside batman.js code or inside an AJAX callback – batman.js will recognize the update either way.
- A property’s dependencies change. When you declare a computed property with
@accessorand bind it to a view, the view will update the DOM whenever that property’s dependencies cause it to change.
(In fact, those two cases are the heart of observability in batman.js: assign a value with
set or delegate to batman.js’s source tracking.)
To force an update, use
set to update a bound property or one of its dependencies.
Other Random Points
- Angular’s “evalute an expression” is like batman.js’s “lookup a keypath”
$scope.$broadcast: it sends messages down the view tree.
- As of batman.js almost-v0.17, there is no analog for
$scope.$emit(which sends events up the view chain)
- batman.js exports global function
$context($0)which is just like
$0is the highlighted element in the Chrome inspector).
I think Angular’s
$scope is brilliant. I imagine it’s performant as well, although I don’t know (and I’m currently writing without internet access). It introduces few gotchas. In general, it seems like it Just WorksTM.
I’m in the market to improve the performance of
Batman.View, but I’m not sure I can take anything from
$scope. All of batman.js depends on playing by the observability rules. I don’t see any way I can get native JS prototypal inheritance to participate in that.
I also like sticking with the “It’s just batman.js” in the view layer. If you can write good
@accessors, then you’ve mastered