New View Manipulation API

Agreements

  • Write up this doc.
  • Cleanup the PR#29339 per this doc.
  • Focus on performance

Mental Model

  • LView and friends is private API and should never leak to the external world.
  • ViewRef and friends should not be implemented in terms of private API such as LView. (ViewRef is a public user rather than private user and hence should use public API)

Motivation

*Ref APIs from ViewEngine wrap underlying concepts such as Element, Component and View in a way so that they can be run from WebWorker. That original goal did not seem to pan out and now they provide more complexity than value. Our current thinking is that they should be deprecated long term.

The issues with current API is:

  • Original goal of Web-Workers did not plan out.
  • Prevents tree-shaking
  • Pulls in a lot of ViewEngine for simple things for Ivy.

We do need replacement APIs for these concepts. For DOM Element it should just be Element. For Component it should just be Component (see markDirty() as an example). The question is what to do with View, ContainerRef, and EmbededeViewFactory?

The solution is to treat the View, ContainerRef, and EmbeddedViewFactory as opaque tokens and provide methods for manipulation. In this way it can be:

  • Tree shakeable
  • Light weight (as one gets hold of these tokens from DOM Elements)

Proposed API

Interfaces

interface View<T extends{} = {}> { __ng_brand__: 'Angular opaque reference representing a View. DO NOT READ/MANIPULATE!'; } interface ViewContainer { __ng_brand__: 'Angular opaque reference representing a ViewContainer. DO NOT READ/MANIPULATE!'; } interface EmbeddedViewFactory<T extends{}> { (context: T): View<T>; }

Retrieval

The point of these methods is to get a hold of a ViewContainer or EmbeddedViewFactory. Just like markDirty() which takes a component these methods take DOM Node to keep the number of mental concepts in the system low, as developers already know about the DOM and no new concepts need to be introduced.

function getEmbeddedViewFactory<T extends{}>( containerComment: RComment ): EmbeddedViewFactory<T>|null; function getViewContainer( containerNative: RElement|RComment ): ViewContainer|null {

View Manipulation

New Views can be created using EmbeddedViewFactory

const node = ...; // Retrieve the comment node for `<ng-template>` const myViewFactory: EmbeddedViewFactory<MyContext> = getEmbeddedViewFactory(node); const myContext: MyContext = {....}; const myView: View = myViewFactory(myContext);

Views can be inserted and removed into the view-container using these APIs:

const node = ...; // Retrieve a node which will act as anchor const myViewContainer: ViewContainer = getViewContainer(node); ViewContainerInsertBefore(myViewContainer, myView);

The goal of the new APIs is to introduce as few new concepts as possible, and keep the code base tree-shakable, and easy to evolve.

Additional list of APIs:

function viewContainerInsertBefore( viewContainer: ViewContainer, view: View, insertBeforeView?: View | null ): void function viewContainerIndexOf( viewContainer: ViewContainer, view: View ): number; function viewContainerRemove( viewContainer: ViewContainer, view: View, shouldDestroy: boolean ): void; function viewContainerLength( viewContainer: ViewContainer ): number; function viewContainerGetAt( viewContainer: ViewContainer, index: number ): View|null;

Things To do

  • Move View interface and friends to api/view_interface.ts
  • Break view.ts into view.ts and api/view.ts where View is only imported in api/view.ts
  • Ensure: that the api/* is not yet made public (don't export it at top level).
  • Ensure: ViewRef and friends only import from api/*.
  • Ensure: Ivy implementation does not import from api/*
Select a repo