refactor(proto_view_factory): Move getChangeDetectorDefinitions out of ProtoViewFactory

Move `getChangeDetectorDefinitions` out of `ProtoViewFactory` since it
does not depend on any state in that object.
This commit is contained in:
Tim Blasi 2015-05-14 12:43:56 -07:00
parent c397297eef
commit 3644036693
2 changed files with 217 additions and 211 deletions

View File

@ -110,37 +110,19 @@ class BindingRecordsCreator {
@Injectable() @Injectable()
export class ProtoViewFactory { export class ProtoViewFactory {
_changeDetection:ChangeDetection; _changeDetection: ChangeDetection;
constructor(changeDetection:ChangeDetection) { constructor(changeDetection: ChangeDetection) {
this._changeDetection = changeDetection; this._changeDetection = changeDetection;
} }
/**
* Returns the data needed to create ChangeDetectors
* for the given ProtoView and all nested ProtoViews.
*/
getChangeDetectorDefinitions(hostComponentMetadata:renderApi.DirectiveMetadata,
rootRenderProtoView: renderApi.ProtoViewDto, allRenderDirectiveMetadata:List<renderApi.DirectiveMetadata>):List<ChangeDetectorDefinition> {
var nestedPvsWithIndex = this._collectNestedProtoViews(rootRenderProtoView);
var nestedPvVariableBindings = this._collectNestedProtoViewsVariableBindings(nestedPvsWithIndex);
var nestedPvVariableNames = this._collectNestedProtoViewsVariableNames(nestedPvsWithIndex, nestedPvVariableBindings);
return this._getChangeDetectorDefinitions(
hostComponentMetadata,
nestedPvsWithIndex,
nestedPvVariableNames,
allRenderDirectiveMetadata
);
}
createAppProtoViews(hostComponentBinding:DirectiveBinding, createAppProtoViews(hostComponentBinding:DirectiveBinding,
rootRenderProtoView: renderApi.ProtoViewDto, allDirectives:List<DirectiveBinding>):List<AppProtoView> { rootRenderProtoView: renderApi.ProtoViewDto, allDirectives:List<DirectiveBinding>):List<AppProtoView> {
var allRenderDirectiveMetadata = ListWrapper.map(allDirectives, directiveBinding => directiveBinding.metadata ); var allRenderDirectiveMetadata = ListWrapper.map(allDirectives, directiveBinding => directiveBinding.metadata);
var nestedPvsWithIndex = this._collectNestedProtoViews(rootRenderProtoView); var nestedPvsWithIndex = _collectNestedProtoViews(rootRenderProtoView);
var nestedPvVariableBindings = this._collectNestedProtoViewsVariableBindings(nestedPvsWithIndex); var nestedPvVariableBindings = _collectNestedProtoViewsVariableBindings(nestedPvsWithIndex);
var nestedPvVariableNames = this._collectNestedProtoViewsVariableNames(nestedPvsWithIndex, nestedPvVariableBindings); var nestedPvVariableNames = _collectNestedProtoViewsVariableNames(nestedPvsWithIndex, nestedPvVariableBindings);
var changeDetectorDefs = this._getChangeDetectorDefinitions( var changeDetectorDefs = _getChangeDetectorDefinitions(
hostComponentBinding.metadata, nestedPvsWithIndex, nestedPvVariableNames, allRenderDirectiveMetadata hostComponentBinding.metadata, nestedPvsWithIndex, nestedPvVariableNames, allRenderDirectiveMetadata
); );
var protoChangeDetectors = ListWrapper.map( var protoChangeDetectors = ListWrapper.map(
@ -148,7 +130,7 @@ export class ProtoViewFactory {
); );
var appProtoViews = ListWrapper.createFixedSize(nestedPvsWithIndex.length); var appProtoViews = ListWrapper.createFixedSize(nestedPvsWithIndex.length);
ListWrapper.forEach(nestedPvsWithIndex, (pvWithIndex) => { ListWrapper.forEach(nestedPvsWithIndex, (pvWithIndex) => {
var appProtoView = this._createAppProtoView( var appProtoView = _createAppProtoView(
pvWithIndex.renderProtoView, pvWithIndex.renderProtoView,
protoChangeDetectors[pvWithIndex.index], protoChangeDetectors[pvWithIndex.index],
nestedPvVariableBindings[pvWithIndex.index], nestedPvVariableBindings[pvWithIndex.index],
@ -162,208 +144,232 @@ export class ProtoViewFactory {
}); });
return appProtoViews; return appProtoViews;
} }
}
_collectNestedProtoViews(renderProtoView:renderApi.ProtoViewDto, parentIndex:number = null, boundElementIndex = null, result:List<RenderProtoViewWithIndex> = null):List<RenderProtoViewWithIndex> { /**
if (isBlank(result)) { * Returns the data needed to create ChangeDetectors
result = []; * for the given ProtoView and all nested ProtoViews.
*/
export function getChangeDetectorDefinitions(
hostComponentMetadata:renderApi.DirectiveMetadata,
rootRenderProtoView: renderApi.ProtoViewDto,
allRenderDirectiveMetadata:List<renderApi.DirectiveMetadata>): List<ChangeDetectorDefinition> {
var nestedPvsWithIndex = _collectNestedProtoViews(rootRenderProtoView);
var nestedPvVariableBindings = _collectNestedProtoViewsVariableBindings(nestedPvsWithIndex);
var nestedPvVariableNames = _collectNestedProtoViewsVariableNames(nestedPvsWithIndex, nestedPvVariableBindings);
return _getChangeDetectorDefinitions(
hostComponentMetadata,
nestedPvsWithIndex,
nestedPvVariableNames,
allRenderDirectiveMetadata
);
}
function _collectNestedProtoViews(renderProtoView:renderApi.ProtoViewDto,
parentIndex:number = null,
boundElementIndex = null,
result:List<RenderProtoViewWithIndex> = null): List<RenderProtoViewWithIndex> {
if (isBlank(result)) {
result = [];
}
ListWrapper.push(result, new RenderProtoViewWithIndex(renderProtoView, result.length, parentIndex, boundElementIndex));
var currentIndex = result.length - 1;
var childBoundElementIndex = 0;
ListWrapper.forEach(renderProtoView.elementBinders, (elementBinder) => {
if (isPresent(elementBinder.nestedProtoView)) {
_collectNestedProtoViews(elementBinder.nestedProtoView, currentIndex, childBoundElementIndex, result);
} }
ListWrapper.push(result, new RenderProtoViewWithIndex(renderProtoView, result.length, parentIndex, boundElementIndex)); childBoundElementIndex++;
var currentIndex = result.length - 1; });
var childBoundElementIndex = 0; return result;
ListWrapper.forEach(renderProtoView.elementBinders, (elementBinder) => { }
if (isPresent(elementBinder.nestedProtoView)) {
this._collectNestedProtoViews(elementBinder.nestedProtoView, currentIndex, childBoundElementIndex, result);
}
childBoundElementIndex++;
});
return result;
}
_getChangeDetectorDefinitions( function _getChangeDetectorDefinitions(
hostComponentMetadata:renderApi.DirectiveMetadata, hostComponentMetadata:renderApi.DirectiveMetadata,
nestedPvsWithIndex: List<RenderProtoViewWithIndex>, nestedPvsWithIndex: List<RenderProtoViewWithIndex>,
nestedPvVariableNames: List<List<string>>, nestedPvVariableNames: List<List<string>>,
allRenderDirectiveMetadata:List<renderApi.DirectiveMetadata>):List<ChangeDetectorDefinition> { allRenderDirectiveMetadata:List<renderApi.DirectiveMetadata>):List<ChangeDetectorDefinition> {
return ListWrapper.map(nestedPvsWithIndex, (pvWithIndex) => { return ListWrapper.map(nestedPvsWithIndex, (pvWithIndex) => {
var elementBinders = pvWithIndex.renderProtoView.elementBinders; var elementBinders = pvWithIndex.renderProtoView.elementBinders;
var bindingRecordsCreator = new BindingRecordsCreator(); var bindingRecordsCreator = new BindingRecordsCreator();
var bindingRecords = bindingRecordsCreator.getBindingRecords(elementBinders, allRenderDirectiveMetadata); var bindingRecords = bindingRecordsCreator.getBindingRecords(elementBinders, allRenderDirectiveMetadata);
var directiveRecords = bindingRecordsCreator.getDirectiveRecords(elementBinders, allRenderDirectiveMetadata); var directiveRecords = bindingRecordsCreator.getDirectiveRecords(elementBinders, allRenderDirectiveMetadata);
var strategyName = DEFAULT; var strategyName = DEFAULT;
var typeString; var typeString;
if (pvWithIndex.renderProtoView.type === renderApi.ProtoViewDto.COMPONENT_VIEW_TYPE) { if (pvWithIndex.renderProtoView.type === renderApi.ProtoViewDto.COMPONENT_VIEW_TYPE) {
strategyName = hostComponentMetadata.changeDetection; strategyName = hostComponentMetadata.changeDetection;
typeString = 'comp'; typeString = 'comp';
} else if (pvWithIndex.renderProtoView.type === renderApi.ProtoViewDto.HOST_VIEW_TYPE) { } else if (pvWithIndex.renderProtoView.type === renderApi.ProtoViewDto.HOST_VIEW_TYPE) {
typeString = 'host'; typeString = 'host';
} else { } else {
typeString = 'embedded'; typeString = 'embedded';
} }
var id = `${hostComponentMetadata.id}_${typeString}_${pvWithIndex.index}`; var id = `${hostComponentMetadata.id}_${typeString}_${pvWithIndex.index}`;
var variableNames = nestedPvVariableNames[pvWithIndex.index]; var variableNames = nestedPvVariableNames[pvWithIndex.index];
return new ChangeDetectorDefinition(id, strategyName, variableNames, bindingRecords, directiveRecords); return new ChangeDetectorDefinition(id, strategyName, variableNames, bindingRecords, directiveRecords);
}); });
} }
_createAppProtoView( function _createAppProtoView(
renderProtoView: renderApi.ProtoViewDto, renderProtoView: renderApi.ProtoViewDto,
protoChangeDetector: ProtoChangeDetector, protoChangeDetector: ProtoChangeDetector,
variableBindings: Map<string, string>, variableBindings: Map<string, string>,
allDirectives:List<DirectiveBinding> allDirectives:List<DirectiveBinding>
):AppProtoView { ):AppProtoView {
var elementBinders = renderProtoView.elementBinders; var elementBinders = renderProtoView.elementBinders;
var protoView = new AppProtoView(renderProtoView.render, protoChangeDetector, variableBindings); var protoView = new AppProtoView(renderProtoView.render, protoChangeDetector, variableBindings);
// TODO: vsavkin refactor to pass element binders into proto view // TODO: vsavkin refactor to pass element binders into proto view
this._createElementBinders(protoView, elementBinders, allDirectives); _createElementBinders(protoView, elementBinders, allDirectives);
this._bindDirectiveEvents(protoView, elementBinders); _bindDirectiveEvents(protoView, elementBinders);
return protoView; return protoView;
} }
_collectNestedProtoViewsVariableBindings( function _collectNestedProtoViewsVariableBindings(
nestedPvsWithIndex: List<RenderProtoViewWithIndex> nestedPvsWithIndex: List<RenderProtoViewWithIndex>
):List<Map<string, string>> { ):List<Map<string, string>> {
return ListWrapper.map(nestedPvsWithIndex, (pvWithIndex) => { return ListWrapper.map(nestedPvsWithIndex, (pvWithIndex) => {
return this._createVariableBindings(pvWithIndex.renderProtoView); return _createVariableBindings(pvWithIndex.renderProtoView);
}); });
} }
_createVariableBindings(renderProtoView):Map { function _createVariableBindings(renderProtoView):Map {
var variableBindings = MapWrapper.create(); var variableBindings = MapWrapper.create();
MapWrapper.forEach(renderProtoView.variableBindings, (mappedName, varName) => { MapWrapper.forEach(renderProtoView.variableBindings, (mappedName, varName) => {
MapWrapper.set(variableBindings, varName, mappedName);
});
ListWrapper.forEach(renderProtoView.elementBinders, binder => {
MapWrapper.forEach(binder.variableBindings, (mappedName, varName) => {
MapWrapper.set(variableBindings, varName, mappedName); MapWrapper.set(variableBindings, varName, mappedName);
}); });
ListWrapper.forEach(renderProtoView.elementBinders, binder => { });
MapWrapper.forEach(binder.variableBindings, (mappedName, varName) => { return variableBindings;
MapWrapper.set(variableBindings, varName, mappedName); }
});
});
return variableBindings;
}
_collectNestedProtoViewsVariableNames( function _collectNestedProtoViewsVariableNames(
nestedPvsWithIndex: List<RenderProtoViewWithIndex>, nestedPvsWithIndex: List<RenderProtoViewWithIndex>,
nestedPvVariableBindings:List<Map<string, string>> nestedPvVariableBindings:List<Map<string, string>>
):List<List<string>> { ):List<List<string>> {
var nestedPvVariableNames = ListWrapper.createFixedSize(nestedPvsWithIndex.length); var nestedPvVariableNames = ListWrapper.createFixedSize(nestedPvsWithIndex.length);
ListWrapper.forEach(nestedPvsWithIndex, (pvWithIndex) => { ListWrapper.forEach(nestedPvsWithIndex, (pvWithIndex) => {
var parentVariableNames = isPresent(pvWithIndex.parentIndex) ? nestedPvVariableNames[pvWithIndex.parentIndex] : null; var parentVariableNames = isPresent(pvWithIndex.parentIndex) ? nestedPvVariableNames[pvWithIndex.parentIndex] : null;
nestedPvVariableNames[pvWithIndex.index] = this._createVariableNames( nestedPvVariableNames[pvWithIndex.index] = _createVariableNames(
parentVariableNames, nestedPvVariableBindings[pvWithIndex.index] parentVariableNames, nestedPvVariableBindings[pvWithIndex.index]
);
});
return nestedPvVariableNames;
}
_createVariableNames(parentVariableNames, variableBindings):List {
var variableNames = isPresent(parentVariableNames) ? ListWrapper.clone(parentVariableNames) : [];
MapWrapper.forEach(variableBindings, (local, v) => {
ListWrapper.push(variableNames, local);
});
return variableNames;
}
_createElementBinders(protoView, elementBinders, allDirectiveBindings) {
for (var i=0; i<elementBinders.length; i++) {
var renderElementBinder = elementBinders[i];
var dirs = elementBinders[i].directives;
var parentPeiWithDistance = this._findParentProtoElementInjectorWithDistance(
i, protoView.elementBinders, elementBinders);
var directiveBindings = ListWrapper.map(dirs, (dir) => allDirectiveBindings[dir.directiveIndex] );
var componentDirectiveBinding = null;
if (directiveBindings.length > 0) {
if (directiveBindings[0].metadata.type === renderApi.DirectiveMetadata.COMPONENT_TYPE) {
componentDirectiveBinding = directiveBindings[0];
}
}
var protoElementInjector = this._createProtoElementInjector(
i, parentPeiWithDistance, renderElementBinder, componentDirectiveBinding, directiveBindings);
this._createElementBinder(protoView, i, renderElementBinder, protoElementInjector, componentDirectiveBinding);
}
}
_findParentProtoElementInjectorWithDistance(binderIndex, elementBinders, renderElementBinders) {
var distance = 0;
do {
var renderElementBinder = renderElementBinders[binderIndex];
binderIndex = renderElementBinder.parentIndex;
if (binderIndex !== -1) {
distance += renderElementBinder.distanceToParent;
var elementBinder = elementBinders[binderIndex];
if (isPresent(elementBinder.protoElementInjector)) {
return new ParentProtoElementInjectorWithDistance(elementBinder.protoElementInjector, distance);
}
}
} while (binderIndex !== -1);
return new ParentProtoElementInjectorWithDistance(null, -1);
}
_createProtoElementInjector(binderIndex, parentPeiWithDistance, renderElementBinder, componentDirectiveBinding, directiveBindings) {
var protoElementInjector = null;
// Create a protoElementInjector for any element that either has bindings *or* has one
// or more var- defined. Elements with a var- defined need a their own element injector
// so that, when hydrating, $implicit can be set to the element.
var hasVariables = MapWrapper.size(renderElementBinder.variableBindings) > 0;
if (directiveBindings.length > 0 || hasVariables) {
protoElementInjector = new ProtoElementInjector(
parentPeiWithDistance.protoElementInjector, binderIndex,
directiveBindings,
isPresent(componentDirectiveBinding), parentPeiWithDistance.distance
);
protoElementInjector.attributes = renderElementBinder.readAttributes;
if (hasVariables) {
protoElementInjector.exportComponent = isPresent(componentDirectiveBinding);
protoElementInjector.exportElement = isBlank(componentDirectiveBinding);
// experiment
var exportImplicitName = MapWrapper.get(renderElementBinder.variableBindings, '\$implicit');
if (isPresent(exportImplicitName)) {
protoElementInjector.exportImplicitName = exportImplicitName;
}
}
}
return protoElementInjector;
}
_createElementBinder(protoView, boundElementIndex, renderElementBinder, protoElementInjector, componentDirectiveBinding) {
var parent = null;
if (renderElementBinder.parentIndex !== -1) {
parent = protoView.elementBinders[renderElementBinder.parentIndex];
}
var elBinder = protoView.bindElement(
parent,
renderElementBinder.distanceToParent,
protoElementInjector,
componentDirectiveBinding
); );
protoView.bindEvent(renderElementBinder.eventBindings, boundElementIndex, -1); });
// variables return nestedPvVariableNames;
// The view's locals needs to have a full set of variable names at construction time }
// in order to prevent new variables from being set later in the lifecycle. Since we don't want
// to actually create variable bindings for the $implicit bindings, add to the
// protoLocals manually.
MapWrapper.forEach(renderElementBinder.variableBindings, (mappedName, varName) => {
MapWrapper.set(protoView.protoLocals, mappedName, null);
});
return elBinder;
}
_bindDirectiveEvents(protoView, elementBinders:List<renderApi.ElementBinder>) { function _createVariableNames(parentVariableNames, variableBindings):List {
for (var boundElementIndex = 0; boundElementIndex < elementBinders.length; ++boundElementIndex) { var variableNames = isPresent(parentVariableNames) ? ListWrapper.clone(parentVariableNames) : [];
var dirs = elementBinders[boundElementIndex].directives; MapWrapper.forEach(variableBindings, (local, v) => {
for (var i = 0; i < dirs.length; i++) { ListWrapper.push(variableNames, local);
var directiveBinder = dirs[i]; });
return variableNames;
}
// directive events function _createElementBinders(protoView, elementBinders, allDirectiveBindings) {
protoView.bindEvent(directiveBinder.eventBindings, boundElementIndex, i); for (var i=0; i<elementBinders.length; i++) {
var renderElementBinder = elementBinders[i];
var dirs = elementBinders[i].directives;
var parentPeiWithDistance = _findParentProtoElementInjectorWithDistance(
i, protoView.elementBinders, elementBinders);
var directiveBindings = ListWrapper.map(dirs, (dir) => allDirectiveBindings[dir.directiveIndex] );
var componentDirectiveBinding = null;
if (directiveBindings.length > 0) {
if (directiveBindings[0].metadata.type === renderApi.DirectiveMetadata.COMPONENT_TYPE) {
componentDirectiveBinding = directiveBindings[0];
} }
} }
var protoElementInjector = _createProtoElementInjector(
i, parentPeiWithDistance, renderElementBinder, componentDirectiveBinding, directiveBindings);
_createElementBinder(protoView, i, renderElementBinder, protoElementInjector, componentDirectiveBinding);
}
}
function _findParentProtoElementInjectorWithDistance(binderIndex, elementBinders, renderElementBinders) {
var distance = 0;
do {
var renderElementBinder = renderElementBinders[binderIndex];
binderIndex = renderElementBinder.parentIndex;
if (binderIndex !== -1) {
distance += renderElementBinder.distanceToParent;
var elementBinder = elementBinders[binderIndex];
if (isPresent(elementBinder.protoElementInjector)) {
return new ParentProtoElementInjectorWithDistance(elementBinder.protoElementInjector, distance);
}
}
} while (binderIndex !== -1);
return new ParentProtoElementInjectorWithDistance(null, -1);
}
function _createProtoElementInjector(binderIndex, parentPeiWithDistance, renderElementBinder, componentDirectiveBinding, directiveBindings) {
var protoElementInjector = null;
// Create a protoElementInjector for any element that either has bindings *or* has one
// or more var- defined. Elements with a var- defined need a their own element injector
// so that, when hydrating, $implicit can be set to the element.
var hasVariables = MapWrapper.size(renderElementBinder.variableBindings) > 0;
if (directiveBindings.length > 0 || hasVariables) {
protoElementInjector = new ProtoElementInjector(
parentPeiWithDistance.protoElementInjector, binderIndex,
directiveBindings,
isPresent(componentDirectiveBinding), parentPeiWithDistance.distance
);
protoElementInjector.attributes = renderElementBinder.readAttributes;
if (hasVariables) {
protoElementInjector.exportComponent = isPresent(componentDirectiveBinding);
protoElementInjector.exportElement = isBlank(componentDirectiveBinding);
// experiment
var exportImplicitName = MapWrapper.get(renderElementBinder.variableBindings, '\$implicit');
if (isPresent(exportImplicitName)) {
protoElementInjector.exportImplicitName = exportImplicitName;
}
}
}
return protoElementInjector;
}
function _createElementBinder(protoView, boundElementIndex, renderElementBinder, protoElementInjector, componentDirectiveBinding) {
var parent = null;
if (renderElementBinder.parentIndex !== -1) {
parent = protoView.elementBinders[renderElementBinder.parentIndex];
}
var elBinder = protoView.bindElement(
parent,
renderElementBinder.distanceToParent,
protoElementInjector,
componentDirectiveBinding
);
protoView.bindEvent(renderElementBinder.eventBindings, boundElementIndex, -1);
// variables
// The view's locals needs to have a full set of variable names at construction time
// in order to prevent new variables from being set later in the lifecycle. Since we don't want
// to actually create variable bindings for the $implicit bindings, add to the
// protoLocals manually.
MapWrapper.forEach(renderElementBinder.variableBindings, (mappedName, varName) => {
MapWrapper.set(protoView.protoLocals, mappedName, null);
});
return elBinder;
}
function _bindDirectiveEvents(protoView, elementBinders:List<renderApi.ElementBinder>) {
for (var boundElementIndex = 0; boundElementIndex < elementBinders.length; ++boundElementIndex) {
var dirs = elementBinders[boundElementIndex].directives;
for (var i = 0; i < dirs.length; i++) {
var directiveBinder = dirs[i];
// directive events
protoView.bindEvent(directiveBinder.eventBindings, boundElementIndex, i);
}
} }
} }
class RenderProtoViewWithIndex { class RenderProtoViewWithIndex {
renderProtoView:renderApi.ProtoViewDto; renderProtoView:renderApi.ProtoViewDto;
index:number; index:number;

View File

@ -17,7 +17,7 @@ import {isBlank} from 'angular2/src/facade/lang';
import {MapWrapper} from 'angular2/src/facade/collection'; import {MapWrapper} from 'angular2/src/facade/collection';
import {ChangeDetection, ChangeDetectorDefinition} from 'angular2/change_detection'; import {ChangeDetection, ChangeDetectorDefinition} from 'angular2/change_detection';
import {ProtoViewFactory} from 'angular2/src/core/compiler/proto_view_factory'; import {ProtoViewFactory, getChangeDetectorDefinitions} from 'angular2/src/core/compiler/proto_view_factory';
import {Component, Directive} from 'angular2/src/core/annotations_impl/annotations'; import {Component, Directive} from 'angular2/src/core/annotations_impl/annotations';
import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver'; import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver';
import {DirectiveBinding} from 'angular2/src/core/compiler/element_injector'; import {DirectiveBinding} from 'angular2/src/core/compiler/element_injector';
@ -45,7 +45,7 @@ export function main() {
it('should create a ChangeDetectorDefinition for the root render proto view', () => { it('should create a ChangeDetectorDefinition for the root render proto view', () => {
var renderPv = createRenderProtoView(); var renderPv = createRenderProtoView();
var defs = protoViewFactory.getChangeDetectorDefinitions(bindDirective(MainComponent).metadata, var defs = getChangeDetectorDefinitions(bindDirective(MainComponent).metadata,
renderPv, []); renderPv, []);
expect(defs.length).toBe(1); expect(defs.length).toBe(1);
expect(defs[0].id).toEqual('MainComponent_comp_0'); expect(defs[0].id).toEqual('MainComponent_comp_0');