refactor(compiler): use the new compiler everywhere
Closes #3605 BREAKING CHANGE: - we don't mark an element as bound any more if it only contains text bindings E.g. <div>{{hello}}</div> This changes the indices when using `DebugElement.componentViewChildren` / `DebugElement.children`. - `@Directive.compileChildren` was removed, `ng-non-bindable` is now builtin and not a directive any more - angular no more adds the `ng-binding` class to elements with bindings - directives are now ordered as they are listed in the View.directives regarding change detection. Previously they had an undefined order. - the `Renderer` interface has new methods `createProtoView` and `registerComponentTemplate`. See `DomRenderer` for default implementations. - reprojection with `ng-content` is now all or nothing per `ng-content` element - angular2 transformer can't be used in tests that modify directive metadata. Use `angular2/src/transform/inliner_for_test` transformer instead.
This commit is contained in:
@ -1,95 +1,13 @@
|
||||
import {Binding, resolveForwardRef, Injectable, Inject} from 'angular2/src/core/di';
|
||||
import {DEFAULT_PIPES_TOKEN} from 'angular2/src/core/pipes';
|
||||
import {
|
||||
Type,
|
||||
isBlank,
|
||||
isType,
|
||||
isPresent,
|
||||
normalizeBlank,
|
||||
stringify,
|
||||
isArray,
|
||||
isPromise
|
||||
} from 'angular2/src/core/facade/lang';
|
||||
import {ProtoViewRef} from 'angular2/src/core/compiler/view_ref';
|
||||
import {ProtoViewFactory} from 'angular2/src/core/compiler/proto_view_factory';
|
||||
|
||||
import {Injectable} from 'angular2/src/core/di';
|
||||
import {Type, isBlank, stringify} from 'angular2/src/core/facade/lang';
|
||||
import {BaseException} from 'angular2/src/core/facade/exceptions';
|
||||
import {Promise, PromiseWrapper} from 'angular2/src/core/facade/async';
|
||||
import {ListWrapper, Map, MapWrapper} from 'angular2/src/core/facade/collection';
|
||||
import {reflector} from 'angular2/src/core/reflection/reflection';
|
||||
import {CompiledHostTemplate} from 'angular2/src/core/compiler/template_commands';
|
||||
|
||||
import {DirectiveResolver} from './directive_resolver';
|
||||
|
||||
import {AppProtoView, AppProtoViewMergeMapping} from './view';
|
||||
import {ProtoViewRef} from './view_ref';
|
||||
import {DirectiveBinding} from './element_injector';
|
||||
import {ViewResolver} from './view_resolver';
|
||||
import {PipeResolver} from './pipe_resolver';
|
||||
import {ViewMetadata} from 'angular2/src/core/metadata';
|
||||
import {ComponentUrlMapper} from './component_url_mapper';
|
||||
import {ProtoViewFactory} from './proto_view_factory';
|
||||
import {UrlResolver} from 'angular2/src/core/services/url_resolver';
|
||||
import {AppRootUrl} from 'angular2/src/core/services/app_root_url';
|
||||
import {ElementBinder} from './element_binder';
|
||||
import {wtfStartTimeRange, wtfEndTimeRange} from '../profile/profile';
|
||||
import {PipeBinding} from '../pipes/pipe_binding';
|
||||
|
||||
import {
|
||||
RenderDirectiveMetadata,
|
||||
ViewDefinition,
|
||||
RenderCompiler,
|
||||
ViewType,
|
||||
RenderProtoViewMergeMapping,
|
||||
RenderProtoViewRef
|
||||
} from 'angular2/src/core/render/api';
|
||||
|
||||
/**
|
||||
* Cache that stores the AppProtoView of the template of a component.
|
||||
* Used to prevent duplicate work and resolve cyclic dependencies.
|
||||
*/
|
||||
@Injectable()
|
||||
export class CompilerCache {
|
||||
_cache = new Map<Type, AppProtoView>();
|
||||
_hostCache = new Map<Type, AppProtoView>();
|
||||
|
||||
set(component: Type, protoView: AppProtoView): void { this._cache.set(component, protoView); }
|
||||
|
||||
get(component: Type): AppProtoView {
|
||||
var result = this._cache.get(component);
|
||||
return normalizeBlank(result);
|
||||
}
|
||||
|
||||
setHost(component: Type, protoView: AppProtoView): void {
|
||||
this._hostCache.set(component, protoView);
|
||||
}
|
||||
|
||||
getHost(component: Type): AppProtoView {
|
||||
var result = this._hostCache.get(component);
|
||||
return normalizeBlank(result);
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this._cache.clear();
|
||||
this._hostCache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* ## URL Resolution
|
||||
*
|
||||
* ```
|
||||
* var appRootUrl: AppRootUrl = ...;
|
||||
* var componentUrlMapper: ComponentUrlMapper = ...;
|
||||
* var urlResolver: UrlResolver = ...;
|
||||
*
|
||||
* var componentType: Type = ...;
|
||||
* var componentAnnotation: ComponentAnnotation = ...;
|
||||
* var viewAnnotation: ViewAnnotation = ...;
|
||||
*
|
||||
* // Resolving a URL
|
||||
*
|
||||
* var url = viewAnnotation.templateUrl;
|
||||
* var componentUrl = componentUrlMapper.getUrl(componentType);
|
||||
* var componentResolvedUrl = urlResolver.resolve(appRootUrl.value, componentUrl);
|
||||
* var templateResolvedUrl = urlResolver.resolve(componentResolvedUrl, url);
|
||||
* ```
|
||||
*/
|
||||
/**
|
||||
* Low-level service for compiling {@link Component}s into {@link ProtoViewRef ProtoViews}s, which
|
||||
* can later be used to create and render a Component instance.
|
||||
@ -99,271 +17,36 @@ export class CompilerCache {
|
||||
*/
|
||||
@Injectable()
|
||||
export class Compiler {
|
||||
private _compiling = new Map<Type, Promise<AppProtoView>>();
|
||||
private _appUrl: string;
|
||||
private _defaultPipes: Type[];
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
constructor(private _directiveResolver: DirectiveResolver, private _pipeResolver: PipeResolver,
|
||||
@Inject(DEFAULT_PIPES_TOKEN) _defaultPipes: Type[],
|
||||
private _compilerCache: CompilerCache, private _viewResolver: ViewResolver,
|
||||
private _componentUrlMapper: ComponentUrlMapper, private _urlResolver: UrlResolver,
|
||||
private _render: RenderCompiler, private _protoViewFactory: ProtoViewFactory,
|
||||
appUrl: AppRootUrl) {
|
||||
this._defaultPipes = _defaultPipes;
|
||||
this._appUrl = appUrl.value;
|
||||
}
|
||||
constructor(private _protoViewFactory: ProtoViewFactory) {}
|
||||
|
||||
private _bindDirective(directiveTypeOrBinding): DirectiveBinding {
|
||||
if (directiveTypeOrBinding instanceof DirectiveBinding) {
|
||||
return directiveTypeOrBinding;
|
||||
} else if (directiveTypeOrBinding instanceof Binding) {
|
||||
let annotation = this._directiveResolver.resolve(directiveTypeOrBinding.token);
|
||||
return DirectiveBinding.createFromBinding(directiveTypeOrBinding, annotation);
|
||||
} else {
|
||||
let annotation = this._directiveResolver.resolve(directiveTypeOrBinding);
|
||||
return DirectiveBinding.createFromType(directiveTypeOrBinding, annotation);
|
||||
}
|
||||
}
|
||||
|
||||
private _bindPipe(typeOrBinding): PipeBinding {
|
||||
let meta = this._pipeResolver.resolve(typeOrBinding);
|
||||
return PipeBinding.createFromType(typeOrBinding, meta);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles a {@link Component} and returns a promise for this component's {@link ProtoViewRef}.
|
||||
*
|
||||
* Returns `ProtoViewRef` that can be later used to instantiate a component via
|
||||
* {@link ViewContainerRef#createHostView} or {@link AppViewManager#createHostViewInContainer}.
|
||||
*/
|
||||
compileInHost(componentType: Type): Promise<ProtoViewRef> {
|
||||
var r = wtfStartTimeRange('Compiler#compile()', stringify(componentType));
|
||||
|
||||
var hostAppProtoView = this._compilerCache.getHost(componentType);
|
||||
var hostPvPromise;
|
||||
if (isPresent(hostAppProtoView)) {
|
||||
hostPvPromise = PromiseWrapper.resolve(hostAppProtoView);
|
||||
} else {
|
||||
var componentBinding: DirectiveBinding = this._bindDirective(componentType);
|
||||
Compiler._assertTypeIsComponent(componentBinding);
|
||||
|
||||
var directiveMetadata = componentBinding.metadata;
|
||||
hostPvPromise = this._render.compileHost(directiveMetadata)
|
||||
.then((hostRenderPv) => {
|
||||
var protoViews = this._protoViewFactory.createAppProtoViews(
|
||||
componentBinding, hostRenderPv, [componentBinding], []);
|
||||
return this._compileNestedProtoViews(protoViews, componentType,
|
||||
new Map<Type, AppProtoView>());
|
||||
})
|
||||
.then((appProtoView) => {
|
||||
this._compilerCache.setHost(componentType, appProtoView);
|
||||
return appProtoView;
|
||||
});
|
||||
}
|
||||
return hostPvPromise.then((hostAppProtoView) => {
|
||||
wtfEndTimeRange(r);
|
||||
return hostAppProtoView.ref;
|
||||
});
|
||||
}
|
||||
|
||||
private _compile(componentBinding: DirectiveBinding,
|
||||
componentPath: Map<Type, AppProtoView>): Promise<AppProtoView>|
|
||||
AppProtoView {
|
||||
var component = <Type>componentBinding.key.token;
|
||||
var protoView = this._compilerCache.get(component);
|
||||
if (isPresent(protoView)) {
|
||||
// The component has already been compiled into an AppProtoView,
|
||||
// returns a plain AppProtoView, not wrapped inside of a Promise, for performance reasons.
|
||||
return protoView;
|
||||
}
|
||||
var resultPromise = this._compiling.get(component);
|
||||
if (isPresent(resultPromise)) {
|
||||
// The component is already being compiled, attach to the existing Promise
|
||||
// instead of re-compiling the component.
|
||||
// It happens when a template references a component multiple times.
|
||||
return resultPromise;
|
||||
}
|
||||
var view = this._viewResolver.resolve(component);
|
||||
|
||||
var directives = this._flattenDirectives(view);
|
||||
|
||||
for (var i = 0; i < directives.length; i++) {
|
||||
if (!Compiler._isValidDirective(directives[i])) {
|
||||
throw new BaseException(
|
||||
`Unexpected directive value '${stringify(directives[i])}' on the View of component '${stringify(component)}'`);
|
||||
var metadatas = reflector.annotations(componentType);
|
||||
var compiledHostTemplate = null;
|
||||
for (var i = 0; i < metadatas.length; i++) {
|
||||
var metadata = metadatas[i];
|
||||
if (metadata instanceof CompiledHostTemplate) {
|
||||
compiledHostTemplate = metadata;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var boundDirectives = this._removeDuplicatedDirectives(
|
||||
directives.map(directive => this._bindDirective(directive)));
|
||||
|
||||
var pipes = this._flattenPipes(view);
|
||||
var boundPipes = pipes.map(pipe => this._bindPipe(pipe));
|
||||
|
||||
var renderTemplate = this._buildRenderTemplate(component, view, boundDirectives);
|
||||
resultPromise =
|
||||
this._render.compile(renderTemplate)
|
||||
.then((renderPv) => {
|
||||
var protoViews = this._protoViewFactory.createAppProtoViews(
|
||||
componentBinding, renderPv, boundDirectives, boundPipes);
|
||||
return this._compileNestedProtoViews(protoViews, component, componentPath);
|
||||
})
|
||||
.then((appProtoView) => {
|
||||
this._compilerCache.set(component, appProtoView);
|
||||
MapWrapper.delete(this._compiling, component);
|
||||
return appProtoView;
|
||||
});
|
||||
this._compiling.set(component, resultPromise);
|
||||
return resultPromise;
|
||||
}
|
||||
|
||||
private _removeDuplicatedDirectives(directives: DirectiveBinding[]): DirectiveBinding[] {
|
||||
var directivesMap = new Map<number, DirectiveBinding>();
|
||||
directives.forEach((dirBinding) => { directivesMap.set(dirBinding.key.id, dirBinding); });
|
||||
return MapWrapper.values(directivesMap);
|
||||
}
|
||||
|
||||
private _compileNestedProtoViews(appProtoViews: AppProtoView[], componentType: Type,
|
||||
componentPath: Map<Type, AppProtoView>): Promise<AppProtoView> {
|
||||
var nestedPVPromises = [];
|
||||
componentPath = MapWrapper.clone(componentPath);
|
||||
if (appProtoViews[0].type === ViewType.COMPONENT) {
|
||||
componentPath.set(componentType, appProtoViews[0]);
|
||||
}
|
||||
appProtoViews.forEach(appProtoView => {
|
||||
this._collectComponentElementBinders(appProtoView)
|
||||
.forEach((elementBinder: ElementBinder) => {
|
||||
var nestedComponent = elementBinder.componentDirective;
|
||||
var nestedComponentType = <Type>nestedComponent.key.token;
|
||||
var elementBinderDone =
|
||||
(nestedPv: AppProtoView) => { elementBinder.nestedProtoView = nestedPv; };
|
||||
if (componentPath.has(nestedComponentType)) {
|
||||
// cycle...
|
||||
if (appProtoView.isEmbeddedFragment) {
|
||||
throw new BaseException(
|
||||
`<ng-content> is used within the recursive path of ${stringify(nestedComponentType)}`);
|
||||
} else if (appProtoView.type === ViewType.COMPONENT) {
|
||||
throw new BaseException(
|
||||
`Unconditional component cycle in ${stringify(nestedComponentType)}`);
|
||||
} else {
|
||||
elementBinderDone(componentPath.get(nestedComponentType));
|
||||
}
|
||||
} else {
|
||||
var nestedCall = this._compile(nestedComponent, componentPath);
|
||||
if (isPromise(nestedCall)) {
|
||||
nestedPVPromises.push((<Promise<AppProtoView>>nestedCall).then(elementBinderDone));
|
||||
} else {
|
||||
elementBinderDone(<AppProtoView>nestedCall);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
return PromiseWrapper.all(nestedPVPromises)
|
||||
.then(_ => PromiseWrapper.all(
|
||||
appProtoViews.map(appProtoView => this._mergeProtoView(appProtoView))))
|
||||
.then(_ => appProtoViews[0]);
|
||||
}
|
||||
|
||||
private _mergeProtoView(appProtoView: AppProtoView): Promise<any> {
|
||||
if (appProtoView.type !== ViewType.HOST && appProtoView.type !== ViewType.EMBEDDED) {
|
||||
return null;
|
||||
}
|
||||
return this._render.mergeProtoViewsRecursively(this._collectMergeRenderProtoViews(appProtoView))
|
||||
.then((mergeResult: RenderProtoViewMergeMapping) => {
|
||||
appProtoView.mergeMapping = new AppProtoViewMergeMapping(mergeResult);
|
||||
});
|
||||
}
|
||||
|
||||
private _collectMergeRenderProtoViews(appProtoView:
|
||||
AppProtoView): Array<RenderProtoViewRef | any[]> {
|
||||
var result = [appProtoView.render];
|
||||
for (var i = 0; i < appProtoView.elementBinders.length; i++) {
|
||||
var binder = appProtoView.elementBinders[i];
|
||||
if (isPresent(binder.nestedProtoView)) {
|
||||
if (binder.hasStaticComponent() ||
|
||||
(binder.hasEmbeddedProtoView() && binder.nestedProtoView.isEmbeddedFragment)) {
|
||||
result.push(this._collectMergeRenderProtoViews(binder.nestedProtoView));
|
||||
} else {
|
||||
result.push(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private _collectComponentElementBinders(appProtoView: AppProtoView): ElementBinder[] {
|
||||
var componentElementBinders = [];
|
||||
appProtoView.elementBinders.forEach((elementBinder) => {
|
||||
if (isPresent(elementBinder.componentDirective)) {
|
||||
componentElementBinders.push(elementBinder);
|
||||
}
|
||||
});
|
||||
return componentElementBinders;
|
||||
}
|
||||
|
||||
private _buildRenderTemplate(component, view, directives): ViewDefinition {
|
||||
var componentUrl =
|
||||
this._urlResolver.resolve(this._appUrl, this._componentUrlMapper.getUrl(component));
|
||||
var templateAbsUrl = null;
|
||||
var styleAbsUrls = null;
|
||||
if (isPresent(view.templateUrl) && view.templateUrl.trim().length > 0) {
|
||||
templateAbsUrl = this._urlResolver.resolve(componentUrl, view.templateUrl);
|
||||
} else if (isPresent(view.template)) {
|
||||
// Note: If we have an inline template, we also need to send
|
||||
// the url for the component to the render so that it
|
||||
// is able to resolve urls in stylesheets.
|
||||
templateAbsUrl = componentUrl;
|
||||
}
|
||||
if (isPresent(view.styleUrls)) {
|
||||
styleAbsUrls =
|
||||
ListWrapper.map(view.styleUrls, url => this._urlResolver.resolve(componentUrl, url));
|
||||
}
|
||||
return new ViewDefinition({
|
||||
componentId: stringify(component),
|
||||
templateAbsUrl: templateAbsUrl, template: view.template,
|
||||
styleAbsUrls: styleAbsUrls,
|
||||
styles: view.styles,
|
||||
directives: ListWrapper.map(directives, directiveBinding => directiveBinding.metadata),
|
||||
encapsulation: view.encapsulation
|
||||
});
|
||||
}
|
||||
|
||||
private _flattenPipes(view: ViewMetadata): any[] {
|
||||
if (isBlank(view.pipes)) return this._defaultPipes;
|
||||
var pipes = ListWrapper.clone(this._defaultPipes);
|
||||
this._flattenList(view.pipes, pipes);
|
||||
return pipes;
|
||||
}
|
||||
|
||||
private _flattenDirectives(view: ViewMetadata): Type[] {
|
||||
if (isBlank(view.directives)) return [];
|
||||
var directives = [];
|
||||
this._flattenList(view.directives, directives);
|
||||
return directives;
|
||||
}
|
||||
|
||||
private _flattenList(tree: any[], out: Array<Type | Binding | any[]>): void {
|
||||
for (var i = 0; i < tree.length; i++) {
|
||||
var item = resolveForwardRef(tree[i]);
|
||||
if (isArray(item)) {
|
||||
this._flattenList(item, out);
|
||||
} else {
|
||||
out.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static _isValidDirective(value: Type | Binding): boolean {
|
||||
return isPresent(value) && (value instanceof Type || value instanceof Binding);
|
||||
}
|
||||
|
||||
private static _assertTypeIsComponent(directiveBinding: DirectiveBinding): void {
|
||||
if (directiveBinding.metadata.type !== RenderDirectiveMetadata.COMPONENT_TYPE) {
|
||||
if (isBlank(compiledHostTemplate)) {
|
||||
throw new BaseException(
|
||||
`Could not load '${stringify(directiveBinding.key.token)}' because it is not a component.`);
|
||||
`No precompiled template for component ${stringify(componentType)} found`);
|
||||
}
|
||||
return PromiseWrapper.resolve(this._createProtoView(compiledHostTemplate));
|
||||
}
|
||||
|
||||
private _createProtoView(compiledHostTemplate: CompiledHostTemplate): ProtoViewRef {
|
||||
return this._protoViewFactory.createHost(compiledHostTemplate).ref;
|
||||
}
|
||||
|
||||
clearCache() { this._protoViewFactory.clearCache(); }
|
||||
}
|
||||
|
||||
export function internalCreateProtoView(compiler: Compiler,
|
||||
compiledHostTemplate: CompiledHostTemplate): ProtoViewRef {
|
||||
return (<any>compiler)._createProtoView(compiledHostTemplate);
|
||||
}
|
@ -119,7 +119,6 @@ export class DirectiveResolver {
|
||||
bindings: dm.bindings,
|
||||
exportAs: dm.exportAs,
|
||||
moduleId: dm.moduleId,
|
||||
compileChildren: dm.compileChildren,
|
||||
queries: mergedQueries,
|
||||
changeDetection: dm.changeDetection,
|
||||
viewBindings: dm.viewBindings
|
||||
@ -134,7 +133,6 @@ export class DirectiveResolver {
|
||||
bindings: dm.bindings,
|
||||
exportAs: dm.exportAs,
|
||||
moduleId: dm.moduleId,
|
||||
compileChildren: dm.compileChildren,
|
||||
queries: mergedQueries
|
||||
});
|
||||
}
|
||||
|
@ -1,26 +1,16 @@
|
||||
import {isBlank, isPresent} from 'angular2/src/core/facade/lang';
|
||||
import {isBlank} from 'angular2/src/core/facade/lang';
|
||||
import {BaseException} from 'angular2/src/core/facade/exceptions';
|
||||
import * as eiModule from './element_injector';
|
||||
import {DirectiveBinding} from './element_injector';
|
||||
import * as viewModule from './view';
|
||||
|
||||
export class ElementBinder {
|
||||
// updated later, so we are able to resolve cycles
|
||||
nestedProtoView: viewModule.AppProtoView = null;
|
||||
|
||||
constructor(public index: number, public parent: ElementBinder, public distanceToParent: number,
|
||||
public protoElementInjector: eiModule.ProtoElementInjector,
|
||||
public componentDirective: DirectiveBinding) {
|
||||
public componentDirective: DirectiveBinding,
|
||||
public nestedProtoView: viewModule.AppProtoView) {
|
||||
if (isBlank(index)) {
|
||||
throw new BaseException('null index not allowed.');
|
||||
}
|
||||
}
|
||||
|
||||
hasStaticComponent(): boolean {
|
||||
return isPresent(this.componentDirective) && isPresent(this.nestedProtoView);
|
||||
}
|
||||
|
||||
hasEmbeddedProtoView(): boolean {
|
||||
return !isPresent(this.componentDirective) && isPresent(this.nestedProtoView);
|
||||
}
|
||||
}
|
||||
|
@ -170,7 +170,7 @@ export class DirectiveBinding extends ResolvedBinding {
|
||||
type: meta instanceof ComponentMetadata ? RenderDirectiveMetadata.COMPONENT_TYPE :
|
||||
RenderDirectiveMetadata.DIRECTIVE_TYPE,
|
||||
selector: meta.selector,
|
||||
compileChildren: meta.compileChildren,
|
||||
compileChildren: true,
|
||||
outputs: meta.outputs,
|
||||
host: isPresent(meta.host) ? MapWrapper.createFromStringMap(meta.host) : null,
|
||||
inputs: meta.inputs,
|
||||
@ -214,6 +214,7 @@ export class DirectiveBinding extends ResolvedBinding {
|
||||
|
||||
// TODO(rado): benchmark and consider rolling in as ElementInjector fields.
|
||||
export class PreBuiltObjects {
|
||||
nestedView: viewModule.AppView = null;
|
||||
constructor(public viewManager: avmModule.AppViewManager, public view: viewModule.AppView,
|
||||
public elementRef: ElementRef, public templateRef: TemplateRef) {}
|
||||
}
|
||||
@ -474,6 +475,8 @@ export class ElementInjector extends TreeNode<ElementInjector> implements Depend
|
||||
return new ViewContainerRef(this._preBuiltObjects.viewManager, this.getElementRef());
|
||||
}
|
||||
|
||||
getNestedView(): viewModule.AppView { return this._preBuiltObjects.nestedView; }
|
||||
|
||||
getView(): viewModule.AppView { return this._preBuiltObjects.view; }
|
||||
|
||||
directParent(): ElementInjector { return this._proto.distanceToParent < 2 ? this.parent : null; }
|
||||
|
@ -33,6 +33,7 @@ export class ElementRef implements RenderElementRef {
|
||||
/**
|
||||
* @private
|
||||
*
|
||||
* TODO(tbosch): remove this when the new compiler lands
|
||||
* Index of the element inside the `RenderViewRef`.
|
||||
*
|
||||
* This is used internally by the Angular framework to locate elements.
|
||||
@ -42,11 +43,10 @@ export class ElementRef implements RenderElementRef {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
constructor(parentView: ViewRef, boundElementIndex: number, renderBoundElementIndex: number,
|
||||
private _renderer: Renderer) {
|
||||
constructor(parentView: ViewRef, boundElementIndex: number, private _renderer: Renderer) {
|
||||
this.parentView = parentView;
|
||||
this.boundElementIndex = boundElementIndex;
|
||||
this.renderBoundElementIndex = renderBoundElementIndex;
|
||||
this.renderBoundElementIndex = boundElementIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,8 +1,5 @@
|
||||
import {Injectable} from 'angular2/src/core/di';
|
||||
|
||||
import {ListWrapper, MapWrapper} from 'angular2/src/core/facade/collection';
|
||||
import {StringWrapper, isPresent, isBlank, assertionsEnabled} from 'angular2/src/core/facade/lang';
|
||||
import {BaseException} from 'angular2/src/core/facade/exceptions';
|
||||
import {isPresent, isBlank, Type, isArray, isNumber} from 'angular2/src/core/facade/lang';
|
||||
import {reflector} from 'angular2/src/core/reflection/reflection';
|
||||
|
||||
import {
|
||||
@ -17,23 +14,340 @@ import {
|
||||
ASTWithSource
|
||||
} from 'angular2/src/core/change_detection/change_detection';
|
||||
|
||||
import {PipeBinding} from 'angular2/src/core/pipes/pipe_binding';
|
||||
import {ProtoPipes} from 'angular2/src/core/pipes/pipes';
|
||||
|
||||
import {
|
||||
RenderDirectiveMetadata,
|
||||
RenderElementBinder,
|
||||
PropertyBindingType,
|
||||
DirectiveBinder,
|
||||
ProtoViewDto,
|
||||
ViewType
|
||||
ViewType,
|
||||
RenderProtoViewRef
|
||||
} from 'angular2/src/core/render/api';
|
||||
import {AppProtoView} from './view';
|
||||
|
||||
import {Injectable, Binding, resolveForwardRef, Inject} from 'angular2/src/core/di';
|
||||
|
||||
import {PipeBinding} from '../pipes/pipe_binding';
|
||||
import {ProtoPipes} from '../pipes/pipes';
|
||||
|
||||
import {AppProtoView, AppProtoViewMergeInfo} from './view';
|
||||
import {ElementBinder} from './element_binder';
|
||||
import {ProtoElementInjector, DirectiveBinding} from './element_injector';
|
||||
import {DirectiveResolver} from './directive_resolver';
|
||||
import {ViewResolver} from './view_resolver';
|
||||
import {PipeResolver} from './pipe_resolver';
|
||||
import {ViewMetadata} from '../metadata/view';
|
||||
import {DEFAULT_PIPES_TOKEN} from 'angular2/src/core/pipes';
|
||||
|
||||
import {
|
||||
visitAllCommands,
|
||||
CompiledTemplate,
|
||||
CompiledHostTemplate,
|
||||
TemplateCmd,
|
||||
CommandVisitor,
|
||||
EmbeddedTemplateCmd,
|
||||
BeginComponentCmd,
|
||||
BeginElementCmd,
|
||||
IBeginElementCmd,
|
||||
TextCmd,
|
||||
NgContentCmd
|
||||
} from './template_commands';
|
||||
|
||||
import {Renderer} from 'angular2/render';
|
||||
import {APP_ID} from 'angular2/src/core/render/dom/dom_tokens';
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class ProtoViewFactory {
|
||||
private _cache: Map<number, AppProtoView> = new Map<number, AppProtoView>();
|
||||
private _defaultPipes: Type[];
|
||||
private _appId: string;
|
||||
|
||||
constructor(private _renderer: Renderer, @Inject(DEFAULT_PIPES_TOKEN) defaultPipes: Type[],
|
||||
private _directiveResolver: DirectiveResolver, private _viewResolver: ViewResolver,
|
||||
private _pipeResolver: PipeResolver, @Inject(APP_ID) appId: string) {
|
||||
this._defaultPipes = defaultPipes;
|
||||
this._appId = appId;
|
||||
}
|
||||
|
||||
clearCache() { this._cache.clear(); }
|
||||
|
||||
createHost(compiledHostTemplate: CompiledHostTemplate): AppProtoView {
|
||||
var compiledTemplate = compiledHostTemplate.getTemplate();
|
||||
var result = this._cache.get(compiledTemplate.id);
|
||||
if (isBlank(result)) {
|
||||
var templateData = compiledTemplate.getData(this._appId);
|
||||
result =
|
||||
new AppProtoView(templateData.commands, ViewType.HOST, true,
|
||||
templateData.changeDetectorFactory, null, new ProtoPipes(new Map()));
|
||||
this._cache.set(compiledTemplate.id, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private _createComponent(cmd: BeginComponentCmd): AppProtoView {
|
||||
var nestedProtoView = this._cache.get(cmd.templateId);
|
||||
if (isBlank(nestedProtoView)) {
|
||||
var component = cmd.directives[0];
|
||||
var view = this._viewResolver.resolve(component);
|
||||
var compiledTemplateData = cmd.template.getData(this._appId);
|
||||
|
||||
this._renderer.registerComponentTemplate(cmd.templateId, compiledTemplateData.commands,
|
||||
compiledTemplateData.styles);
|
||||
var boundPipes = this._flattenPipes(view).map(pipe => this._bindPipe(pipe));
|
||||
|
||||
nestedProtoView = new AppProtoView(compiledTemplateData.commands, ViewType.COMPONENT, true,
|
||||
compiledTemplateData.changeDetectorFactory, null,
|
||||
ProtoPipes.fromBindings(boundPipes));
|
||||
// Note: The cache is updated before recursing
|
||||
// to be able to resolve cycles
|
||||
this._cache.set(cmd.template.id, nestedProtoView);
|
||||
this._initializeProtoView(nestedProtoView, null);
|
||||
}
|
||||
return nestedProtoView;
|
||||
}
|
||||
|
||||
private _createEmbeddedTemplate(cmd: EmbeddedTemplateCmd, parent: AppProtoView): AppProtoView {
|
||||
var nestedProtoView = new AppProtoView(
|
||||
cmd.children, ViewType.EMBEDDED, cmd.isMerged, cmd.changeDetectorFactory,
|
||||
arrayToMap(cmd.variableNameAndValues, true), new ProtoPipes(parent.pipes.config));
|
||||
if (cmd.isMerged) {
|
||||
this.initializeProtoViewIfNeeded(nestedProtoView);
|
||||
}
|
||||
return nestedProtoView;
|
||||
}
|
||||
|
||||
initializeProtoViewIfNeeded(protoView: AppProtoView) {
|
||||
if (!protoView.isInitialized()) {
|
||||
var render = this._renderer.createProtoView(protoView.templateCmds);
|
||||
this._initializeProtoView(protoView, render);
|
||||
}
|
||||
}
|
||||
|
||||
private _initializeProtoView(protoView: AppProtoView, render: RenderProtoViewRef) {
|
||||
var initializer = new _ProtoViewInitializer(protoView, this._directiveResolver, this);
|
||||
visitAllCommands(initializer, protoView.templateCmds);
|
||||
var mergeInfo =
|
||||
new AppProtoViewMergeInfo(initializer.mergeEmbeddedViewCount, initializer.mergeElementCount,
|
||||
initializer.mergeViewCount);
|
||||
protoView.init(render, initializer.elementBinders, initializer.boundTextCount, mergeInfo,
|
||||
initializer.variableLocations);
|
||||
}
|
||||
|
||||
private _bindPipe(typeOrBinding): PipeBinding {
|
||||
let meta = this._pipeResolver.resolve(typeOrBinding);
|
||||
return PipeBinding.createFromType(typeOrBinding, meta);
|
||||
}
|
||||
|
||||
private _flattenPipes(view: ViewMetadata): any[] {
|
||||
if (isBlank(view.pipes)) return this._defaultPipes;
|
||||
var pipes = ListWrapper.clone(this._defaultPipes);
|
||||
_flattenList(view.pipes, pipes);
|
||||
return pipes;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function createComponent(protoViewFactory: ProtoViewFactory, cmd: BeginComponentCmd): AppProtoView {
|
||||
return (<any>protoViewFactory)._createComponent(cmd);
|
||||
}
|
||||
|
||||
function createEmbeddedTemplate(protoViewFactory: ProtoViewFactory, cmd: EmbeddedTemplateCmd,
|
||||
parent: AppProtoView): AppProtoView {
|
||||
return (<any>protoViewFactory)._createEmbeddedTemplate(cmd, parent);
|
||||
}
|
||||
|
||||
class _ProtoViewInitializer implements CommandVisitor {
|
||||
variableLocations: Map<string, number> = new Map<string, number>();
|
||||
boundTextCount: number = 0;
|
||||
boundElementIndex: number = 0;
|
||||
elementBinderStack: ElementBinder[] = [];
|
||||
distanceToParentElementBinder: number = 0;
|
||||
distanceToParentProtoElementInjector: number = 0;
|
||||
elementBinders: ElementBinder[] = [];
|
||||
mergeEmbeddedViewCount: number = 0;
|
||||
mergeElementCount: number = 0;
|
||||
mergeViewCount: number = 1;
|
||||
|
||||
constructor(private _protoView: AppProtoView, private _directiveResolver: DirectiveResolver,
|
||||
private _protoViewFactory: ProtoViewFactory) {}
|
||||
|
||||
visitText(cmd: TextCmd, context: any): any {
|
||||
if (cmd.isBound) {
|
||||
this.boundTextCount++;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
visitNgContent(cmd: NgContentCmd, context: any): any { return null; }
|
||||
visitBeginElement(cmd: BeginElementCmd, context: any): any {
|
||||
if (cmd.isBound) {
|
||||
this._visitBeginBoundElement(cmd, null);
|
||||
} else {
|
||||
this._visitBeginElement(cmd, null, null);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
visitEndElement(context: any): any { return this._visitEndElement(); }
|
||||
visitBeginComponent(cmd: BeginComponentCmd, context: any): any {
|
||||
var nestedProtoView = createComponent(this._protoViewFactory, cmd);
|
||||
return this._visitBeginBoundElement(cmd, nestedProtoView);
|
||||
}
|
||||
visitEndComponent(context: any): any { return this._visitEndElement(); }
|
||||
visitEmbeddedTemplate(cmd: EmbeddedTemplateCmd, context: any): any {
|
||||
var nestedProtoView = createEmbeddedTemplate(this._protoViewFactory, cmd, this._protoView);
|
||||
if (cmd.isMerged) {
|
||||
this.mergeEmbeddedViewCount++;
|
||||
}
|
||||
this._visitBeginBoundElement(cmd, nestedProtoView);
|
||||
return this._visitEndElement();
|
||||
}
|
||||
|
||||
private _visitBeginBoundElement(cmd: IBeginElementCmd, nestedProtoView: AppProtoView): any {
|
||||
if (isPresent(nestedProtoView) && nestedProtoView.isMergable) {
|
||||
this.mergeElementCount += nestedProtoView.mergeInfo.elementCount;
|
||||
this.mergeViewCount += nestedProtoView.mergeInfo.viewCount;
|
||||
this.mergeEmbeddedViewCount += nestedProtoView.mergeInfo.embeddedViewCount;
|
||||
}
|
||||
var elementBinder = _createElementBinder(
|
||||
this._directiveResolver, nestedProtoView, this.elementBinderStack, this.boundElementIndex,
|
||||
this.distanceToParentElementBinder, this.distanceToParentProtoElementInjector, cmd);
|
||||
this.elementBinders.push(elementBinder);
|
||||
var protoElementInjector = elementBinder.protoElementInjector;
|
||||
for (var i = 0; i < cmd.variableNameAndValues.length; i += 2) {
|
||||
this.variableLocations.set(<string>cmd.variableNameAndValues[i], this.boundElementIndex);
|
||||
}
|
||||
this.boundElementIndex++;
|
||||
this.mergeElementCount++;
|
||||
return this._visitBeginElement(cmd, elementBinder, protoElementInjector);
|
||||
}
|
||||
|
||||
private _visitBeginElement(cmd: IBeginElementCmd, elementBinder: ElementBinder,
|
||||
protoElementInjector: ProtoElementInjector): any {
|
||||
this.distanceToParentElementBinder =
|
||||
isPresent(elementBinder) ? 1 : this.distanceToParentElementBinder + 1;
|
||||
this.distanceToParentProtoElementInjector =
|
||||
isPresent(protoElementInjector) ? 1 : this.distanceToParentProtoElementInjector + 1;
|
||||
this.elementBinderStack.push(elementBinder);
|
||||
return null;
|
||||
}
|
||||
|
||||
private _visitEndElement(): any {
|
||||
var parentElementBinder = this.elementBinderStack.pop();
|
||||
var parentProtoElementInjector =
|
||||
isPresent(parentElementBinder) ? parentElementBinder.protoElementInjector : null;
|
||||
this.distanceToParentElementBinder = isPresent(parentElementBinder) ?
|
||||
parentElementBinder.distanceToParent :
|
||||
this.distanceToParentElementBinder - 1;
|
||||
this.distanceToParentProtoElementInjector = isPresent(parentProtoElementInjector) ?
|
||||
parentProtoElementInjector.distanceToParent :
|
||||
this.distanceToParentProtoElementInjector - 1;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function _createElementBinder(directiveResolver: DirectiveResolver, nestedProtoView: AppProtoView,
|
||||
elementBinderStack: ElementBinder[], boundElementIndex: number,
|
||||
distanceToParentBinder: number, distanceToParentPei: number,
|
||||
beginElementCmd: IBeginElementCmd): ElementBinder {
|
||||
var parentElementBinder: ElementBinder = null;
|
||||
var parentProtoElementInjector: ProtoElementInjector = null;
|
||||
if (distanceToParentBinder > 0) {
|
||||
parentElementBinder = elementBinderStack[elementBinderStack.length - distanceToParentBinder];
|
||||
}
|
||||
if (isBlank(parentElementBinder)) {
|
||||
distanceToParentBinder = -1;
|
||||
}
|
||||
if (distanceToParentPei > 0) {
|
||||
var peiBinder = elementBinderStack[elementBinderStack.length - distanceToParentPei];
|
||||
if (isPresent(peiBinder)) {
|
||||
parentProtoElementInjector = peiBinder.protoElementInjector;
|
||||
}
|
||||
}
|
||||
if (isBlank(parentProtoElementInjector)) {
|
||||
distanceToParentPei = -1;
|
||||
}
|
||||
var componentDirectiveBinding: DirectiveBinding = null;
|
||||
var isEmbeddedTemplate = false;
|
||||
var directiveBindings: DirectiveBinding[] =
|
||||
beginElementCmd.directives.map(type => bindDirective(directiveResolver, type));
|
||||
if (beginElementCmd instanceof BeginComponentCmd) {
|
||||
componentDirectiveBinding = directiveBindings[0];
|
||||
} else if (beginElementCmd instanceof EmbeddedTemplateCmd) {
|
||||
isEmbeddedTemplate = true;
|
||||
}
|
||||
|
||||
var protoElementInjector = null;
|
||||
// Create a protoElementInjector for any element that either has bindings *or* has one
|
||||
// or more var- defined *or* for <template> elements:
|
||||
// - Elements with a var- defined need a their own element injector
|
||||
// so that, when hydrating, $implicit can be set to the element.
|
||||
// - <template> elements need their own ElementInjector so that we can query their TemplateRef
|
||||
var hasVariables = beginElementCmd.variableNameAndValues.length > 0;
|
||||
if (directiveBindings.length > 0 || hasVariables || isEmbeddedTemplate) {
|
||||
var directiveVariableBindings = new Map<string, number>();
|
||||
if (!isEmbeddedTemplate) {
|
||||
directiveVariableBindings =
|
||||
createDirectiveVariableBindings(beginElementCmd.variableNameAndValues, directiveBindings);
|
||||
}
|
||||
protoElementInjector = ProtoElementInjector.create(
|
||||
parentProtoElementInjector, boundElementIndex, directiveBindings,
|
||||
isPresent(componentDirectiveBinding), distanceToParentPei, directiveVariableBindings);
|
||||
protoElementInjector.attributes = arrayToMap(beginElementCmd.attrNameAndValues, false);
|
||||
}
|
||||
|
||||
return new ElementBinder(boundElementIndex, parentElementBinder, distanceToParentBinder,
|
||||
protoElementInjector, componentDirectiveBinding, nestedProtoView);
|
||||
}
|
||||
|
||||
function bindDirective(directiveResolver: DirectiveResolver, type: Type): DirectiveBinding {
|
||||
let annotation = directiveResolver.resolve(type);
|
||||
return DirectiveBinding.createFromType(type, annotation);
|
||||
}
|
||||
|
||||
export function createDirectiveVariableBindings(variableNameAndValues: Array<string | number>,
|
||||
directiveBindings: DirectiveBinding[]):
|
||||
Map<string, number> {
|
||||
var directiveVariableBindings = new Map<string, number>();
|
||||
for (var i = 0; i < variableNameAndValues.length; i += 2) {
|
||||
var templateName = <string>variableNameAndValues[i];
|
||||
var dirIndex = <number>variableNameAndValues[i + 1];
|
||||
if (isNumber(dirIndex)) {
|
||||
directiveVariableBindings.set(templateName, dirIndex);
|
||||
} else {
|
||||
// a variable without a directive index -> reference the element
|
||||
directiveVariableBindings.set(templateName, null);
|
||||
}
|
||||
}
|
||||
return directiveVariableBindings;
|
||||
}
|
||||
|
||||
|
||||
function arrayToMap(arr: string[], inverse: boolean): Map<string, string> {
|
||||
var result = new Map<string, string>();
|
||||
for (var i = 0; i < arr.length; i += 2) {
|
||||
if (inverse) {
|
||||
result.set(arr[i + 1], arr[i]);
|
||||
} else {
|
||||
result.set(arr[i], arr[i + 1]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function _flattenList(tree: any[], out: Array<Type | Binding | any[]>): void {
|
||||
for (var i = 0; i < tree.length; i++) {
|
||||
var item = resolveForwardRef(tree[i]);
|
||||
if (isArray(item)) {
|
||||
_flattenList(item, out);
|
||||
} else {
|
||||
out.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class BindingRecordsCreator {
|
||||
_directiveRecordsMap = new Map<number, DirectiveRecord>();
|
||||
_directiveRecordsMap: Map<number, DirectiveRecord> = new Map<number, DirectiveRecord>();
|
||||
|
||||
getEventBindingRecords(elementBinders: RenderElementBinder[],
|
||||
allDirectiveMetadatas: RenderDirectiveMetadata[]): BindingRecord[] {
|
||||
@ -199,58 +513,6 @@ export class BindingRecordsCreator {
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ProtoViewFactory {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
constructor(public _changeDetection: ChangeDetection) {}
|
||||
|
||||
createAppProtoViews(hostComponentBinding: DirectiveBinding, rootRenderProtoView: ProtoViewDto,
|
||||
allDirectives: DirectiveBinding[], pipes: PipeBinding[]): AppProtoView[] {
|
||||
var allRenderDirectiveMetadata =
|
||||
ListWrapper.map(allDirectives, directiveBinding => directiveBinding.metadata);
|
||||
var nestedPvsWithIndex = _collectNestedProtoViews(rootRenderProtoView);
|
||||
var nestedPvVariableBindings = _collectNestedProtoViewsVariableBindings(nestedPvsWithIndex);
|
||||
var nestedPvVariableNames = _collectNestedProtoViewsVariableNames(nestedPvsWithIndex);
|
||||
|
||||
var protoChangeDetectors =
|
||||
this._getProtoChangeDetectors(hostComponentBinding, nestedPvsWithIndex,
|
||||
nestedPvVariableNames, allRenderDirectiveMetadata);
|
||||
|
||||
var appProtoViews = ListWrapper.createFixedSize(nestedPvsWithIndex.length);
|
||||
ListWrapper.forEach(nestedPvsWithIndex, (pvWithIndex: RenderProtoViewWithIndex) => {
|
||||
var appProtoView =
|
||||
_createAppProtoView(pvWithIndex.renderProtoView, protoChangeDetectors[pvWithIndex.index],
|
||||
nestedPvVariableBindings[pvWithIndex.index], allDirectives, pipes);
|
||||
if (isPresent(pvWithIndex.parentIndex)) {
|
||||
var parentView = appProtoViews[pvWithIndex.parentIndex];
|
||||
parentView.elementBinders[pvWithIndex.boundElementIndex].nestedProtoView = appProtoView;
|
||||
}
|
||||
appProtoViews[pvWithIndex.index] = appProtoView;
|
||||
});
|
||||
return appProtoViews;
|
||||
}
|
||||
|
||||
private _getProtoChangeDetectors(hostComponentBinding: DirectiveBinding,
|
||||
nestedPvsWithIndex: RenderProtoViewWithIndex[],
|
||||
nestedPvVariableNames: string[][],
|
||||
allRenderDirectiveMetadata: any[]): ProtoChangeDetector[] {
|
||||
if (this._changeDetection.generateDetectors) {
|
||||
var changeDetectorDefs = _getChangeDetectorDefinitions(
|
||||
hostComponentBinding.metadata, nestedPvsWithIndex, nestedPvVariableNames,
|
||||
allRenderDirectiveMetadata, this._changeDetection.genConfig);
|
||||
return changeDetectorDefs.map(changeDetectorDef =>
|
||||
this._changeDetection.getProtoChangeDetector(
|
||||
changeDetectorDef.id, changeDetectorDef));
|
||||
} else {
|
||||
var changeDetectorIds =
|
||||
_getChangeDetectorDefinitionIds(hostComponentBinding.metadata, nestedPvsWithIndex);
|
||||
return changeDetectorIds.map(id => this._changeDetection.getProtoChangeDetector(id, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data needed to create ChangeDetectors
|
||||
* for the given ProtoView and all nested ProtoViews.
|
||||
@ -311,11 +573,6 @@ function _getChangeDetectorDefinitions(
|
||||
});
|
||||
}
|
||||
|
||||
function _getChangeDetectorDefinitionIds(hostComponentMetadata: RenderDirectiveMetadata,
|
||||
nestedPvsWithIndex: RenderProtoViewWithIndex[]): string[] {
|
||||
return nestedPvsWithIndex.map(pvWithIndex => _protoViewId(hostComponentMetadata, pvWithIndex));
|
||||
}
|
||||
|
||||
function _protoViewId(hostComponentMetadata: RenderDirectiveMetadata,
|
||||
pvWithIndex: RenderProtoViewWithIndex): string {
|
||||
var typeString;
|
||||
@ -329,36 +586,6 @@ function _protoViewId(hostComponentMetadata: RenderDirectiveMetadata,
|
||||
return `${hostComponentMetadata.id}_${typeString}_${pvWithIndex.index}`;
|
||||
}
|
||||
|
||||
function _createAppProtoView(
|
||||
renderProtoView: ProtoViewDto, protoChangeDetector: ProtoChangeDetector,
|
||||
variableBindings: Map<string, string>, allDirectives: DirectiveBinding[], pipes: PipeBinding[]):
|
||||
AppProtoView {
|
||||
var elementBinders = renderProtoView.elementBinders;
|
||||
// Embedded ProtoViews that contain `<ng-content>` will be merged into their parents and use
|
||||
// a RenderFragmentRef. I.e. renderProtoView.transitiveNgContentCount > 0.
|
||||
var protoPipes = new ProtoPipes(pipes);
|
||||
var protoView = new AppProtoView(
|
||||
renderProtoView.type, renderProtoView.transitiveNgContentCount > 0, renderProtoView.render,
|
||||
protoChangeDetector, variableBindings, createVariableLocations(elementBinders),
|
||||
renderProtoView.textBindings.length, protoPipes);
|
||||
_createElementBinders(protoView, elementBinders, allDirectives);
|
||||
return protoView;
|
||||
}
|
||||
|
||||
function _collectNestedProtoViewsVariableBindings(nestedPvsWithIndex: RenderProtoViewWithIndex[]):
|
||||
Array<Map<string, string>> {
|
||||
return ListWrapper.map(nestedPvsWithIndex, (pvWithIndex) => {
|
||||
return _createVariableBindings(pvWithIndex.renderProtoView);
|
||||
});
|
||||
}
|
||||
|
||||
function _createVariableBindings(renderProtoView): Map<string, string> {
|
||||
var variableBindings = new Map<string, string>();
|
||||
MapWrapper.forEach(renderProtoView.variableBindings,
|
||||
(mappedName, varName) => { variableBindings.set(varName, mappedName); });
|
||||
return variableBindings;
|
||||
}
|
||||
|
||||
function _collectNestedProtoViewsVariableNames(nestedPvsWithIndex: RenderProtoViewWithIndex[]):
|
||||
string[][] {
|
||||
var nestedPvVariableNames = ListWrapper.createFixedSize(nestedPvsWithIndex.length);
|
||||
@ -382,150 +609,7 @@ function _createVariableNames(parentVariableNames: string[], renderProtoView): s
|
||||
return res;
|
||||
}
|
||||
|
||||
export function createVariableLocations(elementBinders: RenderElementBinder[]):
|
||||
Map<string, number> {
|
||||
var variableLocations = new Map<string, number>();
|
||||
for (var i = 0; i < elementBinders.length; i++) {
|
||||
var binder = elementBinders[i];
|
||||
MapWrapper.forEach(binder.variableBindings,
|
||||
(mappedName, varName) => { variableLocations.set(mappedName, i); });
|
||||
}
|
||||
return variableLocations;
|
||||
}
|
||||
|
||||
function _createElementBinders(protoView, elementBinders, allDirectiveBindings) {
|
||||
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 === RenderDirectiveMetadata.COMPONENT_TYPE) {
|
||||
componentDirectiveBinding = directiveBindings[0];
|
||||
}
|
||||
}
|
||||
var protoElementInjector =
|
||||
_createProtoElementInjector(i, parentPeiWithDistance, renderElementBinder,
|
||||
componentDirectiveBinding, directiveBindings);
|
||||
|
||||
_createElementBinder(protoView, i, renderElementBinder, protoElementInjector,
|
||||
componentDirectiveBinding, directiveBindings);
|
||||
}
|
||||
}
|
||||
|
||||
function _findParentProtoElementInjectorWithDistance(
|
||||
binderIndex, elementBinders, renderElementBinders): ParentProtoElementInjectorWithDistance {
|
||||
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, 0);
|
||||
}
|
||||
|
||||
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 *or* for <template> elements:
|
||||
// - Elements with a var- defined need a their own element injector
|
||||
// so that, when hydrating, $implicit can be set to the element.
|
||||
// - <template> elements need their own ElementInjector so that we can query their TemplateRef
|
||||
var hasVariables = MapWrapper.size(renderElementBinder.variableBindings) > 0;
|
||||
if (directiveBindings.length > 0 || hasVariables ||
|
||||
isPresent(renderElementBinder.nestedProtoView)) {
|
||||
var directiveVariableBindings =
|
||||
createDirectiveVariableBindings(renderElementBinder, directiveBindings);
|
||||
protoElementInjector =
|
||||
ProtoElementInjector.create(parentPeiWithDistance.protoElementInjector, binderIndex,
|
||||
directiveBindings, isPresent(componentDirectiveBinding),
|
||||
parentPeiWithDistance.distance, directiveVariableBindings);
|
||||
protoElementInjector.attributes = renderElementBinder.readAttributes;
|
||||
}
|
||||
return protoElementInjector;
|
||||
}
|
||||
|
||||
function _createElementBinder(protoView: AppProtoView, boundElementIndex, renderElementBinder,
|
||||
protoElementInjector, componentDirectiveBinding, directiveBindings):
|
||||
ElementBinder {
|
||||
var parent = null;
|
||||
if (renderElementBinder.parentIndex !== -1) {
|
||||
parent = protoView.elementBinders[renderElementBinder.parentIndex];
|
||||
}
|
||||
var elBinder = protoView.bindElement(parent, renderElementBinder.distanceToParent,
|
||||
protoElementInjector, componentDirectiveBinding);
|
||||
// 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) => { protoView.protoLocals.set(mappedName, null); });
|
||||
return elBinder;
|
||||
}
|
||||
|
||||
export function createDirectiveVariableBindings(renderElementBinder: RenderElementBinder,
|
||||
directiveBindings: DirectiveBinding[]):
|
||||
Map<string, number> {
|
||||
var directiveVariableBindings = new Map<string, number>();
|
||||
MapWrapper.forEach(renderElementBinder.variableBindings, (templateName, exportAs) => {
|
||||
var dirIndex = _findDirectiveIndexByExportAs(renderElementBinder, directiveBindings, exportAs);
|
||||
directiveVariableBindings.set(templateName, dirIndex);
|
||||
});
|
||||
return directiveVariableBindings;
|
||||
}
|
||||
|
||||
function _findDirectiveIndexByExportAs(renderElementBinder, directiveBindings, exportAs) {
|
||||
var matchedDirectiveIndex = null;
|
||||
var matchedDirective;
|
||||
|
||||
for (var i = 0; i < directiveBindings.length; ++i) {
|
||||
var directive = directiveBindings[i];
|
||||
|
||||
if (_directiveExportAs(directive) == exportAs) {
|
||||
if (isPresent(matchedDirective)) {
|
||||
throw new BaseException(
|
||||
`More than one directive have exportAs = '${exportAs}'. Directives: [${matchedDirective.displayName}, ${directive.displayName}]`);
|
||||
}
|
||||
|
||||
matchedDirectiveIndex = i;
|
||||
matchedDirective = directive;
|
||||
}
|
||||
}
|
||||
|
||||
if (isBlank(matchedDirective) && !StringWrapper.equals(exportAs, "$implicit")) {
|
||||
throw new BaseException(`Cannot find directive with exportAs = '${exportAs}'`);
|
||||
}
|
||||
|
||||
return matchedDirectiveIndex;
|
||||
}
|
||||
|
||||
function _directiveExportAs(directive): string {
|
||||
var directiveExportAs = directive.metadata.exportAs;
|
||||
if (isBlank(directiveExportAs) &&
|
||||
directive.metadata.type === RenderDirectiveMetadata.COMPONENT_TYPE) {
|
||||
return "$implicit";
|
||||
} else {
|
||||
return directiveExportAs;
|
||||
}
|
||||
}
|
||||
|
||||
class RenderProtoViewWithIndex {
|
||||
constructor(public renderProtoView: ProtoViewDto, public index: number,
|
||||
public parentIndex: number, public boundElementIndex: number) {}
|
||||
}
|
||||
|
||||
class ParentProtoElementInjectorWithDistance {
|
||||
constructor(public protoElementInjector: ProtoElementInjector, public distance: number) {}
|
||||
}
|
||||
|
@ -52,5 +52,7 @@ export class TemplateRef {
|
||||
/**
|
||||
* Allows you to check if this Embedded Template defines Local Variable with name matching `name`.
|
||||
*/
|
||||
hasLocal(name: string): boolean { return this._getProtoView().variableBindings.has(name); }
|
||||
hasLocal(name: string): boolean {
|
||||
return this._getProtoView().templateVariableBindings.has(name);
|
||||
}
|
||||
}
|
||||
|
@ -33,45 +33,12 @@ import {ViewRef, ProtoViewRef, internalView} from './view_ref';
|
||||
import {ElementRef} from './element_ref';
|
||||
import {ProtoPipes} from 'angular2/src/core/pipes/pipes';
|
||||
import {camelCaseToDashCase} from 'angular2/src/core/render/dom/util';
|
||||
import {TemplateCmd} from './template_commands';
|
||||
|
||||
export {DebugContext} from 'angular2/src/core/change_detection/interfaces';
|
||||
|
||||
const REFLECT_PREFIX: string = 'ng-reflect-';
|
||||
|
||||
export class AppProtoViewMergeMapping {
|
||||
renderProtoViewRef: renderApi.RenderProtoViewRef;
|
||||
renderFragmentCount: number;
|
||||
renderElementIndices: number[];
|
||||
renderInverseElementIndices: number[];
|
||||
renderTextIndices: number[];
|
||||
nestedViewIndicesByElementIndex: number[];
|
||||
hostElementIndicesByViewIndex: number[];
|
||||
nestedViewCountByViewIndex: number[];
|
||||
constructor(renderProtoViewMergeMapping: renderApi.RenderProtoViewMergeMapping) {
|
||||
this.renderProtoViewRef = renderProtoViewMergeMapping.mergedProtoViewRef;
|
||||
this.renderFragmentCount = renderProtoViewMergeMapping.fragmentCount;
|
||||
this.renderElementIndices = renderProtoViewMergeMapping.mappedElementIndices;
|
||||
this.renderInverseElementIndices = inverseIndexMapping(
|
||||
this.renderElementIndices, renderProtoViewMergeMapping.mappedElementCount);
|
||||
this.renderTextIndices = renderProtoViewMergeMapping.mappedTextIndices;
|
||||
this.hostElementIndicesByViewIndex = renderProtoViewMergeMapping.hostElementIndicesByViewIndex;
|
||||
this.nestedViewIndicesByElementIndex =
|
||||
inverseIndexMapping(this.hostElementIndicesByViewIndex, this.renderElementIndices.length);
|
||||
this.nestedViewCountByViewIndex = renderProtoViewMergeMapping.nestedViewCountByViewIndex;
|
||||
}
|
||||
}
|
||||
|
||||
function inverseIndexMapping(input: number[], resultLength: number): number[] {
|
||||
var result = ListWrapper.createGrowableSize(resultLength);
|
||||
for (var i = 0; i < input.length; i++) {
|
||||
var value = input[i];
|
||||
if (isPresent(value)) {
|
||||
result[input[i]] = i;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export class AppViewContainer {
|
||||
// The order in this list matches the DOM order.
|
||||
views: AppView[] = [];
|
||||
@ -109,7 +76,6 @@ export class AppView implements ChangeDispatcher, RenderEventDispatcher {
|
||||
ref: ViewRef;
|
||||
changeDetector: ChangeDetector = null;
|
||||
|
||||
|
||||
/**
|
||||
* The context against which data-binding expressions in this view are evaluated against.
|
||||
* This is always a component instance.
|
||||
@ -125,10 +91,10 @@ export class AppView implements ChangeDispatcher, RenderEventDispatcher {
|
||||
locals: Locals;
|
||||
|
||||
constructor(public renderer: renderApi.Renderer, public proto: AppProtoView,
|
||||
public mainMergeMapping: AppProtoViewMergeMapping, public viewOffset: number,
|
||||
public elementOffset: number, public textOffset: number,
|
||||
public viewOffset: number, public elementOffset: number, public textOffset: number,
|
||||
protoLocals: Map<string, any>, public render: renderApi.RenderViewRef,
|
||||
public renderFragment: renderApi.RenderFragmentRef) {
|
||||
public renderFragment: renderApi.RenderFragmentRef,
|
||||
public containerElementInjector: ElementInjector) {
|
||||
this.ref = new ViewRef(this);
|
||||
|
||||
this.locals = new Locals(null, MapWrapper.clone(protoLocals)); // TODO optimize this
|
||||
@ -148,10 +114,10 @@ export class AppView implements ChangeDispatcher, RenderEventDispatcher {
|
||||
|
||||
setLocal(contextName: string, value: any): void {
|
||||
if (!this.hydrated()) throw new BaseException('Cannot set locals on dehydrated view.');
|
||||
if (!this.proto.variableBindings.has(contextName)) {
|
||||
if (!this.proto.templateVariableBindings.has(contextName)) {
|
||||
return;
|
||||
}
|
||||
var templateName = this.proto.variableBindings.get(contextName);
|
||||
var templateName = this.proto.templateVariableBindings.get(contextName);
|
||||
this.locals.set(templateName, value);
|
||||
}
|
||||
|
||||
@ -175,9 +141,7 @@ export class AppView implements ChangeDispatcher, RenderEventDispatcher {
|
||||
// dispatch to element injector or text nodes based on context
|
||||
notifyOnBinding(b: BindingTarget, currentValue: any): void {
|
||||
if (b.isTextNode()) {
|
||||
this.renderer.setText(
|
||||
this.render, this.mainMergeMapping.renderTextIndices[b.elementIndex + this.textOffset],
|
||||
currentValue);
|
||||
this.renderer.setText(this.render, b.elementIndex + this.textOffset, currentValue);
|
||||
} else {
|
||||
var elementRef = this.elementRefs[this.elementOffset + b.elementIndex];
|
||||
if (b.isElementProperty()) {
|
||||
@ -226,13 +190,14 @@ export class AppView implements ChangeDispatcher, RenderEventDispatcher {
|
||||
}
|
||||
|
||||
getNestedView(boundElementIndex: number): AppView {
|
||||
var viewIndex = this.mainMergeMapping.nestedViewIndicesByElementIndex[boundElementIndex];
|
||||
return isPresent(viewIndex) ? this.views[viewIndex] : null;
|
||||
var eli = this.elementInjectors[boundElementIndex];
|
||||
return isPresent(eli) ? eli.getNestedView() : null;
|
||||
}
|
||||
|
||||
getHostElement(): ElementRef {
|
||||
var boundElementIndex = this.mainMergeMapping.hostElementIndicesByViewIndex[this.viewOffset];
|
||||
return isPresent(boundElementIndex) ? this.elementRefs[boundElementIndex] : null;
|
||||
getContainerElement(): ElementRef {
|
||||
return isPresent(this.containerElementInjector) ?
|
||||
this.containerElementInjector.getElementRef() :
|
||||
null;
|
||||
}
|
||||
|
||||
getDebugContext(elementIndex: number, directiveIndex: DirectiveIndex): DebugContext {
|
||||
@ -241,11 +206,11 @@ export class AppView implements ChangeDispatcher, RenderEventDispatcher {
|
||||
var hasRefForIndex = offsettedIndex < this.elementRefs.length;
|
||||
|
||||
var elementRef = hasRefForIndex ? this.elementRefs[this.elementOffset + elementIndex] : null;
|
||||
var host = this.getHostElement();
|
||||
var container = this.getContainerElement();
|
||||
var ei = hasRefForIndex ? this.elementInjectors[this.elementOffset + elementIndex] : null;
|
||||
|
||||
var element = isPresent(elementRef) ? elementRef.nativeElement : null;
|
||||
var componentElement = isPresent(host) ? host.nativeElement : null;
|
||||
var componentElement = isPresent(container) ? container.nativeElement : null;
|
||||
var directive = isPresent(directiveIndex) ? this.getDirectiveFor(directiveIndex) : null;
|
||||
var injector = isPresent(ei) ? ei.getInjector() : null;
|
||||
|
||||
@ -269,10 +234,9 @@ export class AppView implements ChangeDispatcher, RenderEventDispatcher {
|
||||
}
|
||||
|
||||
// implementation of RenderEventDispatcher#dispatchRenderEvent
|
||||
dispatchRenderEvent(renderElementIndex: number, eventName: string,
|
||||
dispatchRenderEvent(boundElementIndex: number, eventName: string,
|
||||
locals: Map<string, any>): boolean {
|
||||
var elementRef =
|
||||
this.elementRefs[this.mainMergeMapping.renderInverseElementIndices[renderElementIndex]];
|
||||
var elementRef = this.elementRefs[boundElementIndex];
|
||||
var view = internalView(elementRef.parentView);
|
||||
return view.dispatchEvent(elementRef.boundElementIndex, eventName, locals);
|
||||
}
|
||||
@ -326,36 +290,53 @@ class EventEvaluationError extends WrappedException {
|
||||
}
|
||||
}
|
||||
|
||||
export class AppProtoViewMergeInfo {
|
||||
constructor(public embeddedViewCount: number, public elementCount: number,
|
||||
public viewCount: number) {}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export class AppProtoView {
|
||||
elementBinders: ElementBinder[] = [];
|
||||
protoLocals = new Map<string, any>();
|
||||
mergeMapping: AppProtoViewMergeMapping;
|
||||
ref: ProtoViewRef;
|
||||
protoLocals: Map<string, any>;
|
||||
|
||||
constructor(public type: renderApi.ViewType, public isEmbeddedFragment: boolean,
|
||||
public render: renderApi.RenderProtoViewRef,
|
||||
public protoChangeDetector: ProtoChangeDetector,
|
||||
public variableBindings: Map<string, string>,
|
||||
public variableLocations: Map<string, number>, public textBindingCount: number,
|
||||
public pipes: ProtoPipes) {
|
||||
elementBinders: ElementBinder[] = null;
|
||||
mergeInfo: AppProtoViewMergeInfo = null;
|
||||
variableLocations: Map<string, number> = null;
|
||||
textBindingCount = null;
|
||||
render: renderApi.RenderProtoViewRef = null;
|
||||
|
||||
constructor(public templateCmds: TemplateCmd[], public type: renderApi.ViewType,
|
||||
public isMergable: boolean, public changeDetectorFactory: Function,
|
||||
public templateVariableBindings: Map<string, string>, public pipes: ProtoPipes) {
|
||||
this.ref = new ProtoViewRef(this);
|
||||
if (isPresent(variableBindings)) {
|
||||
MapWrapper.forEach(variableBindings,
|
||||
}
|
||||
|
||||
init(render: renderApi.RenderProtoViewRef, elementBinders: ElementBinder[],
|
||||
textBindingCount: number, mergeInfo: AppProtoViewMergeInfo,
|
||||
variableLocations: Map<string, number>) {
|
||||
this.render = render;
|
||||
this.elementBinders = elementBinders;
|
||||
this.textBindingCount = textBindingCount;
|
||||
this.mergeInfo = mergeInfo;
|
||||
this.variableLocations = variableLocations;
|
||||
this.protoLocals = new Map<string, any>();
|
||||
if (isPresent(this.templateVariableBindings)) {
|
||||
MapWrapper.forEach(this.templateVariableBindings,
|
||||
(templateName, _) => { this.protoLocals.set(templateName, null); });
|
||||
}
|
||||
if (isPresent(variableLocations)) {
|
||||
// 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(variableLocations,
|
||||
(_, templateName) => { this.protoLocals.set(templateName, null); });
|
||||
}
|
||||
}
|
||||
|
||||
bindElement(parent: ElementBinder, distanceToParent: number,
|
||||
protoElementInjector: ProtoElementInjector,
|
||||
componentDirective: DirectiveBinding = null): ElementBinder {
|
||||
var elBinder = new ElementBinder(this.elementBinders.length, parent, distanceToParent,
|
||||
protoElementInjector, componentDirective);
|
||||
|
||||
this.elementBinders.push(elBinder);
|
||||
return elBinder;
|
||||
}
|
||||
}
|
||||
isInitialized(): boolean { return isPresent(this.elementBinders); }
|
||||
}
|
@ -1,4 +1,11 @@
|
||||
import {Injector, Binding, Injectable, ResolvedBinding} from 'angular2/src/core/di';
|
||||
import {
|
||||
Injector,
|
||||
Inject,
|
||||
Binding,
|
||||
Injectable,
|
||||
ResolvedBinding,
|
||||
forwardRef
|
||||
} from 'angular2/src/core/di';
|
||||
import {isPresent, isBlank} from 'angular2/src/core/facade/lang';
|
||||
import {BaseException} from 'angular2/src/core/facade/exceptions';
|
||||
import * as viewModule from './view';
|
||||
@ -17,6 +24,7 @@ import {AppViewManagerUtils} from './view_manager_utils';
|
||||
import {AppViewPool} from './view_pool';
|
||||
import {AppViewListener} from './view_listener';
|
||||
import {wtfCreateScope, wtfLeave, WtfScopeFn} from '../profile/profile';
|
||||
import {ProtoViewFactory} from './proto_view_factory';
|
||||
|
||||
/**
|
||||
* Service exposing low level API for creating, moving and destroying Views.
|
||||
@ -26,11 +34,15 @@ import {wtfCreateScope, wtfLeave, WtfScopeFn} from '../profile/profile';
|
||||
*/
|
||||
@Injectable()
|
||||
export class AppViewManager {
|
||||
private _protoViewFactory: ProtoViewFactory;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
constructor(private _viewPool: AppViewPool, private _viewListener: AppViewListener,
|
||||
private _utils: AppViewManagerUtils, private _renderer: Renderer) {}
|
||||
private _utils: AppViewManagerUtils, private _renderer: Renderer,
|
||||
@Inject(forwardRef(() => ProtoViewFactory)) _protoViewFactory) {
|
||||
this._protoViewFactory = _protoViewFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link ViewContainerRef} of the View Container at the specified location.
|
||||
@ -138,13 +150,13 @@ export class AppViewManager {
|
||||
injector: Injector): HostViewRef {
|
||||
var s = this._createRootHostViewScope();
|
||||
var hostProtoView: viewModule.AppProtoView = internalProtoView(hostProtoViewRef);
|
||||
this._protoViewFactory.initializeProtoViewIfNeeded(hostProtoView);
|
||||
var hostElementSelector = overrideSelector;
|
||||
if (isBlank(hostElementSelector)) {
|
||||
hostElementSelector = hostProtoView.elementBinders[0].componentDirective.metadata.selector;
|
||||
}
|
||||
var renderViewWithFragments = this._renderer.createRootHostView(
|
||||
hostProtoView.mergeMapping.renderProtoViewRef,
|
||||
hostProtoView.mergeMapping.renderFragmentCount, hostElementSelector);
|
||||
hostProtoView.render, hostProtoView.mergeInfo.embeddedViewCount + 1, hostElementSelector);
|
||||
var hostView = this._createMainView(hostProtoView, renderViewWithFragments);
|
||||
|
||||
this._renderer.hydrateView(hostView.render);
|
||||
@ -196,6 +208,7 @@ export class AppViewManager {
|
||||
if (protoView.type !== ViewType.EMBEDDED) {
|
||||
throw new BaseException('This method can only be called with embedded ProtoViews!');
|
||||
}
|
||||
this._protoViewFactory.initializeProtoViewIfNeeded(protoView);
|
||||
return wtfLeave(s, this._createViewInContainer(viewContainerLocation, index, protoView,
|
||||
templateRef.elementRef, null));
|
||||
}
|
||||
@ -226,6 +239,7 @@ export class AppViewManager {
|
||||
if (protoView.type !== ViewType.HOST) {
|
||||
throw new BaseException('This method can only be called with host ProtoViews!');
|
||||
}
|
||||
this._protoViewFactory.initializeProtoViewIfNeeded(protoView);
|
||||
return wtfLeave(
|
||||
s, this._createViewInContainer(viewContainerLocation, index, protoView,
|
||||
viewContainerLocation, imperativelyCreatedInjector));
|
||||
@ -345,8 +359,8 @@ export class AppViewManager {
|
||||
var view = this._viewPool.getView(protoView);
|
||||
if (isBlank(view)) {
|
||||
view = this._createMainView(
|
||||
protoView, this._renderer.createView(protoView.mergeMapping.renderProtoViewRef,
|
||||
protoView.mergeMapping.renderFragmentCount));
|
||||
protoView,
|
||||
this._renderer.createView(protoView.render, protoView.mergeInfo.embeddedViewCount + 1));
|
||||
}
|
||||
return view;
|
||||
}
|
||||
@ -385,8 +399,7 @@ export class AppViewManager {
|
||||
}
|
||||
var viewContainers = view.viewContainers;
|
||||
var startViewOffset = view.viewOffset;
|
||||
var endViewOffset =
|
||||
view.viewOffset + view.mainMergeMapping.nestedViewCountByViewIndex[view.viewOffset];
|
||||
var endViewOffset = view.viewOffset + view.proto.mergeInfo.viewCount - 1;
|
||||
var elementOffset = view.elementOffset;
|
||||
for (var viewIdx = startViewOffset; viewIdx <= endViewOffset; viewIdx++) {
|
||||
var currView = view.views[viewIdx];
|
||||
|
@ -3,7 +3,6 @@ import {ListWrapper, MapWrapper, Map, StringMapWrapper} from 'angular2/src/core/
|
||||
import * as eli from './element_injector';
|
||||
import {isPresent, isBlank} from 'angular2/src/core/facade/lang';
|
||||
import * as viewModule from './view';
|
||||
import {internalView} from './view_ref';
|
||||
import * as avmModule from './view_manager';
|
||||
import {ElementRef} from './element_ref';
|
||||
import {TemplateRef} from './template_ref';
|
||||
@ -27,42 +26,52 @@ export class AppViewManagerUtils {
|
||||
var renderFragments = renderViewWithFragments.fragmentRefs;
|
||||
var renderView = renderViewWithFragments.viewRef;
|
||||
|
||||
var elementCount = mergedParentViewProto.mergeMapping.renderElementIndices.length;
|
||||
var viewCount = mergedParentViewProto.mergeMapping.nestedViewCountByViewIndex[0] + 1;
|
||||
var elementCount = mergedParentViewProto.mergeInfo.elementCount;
|
||||
var viewCount = mergedParentViewProto.mergeInfo.viewCount;
|
||||
var elementRefs: ElementRef[] = ListWrapper.createFixedSize(elementCount);
|
||||
var viewContainers = ListWrapper.createFixedSize(elementCount);
|
||||
var preBuiltObjects: eli.PreBuiltObjects[] = ListWrapper.createFixedSize(elementCount);
|
||||
var elementInjectors = ListWrapper.createFixedSize(elementCount);
|
||||
var elementInjectors: eli.ElementInjector[] = ListWrapper.createFixedSize(elementCount);
|
||||
var views = ListWrapper.createFixedSize(viewCount);
|
||||
|
||||
var elementOffset = 0;
|
||||
var textOffset = 0;
|
||||
var fragmentIdx = 0;
|
||||
var containerElementIndicesByViewIndex: number[] = ListWrapper.createFixedSize(viewCount);
|
||||
for (var viewOffset = 0; viewOffset < viewCount; viewOffset++) {
|
||||
var hostElementIndex =
|
||||
mergedParentViewProto.mergeMapping.hostElementIndicesByViewIndex[viewOffset];
|
||||
var parentView = isPresent(hostElementIndex) ?
|
||||
internalView(elementRefs[hostElementIndex].parentView) :
|
||||
null;
|
||||
var containerElementIndex = containerElementIndicesByViewIndex[viewOffset];
|
||||
var containerElementInjector =
|
||||
isPresent(containerElementIndex) ? elementInjectors[containerElementIndex] : null;
|
||||
var parentView =
|
||||
isPresent(containerElementInjector) ? preBuiltObjects[containerElementIndex].view : null;
|
||||
var protoView =
|
||||
isPresent(hostElementIndex) ?
|
||||
parentView.proto.elementBinders[hostElementIndex - parentView.elementOffset]
|
||||
isPresent(containerElementIndex) ?
|
||||
parentView.proto.elementBinders[containerElementIndex - parentView.elementOffset]
|
||||
.nestedProtoView :
|
||||
mergedParentViewProto;
|
||||
var renderFragment = null;
|
||||
if (viewOffset === 0 || protoView.type === ViewType.EMBEDDED) {
|
||||
renderFragment = renderFragments[fragmentIdx++];
|
||||
}
|
||||
var currentView = new viewModule.AppView(
|
||||
renderer, protoView, mergedParentViewProto.mergeMapping, viewOffset, elementOffset,
|
||||
textOffset, protoView.protoLocals, renderView, renderFragment);
|
||||
var currentView = new viewModule.AppView(renderer, protoView, viewOffset, elementOffset,
|
||||
textOffset, protoView.protoLocals, renderView,
|
||||
renderFragment, containerElementInjector);
|
||||
views[viewOffset] = currentView;
|
||||
if (isPresent(containerElementIndex)) {
|
||||
preBuiltObjects[containerElementIndex].nestedView = currentView;
|
||||
}
|
||||
var rootElementInjectors = [];
|
||||
var nestedViewOffset = viewOffset + 1;
|
||||
for (var binderIdx = 0; binderIdx < protoView.elementBinders.length; binderIdx++) {
|
||||
var binder = protoView.elementBinders[binderIdx];
|
||||
var boundElementIndex = elementOffset + binderIdx;
|
||||
var elementInjector = null;
|
||||
|
||||
if (isPresent(binder.nestedProtoView) && binder.nestedProtoView.isMergable) {
|
||||
containerElementIndicesByViewIndex[nestedViewOffset] = boundElementIndex;
|
||||
nestedViewOffset += binder.nestedProtoView.mergeInfo.viewCount;
|
||||
}
|
||||
|
||||
// elementInjectors and rootElementInjectors
|
||||
var protoElementInjector = binder.protoElementInjector;
|
||||
if (isPresent(protoElementInjector)) {
|
||||
@ -78,19 +87,20 @@ export class AppViewManagerUtils {
|
||||
elementInjectors[boundElementIndex] = elementInjector;
|
||||
|
||||
// elementRefs
|
||||
var el = new ElementRef(
|
||||
currentView.ref, boundElementIndex,
|
||||
mergedParentViewProto.mergeMapping.renderElementIndices[boundElementIndex], renderer);
|
||||
var el = new ElementRef(currentView.ref, boundElementIndex, renderer);
|
||||
elementRefs[el.boundElementIndex] = el;
|
||||
|
||||
// preBuiltObjects
|
||||
if (isPresent(elementInjector)) {
|
||||
var templateRef = binder.hasEmbeddedProtoView() ? new TemplateRef(el) : null;
|
||||
var templateRef = isPresent(binder.nestedProtoView) &&
|
||||
binder.nestedProtoView.type === ViewType.EMBEDDED ?
|
||||
new TemplateRef(el) :
|
||||
null;
|
||||
preBuiltObjects[boundElementIndex] =
|
||||
new eli.PreBuiltObjects(viewManager, currentView, el, templateRef);
|
||||
}
|
||||
}
|
||||
currentView.init(protoView.protoChangeDetector.instantiate(currentView), elementInjectors,
|
||||
currentView.init(protoView.changeDetectorFactory(currentView), elementInjectors,
|
||||
rootElementInjectors, preBuiltObjects, views, elementRefs, viewContainers);
|
||||
if (isPresent(parentView) && protoView.type === ViewType.COMPONENT) {
|
||||
parentView.changeDetector.addShadowDomChild(currentView.changeDetector);
|
||||
@ -166,20 +176,19 @@ export class AppViewManagerUtils {
|
||||
_hydrateView(initView: viewModule.AppView, imperativelyCreatedInjector: Injector,
|
||||
hostElementInjector: eli.ElementInjector, context: Object, parentLocals: Locals) {
|
||||
var viewIdx = initView.viewOffset;
|
||||
var endViewOffset = viewIdx + initView.mainMergeMapping.nestedViewCountByViewIndex[viewIdx];
|
||||
var endViewOffset = viewIdx + initView.proto.mergeInfo.viewCount - 1;
|
||||
while (viewIdx <= endViewOffset) {
|
||||
var currView = initView.views[viewIdx];
|
||||
var currProtoView = currView.proto;
|
||||
if (currView !== initView && currView.proto.type === ViewType.EMBEDDED) {
|
||||
// Don't hydrate components of embedded fragment views.
|
||||
viewIdx += initView.mainMergeMapping.nestedViewCountByViewIndex[viewIdx] + 1;
|
||||
viewIdx += currView.proto.mergeInfo.viewCount;
|
||||
} else {
|
||||
if (currView !== initView) {
|
||||
// hydrate a nested component view
|
||||
imperativelyCreatedInjector = null;
|
||||
parentLocals = null;
|
||||
var hostElementIndex = initView.mainMergeMapping.hostElementIndicesByViewIndex[viewIdx];
|
||||
hostElementInjector = initView.elementInjectors[hostElementIndex];
|
||||
hostElementInjector = currView.containerElementInjector;
|
||||
context = hostElementInjector.getComponent();
|
||||
}
|
||||
currView.context = context;
|
||||
@ -233,8 +242,7 @@ export class AppViewManagerUtils {
|
||||
}
|
||||
|
||||
dehydrateView(initView: viewModule.AppView) {
|
||||
var endViewOffset = initView.viewOffset +
|
||||
initView.mainMergeMapping.nestedViewCountByViewIndex[initView.viewOffset];
|
||||
var endViewOffset = initView.viewOffset + initView.proto.mergeInfo.viewCount - 1;
|
||||
for (var viewIdx = initView.viewOffset; viewIdx <= endViewOffset; viewIdx++) {
|
||||
var currView = initView.views[viewIdx];
|
||||
if (currView.hydrated()) {
|
||||
|
Reference in New Issue
Block a user