fix: make all tests green with new view engine and JIT

Note that this does not yet include enabling the view engine
by default.

Included refactoring:
- view engine: split namespace of elements / attributes already
  when creating the `NodeDef`
- view engine: when injecting the old `Renderer`, use an implementation
  that is based on `RendererV2`
- view engine: store view queries in the component view, not
  on the host element
This commit is contained in:
Tobias Bosch
2017-02-17 08:56:36 -08:00
committed by Igor Minar
parent 74ce121dba
commit b9f17a9cb2
37 changed files with 527 additions and 300 deletions

View File

@ -92,8 +92,12 @@ export class ComponentRef_<C> extends ComponentRef<C> {
* @stable
*/
export class ComponentFactory<C> {
/** @internal */
_viewClass: Type<AppView<any>>;
/**
* TODO(tbosch): type this properly to ViewDefinitionFactory again once the view engine
* is the default.
* @internal
*/
_viewClass: any;
constructor(
public selector: string, _viewClass: Type<AppView<any>>, public componentType: Type<any>) {
this._viewClass = _viewClass;

View File

@ -10,7 +10,7 @@ import {isDevMode} from '../application_ref';
import {SecurityContext} from '../security';
import {BindingDef, BindingType, DebugContext, DisposableFn, ElementData, ElementOutputDef, NodeData, NodeDef, NodeFlags, NodeType, QueryValueType, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, asElementData} from './types';
import {checkAndUpdateBinding, dispatchEvent, elementEventFullName, filterQueryId, getParentRenderElement, resolveViewDefinition, sliceErrorStack, splitMatchedQueriesDsl} from './util';
import {checkAndUpdateBinding, dispatchEvent, elementEventFullName, filterQueryId, getParentRenderElement, resolveViewDefinition, sliceErrorStack, splitMatchedQueriesDsl, splitNamespace} from './util';
export function anchorDef(
flags: NodeFlags, matchedQueriesDsl: [string | number, QueryValueType][],
@ -36,6 +36,7 @@ export function anchorDef(
bindings: [],
disposableCount: 0,
element: {
ns: undefined,
name: undefined,
attrs: undefined,
outputs: [], template, source,
@ -54,8 +55,8 @@ export function anchorDef(
export function elementDef(
flags: NodeFlags, matchedQueriesDsl: [string | number, QueryValueType][],
ngContentIndex: number, childCount: number, name: string,
fixedAttrs: {[name: string]: string} = {},
ngContentIndex: number, childCount: number, namespaceAndName: string,
fixedAttrs: [string, string][] = [],
bindings?:
([BindingType.ElementClass, string] | [BindingType.ElementStyle, string, string] |
[BindingType.ElementAttribute | BindingType.ElementProperty, string, SecurityContext])[],
@ -63,13 +64,18 @@ export function elementDef(
// skip the call to sliceErrorStack itself + the call to this function.
const source = isDevMode() ? sliceErrorStack(2, 3) : '';
const {matchedQueries, references, matchedQueryIds} = splitMatchedQueriesDsl(matchedQueriesDsl);
let ns: string;
let name: string;
if (namespaceAndName) {
[ns, name] = splitNamespace(namespaceAndName);
}
bindings = bindings || [];
const bindingDefs: BindingDef[] = new Array(bindings.length);
for (let i = 0; i < bindings.length; i++) {
const entry = bindings[i];
let bindingDef: BindingDef;
const bindingType = entry[0];
const name = entry[1];
const [ns, name] = splitNamespace(entry[1]);
let securityContext: SecurityContext;
let suffix: string;
switch (bindingType) {
@ -81,7 +87,7 @@ export function elementDef(
securityContext = <SecurityContext>entry[2];
break;
}
bindingDefs[i] = {type: bindingType, name, nonMinifiedName: name, securityContext, suffix};
bindingDefs[i] = {type: bindingType, ns, name, nonMinifiedName: name, securityContext, suffix};
}
outputs = outputs || [];
const outputDefs: ElementOutputDef[] = new Array(outputs.length);
@ -96,6 +102,11 @@ export function elementDef(
}
outputDefs[i] = {eventName: eventName, target: target};
}
fixedAttrs = fixedAttrs || [];
const attrs = <[string, string, string][]>fixedAttrs.map(([namespaceAndName, value]) => {
const [ns, name] = splitNamespace(namespaceAndName);
return [ns, name, value];
});
return {
type: NodeType.Element,
// will bet set by the view definition
@ -112,8 +123,9 @@ export function elementDef(
bindings: bindingDefs,
disposableCount: outputDefs.length,
element: {
ns,
name,
attrs: fixedAttrs,
attrs,
outputs: outputDefs, source,
template: undefined,
// will bet set by the view definition
@ -136,9 +148,7 @@ export function createElement(view: ViewData, renderHost: any, def: NodeDef): El
let el: any;
if (view.parent || !rootSelectorOrNode) {
if (elDef.name) {
// TODO(vicb): move the namespace to the node definition
const nsAndName = splitNamespace(elDef.name);
el = renderer.createElement(nsAndName[1], nsAndName[0]);
el = renderer.createElement(elDef.name, elDef.ns);
} else {
el = renderer.createComment('');
}
@ -150,10 +160,9 @@ export function createElement(view: ViewData, renderHost: any, def: NodeDef): El
el = renderer.selectRootElement(rootSelectorOrNode);
}
if (elDef.attrs) {
for (let attrName in elDef.attrs) {
// TODO(vicb): move the namespace to the node definition
const nsAndName = splitNamespace(attrName);
renderer.setAttribute(el, nsAndName[1], elDef.attrs[attrName], nsAndName[0]);
for (let i = 0; i < elDef.attrs.length; i++) {
const [ns, name, value] = elDef.attrs[i];
renderer.setAttribute(el, name, value, ns);
}
}
if (elDef.outputs.length) {
@ -217,11 +226,11 @@ function checkAndUpdateElementValue(view: ViewData, def: NodeDef, bindingIdx: nu
return;
}
const binding = def.bindings[bindingIdx];
const name = binding.name;
const renderNode = asElementData(view, def.index).renderElement;
const name = binding.name;
switch (binding.type) {
case BindingType.ElementAttribute:
setElementAttribute(view, binding, renderNode, name, value);
setElementAttribute(view, binding, renderNode, binding.ns, name, value);
break;
case BindingType.ElementClass:
setElementClass(view, renderNode, name, value);
@ -236,17 +245,15 @@ function checkAndUpdateElementValue(view: ViewData, def: NodeDef, bindingIdx: nu
}
function setElementAttribute(
view: ViewData, binding: BindingDef, renderNode: any, name: string, value: any) {
view: ViewData, binding: BindingDef, renderNode: any, ns: string, name: string, value: any) {
const securityContext = binding.securityContext;
let renderValue = securityContext ? view.root.sanitizer.sanitize(securityContext, value) : value;
renderValue = renderValue != null ? renderValue.toString() : null;
const renderer = view.renderer;
// TODO(vicb): move the namespace to the node definition
const nsAndName = splitNamespace(name);
if (value != null) {
renderer.setAttribute(renderNode, nsAndName[1], renderValue, nsAndName[0]);
renderer.setAttribute(renderNode, name, renderValue, ns);
} else {
renderer.removeAttribute(renderNode, nsAndName[1], nsAndName[0]);
renderer.removeAttribute(renderNode, name, ns);
}
}
@ -285,13 +292,3 @@ function setElementProperty(
let renderValue = securityContext ? view.root.sanitizer.sanitize(securityContext, value) : value;
view.renderer.setProperty(renderNode, name, renderValue);
}
const NS_PREFIX_RE = /^:([^:]+):(.+)$/;
function splitNamespace(name: string): string[] {
if (name[0] === ':') {
const match = name.match(NS_PREFIX_RE);
return [match[1], match[2]];
}
return ['', name];
}

View File

@ -11,10 +11,10 @@ export {ngContentDef} from './ng_content';
export {directiveDef, pipeDef, providerDef} from './provider';
export {pureArrayDef, pureObjectDef, purePipeDef} from './pure_expression';
export {queryDef} from './query';
export {createComponentFactory} from './refs';
export {ViewRef_, createComponentFactory, nodeValue} from './refs';
export {initServicesIfNeeded} from './services';
export {textDef} from './text';
export {createComponentRenderTypeV2, elementEventFullName, nodeValue, rootRenderNodes, unwrapValue} from './util';
export {createComponentRenderTypeV2, elementEventFullName, rootRenderNodes, unwrapValue} from './util';
export {viewDef} from './view';
export {attachEmbeddedView, detachEmbeddedView, moveEmbeddedView} from './view_attach';

View File

@ -12,9 +12,9 @@ import {ElementRef} from '../linker/element_ref';
import {TemplateRef} from '../linker/template_ref';
import {ViewContainerRef} from '../linker/view_container_ref';
import {ViewEncapsulation} from '../metadata/view';
import {ComponentRenderTypeV2, RenderComponentType as RenderComponentTypeV1, Renderer as RendererV1, RendererFactoryV2, RendererV2, RootRenderer as RootRendererV1} from '../render/api';
import {ComponentRenderTypeV2, Renderer as RendererV1, RendererFactoryV2, RendererV2} from '../render/api';
import {createChangeDetectorRef, createInjector, createTemplateRef, createViewContainerRef} from './refs';
import {createChangeDetectorRef, createInjector, createRendererV1, createTemplateRef, createViewContainerRef} from './refs';
import {BindingDef, BindingType, DepDef, DepFlags, DirectiveOutputDef, DisposableFn, NodeData, NodeDef, NodeFlags, NodeType, ProviderData, ProviderType, QueryBindingType, QueryDef, QueryValueType, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewState, asElementData, asProviderData} from './types';
import {checkAndUpdateBinding, dispatchEvent, filterQueryId, isComponentView, splitMatchedQueriesDsl, tokenKey, viewParentEl} from './util';
@ -40,6 +40,7 @@ export function directiveDef(
bindings[bindingIndex] = {
type: BindingType.DirectiveProperty,
name: prop, nonMinifiedName,
ns: undefined,
securityContext: undefined,
suffix: undefined
};
@ -337,14 +338,7 @@ export function resolveDep(
switch (tokenKey) {
case RendererV1TokenKey: {
const compView = findCompView(view, elDef, allowPrivateServices);
const compDef = compView.parentNodeDef;
const rootRendererV1: RootRendererV1 = view.root.injector.get(RootRendererV1);
// Note: Don't fill in the styles as they have been installed already via the RendererV2!
const compRenderType = compDef.provider.componentRenderType;
return rootRendererV1.renderComponent(new RenderComponentTypeV1(
compRenderType ? compRenderType.id : '0', '', 0,
compRenderType ? compRenderType.encapsulation : ViewEncapsulation.None, [], {}));
return createRendererV1(compView);
}
case RendererV2TokenKey: {
const compView = findCompView(view, elDef, allowPrivateServices);

View File

@ -29,6 +29,7 @@ function _pureExpressionDef(type: PureExpressionType, propertyNames: string[]):
bindings[i] = {
type: BindingType.PureExpressionProperty,
name: prop,
ns: undefined,
nonMinifiedName: prop,
securityContext: undefined,
suffix: undefined

View File

@ -80,14 +80,14 @@ export function dirtyParentQueries(view: ViewData) {
}
// view queries
let compDef = view.parentNodeDef;
view = view.parent;
if (view) {
for (let i = compDef.index + 1; i <= compDef.index + compDef.childCount; i++) {
if (view.def.nodeFlags & NodeFlags.HasViewQuery) {
for (let i = 0; i < view.def.nodes.length; i++) {
const nodeDef = view.def.nodes[i];
if ((nodeDef.flags & NodeFlags.HasViewQuery) && (nodeDef.flags & NodeFlags.HasDynamicQuery)) {
asQueryList(view, i).setDirty();
}
// only visit the root nodes
i += nodeDef.childCount;
}
}
}
@ -97,16 +97,16 @@ export function checkAndUpdateQuery(view: ViewData, nodeDef: NodeDef) {
if (!queryList.dirty) {
return;
}
const providerDef = nodeDef.parent;
const providerData = asProviderData(view, providerDef.index);
let directiveInstance: any;
let newValues: any[];
if (nodeDef.flags & NodeFlags.HasContentQuery) {
const elementDef = providerDef.parent;
const elementDef = nodeDef.parent.parent;
newValues = calcQueryValues(
view, elementDef.index, elementDef.index + elementDef.childCount, nodeDef.query, []);
directiveInstance = asProviderData(view, nodeDef.parent.index).instance;
} else if (nodeDef.flags & NodeFlags.HasViewQuery) {
const compView = providerData.componentView;
newValues = calcQueryValues(compView, 0, compView.def.nodes.length - 1, nodeDef.query, []);
newValues = calcQueryValues(view, 0, view.def.nodes.length - 1, nodeDef.query, []);
directiveInstance = view.component;
}
queryList.reset(newValues);
const bindings = nodeDef.query.bindings;
@ -123,7 +123,7 @@ export function checkAndUpdateQuery(view: ViewData, nodeDef: NodeDef) {
notify = true;
break;
}
providerData.instance[binding.propName] = boundValue;
directiveInstance[binding.propName] = boundValue;
}
if (notify) {
queryList.notifyOnChanges();

View File

@ -6,6 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import {NoOpAnimationPlayer} from '../animation/animation_player';
import {ApplicationRef} from '../application_ref';
import {ChangeDetectorRef} from '../change_detection/change_detection';
import {Injector} from '../di';
import {ComponentFactory, ComponentRef} from '../linker/component_factory';
@ -13,10 +15,12 @@ import {ElementRef} from '../linker/element_ref';
import {TemplateRef} from '../linker/template_ref';
import {ViewContainerRef} from '../linker/view_container_ref';
import {EmbeddedViewRef, ViewRef} from '../linker/view_ref';
import {Renderer as RendererV1, RendererV2} from '../render/api';
import {Type} from '../type';
import {VERSION} from '../version';
import {ArgumentType, BindingType, DebugContext, DepFlags, ElementData, NodeCheckFn, NodeData, NodeDef, NodeFlags, NodeType, RootData, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewState, asElementData, asProviderData} from './types';
import {isComponentView, renderNode, resolveViewDefinition, rootRenderNodes, tokenKey, viewParentEl} from './util';
import {ArgumentType, BindingType, DebugContext, DepFlags, ElementData, NodeCheckFn, NodeData, NodeDef, NodeFlags, NodeType, RootData, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewState, asElementData, asProviderData, asTextData} from './types';
import {isComponentView, renderNode, resolveViewDefinition, rootRenderNodes, splitNamespace, tokenKey, viewParentEl} from './util';
const EMPTY_CONTEXT = new Object();
@ -26,16 +30,9 @@ export function createComponentFactory(
return new ComponentFactory_(selector, componentType, viewDefFactory);
}
class ComponentFactory_ implements ComponentFactory<any> {
/**
* We are not renaming this field as the old ComponentFactory is using it.
* @internal */
_viewClass: any;
constructor(
public selector: string, public componentType: Type<any>,
_viewDefFactory: ViewDefinitionFactory) {
this._viewClass = _viewDefFactory;
class ComponentFactory_ extends ComponentFactory<any> {
constructor(selector: string, componentType: Type<any>, viewDefFactory: ViewDefinitionFactory) {
super(selector, <any>viewDefFactory, componentType);
}
/**
@ -49,13 +46,16 @@ class ComponentFactory_ implements ComponentFactory<any> {
const view = Services.createRootView(
injector, projectableNodes || [], rootSelectorOrNode, viewDef, EMPTY_CONTEXT);
const component = asProviderData(view, componentNodeIndex).instance;
view.renderer.setAttribute(asElementData(view, 0).renderElement, 'ng-version', VERSION.full);
return new ComponentRef_(view, new ViewRef_(view), component);
}
}
class ComponentRef_ implements ComponentRef<any> {
class ComponentRef_ extends ComponentRef<any> {
private _elDef: NodeDef;
constructor(private _view: ViewData, private _viewRef: ViewRef, private _component: any) {
super();
this._elDef = this._view.def.nodes[0];
}
get location(): ElementRef {
@ -103,7 +103,11 @@ class ViewContainerRef_ implements ViewContainerRef {
}
}
get(index: number): ViewRef { return new ViewRef_(this._data.embeddedViews[index]); }
get(index: number): ViewRef {
const ref = new ViewRef_(this._data.embeddedViews[index]);
ref.attachToViewContainerRef(this);
return ref;
}
get length(): number { return this._data.embeddedViews.length; };
@ -124,8 +128,10 @@ class ViewContainerRef_ implements ViewContainerRef {
}
insert(viewRef: ViewRef, index?: number): ViewRef {
const viewData = (<ViewRef_>viewRef)._view;
const viewRef_ = <ViewRef_>viewRef;
const viewData = viewRef_._view;
Services.attachEmbeddedView(this._data, index, viewData);
viewRef_.attachToViewContainerRef(this);
return viewRef;
}
@ -147,6 +153,7 @@ class ViewContainerRef_ implements ViewContainerRef {
detach(index?: number): ViewRef {
const view = this.get(index);
Services.detachEmbeddedView(this._data, index);
(view as ViewRef_).detachFromContainer();
return view;
}
}
@ -155,11 +162,17 @@ export function createChangeDetectorRef(view: ViewData): ChangeDetectorRef {
return new ViewRef_(view);
}
class ViewRef_ implements EmbeddedViewRef<any> {
export class ViewRef_ implements EmbeddedViewRef<any> {
/** @internal */
_view: ViewData;
private _viewContainerRef: ViewContainerRef;
private _appRef: ApplicationRef;
constructor(_view: ViewData) { this._view = _view; }
constructor(_view: ViewData) {
this._view = _view;
this._viewContainerRef = null;
this._appRef = null;
}
get rootNodes(): any[] { return rootRenderNodes(this._view); }
@ -173,9 +186,40 @@ class ViewRef_ implements EmbeddedViewRef<any> {
checkNoChanges(): void { Services.checkNoChangesView(this._view); }
reattach(): void { this._view.state |= ViewState.ChecksEnabled; }
onDestroy(callback: Function) { this._view.disposables.push(<any>callback); }
onDestroy(callback: Function) {
if (!this._view.disposables) {
this._view.disposables = [];
}
this._view.disposables.push(<any>callback);
}
destroy() { Services.destroyView(this._view); }
destroy() {
if (this._appRef) {
this._appRef.detachView(this);
} else if (this._viewContainerRef) {
this._viewContainerRef.detach(this._viewContainerRef.indexOf(this));
}
Services.destroyView(this._view);
}
detachFromContainer() {
this._appRef = null;
this._viewContainerRef = null;
}
attachToAppRef(appRef: ApplicationRef) {
if (this._viewContainerRef) {
throw new Error('This view is already attached to a ViewContainer!');
}
this._appRef = appRef;
}
attachToViewContainerRef(vcRef: ViewContainerRef) {
if (this._appRef) {
throw new Error('This view is already attached directly to the ApplicationRef!');
}
this._viewContainerRef = vcRef;
}
}
export function createTemplateRef(view: ViewData, def: NodeDef): TemplateRef<any> {
@ -207,3 +251,137 @@ class Injector_ implements Injector {
{flags: DepFlags.None, token, tokenKey: tokenKey(token)}, notFoundValue);
}
}
export function nodeValue(view: ViewData, index: number): any {
const def = view.def.nodes[index];
switch (def.type) {
case NodeType.Element:
if (def.element.template) {
return createTemplateRef(view, def);
} else {
return asElementData(view, def.index).renderElement;
}
case NodeType.Text:
return asTextData(view, def.index).renderText;
case NodeType.Directive:
case NodeType.Pipe:
case NodeType.Provider:
return asProviderData(view, def.index).instance;
}
return undefined;
}
export function createRendererV1(view: ViewData): RendererV1 {
return new RendererAdapter(view.renderer);
}
class RendererAdapter implements RendererV1 {
constructor(private delegate: RendererV2) {}
selectRootElement(selectorOrNode: string|Element): Element {
return this.delegate.selectRootElement(selectorOrNode);
}
createElement(parent: Element|DocumentFragment, namespaceAndName: string): Element {
const [ns, name] = splitNamespace(namespaceAndName);
const el = this.delegate.createElement(name, ns);
if (parent) {
this.delegate.appendChild(parent, el);
}
return el;
}
createViewRoot(hostElement: Element): Element|DocumentFragment { return hostElement; }
createTemplateAnchor(parentElement: Element|DocumentFragment): Comment {
const comment = this.delegate.createComment('');
if (parentElement) {
this.delegate.appendChild(parentElement, comment);
}
return comment;
}
createText(parentElement: Element|DocumentFragment, value: string): any {
const node = this.delegate.createText(value);
if (parentElement) {
this.delegate.appendChild(parentElement, node);
}
return node;
}
projectNodes(parentElement: Element|DocumentFragment, nodes: Node[]) {
for (let i = 0; i < nodes.length; i++) {
this.delegate.appendChild(parentElement, nodes[i]);
}
}
attachViewAfter(node: Node, viewRootNodes: Node[]) {
const parentElement = this.delegate.parentNode(node);
const nextSibling = this.delegate.nextSibling(node);
for (let i = 0; i < viewRootNodes.length; i++) {
this.delegate.insertBefore(parentElement, viewRootNodes[i], nextSibling);
}
}
detachView(viewRootNodes: (Element|Text|Comment)[]) {
for (let i = 0; i < viewRootNodes.length; i++) {
const node = viewRootNodes[i];
const parentElement = this.delegate.parentNode(node);
this.delegate.removeChild(parentElement, node);
}
}
destroyView(hostElement: Element|DocumentFragment, viewAllNodes: Node[]) {
for (let i = 0; i < viewAllNodes.length; i++) {
this.delegate.destroyNode(viewAllNodes[i]);
}
}
listen(renderElement: any, name: string, callback: Function): Function {
return this.delegate.listen(renderElement, name, <any>callback);
}
listenGlobal(target: string, name: string, callback: Function): Function {
return this.delegate.listen(target, name, <any>callback);
}
setElementProperty(
renderElement: Element|DocumentFragment, propertyName: string, propertyValue: any): void {
this.delegate.setProperty(renderElement, propertyName, propertyValue);
}
setElementAttribute(renderElement: Element, namespaceAndName: string, attributeValue: string):
void {
const [ns, name] = splitNamespace(namespaceAndName);
if (attributeValue != null) {
this.delegate.setAttribute(renderElement, name, attributeValue, ns);
} else {
this.delegate.removeAttribute(renderElement, name, ns);
}
}
setBindingDebugInfo(renderElement: Element, propertyName: string, propertyValue: string): void {}
setElementClass(renderElement: Element, className: string, isAdd: boolean): void {
if (isAdd) {
this.delegate.addClass(renderElement, className);
} else {
this.delegate.removeClass(renderElement, className);
}
}
setElementStyle(renderElement: HTMLElement, styleName: string, styleValue: string): void {
if (styleValue != null) {
this.delegate.setStyle(renderElement, styleName, styleValue, false, false);
} else {
this.delegate.removeStyle(renderElement, styleName, false);
}
}
invokeElementMethod(renderElement: Element, methodName: string, args: any[]): void {
(renderElement as any)[methodName].apply(renderElement, args);
}
setText(renderNode: Text, text: string): void { this.delegate.setValue(renderNode, text); }
animate(): NoOpAnimationPlayer { return new NoOpAnimationPlayer(); }
}

View File

@ -168,7 +168,9 @@ function debugUpdateDirectives(check: NodeCheckFn, view: ViewData) {
function debugCheckDirectivesFn(
view: ViewData, nodeIndex: number, argStyle: ArgumentType, ...values: any[]) {
const result = debugCheckFn(check, view, nodeIndex, argStyle, values);
debugSetCurrentNode(view, nextDirectiveWithBinding(view, nodeIndex));
if (view.def.nodes[nodeIndex].type === NodeType.Directive) {
debugSetCurrentNode(view, nextDirectiveWithBinding(view, nodeIndex));
}
return result;
};
}
@ -183,7 +185,10 @@ function debugUpdateRenderer(check: NodeCheckFn, view: ViewData) {
function debugCheckRenderNodeFn(
view: ViewData, nodeIndex: number, argStyle: ArgumentType, ...values: any[]) {
const result = debugCheckFn(check, view, nodeIndex, argStyle, values);
debugSetCurrentNode(view, nextRenderNodeWithBinding(view, nodeIndex));
const nodeDef = view.def.nodes[nodeIndex];
if (nodeDef.type === NodeType.Element || nodeDef.type === NodeType.Text) {
debugSetCurrentNode(view, nextRenderNodeWithBinding(view, nodeIndex));
}
return result;
}
}
@ -271,7 +276,7 @@ class DebugContext_ implements DebugContext {
private compProviderDef: NodeDef;
constructor(public view: ViewData, public nodeIndex: number) {
if (nodeIndex == null) {
this.nodeIndex = 0;
this.nodeIndex = nodeIndex = 0;
}
this.nodeDef = view.def.nodes[nodeIndex];
let elDef = this.nodeDef;

View File

@ -20,6 +20,7 @@ export function textDef(ngContentIndex: number, constants: string[]): NodeDef {
bindings[i - 1] = {
type: BindingType.TextInterpolation,
name: undefined,
ns: undefined,
nonMinifiedName: undefined,
securityContext: undefined,
suffix: constants[i]

View File

@ -161,6 +161,7 @@ export enum NodeFlags {
export interface BindingDef {
type: BindingType;
ns: string;
name: string;
nonMinifiedName: string;
securityContext: SecurityContext;
@ -187,7 +188,9 @@ export enum QueryValueType {
export interface ElementDef {
name: string;
attrs: {[name: string]: string};
ns: string;
/** ns, name, value */
attrs: [string, string, string][];
outputs: ElementOutputDef[];
template: ViewDefinition;
component: NodeDef;

View File

@ -131,21 +131,6 @@ export function renderNode(view: ViewData, def: NodeDef): any {
}
}
export function nodeValue(view: ViewData, index: number): any {
const def = view.def.nodes[index];
switch (def.type) {
case NodeType.Element:
return asElementData(view, def.index).renderElement;
case NodeType.Text:
return asTextData(view, def.index).renderText;
case NodeType.Directive:
case NodeType.Pipe:
case NodeType.Provider:
return asProviderData(view, def.index).instance;
}
return undefined;
}
export function elementEventFullName(target: string, name: string): string {
return target ? `${target}:${name}` : name;
}
@ -184,13 +169,20 @@ export function splitMatchedQueriesDsl(matchedQueriesDsl: [string | number, Quer
}
export function getParentRenderElement(view: ViewData, renderHost: any, def: NodeDef): any {
let parentEl: any;
if (!def.parent) {
parentEl = renderHost;
} else if (def.renderParent) {
parentEl = asElementData(view, def.renderParent.index).renderElement;
let renderParent = def.renderParent;
if (renderParent) {
const parent = def.parent;
if (parent && (parent.type !== NodeType.Element || !parent.element.component ||
(parent.element.component.provider.componentRenderType &&
parent.element.component.provider.componentRenderType.encapsulation ===
ViewEncapsulation.Native))) {
// only children of non components, or children of components with native encapsulation should
// be attached.
return asElementData(view, def.renderParent.index).renderElement;
}
} else {
return renderHost;
}
return parentEl;
}
const VIEW_DEFINITION_CACHE = new WeakMap<any, ViewDefinition>();
@ -332,3 +324,13 @@ function execRenderNodeAction(
break;
}
}
const NS_PREFIX_RE = /^:([^:]+):(.+)$/;
export function splitNamespace(name: string): string[] {
if (name[0] === ':') {
const match = name.match(NS_PREFIX_RE);
return [match[1], match[2]];
}
return ['', name];
}

View File

@ -55,18 +55,12 @@ export function viewDef(
node.reverseChildIndex =
calculateReverseChildIndex(currentParent, i, node.childCount, nodes.length);
// renderParent needs to account for ng-container!
let currentRenderParent: NodeDef;
if (currentParent &&
(currentParent.type !== NodeType.Element || !currentParent.element.component ||
(currentParent.element.component.provider.componentRenderType &&
currentParent.element.component.provider.componentRenderType.encapsulation ===
ViewEncapsulation.Native))) {
// children of components that don't use native encapsulation should never be attached!
if (currentParent && currentParent.type === NodeType.Element && !currentParent.element.name) {
currentRenderParent = currentParent.renderParent;
} else {
currentRenderParent = currentParent;
}
if (currentParent && currentParent.type === NodeType.Element && !currentParent.element.name) {
currentRenderParent = currentParent.renderParent;
} else {
currentRenderParent = currentParent;
}
node.renderParent = currentRenderParent;
@ -98,7 +92,7 @@ export function viewDef(
viewBindingCount += node.bindings.length;
viewDisposableCount += node.disposableCount;
if (!currentParent) {
if (!currentRenderParent) {
lastRootNode = node;
}
if (node.type === NodeType.Provider || node.type === NodeType.Directive) {
@ -204,10 +198,13 @@ function validateNode(parent: NodeDef, node: NodeDef, nodeCount: number) {
}
}
if (node.query) {
const parentType = parent ? parent.type : null;
if (parentType !== NodeType.Directive) {
if (node.flags & NodeFlags.HasContentQuery && (!parent || parent.type !== NodeType.Directive)) {
throw new Error(
`Illegal State: Query nodes need to be children of directives, at index ${node.index}!`);
`Illegal State: Content Query nodes need to be children of directives, at index ${node.index}!`);
}
if (node.flags & NodeFlags.HasViewQuery && parent) {
throw new Error(
`Illegal State: View Query nodes have to be top level nodes, at index ${node.index}!`);
}
}
if (node.childCount) {
@ -593,8 +590,6 @@ function execQueriesAction(
for (let i = 0; i < nodeCount; i++) {
const nodeDef = view.def.nodes[i];
if ((nodeDef.flags & queryFlags) && (nodeDef.flags & staticDynamicQueryFlag)) {
const elDef = nodeDef.parent.parent;
Services.setCurrentNode(view, nodeDef.index);
switch (action) {
case QueryAction.CheckAndUpdate:

View File

@ -10,6 +10,7 @@ import {APP_BOOTSTRAP_LISTENER, APP_INITIALIZER, CompilerFactory, Component, NgM
import {ApplicationRef, ApplicationRef_} from '@angular/core/src/application_ref';
import {ErrorHandler} from '@angular/core/src/error_handler';
import {ComponentRef} from '@angular/core/src/linker/component_factory';
import {TestComponentRenderer} from '@angular/core/testing';
import {BrowserModule} from '@angular/platform-browser';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {DOCUMENT} from '@angular/platform-browser/src/dom/dom_tokens';
@ -19,21 +20,26 @@ import {ServerModule} from '@angular/platform-server';
import {ComponentFixture, ComponentFixtureNoNgZone, TestBed, async, inject, withModule} from '../testing';
@Component({selector: 'comp', template: 'hello'})
@Component({selector: 'bootstrap-app', template: 'hello'})
class SomeComponent {
}
export function main() {
describe('bootstrap', () => {
let mockConsole: MockConsole;
let fakeDoc: Document;
beforeEach(() => {
fakeDoc = getDOM().createHtmlDocument();
const el = getDOM().createElement('comp', fakeDoc);
getDOM().appendChild(fakeDoc.body, el);
mockConsole = new MockConsole();
});
beforeEach(() => { mockConsole = new MockConsole(); });
function createRootEl() {
const doc = TestBed.get(DOCUMENT);
const rootEl = <HTMLElement>getDOM().firstChild(
getDOM().content(getDOM().createTemplate(`<bootstrap-app></bootstrap-app>`)));
const oldRoots = getDOM().querySelectorAll(doc, 'bootstrap-app');
for (let i = 0; i < oldRoots.length; i++) {
getDOM().remove(oldRoots[i]);
}
getDOM().appendChild(doc.body, rootEl);
}
type CreateModuleOptions = {providers?: any[], ngDoBootstrap?: any, bootstrap?: any[]};
@ -52,10 +58,7 @@ export function main() {
const platformModule = getDOM().supportsDOMEvents() ? BrowserModule : ServerModule;
@NgModule({
providers: [
{provide: ErrorHandler, useValue: errorHandler}, {provide: DOCUMENT, useValue: fakeDoc},
options.providers || []
],
providers: [{provide: ErrorHandler, useValue: errorHandler}, options.providers || []],
imports: [platformModule],
declarations: [SomeComponent],
entryComponents: [SomeComponent],
@ -74,7 +77,8 @@ export function main() {
it('should throw when reentering tick', inject([ApplicationRef], (ref: ApplicationRef_) => {
const view = jasmine.createSpyObj('view', ['detach', 'attachToAppRef']);
const viewRef = jasmine.createSpyObj('viewRef', ['detectChanges']);
const viewRef = jasmine.createSpyObj(
'viewRef', ['detectChanges', 'detachFromContainer', 'attachToAppRef']);
viewRef.internalView = view;
view.ref = viewRef;
try {
@ -101,16 +105,13 @@ export function main() {
it('should be called when a component is bootstrapped',
inject([ApplicationRef], (ref: ApplicationRef_) => {
createRootEl();
const compRef = ref.bootstrap(SomeComponent);
expect(capturedCompRefs).toEqual([compRef]);
}));
});
describe('bootstrap', () => {
beforeEach(
() => {
});
it('should throw if an APP_INITIIALIZER is not yet resolved',
withModule(
{
@ -119,6 +120,7 @@ export function main() {
]
},
inject([ApplicationRef], (ref: ApplicationRef_) => {
createRootEl();
expect(() => ref.bootstrap(SomeComponent))
.toThrowError(
'Cannot bootstrap as there are still asynchronous initializers running. Bootstrap components in the `ngDoBootstrap` method of the root module.');
@ -128,8 +130,10 @@ export function main() {
describe('bootstrapModule', () => {
let defaultPlatform: PlatformRef;
beforeEach(
inject([PlatformRef], (_platform: PlatformRef) => { defaultPlatform = _platform; }));
beforeEach(inject([PlatformRef], (_platform: PlatformRef) => {
createRootEl();
defaultPlatform = _platform;
}));
it('should wait for asynchronous app initializers', async(() => {
let resolve: (result: any) => void;
@ -221,8 +225,10 @@ export function main() {
describe('bootstrapModuleFactory', () => {
let defaultPlatform: PlatformRef;
beforeEach(
inject([PlatformRef], (_platform: PlatformRef) => { defaultPlatform = _platform; }));
beforeEach(inject([PlatformRef], (_platform: PlatformRef) => {
createRootEl();
defaultPlatform = _platform;
}));
it('should wait for asynchronous app initializers', async(() => {
let resolve: (result: any) => void;
const promise: Promise<any> = new Promise((res) => { resolve = res; });
@ -346,7 +352,7 @@ export function main() {
it('should not allow to attach a view to both, a view container and the ApplicationRef',
() => {
const comp = TestBed.createComponent(MyComp);
const hostView = comp.componentRef.hostView;
let hostView = comp.componentRef.hostView;
const containerComp = TestBed.createComponent(ContainerComp);
containerComp.detectChanges();
const vc = containerComp.componentInstance.vc;
@ -355,7 +361,7 @@ export function main() {
vc.insert(hostView);
expect(() => appRef.attachView(hostView))
.toThrowError('This view is already attached to a ViewContainer!');
vc.detach(0);
hostView = vc.detach(0);
appRef.attachView(hostView);
expect(() => vc.insert(hostView))

View File

@ -9,7 +9,6 @@
import {AfterContentInit, AfterViewInit, Component, ContentChildren, Directive, Input, QueryList, ViewChildren} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {beforeEach, describe, it} from '@angular/core/testing/testing_internal';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {expect} from '@angular/platform-browser/testing/matchers';

View File

@ -8,7 +8,6 @@
import {Component, Directive, HostBinding, Input, NO_ERRORS_SCHEMA} from '@angular/core';
import {ComponentFixture, TestBed, getTestBed} from '@angular/core/testing';
import {afterEach, beforeEach, describe, expect, iit, it} from '@angular/core/testing/testing_internal';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {DomSanitizer} from '@angular/platform-browser/src/security/dom_sanitization_service';

View File

@ -82,7 +82,7 @@ export function main() {
it('should set attributes on the root node', () => {
const view = createRootView(
compViewDef([
elementDef(NodeFlags.None, null, null, 0, 'div', {'a': 'b'}),
elementDef(NodeFlags.None, null, null, 0, 'div', [['a', 'b']]),
]),
{}, [], rootNode);
expect(rootNode.getAttribute('a')).toBe('b');
@ -92,7 +92,7 @@ export function main() {
rootNode.appendChild(document.createElement('div'));
const view = createRootView(
compViewDef([
elementDef(NodeFlags.None, null, null, 0, 'div', {'a': 'b'}),
elementDef(NodeFlags.None, null, null, 0, 'div', [['a', 'b']]),
]),
{}, [], rootNode);
expect(rootNode.childNodes.length).toBe(0);

View File

@ -57,7 +57,7 @@ export function main() {
it('should set fixed attributes', () => {
const rootNodes = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 0, 'div', {'title': 'a'}),
elementDef(NodeFlags.None, null, null, 0, 'div', [['title', 'a']]),
])).rootNodes;
expect(rootNodes.length).toBe(1);
expect(getDOM().getAttribute(rootNodes[0], 'title')).toBe('a');

View File

@ -55,10 +55,10 @@ export function main() {
const {view: parentView, rootNodes} = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 2, 'div'),
anchorDef(NodeFlags.HasEmbeddedViews, null, null, 0, embeddedViewDef([
elementDef(NodeFlags.None, null, null, 0, 'span', {'name': 'child0'})
elementDef(NodeFlags.None, null, null, 0, 'span', [['name', 'child0']])
])),
anchorDef(NodeFlags.None, null, null, 0, embeddedViewDef([
elementDef(NodeFlags.None, null, null, 0, 'span', {'name': 'child1'})
elementDef(NodeFlags.None, null, null, 0, 'span', [['name', 'child1']])
]))
]));
const viewContainerData = asElementData(parentView, 1);
@ -85,10 +85,10 @@ export function main() {
const {view: parentView, rootNodes} = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 2, 'div'),
anchorDef(NodeFlags.HasEmbeddedViews, null, null, 0, embeddedViewDef([
elementDef(NodeFlags.None, null, null, 0, 'span', {'name': 'child0'})
elementDef(NodeFlags.None, null, null, 0, 'span', [['name', 'child0']])
])),
anchorDef(NodeFlags.None, null, null, 0, embeddedViewDef([
elementDef(NodeFlags.None, null, null, 0, 'span', {'name': 'child1'})
elementDef(NodeFlags.None, null, null, 0, 'span', [['name', 'child1']])
]))
]));
const viewContainerData = asElementData(parentView, 1);
@ -112,9 +112,9 @@ export function main() {
it('should include embedded views in root nodes', () => {
const {view: parentView} = createAndGetRootNodes(compViewDef([
anchorDef(NodeFlags.HasEmbeddedViews, null, null, 0, embeddedViewDef([
elementDef(NodeFlags.None, null, null, 0, 'span', {'name': 'child0'})
elementDef(NodeFlags.None, null, null, 0, 'span', [['name', 'child0']])
])),
elementDef(NodeFlags.None, null, null, 0, 'span', {'name': 'after'})
elementDef(NodeFlags.None, null, null, 0, 'span', [['name', 'after']])
]));
const childView0 = Services.createEmbeddedView(parentView, parentView.def.nodes[0]);

View File

@ -50,12 +50,16 @@ export function main() {
];
}
function viewQueryProviders(compView: ViewDefinition) {
function viewQueryProviders(nodes: NodeDef[]) {
return [
directiveDef(NodeFlags.None, null, 1, QueryService, [], null, null, () => compView),
queryDef(
NodeFlags.HasViewQuery | NodeFlags.HasDynamicQuery, someQueryId,
{'a': QueryBindingType.All})
directiveDef(
NodeFlags.None, null, 0, QueryService, [], null, null,
() => compViewDef([
queryDef(
NodeFlags.HasViewQuery | NodeFlags.HasDynamicQuery, someQueryId,
{'a': QueryBindingType.All}),
...nodes
])),
];
}
@ -106,11 +110,11 @@ export function main() {
describe('view queries', () => {
it('should query providers in the view', () => {
const {view} = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 2, 'div'),
...viewQueryProviders(compViewDef([
elementDef(NodeFlags.None, null, null, 1, 'div'),
...viewQueryProviders([
elementDef(NodeFlags.None, null, null, 1, 'span'),
aServiceProvider(),
])),
]),
]));
Services.checkAndUpdateView(view);
@ -118,15 +122,15 @@ export function main() {
const comp: QueryService = asProviderData(view, 1).instance;
const compView = asProviderData(view, 1).componentView;
expect(comp.a.length).toBe(1);
expect(comp.a.first).toBe(asProviderData(compView, 1).instance);
expect(comp.a.first).toBe(asProviderData(compView, 2).instance);
});
it('should not query providers on the host element', () => {
const {view} = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 3, 'div'),
...viewQueryProviders(compViewDef([
elementDef(NodeFlags.None, null, null, 2, 'div'),
...viewQueryProviders([
elementDef(NodeFlags.None, null, null, 0, 'span'),
])),
]),
aServiceProvider(),
]));
@ -221,13 +225,13 @@ export function main() {
it('should update view queries if embedded views are added or removed', () => {
const {view} = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 2, 'div'),
...viewQueryProviders(compViewDef([
elementDef(NodeFlags.None, null, null, 1, 'div'),
...viewQueryProviders([
anchorDef(NodeFlags.HasEmbeddedViews, null, null, 0, embeddedViewDef([
elementDef(NodeFlags.None, null, null, 1, 'div'),
aServiceProvider(),
])),
])),
]),
]));
Services.checkAndUpdateView(view);
@ -236,13 +240,13 @@ export function main() {
expect(comp.a.length).toBe(0);
const compView = asProviderData(view, 1).componentView;
const childView = Services.createEmbeddedView(compView, compView.def.nodes[0]);
attachEmbeddedView(asElementData(compView, 0), 0, childView);
const childView = Services.createEmbeddedView(compView, compView.def.nodes[1]);
attachEmbeddedView(asElementData(compView, 1), 0, childView);
Services.checkAndUpdateView(view);
expect(comp.a.length).toBe(1);
detachEmbeddedView(asElementData(compView, 0), 0);
detachEmbeddedView(asElementData(compView, 1), 0);
Services.checkAndUpdateView(view);
expect(comp.a.length).toBe(0);