refactor(render): create and store render ProtoViewRef in every app ProtoView

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.
This commit is contained in:
Tobias Bosch
2015-04-07 17:24:09 -07:00
parent d6003ee0ab
commit ca958464c4
16 changed files with 509 additions and 436 deletions

View File

@ -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';
@ -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.

View File

@ -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);
@ -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;
}
@ -113,31 +125,72 @@ export class NewCompiler {
this._flattenDirectives(template),
(directive) => this._bindDirective(directive)
);
pvPromise = this._compileNoRecurse(componentBinding, template, directives).then( (protoView) => {
// Populate the cache before compiling the nested components,
// so that components can reference themselves in their template.
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)}`); }
);
}
return protoView;
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;
}
_compileNoRecurse(componentBinding, template, directives):Promise<ProtoView> {
var component = componentBinding.key.token;
// 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);
}
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);
}
if (PromiseWrapper.isPromise(nestedCall)) {
ListWrapper.push(nestedPVPromises, nestedCall.then(elementBinderDone));
} else if (isPresent(nestedCall)) {
elementBinderDone(nestedCall);
}
binderIndex++;
});
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);
}
}
_buildRenderTemplate(component, template, directives) {
var componentUrl = this._urlResolver.resolve(
this._appUrl, this._componentUrlMapper.getUrl(component)
);
@ -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) {
@ -269,7 +297,7 @@ export class Compiler extends NewCompiler {
new DefaultStepFactory(parser, shadowDomStrategy.render),
templateLoader
),
null, null
null, shadowDomStrategy.render
),
new ProtoViewFactory(changeDetection, shadowDomStrategy)
);

View File

@ -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];
@ -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);

View File

@ -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';
@ -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();
@ -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;
}
}
/**