Angular 2 Rendering Architecture
Warning: This document is publicly readable by anyone
Yegor Jbanov (yjbanov@google.com)
Tobias Bosch (tbosch@google.com)
A note on events and reading the DOM
Separate Angular 2 runtime into two layers application layer and render layer. The application layer contains APIs and runtime that application code interacts with directly. The rendering layer provides a common protocol for performing UI updates. The split will allow Angular 2 applications to run in different environments/platforms while providing the same set of abstractions to application developers (or as Reactjs team likes to say: “learn once, write anywhere”). We want to support the following environments:
The second benefit we are trying to realize with this split is a clearer separation of application logic from the graphical aspects of the application. This would allow developers to optimize the experience based on device capabilities. For example, on low performance devices or devices in battery saving mode an application could run with animations disabled while preserving full functionality.
In this document we define a specification of the rendering layer’s API and protocols.
It is outside the scope of this document to provide exact implementations of the various rendering layers. Specific implementations will be described in separate design docs.
In the past several years platforms diversified significantly from isolated single-process browser pages. Mobile took off. For application developers this presents opportunities in performance and exposure to new markets. We want to make sure that Angular 2 takes advantage of these shifts in the platforms.
New areas available to us:
The following illustrates the layout of the rendering architecture:
One key aspect of the design is that the elements of the application do not directly depend or access the elements of the render code, and vice versa. They can only communicate via a renderer transport. This leads to the following properties:
From the developer’s perspective the architecture splits application elements into two sections we refer to as application layer and render layer. A given feature exists in one of the two, but not both. Here are the examples of what “lives” where:
Where possible, the abstraction layer should not add more abstractions/indirections to the implementation for the browser main process so that we don't get slowed down.
The abstraction layer should be as minimal as possible to make it easy to add more implementations. Concrete implementations could provide more functionality (e.g. reading out an element's position), but an app using these extras would not run in other environments.
Angular is split into the following parts:
The initial refactoring of the architecture won't include a proxy for the web-worker scenario.
View hierarchy management (simplified):
View update:
A note on recursion:
When a component is created, contained child components also need to be created. Only the ViewManager on the application side implements this recursion and calls the renderer with basic operations. Because of this, the methods on the renderer stay simple and are easy to implement.
Renderer api
createView(protoView:RenderProtoViewRef, eventDispatcher:EventDispatcher):RenderViewRef // the app layer needs to know // when animations are done... destroyView(view:RenderViewRef):Promise // called on renderer of child component view! attachComponentView(hostElementRef:RenderElementRef, componentView:RenderViewRef) // called on renderer of child component view! detachComponentView(hostElementRef:RenderElementRef, componentView:RenderView) attachViewInContainer(hostElementRef:RenderElementRef, vcElementRef:RenderElementRef, atIndex:number, viewRef:RenderViewRef) detachViewInContainer(parentView:RenderViewRef, boundElementIndex:number, atIndex:number, view:RenderViewRef) // called on renderer of view that contains the renderLocation // -> renderer knows how to interpret the renderLocation! becomeHostView(parentHostElementRef:RenderElementRef, renderLocation: any, protoView:RenderProtoViewRef): RenderViewRef unbecomeHostView(parentHostElementRef:RenderElementRef, hostView:RenderViewRef) hydrateView(hostElementRef:RenderElementRef, childView:RenderViewRef) dehydrateView(view:RenderView) setElementProperty(elementRef:RenderElementRef, propertyName:string, propertyValue:any) setText(view:RenderViewRef, textNodeIndex:number, text:string) |
RenderView
class AbstractRenderView { hostElements:Array<any>; lightDoms:Array<any>; } |
Custom render services are services that live on the render layer and are accessible via an interface on the app layer. They are used to abstract things like the document title, cookies, animations, … away from the application layer. Angular will provide a general RPC mechanism that allows to create client and services given an interface with custom serialization logic.
The Renderer interface itself is a special case of a render service.
Every component can define its own renderer. By default, every component is using the DomRenderer. This renderer is made up of the DomCompiler, which compiles templates written in html. To instantiate views, it uses the DomViewManager, which clones the DOM elements of the compiler and inserts them the main browser document.
Some more use cases for other renderer implementations:
The NativeScript renderer will probably use a compiler that is based on XML but shares most of the functionality with the DomRenderer. For creating views, the NativeScript renderer will use the default mechanism of NativeScript to create native elements out of XML. I.e. the RenderViews won't contain DOM nor XML but custom JS classes.
See https://github.com/NativeScript/nativescript-angular
The DOM is only read when events are fired. As a DOM event is not serializable we need to know which properties that application logic is interested in. This is done via transforming event expressions:
E.g. to read out the value of an input element when the user changes it:
<input (change)="updateState($target.value)"> |
The compiler in the render layer already parses all expressions. Because of this, it knows which properties should be read (in the example: property value). So when the event happens the render view will read out the needed properties and send them over to the application side as well.
How about preventDefault?
Via the render interface there is a single place where all changes to the UI go through. Animations will hook into this place and maybe delay some of them.