Skip to content

Commit

Permalink
refactor(render): create and store render ProtoViewRef in every app P…
Browse files Browse the repository at this point in the history
…rotoView

Needed to change Renderer.mergeChildComponentProtoViews to not create
new ProtoViews to be able to deal with cyclic references.

This commit is part of using the new render layer in Angular.
  • Loading branch information
tbosch committed Apr 8, 2015
1 parent d6003ee commit ca95846
Show file tree
Hide file tree
Showing 16 changed files with 504 additions and 431 deletions.
12 changes: 5 additions & 7 deletions modules/angular2/src/core/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {Type, isBlank, isPresent, BaseException, assertionsEnabled, print, strin
import {BrowserDomAdapter} from 'angular2/src/dom/browser_adapter';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {Compiler, CompilerCache} from './compiler/compiler';
import {ProtoView} from './compiler/view';
import {Reflector, reflector} from 'angular2/src/reflection/reflection';
import {Parser, Lexer, ChangeDetection, dynamicChangeDetection, jitChangeDetection} from 'angular2/change_detection';
import {ExceptionHandler} from './exception_handler';
Expand Down Expand Up @@ -72,12 +71,11 @@ function _injectorBindings(appComponentType): List<Binding> {
throw new BaseException(`Only Components can be bootstrapped; ` +
`Directive of ${stringify(type)} is not a Component`);
}
return compiler.compile(appComponentAnnotatedType.type).then(
(protoView) => {
var appProtoView = ProtoView.createRootProtoView(protoView, appElement,
DirectiveBinding.createFromType(appComponentAnnotatedType.type, appComponentAnnotatedType.annotation),
changeDetection.createProtoChangeDetector('root'),
strategy);
return compiler.compileRoot(
appElement,
DirectiveBinding.createFromType(appComponentAnnotatedType.type, appComponentAnnotatedType.annotation)
).then(
(appProtoView) => {
// The light Dom of the app element is not considered part of
// the angular application. Thus the context and lightDomInjector are
// empty.
Expand Down
114 changes: 71 additions & 43 deletions modules/angular2/src/core/compiler/compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,17 @@ export class NewCompiler {
return DirectiveBinding.createFromType(meta.type, meta.annotation);
}

// Create a rootView as if the compiler encountered <rootcmp></rootcmp>.
// Used for bootstrapping.
compileRoot(elementOrSelector, componentBinding:DirectiveBinding):Promise<ProtoView> {
return this._renderer.createRootProtoView(elementOrSelector, 'root').then( (rootRenderPv) => {
return this._compileNestedProtoViews(null, rootRenderPv, [componentBinding], true)
}).then( (rootProtoView) => {
rootProtoView.instantiateInPlace = true;
return rootProtoView;
});
}

compile(component: Type):Promise<ProtoView> {
var protoView = this._compile(this._bindDirective(component));
return PromiseWrapper.isPromise(protoView) ? protoView : PromiseWrapper.resolve(protoView);
Expand All @@ -96,7 +107,8 @@ export class NewCompiler {
var protoView = this._compilerCache.get(component);
if (isPresent(protoView)) {
// The component has already been compiled into a ProtoView,
// returns a resolved Promise.
// returns a plain ProtoView, not wrapped inside of a Promise.
// Needed for recursive components.
return protoView;
}

Expand All @@ -113,31 +125,72 @@ export class NewCompiler {
this._flattenDirectives(template),
(directive) => this._bindDirective(directive)
);
var renderTemplate = this._buildRenderTemplate(component, template, directives);
pvPromise = this._renderer.compile(renderTemplate).then( (renderPv) => {
return this._compileNestedProtoViews(componentBinding, renderPv, directives, true);
});

MapWrapper.set(this._compiling, component, pvPromise);
return pvPromise;
}

pvPromise = this._compileNoRecurse(componentBinding, template, directives).then( (protoView) => {
// TODO(tbosch): union type return ProtoView or Promise<ProtoView>
_compileNestedProtoViews(componentBinding, renderPv, directives, isComponentRootView) {
var nestedPVPromises = [];
var protoView = this._protoViewFactory.createProtoView(componentBinding, renderPv, directives);
if (isComponentRootView && isPresent(componentBinding)) {
// Populate the cache before compiling the nested components,
// so that components can reference themselves in their template.
var component = componentBinding.key.token;
this._compilerCache.set(component, protoView);
MapWrapper.delete(this._compiling, component);
}

// Compile all the components from the template
var nestedPVPromises = this._compileNestedComponents(protoView);
if (nestedPVPromises.length > 0) {
// Returns ProtoView Promise when there are any asynchronous nested ProtoViews.
// The promise will resolved after nested ProtoViews are compiled.
return PromiseWrapper.then(PromiseWrapper.all(nestedPVPromises),
(_) => protoView,
(e) => { throw new BaseException(`${e} -> Failed to compile ${stringify(component)}`); }
);
var binderIndex = 0;
ListWrapper.forEach(protoView.elementBinders, (elementBinder) => {
var nestedComponent = elementBinder.componentDirective;
var nestedRenderProtoView = renderPv.elementBinders[binderIndex].nestedProtoView;
var elementBinderDone = (nestedPv) => {
elementBinder.nestedProtoView = nestedPv;
// Can't set the parentProtoView for components,
// as their ProtoView might be used in multiple other components.
nestedPv.parentProtoView = isPresent(nestedComponent) ? null : protoView;
};
var nestedCall = null;
if (isPresent(nestedComponent)) {
if (!(nestedComponent.annotation instanceof DynamicComponent)) {
nestedCall = this._compile(nestedComponent);
}
} else if (isPresent(nestedRenderProtoView)) {
nestedCall = this._compileNestedProtoViews(componentBinding, nestedRenderProtoView, directives, false);
}
return protoView;
if (PromiseWrapper.isPromise(nestedCall)) {
ListWrapper.push(nestedPVPromises, nestedCall.then(elementBinderDone));
} else if (isPresent(nestedCall)) {
elementBinderDone(nestedCall);
}
binderIndex++;
});
MapWrapper.set(this._compiling, component, pvPromise);
return pvPromise;

var protoViewDone = (_) => {
var childComponentRenderPvRefs = [];
ListWrapper.forEach(protoView.elementBinders, (eb) => {
if (isPresent(eb.componentDirective)) {
var componentPv = eb.nestedProtoView;
ListWrapper.push(childComponentRenderPvRefs, isPresent(componentPv) ? componentPv.render : null);
}
});
this._renderer.mergeChildComponentProtoViews(protoView.render, childComponentRenderPvRefs);
return protoView;
};
if (nestedPVPromises.length > 0) {
return PromiseWrapper.all(nestedPVPromises).then(protoViewDone);
} else {
return protoViewDone(null);
}
}

_compileNoRecurse(componentBinding, template, directives):Promise<ProtoView> {
var component = componentBinding.key.token;
_buildRenderTemplate(component, template, directives) {
var componentUrl = this._urlResolver.resolve(
this._appUrl, this._componentUrlMapper.getUrl(component)
);
Expand All @@ -150,37 +203,12 @@ export class NewCompiler {
// is able to resolve urls in stylesheets.
templateAbsUrl = componentUrl;
}
var renderTemplate = new renderApi.Template({
return new renderApi.Template({
componentId: stringify(component),
absUrl: templateAbsUrl,
inline: template.inline,
directives: ListWrapper.map(directives, this._buildRenderDirective)
});
return this._renderer.compile(renderTemplate).then( (renderPv) => {
return this._protoViewFactory.createProtoView(componentBinding.annotation, renderPv, directives);
});
}

_compileNestedComponents(protoView, nestedPVPromises = null):List<Promise> {
if (isBlank(nestedPVPromises)) {
nestedPVPromises = [];
}
ListWrapper.map(protoView.elementBinders, (elementBinder) => {
var nestedComponent = elementBinder.componentDirective;
if (isPresent(nestedComponent) && !(nestedComponent.annotation instanceof DynamicComponent)) {
var nestedCall = this._compile(nestedComponent);
if (PromiseWrapper.isPromise(nestedCall)) {
ListWrapper.push(nestedPVPromises, nestedCall.then( (nestedPv) => {
elementBinder.nestedProtoView = nestedPv;
}));
} else {
elementBinder.nestedProtoView = nestedCall;
}
} else if (isPresent(elementBinder.nestedProtoView)) {
this._compileNestedComponents(elementBinder.nestedProtoView, nestedPVPromises);
}
});
return nestedPVPromises;
}

_buildRenderDirective(directiveBinding) {
Expand Down Expand Up @@ -269,7 +297,7 @@ export class Compiler extends NewCompiler {
new DefaultStepFactory(parser, shadowDomStrategy.render),
templateLoader
),
null, null
null, shadowDomStrategy.render
),
new ProtoViewFactory(changeDetection, shadowDomStrategy)
);
Expand Down
26 changes: 13 additions & 13 deletions modules/angular2/src/core/compiler/proto_view_factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,19 @@ export class ProtoViewFactory {
this._shadowDomStrategy = shadowDomStrategy;
}

createProtoView(componentAnnotation:Component, renderProtoView: renderApi.ProtoView, directives:List<DirectiveBinding>):ProtoView {
return this._createProtoView(null, componentAnnotation, renderProtoView, directives);
}

_createProtoView(parent:ProtoView, componentAnnotation:Component, renderProtoView: renderApi.ProtoView, directives:List<DirectiveBinding>):ProtoView {
var protoChangeDetector = this._changeDetection.createProtoChangeDetector('dummy',
componentAnnotation.changeDetection);
createProtoView(componentBinding:DirectiveBinding, renderProtoView: renderApi.ProtoView, directives:List<DirectiveBinding>):ProtoView {
var protoChangeDetector;
if (isBlank(componentBinding)) {
protoChangeDetector = this._changeDetection.createProtoChangeDetector('root', null);
} else {
var componentAnnotation:Component = componentBinding.annotation;
protoChangeDetector = this._changeDetection.createProtoChangeDetector(
'dummy', componentAnnotation.changeDetection
);
}
var domProtoView = this._getDomProtoView(renderProtoView.render);
var protoView = new ProtoView(domProtoView.element, protoChangeDetector,
this._shadowDomStrategy, parent);
var protoView = new ProtoView(renderProtoView.render, domProtoView.element, protoChangeDetector,
this._shadowDomStrategy, null);

for (var i=0; i<renderProtoView.elementBinders.length; i++) {
var renderElementBinder = renderProtoView.elementBinders[i];
Expand All @@ -42,13 +45,10 @@ export class ProtoViewFactory {
i, parentPeiWithDistance,
sortedDirectives, renderElementBinder
);
var elementBinder = this._createElementBinder(
this._createElementBinder(
protoView, renderElementBinder, domElementBinder, protoElementInjector, sortedDirectives
);
this._createDirectiveBinders(protoView, sortedDirectives);
if (isPresent(renderElementBinder.nestedProtoView)) {
elementBinder.nestedProtoView = this._createProtoView(protoView, componentAnnotation, renderElementBinder.nestedProtoView, directives);
}
}
MapWrapper.forEach(renderProtoView.variableBindings, (mappedName, varName) => {
protoView.bindVariable(varName, mappedName);
Expand Down
26 changes: 4 additions & 22 deletions modules/angular2/src/core/compiler/view.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {Content} from './shadow_dom_emulation/content_tag';
import {ShadowDomStrategy} from './shadow_dom_strategy';
import {ViewPool} from './view_pool';
import {EventManager} from 'angular2/src/render/dom/events/event_manager';
import * as renderApi from 'angular2/src/render/api';

const NG_BINDING_CLASS = 'ng-binding';
const NG_BINDING_CLASS_SELECTOR = '.ng-binding';
Expand Down Expand Up @@ -283,11 +284,14 @@ export class ProtoView {

_directiveMementosMap:Map;
_directiveMementos:List;
render:renderApi.ProtoViewRef;

constructor(
render:renderApi.ProtoViewRef,
template,
protoChangeDetector:ProtoChangeDetector,
shadowDomStrategy:ShadowDomStrategy, parentProtoView:ProtoView = null) {
this.render = render;
this.element = template;
this.elementBinders = [];
this.variableBindings = MapWrapper.create();
Expand Down Expand Up @@ -642,28 +646,6 @@ export class ProtoView {

return MapWrapper.get(this._directiveMementosMap, id);
}

// Create a rootView as if the compiler encountered <rootcmp></rootcmp>,
// and the component template is already compiled into protoView.
// Used for bootstrapping.
static createRootProtoView(protoView: ProtoView,
insertionElement,
rootComponentBinding: DirectiveBinding,
protoChangeDetector:ProtoChangeDetector,
shadowDomStrategy: ShadowDomStrategy
): ProtoView {

DOM.addClass(insertionElement, NG_BINDING_CLASS);
var cmpType = rootComponentBinding.key.token;
var rootProtoView = new ProtoView(insertionElement, protoChangeDetector, shadowDomStrategy);
rootProtoView.instantiateInPlace = true;
var binder = rootProtoView.bindElement(null, 0,
new ProtoElementInjector(null, 0, [cmpType], true));
binder.componentDirective = rootComponentBinding;
binder.nestedProtoView = protoView;
shadowDomStrategy.shimAppElement(cmpType, insertionElement);
return rootProtoView;
}
}

/**
Expand Down
10 changes: 5 additions & 5 deletions modules/angular2/src/render/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,20 +141,20 @@ export class Renderer {
compile(template:Template):Promise<ProtoView> { return null; }

/**
* Creates a new ProtoView with preset nested components,
* Sets the preset nested components,
* which will be instantiated when this protoView is instantiated.
* Note: We can't create new ProtoViewRefs here as we need to support cycles / recursive components.
* @param {List<ProtoViewRef>} protoViewRefs
* ProtoView for every element with a component in this protoView or in a view container's protoView
* @return {List<ProtoViewRef>}
* new ProtoViewRef for the given protoView and all of its view container's protoViews
*/
mergeChildComponentProtoViews(protoViewRef:ProtoViewRef, protoViewRefs:List<ProtoViewRef>):List<ProtoViewRef> { return null; }
mergeChildComponentProtoViews(protoViewRef:ProtoViewRef, componentProtoViewRefs:List<ProtoViewRef>) { return null; }

/**
* Creats a ProtoView that will create a root view for the given element,
* i.e. it will not clone the element but only attach other proto views to it.
* Contains a single nested component with the given componentId.
*/
createRootProtoView(selectorOrElement):ProtoViewRef { return null; }
createRootProtoView(selectorOrElement, componentId):Promise<ProtoView> { return null; }

/**
* Creates a view and all of its nested child components.
Expand Down
24 changes: 10 additions & 14 deletions modules/angular2/src/render/dom/direct_dom_renderer.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Promise} from 'angular2/src/facade/async';
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
import {List, ListWrapper} from 'angular2/src/facade/collection';
import {isBlank, isPresent} from 'angular2/src/facade/lang';

Expand Down Expand Up @@ -26,10 +26,6 @@ function _wrapView(view:View) {
return new _DirectDomViewRef(view);
}

function _wrapProtoView(protoView:ProtoView) {
return new DirectDomProtoViewRef(protoView);
}

function _collectComponentChildViewRefs(view, target = null) {
if (isBlank(target)) {
target = [];
Expand Down Expand Up @@ -83,22 +79,22 @@ export class DirectDomRenderer extends api.Renderer {
return this._compiler.compile(template);
}

mergeChildComponentProtoViews(protoViewRef:api.ProtoViewRef, protoViewRefs:List<api.ProtoViewRef>):List<api.ProtoViewRef> {
var protoViews = [];
mergeChildComponentProtoViews(protoViewRef:api.ProtoViewRef, protoViewRefs:List<api.ProtoViewRef>) {
_resolveProtoView(protoViewRef).mergeChildComponentProtoViews(
ListWrapper.map(protoViewRefs, _resolveProtoView),
protoViews
ListWrapper.map(protoViewRefs, _resolveProtoView)
);
return ListWrapper.map(protoViews, _wrapProtoView);
}

createRootProtoView(selectorOrElement):api.ProtoViewRef {
createRootProtoView(selectorOrElement, componentId):Promise<api.ProtoView> {
var element = selectorOrElement; // TODO: select the element if it is not a real element...
var rootProtoViewBuilder = new ProtoViewBuilder(element);
rootProtoViewBuilder.setIsRootView(true);
rootProtoViewBuilder.bindElement(element, 'root element').setComponentId('root');
this._shadowDomStrategy.processElement(null, 'root', element);
return rootProtoViewBuilder.build().render;
var elBinder = rootProtoViewBuilder.bindElement(element, 'root element');
elBinder.setComponentId(componentId);
elBinder.bindDirective(0);

this._shadowDomStrategy.processElement(null, componentId, element);
return PromiseWrapper.resolve(rootProtoViewBuilder.build());
}

createView(protoViewRef:api.ProtoViewRef):List<api.ViewRef> {
Expand Down
24 changes: 0 additions & 24 deletions modules/angular2/src/render/dom/view/element_binder.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import {AST} from 'angular2/change_detection';
import {SetterFn} from 'angular2/src/reflection/types';
import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
import {List, ListWrapper} from 'angular2/src/facade/collection';
import * as protoViewModule from './proto_view';

/**
* Note: Code that uses this class assumes that is immutable!
*/
export class ElementBinder {
contentTagSelector: string;
textNodeIndices: List<number>;
Expand Down Expand Up @@ -39,24 +35,4 @@ export class ElementBinder {
this.distanceToParent = distanceToParent;
this.propertySetters = propertySetters;
}

mergeChildComponentProtoViews(protoViews:List<protoViewModule.ProtoView>, target:List<protoViewModule.ProtoView>):ElementBinder {
var nestedProtoView;
if (isPresent(this.componentId)) {
nestedProtoView = ListWrapper.removeAt(protoViews, 0);
} else if (isPresent(this.nestedProtoView)) {
nestedProtoView = this.nestedProtoView.mergeChildComponentProtoViews(protoViews, target);
}
return new ElementBinder({
parentIndex: this.parentIndex,
textNodeIndices: this.textNodeIndices,
contentTagSelector: this.contentTagSelector,
nestedProtoView: nestedProtoView,
componentId: this.componentId,
eventLocals: this.eventLocals,
eventNames: this.eventNames,
distanceToParent: this.distanceToParent,
propertySetters: this.propertySetters
});
}
}

0 comments on commit ca95846

Please sign in to comment.