feat(compiler): implement style encapsulation for new view engine (#14518)

Included refactoring:
- splits the `RendererV2` into a `RendererFactoryV2` and a `RendererV2`
- makes the `DebugRendererV2` a private class in `@angular/core`
- remove `setBindingDebugInfo` from `RendererV2`, but rename `RendererV2.setText` to 
  `RendererV2.setValue` and allow it on comments and text nodes.

Part of #14013
This commit is contained in:
Tobias Bosch
2017-02-16 13:55:55 -08:00
committed by Igor Minar
parent ba17dcbf2b
commit 0fa3895d5b
38 changed files with 828 additions and 595 deletions

View File

@ -93,8 +93,6 @@ export const __core_private__: {
makeDecorator: typeof decorators.makeDecorator,
DebugDomRootRenderer: typeof debug.DebugDomRootRenderer,
_DebugDomRootRenderer: debug.DebugDomRootRenderer,
DebugDomRendererV2: typeof debug.DebugDomRendererV2,
_DebugDomRendererV2: debug.DebugDomRendererV2,
Console: typeof console.Console,
_Console: console.Console,
reflector: typeof reflection.reflector,
@ -158,7 +156,6 @@ export const __core_private__: {
ReflectionCapabilities: reflection_capabilities.ReflectionCapabilities,
makeDecorator: decorators.makeDecorator,
DebugDomRootRenderer: debug.DebugDomRootRenderer,
DebugDomRendererV2: debug.DebugDomRendererV2,
Console: console.Console,
reflector: reflection.reflector,
Reflector: reflection.Reflector,

View File

@ -10,7 +10,7 @@ import {AnimationKeyframe} from '../animation/animation_keyframe';
import {AnimationPlayer} from '../animation/animation_player';
import {AnimationStyles} from '../animation/animation_styles';
import {isPresent} from '../facade/lang';
import {RenderComponentType, RenderDebugInfo, Renderer, RendererV2, RootRenderer} from '../render/api';
import {RenderComponentType, RenderDebugInfo, Renderer, RootRenderer} from '../render/api';
import {DebugElement, DebugNode, EventListener, getDebugNode, indexDebugNode, removeDebugNodeFromIndex} from './debug_node';
@ -156,151 +156,3 @@ export class DebugDomRenderer implements Renderer {
element, startingStyles, keyframes, duration, delay, easing, previousPlayers);
}
}
export class DebugDomRendererV2 implements RendererV2 {
constructor(private _delegate: RendererV2) {}
createElement(name: string, namespace?: string, debugInfo?: any): any {
const el = this._delegate.createElement(name, namespace, debugInfo);
const debugEl = new DebugElement(el, null, debugInfo);
debugEl.name = name;
indexDebugNode(debugEl);
return el;
}
createComment(value: string, debugInfo?: any): any {
const comment = this._delegate.createComment(value, debugInfo);
const debugEl = new DebugNode(comment, null, debugInfo);
indexDebugNode(debugEl);
return comment;
}
createText(value: string, debugInfo?: any): any {
const text = this._delegate.createText(value, debugInfo);
const debugEl = new DebugNode(text, null, debugInfo);
indexDebugNode(debugEl);
return text;
}
appendChild(parent: any, newChild: any): void {
const debugEl = getDebugNode(parent);
const debugChildEl = getDebugNode(newChild);
if (debugEl && debugChildEl && debugEl instanceof DebugElement) {
debugEl.addChild(debugChildEl);
}
this._delegate.appendChild(parent, newChild);
}
insertBefore(parent: any, newChild: any, refChild: any): void {
const debugEl = getDebugNode(parent);
const debugChildEl = getDebugNode(newChild);
const debugRefEl = getDebugNode(refChild);
if (debugEl && debugChildEl && debugEl instanceof DebugElement) {
debugEl.insertBefore(debugRefEl, debugChildEl);
}
this._delegate.insertBefore(parent, newChild, refChild);
}
removeChild(parent: any, oldChild: any): void {
const debugEl = getDebugNode(parent);
const debugChildEl = getDebugNode(oldChild);
if (debugEl && debugChildEl && debugEl instanceof DebugElement) {
debugEl.removeChild(debugChildEl);
}
this._delegate.removeChild(parent, oldChild);
}
selectRootElement(selectorOrNode: string|any, debugInfo?: any): any {
const el = this._delegate.selectRootElement(selectorOrNode, debugInfo);
const debugEl = new DebugElement(el, null, debugInfo);
indexDebugNode(debugEl);
return el;
}
parentNode(node: any): any { return this._delegate.parentNode(node); }
nextSibling(node: any): any { return this._delegate.nextSibling(node); }
setAttribute(el: any, name: string, value: string, namespace?: string): void {
const debugEl = getDebugNode(el);
if (debugEl && debugEl instanceof DebugElement) {
const fullName = namespace ? namespace + ':' + name : name;
debugEl.attributes[fullName] = value;
}
this._delegate.setAttribute(el, name, value, namespace);
}
removeAttribute(el: any, name: string, namespace?: string): void {
const debugEl = getDebugNode(el);
if (debugEl && debugEl instanceof DebugElement) {
const fullName = namespace ? namespace + ':' + name : name;
debugEl.attributes[fullName] = null;
}
this._delegate.removeAttribute(el, name, namespace);
}
setBindingDebugInfo(el: any, propertyName: string, propertyValue: string): void {
this._delegate.setBindingDebugInfo(el, propertyName, propertyValue);
}
removeBindingDebugInfo(el: any, propertyName: string): void {
this._delegate.removeBindingDebugInfo(el, propertyName);
}
addClass(el: any, name: string): void {
const debugEl = getDebugNode(el);
if (debugEl && debugEl instanceof DebugElement) {
debugEl.classes[name] = true;
}
this._delegate.addClass(el, name);
}
removeClass(el: any, name: string): void {
const debugEl = getDebugNode(el);
if (debugEl && debugEl instanceof DebugElement) {
debugEl.classes[name] = false;
}
this._delegate.removeClass(el, name);
}
setStyle(el: any, style: string, value: any, hasVendorPrefix: boolean, hasImportant: boolean):
void {
const debugEl = getDebugNode(el);
if (debugEl && debugEl instanceof DebugElement) {
debugEl.styles[style] = value;
}
this._delegate.setStyle(el, style, value, hasVendorPrefix, hasImportant);
}
removeStyle(el: any, style: string, hasVendorPrefix: boolean): void {
const debugEl = getDebugNode(el);
if (debugEl && debugEl instanceof DebugElement) {
debugEl.styles[style] = null;
}
this._delegate.removeStyle(el, style, hasVendorPrefix);
}
setProperty(el: any, name: string, value: any): void {
const debugEl = getDebugNode(el);
if (debugEl && debugEl instanceof DebugElement) {
debugEl.properties[name] = value;
}
this._delegate.setProperty(el, name, value);
}
setText(node: any, value: string): void { this._delegate.setText(node, value); }
listen(
target: 'document'|'windows'|'body'|any, eventName: string,
callback: (event: any) => boolean): () => void {
if (typeof target !== 'string') {
const debugEl = getDebugNode(target);
if (debugEl) {
debugEl.listeners.push(new EventListener(eventName, callback));
}
}
return this._delegate.listen(target, eventName, callback);
}
}

View File

@ -7,4 +7,4 @@
*/
// Public API for render
export {RENDERER_V2_DIRECT, RenderComponentType, Renderer, RendererV2, RootRenderer} from './render/api';
export {ComponentRenderTypeV2, RenderComponentType, Renderer, RendererFactoryV2, RendererV2, RootRenderer} from './render/api';

View File

@ -12,13 +12,6 @@ import {AnimationStyles} from '../../src/animation/animation_styles';
import {InjectionToken, Injector} from '../di';
import {ViewEncapsulation} from '../metadata/view';
/**
* Provide a concrete implementation of {@link RendererV2}
*
* @experimental
*/
export const RENDERER_V2_DIRECT = new InjectionToken<RendererV2>('Renderer V2');
/**
* @experimental
*/
@ -98,56 +91,6 @@ export abstract class Renderer {
previousPlayers?: AnimationPlayer[]): AnimationPlayer;
}
/**
* @experimental
*/
export abstract class RendererV2 {
abstract createElement(name: string, namespace?: string, debugInfo?: RenderDebugContext): any;
abstract createComment(value: string, debugInfo?: RenderDebugContext): any;
abstract createText(value: string, debugInfo?: RenderDebugContext): any;
abstract appendChild(parent: any, newChild: any): void;
abstract insertBefore(parent: any, newChild: any, refChild: any): void;
abstract removeChild(parent: any, oldChild: any): void;
abstract selectRootElement(selectorOrNode: string|any, debugInfo?: RenderDebugContext): any;
/**
* Attention: On WebWorkers, this will always return a value,
* as we are asking for a result synchronously. I.e.
* the caller can't rely on checking whether this is null or not.
*/
abstract parentNode(node: any): any;
/**
* Attention: On WebWorkers, this will always return a value,
* as we are asking for a result synchronously. I.e.
* the caller can't rely on checking whether this is null or not.
*/
abstract nextSibling(node: any): any;
abstract setAttribute(el: any, name: string, value: string, namespace?: string): void;
abstract removeAttribute(el: any, name: string, namespace?: string): void;
abstract setBindingDebugInfo(el: any, propertyName: string, propertyValue: string): void;
abstract removeBindingDebugInfo(el: any, propertyName: string): void;
abstract addClass(el: any, name: string): void;
abstract removeClass(el: any, name: string): void;
abstract setStyle(
el: any, style: string, value: any, hasVendorPrefix: boolean, hasImportant: boolean): void;
abstract removeStyle(el: any, style: string, hasVendorPrefix: boolean): void;
abstract setProperty(el: any, name: string, value: any): void;
abstract setText(node: any, value: string): void;
abstract listen(
target: 'window'|'document'|'body'|any, eventName: string,
callback: (event: any) => boolean): () => void;
}
export abstract class RenderDebugContext {
abstract get injector(): Injector;
abstract get component(): any;
abstract get providerTokens(): any[];
abstract get references(): {[key: string]: any};
abstract get context(): any;
abstract get source(): string;
abstract get componentRenderElement(): any;
abstract get renderNode(): any;
}
/**
* Injectable service that provides a low-level interface for modifying the UI.
*
@ -164,3 +107,64 @@ export abstract class RenderDebugContext {
export abstract class RootRenderer {
abstract renderComponent(componentType: RenderComponentType): Renderer;
}
/**
* @experimental
*/
export interface ComponentRenderTypeV2 {
id: string;
encapsulation: ViewEncapsulation;
styles: (string|any[])[];
data: {[kind: string]: any[]};
}
/**
* @experimental
*/
export abstract class RendererFactoryV2 {
abstract createRenderer(hostElement: any, type: ComponentRenderTypeV2): RendererV2;
}
/**
* @experimental
*/
export abstract class RendererV2 {
abstract destroy(): void;
abstract createElement(name: string, namespace?: string): any;
abstract createComment(value: string): any;
abstract createText(value: string): any;
/**
* This property is allowed to be null / undefined,
* in which case the view engine won't call it.
* This is used as a performance optimization for production mode.
*/
destroyNode: (node: any) => void | null;
abstract appendChild(parent: any, newChild: any): void;
abstract insertBefore(parent: any, newChild: any, refChild: any): void;
abstract removeChild(parent: any, oldChild: any): void;
abstract selectRootElement(selectorOrNode: string|any): any;
/**
* Attention: On WebWorkers, this will always return a value,
* as we are asking for a result synchronously. I.e.
* the caller can't rely on checking whether this is null or not.
*/
abstract parentNode(node: any): any;
/**
* Attention: On WebWorkers, this will always return a value,
* as we are asking for a result synchronously. I.e.
* the caller can't rely on checking whether this is null or not.
*/
abstract nextSibling(node: any): any;
abstract setAttribute(el: any, name: string, value: string, namespace?: string): void;
abstract removeAttribute(el: any, name: string, namespace?: string): void;
abstract addClass(el: any, name: string): void;
abstract removeClass(el: any, name: string): void;
abstract setStyle(
el: any, style: string, value: any, hasVendorPrefix: boolean, hasImportant: boolean): void;
abstract removeStyle(el: any, style: string, hasVendorPrefix: boolean): void;
abstract setProperty(el: any, name: string, value: any): void;
abstract setValue(node: any, value: string): void;
abstract listen(
target: 'window'|'document'|'body'|any, eventName: string,
callback: (event: any) => boolean): () => void;
}

View File

@ -132,7 +132,7 @@ export function elementDef(
export function createElement(view: ViewData, renderHost: any, def: NodeDef): ElementData {
const elDef = def.element;
const rootSelectorOrNode = view.root.selectorOrNode;
const renderer = view.root.renderer;
const renderer = view.renderer;
let el: any;
if (view.parent || !rootSelectorOrNode) {
if (elDef.name) {
@ -240,7 +240,7 @@ function setElementAttribute(
const securityContext = binding.securityContext;
let renderValue = securityContext ? view.root.sanitizer.sanitize(securityContext, value) : value;
renderValue = renderValue != null ? renderValue.toString() : null;
const renderer = view.root.renderer;
const renderer = view.renderer;
// TODO(vicb): move the namespace to the node definition
const nsAndName = splitNamespace(name);
if (value != null) {
@ -251,7 +251,7 @@ function setElementAttribute(
}
function setElementClass(view: ViewData, renderNode: any, name: string, value: boolean) {
const renderer = view.root.renderer;
const renderer = view.renderer;
if (value) {
renderer.addClass(renderNode, name);
} else {
@ -271,7 +271,7 @@ function setElementStyle(
} else {
renderValue = null;
}
const renderer = view.root.renderer;
const renderer = view.renderer;
if (renderValue != null) {
renderer.setStyle(renderNode, name, renderValue, false, false);
} else {
@ -283,7 +283,7 @@ function setElementProperty(
view: ViewData, binding: BindingDef, renderNode: any, name: string, value: any) {
const securityContext = binding.securityContext;
let renderValue = securityContext ? view.root.sanitizer.sanitize(securityContext, value) : value;
view.root.renderer.setProperty(renderNode, name, renderValue);
view.renderer.setProperty(renderNode, name, renderValue);
}
const NS_PREFIX_RE = /^:([^:]+):(.+)$/;

View File

@ -14,7 +14,7 @@ export {queryDef} from './query';
export {createComponentFactory} from './refs';
export {initServicesIfNeeded} from './services';
export {textDef} from './text';
export {elementEventFullName, nodeValue, rootRenderNodes, unwrapValue} from './util';
export {createComponentRenderTypeV2, elementEventFullName, nodeValue, rootRenderNodes, unwrapValue} from './util';
export {viewDef} from './view';
export {attachEmbeddedView, detachEmbeddedView, moveEmbeddedView} from './view_attach';

View File

@ -11,13 +11,15 @@ import {Injector} from '../di';
import {ElementRef} from '../linker/element_ref';
import {TemplateRef} from '../linker/template_ref';
import {ViewContainerRef} from '../linker/view_container_ref';
import * as v1renderer from '../render/api';
import {ViewEncapsulation} from '../metadata/view';
import {ComponentRenderTypeV2, RenderComponentType as RenderComponentTypeV1, Renderer as RendererV1, RendererFactoryV2, RendererV2, RootRenderer as RootRendererV1} from '../render/api';
import {createChangeDetectorRef, createInjector, 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';
const RendererV1TokenKey = tokenKey(v1renderer.Renderer);
const RendererV1TokenKey = tokenKey(RendererV1);
const RendererV2TokenKey = tokenKey(RendererV2);
const ElementRefTokenKey = tokenKey(ElementRef);
const ViewContainerRefTokenKey = tokenKey(ViewContainerRef);
const TemplateRefTokenKey = tokenKey(TemplateRef);
@ -29,7 +31,8 @@ const NOT_CREATED = new Object();
export function directiveDef(
flags: NodeFlags, matchedQueries: [string | number, QueryValueType][], childCount: number,
ctor: any, deps: ([DepFlags, any] | any)[], props?: {[name: string]: [number, string]},
outputs?: {[name: string]: string}, component?: () => ViewDefinition): NodeDef {
outputs?: {[name: string]: string}, component?: () => ViewDefinition,
componentRenderType?: ComponentRenderTypeV2): NodeDef {
const bindings: BindingDef[] = [];
if (props) {
for (let prop in props) {
@ -50,7 +53,7 @@ export function directiveDef(
}
return _def(
NodeType.Directive, flags, matchedQueries, childCount, ProviderType.Class, ctor, ctor, deps,
bindings, outputDefs, component);
bindings, outputDefs, component, componentRenderType);
}
export function pipeDef(flags: NodeFlags, ctor: any, deps: ([DepFlags, any] | any)[]): NodeDef {
@ -67,8 +70,13 @@ export function _def(
type: NodeType, flags: NodeFlags, matchedQueriesDsl: [string | number, QueryValueType][],
childCount: number, providerType: ProviderType, token: any, value: any,
deps: ([DepFlags, any] | any)[], bindings?: BindingDef[], outputs?: DirectiveOutputDef[],
component?: () => ViewDefinition): NodeDef {
component?: () => ViewDefinition, componentRenderType?: ComponentRenderTypeV2): NodeDef {
const {matchedQueries, references, matchedQueryIds} = splitMatchedQueriesDsl(matchedQueriesDsl);
// This is needed as the jit compiler always uses an empty hash as default ComponentRenderTypeV2,
// which is not filled for host views.
if (componentRenderType && componentRenderType.encapsulation == null) {
componentRenderType = null;
}
if (!outputs) {
outputs = [];
}
@ -111,7 +119,7 @@ export function _def(
type: providerType,
token,
tokenKey: tokenKey(token), value,
deps: depDefs, outputs, component
deps: depDefs, outputs, component, componentRenderType
},
text: undefined,
pureExpression: undefined,
@ -328,16 +336,19 @@ export function resolveDep(
if (elDef) {
switch (tokenKey) {
case RendererV1TokenKey: {
let compView = view;
while (compView && !isComponentView(compView)) {
compView = compView.parent;
}
const rootRenderer: v1renderer.RootRenderer =
view.root.injector.get(v1renderer.RootRenderer);
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!
return rootRenderer.renderComponent(new v1renderer.RenderComponentType(
view.def.component.id, '', 0, view.def.component.encapsulation, [], {}));
// 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, [], {}));
}
case RendererV2TokenKey: {
const compView = findCompView(view, elDef, allowPrivateServices);
return compView.renderer;
}
case ElementRefTokenKey:
return new ElementRef(asElementData(view, elDef.index).renderElement);
@ -350,15 +361,7 @@ export function resolveDep(
break;
}
case ChangeDetectorRefTokenKey: {
let cdView: ViewData;
if (allowPrivateServices) {
cdView = asProviderData(view, elDef.element.component.index).componentView;
} else {
cdView = view;
while (cdView.parent && !isComponentView(cdView)) {
cdView = cdView.parent;
}
}
let cdView = findCompView(view, elDef, allowPrivateServices);
return createChangeDetectorRef(cdView);
}
case InjectorRefTokenKey:
@ -383,6 +386,19 @@ export function resolveDep(
return startView.root.injector.get(depDef.token, notFoundValue);
}
function findCompView(view: ViewData, elDef: NodeDef, allowPrivateServices: boolean) {
let compView: ViewData;
if (allowPrivateServices) {
compView = asProviderData(view, elDef.element.component.index).componentView;
} else {
compView = view;
while (compView.parent && !isComponentView(compView)) {
compView = compView.parent;
}
}
return compView;
}
function checkAndUpdateProp(
view: ViewData, providerData: ProviderData, def: NodeDef, bindingIdx: number, value: any,
changes: SimpleChanges): SimpleChanges {

View File

@ -201,8 +201,9 @@ export function createInjector(view: ViewData, elDef: NodeDef): Injector {
class Injector_ implements Injector {
constructor(private view: ViewData, private elDef: NodeDef) {}
get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND): any {
const allowPrivateServices = !!this.elDef.element.component;
return Services.resolveDep(
this.view, this.elDef, true, {flags: DepFlags.None, token, tokenKey: tokenKey(token)},
notFoundValue);
this.view, this.elDef, allowPrivateServices,
{flags: DepFlags.None, token, tokenKey: tokenKey(token)}, notFoundValue);
}
}

View File

@ -7,8 +7,9 @@
*/
import {isDevMode} from '../application_ref';
import {DebugElement, DebugNode, EventListener, getDebugNode, indexDebugNode, removeDebugNodeFromIndex} from '../debug/debug_node';
import {Injector} from '../di';
import {RendererV2} from '../render/api';
import {ComponentRenderTypeV2, RendererFactoryV2, RendererV2} from '../render/api';
import {Sanitizer, SecurityContext} from '../security';
import {isViewDebugError, viewDestroyedError, viewWrappedDebugError} from './errors';
@ -87,52 +88,59 @@ function createDebugServices() {
function createProdRootView(
injector: Injector, projectableNodes: any[][], rootSelectorOrNode: string | any,
def: ViewDefinition, context?: any): ViewData {
const rendererFactory: RendererFactoryV2 = injector.get(RendererFactoryV2);
return createRootView(
createRootData(injector, projectableNodes, rootSelectorOrNode), def, context);
createRootData(injector, rendererFactory, projectableNodes, rootSelectorOrNode), def,
context);
}
function debugCreateRootView(
injector: Injector, projectableNodes: any[][], rootSelectorOrNode: string | any,
def: ViewDefinition, context?: any): ViewData {
const root = createRootData(injector, projectableNodes, rootSelectorOrNode);
const debugRoot: RootData = {
injector: root.injector,
projectableNodes: root.projectableNodes,
selectorOrNode: root.selectorOrNode,
renderer: new DebugRenderer(root.renderer),
sanitizer: root.sanitizer
};
return callWithDebugContext('create', createRootView, null, [debugRoot, def, context]);
const rendererFactory: RendererFactoryV2 = injector.get(RendererFactoryV2);
const root = createRootData(
injector, new DebugRendererFactoryV2(rendererFactory), projectableNodes, rootSelectorOrNode);
return callWithDebugContext(DebugAction.create, createRootView, null, [root, def, context]);
}
function createRootData(
injector: Injector, projectableNodes: any[][], rootSelectorOrNode: any): RootData {
injector: Injector, rendererFactory: RendererFactoryV2, projectableNodes: any[][],
rootSelectorOrNode: any): RootData {
const sanitizer = injector.get(Sanitizer);
const renderer = injector.get(RendererV2);
const rootElement =
rootSelectorOrNode ? renderer.selectRootElement(rootSelectorOrNode) : undefined;
return {injector, projectableNodes, selectorOrNode: rootSelectorOrNode, sanitizer, renderer};
const renderer = rendererFactory.createRenderer(null, null);
return {
injector,
projectableNodes,
selectorOrNode: rootSelectorOrNode, sanitizer, rendererFactory, renderer
};
}
function debugCreateEmbeddedView(parent: ViewData, anchorDef: NodeDef, context?: any): ViewData {
return callWithDebugContext('create', createEmbeddedView, null, [parent, anchorDef, context]);
return callWithDebugContext(
DebugAction.create, createEmbeddedView, null, [parent, anchorDef, context]);
}
function debugCheckAndUpdateView(view: ViewData) {
return callWithDebugContext('detectChanges', checkAndUpdateView, null, [view]);
return callWithDebugContext(DebugAction.detectChanges, checkAndUpdateView, null, [view]);
}
function debugCheckNoChangesView(view: ViewData) {
return callWithDebugContext('checkNoChanges', checkNoChangesView, null, [view]);
return callWithDebugContext(DebugAction.checkNoChanges, checkNoChangesView, null, [view]);
}
function debugDestroyView(view: ViewData) {
return callWithDebugContext('destroyView', destroyView, null, [view]);
return callWithDebugContext(DebugAction.destroy, destroyView, null, [view]);
}
enum DebugAction {
create,
detectChanges,
checkNoChanges,
destroy,
handleEvent
}
let _currentAction: string;
let _currentAction: DebugAction;
let _currentView: ViewData;
let _currentNodeIndex: number;
@ -143,16 +151,16 @@ function debugSetCurrentNode(view: ViewData, nodeIndex: number) {
function debugHandleEvent(view: ViewData, nodeIndex: number, eventName: string, event: any) {
if (view.state & ViewState.Destroyed) {
throw viewDestroyedError(_currentAction);
throw viewDestroyedError(DebugAction[_currentAction]);
}
debugSetCurrentNode(view, nodeIndex);
return callWithDebugContext(
'handleEvent', view.def.handleEvent, null, [view, nodeIndex, eventName, event]);
DebugAction.handleEvent, view.def.handleEvent, null, [view, nodeIndex, eventName, event]);
}
function debugUpdateDirectives(check: NodeCheckFn, view: ViewData) {
if (view.state & ViewState.Destroyed) {
throw viewDestroyedError(_currentAction);
throw viewDestroyedError(DebugAction[_currentAction]);
}
debugSetCurrentNode(view, nextDirectiveWithBinding(view, 0));
return view.def.updateDirectives(debugCheckDirectivesFn, view);
@ -167,7 +175,7 @@ function debugUpdateDirectives(check: NodeCheckFn, view: ViewData) {
function debugUpdateRenderer(check: NodeCheckFn, view: ViewData) {
if (view.state & ViewState.Destroyed) {
throw viewDestroyedError(_currentAction);
throw viewDestroyedError(DebugAction[_currentAction]);
}
debugSetCurrentNode(view, nextRenderNodeWithBinding(view, 0));
return view.def.updateRenderer(debugCheckRenderNodeFn, view);
@ -183,35 +191,41 @@ function debugUpdateRenderer(check: NodeCheckFn, view: ViewData) {
function debugCheckFn(
delegate: NodeCheckFn, view: ViewData, nodeIndex: number, argStyle: ArgumentType,
givenValues: any[]) {
const values = argStyle === ArgumentType.Dynamic ? givenValues[0] : givenValues;
const nodeDef = view.def.nodes[nodeIndex];
for (let i = 0; i < nodeDef.bindings.length; i++) {
const binding = nodeDef.bindings[i];
const value = values[i];
if ((binding.type === BindingType.ElementProperty ||
binding.type === BindingType.DirectiveProperty) &&
checkBinding(view, nodeDef, i, value)) {
if (_currentAction === DebugAction.detectChanges) {
const values = argStyle === ArgumentType.Dynamic ? givenValues[0] : givenValues;
const nodeDef = view.def.nodes[nodeIndex];
if (nodeDef.type === NodeType.Directive || nodeDef.type === NodeType.Element) {
const bindingValues: {[key: string]: string} = {};
for (let i = 0; i < nodeDef.bindings.length; i++) {
const binding = nodeDef.bindings[i];
const value = values[i];
if ((binding.type === BindingType.ElementProperty ||
binding.type === BindingType.DirectiveProperty) &&
checkBinding(view, nodeDef, i, value)) {
bindingValues[normalizeDebugBindingName(binding.nonMinifiedName)] =
normalizeDebugBindingValue(value);
}
}
const elDef = nodeDef.type === NodeType.Directive ? nodeDef.parent : nodeDef;
setBindingDebugInfo(
view.root.renderer, asElementData(view, elDef.index).renderElement,
binding.nonMinifiedName, value);
const el = asElementData(view, elDef.index).renderElement;
if (!elDef.element.name) {
// a comment.
view.renderer.setValue(el, `bindings=${JSON.stringify(bindingValues, null, 2)}`);
} else {
// a regular element.
for (let attr in bindingValues) {
view.renderer.setAttribute(el, attr, bindingValues[attr]);
}
}
}
}
return (<any>delegate)(view, nodeIndex, argStyle, ...givenValues);
};
function setBindingDebugInfo(renderer: RendererV2, renderNode: any, propName: string, value: any) {
const renderName = `ng-reflect-${camelCaseToDashCase(propName)}`;
if (value) {
try {
renderer.setBindingDebugInfo(renderNode, renderName, value.toString());
} catch (e) {
renderer.setBindingDebugInfo(
renderNode, renderName, '[ERROR] Exception while trying to serialize the value');
}
} else {
renderer.removeBindingDebugInfo(renderNode, renderName);
}
function normalizeDebugBindingName(name: string) {
// Attribute names with `$` (eg `x-y$`) are valid per spec, but unsupported by some browsers
name = camelCaseToDashCase(name.replace(/\$/g, '_'));
return `ng-reflect-${name}`;
}
const CAMEL_CASE_REGEXP = /([A-Z])/g;
@ -220,6 +234,15 @@ function camelCaseToDashCase(input: string): string {
return input.replace(CAMEL_CASE_REGEXP, (...m: any[]) => '-' + m[1].toLowerCase());
}
function normalizeDebugBindingValue(value: any): string {
try {
// Limit the size of the value as otherwise the DOM just gets polluted.
return value ? value.toString().slice(0, 20) : value;
} catch (e) {
return '[ERROR] Exception while trying to serialize the value';
}
}
function nextDirectiveWithBinding(view: ViewData, nodeIndex: number): number {
for (let i = nodeIndex; i < view.def.nodes.length; i++) {
const nodeDef = view.def.nodes[i];
@ -241,64 +264,6 @@ function nextRenderNodeWithBinding(view: ViewData, nodeIndex: number): number {
return undefined;
}
class DebugRenderer implements RendererV2 {
constructor(private _delegate: RendererV2) {}
createElement(name: string, namespace?: string): any {
return this._delegate.createElement(name, namespace, getCurrentDebugContext());
}
createComment(value: string): any {
return this._delegate.createComment(value, getCurrentDebugContext());
}
createText(value: string): any {
return this._delegate.createText(value, getCurrentDebugContext());
}
appendChild(parent: any, newChild: any): void {
return this._delegate.appendChild(parent, newChild);
}
insertBefore(parent: any, newChild: any, refChild: any): void {
return this._delegate.insertBefore(parent, newChild, refChild);
}
removeChild(parent: any, oldChild: any): void {
return this._delegate.removeChild(parent, oldChild);
}
selectRootElement(selectorOrNode: string|any): any {
return this._delegate.selectRootElement(selectorOrNode, getCurrentDebugContext());
}
parentNode(node: any): any { return this._delegate.parentNode(node); }
nextSibling(node: any): any { return this._delegate.nextSibling(node); }
setAttribute(el: any, name: string, value: string, namespace?: string): void {
return this._delegate.setAttribute(el, name, value, namespace);
}
removeAttribute(el: any, name: string, namespace?: string): void {
return this._delegate.removeAttribute(el, name, namespace);
}
setBindingDebugInfo(el: any, propertyName: string, propertyValue: string): void {
this._delegate.setBindingDebugInfo(el, propertyName, propertyValue);
}
removeBindingDebugInfo(el: any, propertyName: string): void {
this._delegate.removeBindingDebugInfo(el, propertyName);
}
addClass(el: any, name: string): void { return this._delegate.addClass(el, name); }
removeClass(el: any, name: string): void { return this._delegate.removeClass(el, name); }
setStyle(el: any, style: string, value: any, hasVendorPrefix: boolean, hasImportant: boolean):
void {
return this._delegate.setStyle(el, style, value, hasVendorPrefix, hasImportant);
}
removeStyle(el: any, style: string, hasVendorPrefix: boolean): void {
return this._delegate.removeStyle(el, style, hasVendorPrefix);
}
setProperty(el: any, name: string, value: any): void {
return this._delegate.setProperty(el, name, value);
}
setText(node: any, value: string): void { return this._delegate.setText(node, value); }
listen(
target: 'window'|'document'|'body'|any, eventName: string,
callback: (event: any) => boolean): () => void {
return this._delegate.listen(target, eventName, callback);
}
}
class DebugContext_ implements DebugContext {
private nodeDef: NodeDef;
private elView: ViewData;
@ -401,7 +366,7 @@ function collectReferences(view: ViewData, nodeDef: NodeDef, references: {[key:
}
}
function callWithDebugContext(action: string, fn: any, self: any, args: any[]) {
function callWithDebugContext(action: DebugAction, fn: any, self: any, args: any[]) {
const oldAction = _currentAction;
const oldView = _currentView;
const oldNodeIndex = _currentNodeIndex;
@ -421,6 +386,163 @@ function callWithDebugContext(action: string, fn: any, self: any, args: any[]) {
}
}
function getCurrentDebugContext() {
export function getCurrentDebugContext(): DebugContext {
return new DebugContext_(_currentView, _currentNodeIndex);
}
}
class DebugRendererFactoryV2 implements RendererFactoryV2 {
constructor(private delegate: RendererFactoryV2) {}
createRenderer(element: any, renderData: ComponentRenderTypeV2): RendererV2 {
return new DebugRendererV2(this.delegate.createRenderer(element, renderData));
}
}
class DebugRendererV2 implements RendererV2 {
constructor(private delegate: RendererV2) {}
destroyNode(node: any) {
removeDebugNodeFromIndex(getDebugNode(node));
if (this.delegate.destroyNode) {
this.delegate.destroyNode(node);
}
}
destroy() { this.delegate.destroy(); }
createElement(name: string, namespace?: string): any {
const el = this.delegate.createElement(name, namespace);
const debugEl = new DebugElement(el, null, getCurrentDebugContext());
debugEl.name = name;
indexDebugNode(debugEl);
return el;
}
createComment(value: string): any {
const comment = this.delegate.createComment(value);
const debugEl = new DebugNode(comment, null, getCurrentDebugContext());
indexDebugNode(debugEl);
return comment;
}
createText(value: string): any {
const text = this.delegate.createText(value);
const debugEl = new DebugNode(text, null, getCurrentDebugContext());
indexDebugNode(debugEl);
return text;
}
appendChild(parent: any, newChild: any): void {
const debugEl = getDebugNode(parent);
const debugChildEl = getDebugNode(newChild);
if (debugEl && debugChildEl && debugEl instanceof DebugElement) {
debugEl.addChild(debugChildEl);
}
this.delegate.appendChild(parent, newChild);
}
insertBefore(parent: any, newChild: any, refChild: any): void {
const debugEl = getDebugNode(parent);
const debugChildEl = getDebugNode(newChild);
const debugRefEl = getDebugNode(refChild);
if (debugEl && debugChildEl && debugEl instanceof DebugElement) {
debugEl.insertBefore(debugRefEl, debugChildEl);
}
this.delegate.insertBefore(parent, newChild, refChild);
}
removeChild(parent: any, oldChild: any): void {
const debugEl = getDebugNode(parent);
const debugChildEl = getDebugNode(oldChild);
if (debugEl && debugChildEl && debugEl instanceof DebugElement) {
debugEl.removeChild(debugChildEl);
}
this.delegate.removeChild(parent, oldChild);
}
selectRootElement(selectorOrNode: string|any): any {
const el = this.delegate.selectRootElement(selectorOrNode);
const debugEl = new DebugElement(el, null, getCurrentDebugContext());
indexDebugNode(debugEl);
return el;
}
setAttribute(el: any, name: string, value: string, namespace?: string): void {
const debugEl = getDebugNode(el);
if (debugEl && debugEl instanceof DebugElement) {
const fullName = namespace ? namespace + ':' + name : name;
debugEl.attributes[fullName] = value;
}
this.delegate.setAttribute(el, name, value, namespace);
}
removeAttribute(el: any, name: string, namespace?: string): void {
const debugEl = getDebugNode(el);
if (debugEl && debugEl instanceof DebugElement) {
const fullName = namespace ? namespace + ':' + name : name;
debugEl.attributes[fullName] = null;
}
this.delegate.removeAttribute(el, name, namespace);
}
addClass(el: any, name: string): void {
const debugEl = getDebugNode(el);
if (debugEl && debugEl instanceof DebugElement) {
debugEl.classes[name] = true;
}
this.delegate.addClass(el, name);
}
removeClass(el: any, name: string): void {
const debugEl = getDebugNode(el);
if (debugEl && debugEl instanceof DebugElement) {
debugEl.classes[name] = false;
}
this.delegate.removeClass(el, name);
}
setStyle(el: any, style: string, value: any, hasVendorPrefix: boolean, hasImportant: boolean):
void {
const debugEl = getDebugNode(el);
if (debugEl && debugEl instanceof DebugElement) {
debugEl.styles[style] = value;
}
this.delegate.setStyle(el, style, value, hasVendorPrefix, hasImportant);
}
removeStyle(el: any, style: string, hasVendorPrefix: boolean): void {
const debugEl = getDebugNode(el);
if (debugEl && debugEl instanceof DebugElement) {
debugEl.styles[style] = null;
}
this.delegate.removeStyle(el, style, hasVendorPrefix);
}
setProperty(el: any, name: string, value: any): void {
const debugEl = getDebugNode(el);
if (debugEl && debugEl instanceof DebugElement) {
debugEl.properties[name] = value;
}
this.delegate.setProperty(el, name, value);
}
listen(
target: 'document'|'windows'|'body'|any, eventName: string,
callback: (event: any) => boolean): () => void {
if (typeof target !== 'string') {
const debugEl = getDebugNode(target);
if (debugEl) {
debugEl.listeners.push(new EventListener(eventName, callback));
}
}
return this.delegate.listen(target, eventName, callback);
}
parentNode(node: any): any { return this.delegate.parentNode(node); }
nextSibling(node: any): any { return this.delegate.nextSibling(node); }
setValue(node: any, value: string): void { return this.delegate.setValue(node, value); }
}

View File

@ -54,7 +54,7 @@ export function textDef(ngContentIndex: number, constants: string[]): NodeDef {
export function createText(view: ViewData, renderHost: any, def: NodeDef): TextData {
let renderNode: any;
const renderer = view.root.renderer;
const renderer = view.renderer;
renderNode = renderer.createText(def.text.prefix);
const parentEl = getParentRenderElement(view, renderHost, def);
if (parentEl) {
@ -119,7 +119,7 @@ export function checkAndUpdateTextInline(
}
value = def.text.prefix + value;
const renderNode = asTextData(view, def.index).renderText;
view.root.renderer.setText(renderNode, value);
view.renderer.setValue(renderNode, value);
}
}
@ -140,7 +140,7 @@ export function checkAndUpdateTextDynamic(view: ViewData, def: NodeDef, values:
}
value = def.text.prefix + value;
const renderNode = asTextData(view, def.index).renderText;
view.root.renderer.setText(renderNode, value);
view.renderer.setValue(renderNode, value);
}
}

View File

@ -14,7 +14,7 @@ import {TemplateRef} from '../linker/template_ref';
import {ViewContainerRef} from '../linker/view_container_ref';
import {ViewRef} from '../linker/view_ref';
import {ViewEncapsulation} from '../metadata/view';
import {RenderDebugContext, RendererV2} from '../render/api';
import {ComponentRenderTypeV2, RendererFactoryV2, RendererV2} from '../render/api';
import {Sanitizer, SecurityContext} from '../security';
// -------------------------------------
@ -23,7 +23,6 @@ import {Sanitizer, SecurityContext} from '../security';
export interface ViewDefinition {
flags: ViewFlags;
component: ComponentDefinition;
updateDirectives: ViewUpdateFn;
updateRenderer: ViewUpdateFn;
handleEvent: ViewHandleEventFn;
@ -75,13 +74,7 @@ export enum ArgumentType {
*/
export enum ViewFlags {
None = 0,
OnPush = 1 << 1
}
export interface ComponentDefinition {
id: string;
encapsulation: ViewEncapsulation;
styles: string[];
OnPush = 1 << 1,
}
/**
@ -223,6 +216,7 @@ export interface ProviderDef {
value: any;
deps: DepDef[];
outputs: DirectiveOutputDef[];
componentRenderType: ComponentRenderTypeV2;
// closure to allow recursive components
component: ViewDefinitionFactory;
}
@ -306,6 +300,7 @@ export interface NgContentDef {
export interface ViewData {
def: ViewDefinition;
root: RootData;
renderer: RendererV2;
// index of component provider / anchor.
parentNodeDef: NodeDef;
parent: ViewData;
@ -426,12 +421,21 @@ export interface RootData {
projectableNodes: any[][];
selectorOrNode: any;
renderer: RendererV2;
rendererFactory: RendererFactoryV2;
sanitizer: Sanitizer;
}
export abstract class DebugContext extends RenderDebugContext {
export abstract class DebugContext {
abstract get view(): ViewData;
abstract get nodeIndex(): number;
abstract get injector(): Injector;
abstract get component(): any;
abstract get providerTokens(): any[];
abstract get references(): {[key: string]: any};
abstract get context(): any;
abstract get source(): string;
abstract get componentRenderElement(): any;
abstract get renderNode(): any;
}
// -------------------------------------

View File

@ -14,7 +14,8 @@ import {looseIdentical, stringify} from '../facade/lang';
import {TemplateRef} from '../linker/template_ref';
import {ViewContainerRef} from '../linker/view_container_ref';
import {ViewRef} from '../linker/view_ref';
import {Renderer} from '../render/api';
import {ViewEncapsulation} from '../metadata/view';
import {ComponentRenderTypeV2, Renderer} from '../render/api';
import {expressionChangedAfterItHasBeenCheckedError, isViewDebugError, viewDestroyedError, viewWrappedDebugError} from './errors';
import {DebugContext, ElementData, NodeData, NodeDef, NodeFlags, NodeType, QueryValueType, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewState, asElementData, asProviderData, asTextData} from './types';
@ -40,6 +41,23 @@ export function unwrapValue(value: any): any {
return value;
}
let _renderCompCount = 0;
export function createComponentRenderTypeV2(values: {
styles: (string | any[])[],
encapsulation: ViewEncapsulation,
data: {[kind: string]: any[]}
}): ComponentRenderTypeV2 {
const isFilled = values && (values.encapsulation !== ViewEncapsulation.None ||
values.styles.length || Object.keys(values.data).length);
if (isFilled) {
const id = `c${_renderCompCount++}`;
return {id: id, styles: values.styles, encapsulation: values.encapsulation, data: values.data};
} else {
return null;
}
}
export function checkBinding(
view: ViewData, def: NodeDef, bindingIdx: number, value: any): boolean {
const oldValue = view.oldValues[def.bindingIndex + bindingIdx];
@ -220,7 +238,7 @@ export function visitRootRenderNodes(
view: ViewData, action: RenderNodeAction, parentNode: any, nextSibling: any, target: any[]) {
// We need to re-compute the parent node in case the nodes have been moved around manually
if (action === RenderNodeAction.RemoveChild) {
parentNode = view.root.renderer.parentNode(renderNode(view, view.def.lastRootNode));
parentNode = view.renderer.parentNode(renderNode(view, view.def.lastRootNode));
}
visitSiblingRenderNodes(
view, action, 0, view.def.nodes.length - 1, parentNode, nextSibling, target);
@ -298,7 +316,7 @@ function visitRenderNode(
function execRenderNodeAction(
view: ViewData, renderNode: any, action: RenderNodeAction, parentNode: any, nextSibling: any,
target: any[]) {
const renderer = view.root.renderer;
const renderer = view.renderer;
switch (action) {
case RenderNodeAction.AppendChild:
renderer.appendChild(parentNode, renderNode);

View File

@ -7,6 +7,7 @@
*/
import {ViewEncapsulation} from '../metadata/view';
import {ComponentRenderTypeV2, RendererV2} from '../render/api';
import {checkAndUpdateElementDynamic, checkAndUpdateElementInline, createElement} from './element';
import {expressionChangedAfterItHasBeenCheckedError} from './errors';
@ -15,15 +16,14 @@ import {callLifecycleHooksChildrenFirst, checkAndUpdateDirectiveDynamic, checkAn
import {checkAndUpdatePureExpressionDynamic, checkAndUpdatePureExpressionInline, createPureExpression} from './pure_expression';
import {checkAndUpdateQuery, createQuery, queryDef} from './query';
import {checkAndUpdateTextDynamic, checkAndUpdateTextInline, createText} from './text';
import {ArgumentType, ComponentDefinition, ElementDef, NodeData, NodeDef, NodeFlags, NodeType, ProviderData, ProviderDef, RootData, Services, TextDef, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewHandleEventFn, ViewState, ViewUpdateFn, asElementData, asProviderData, asPureExpressionData, asQueryList} from './types';
import {ArgumentType, ElementDef, NodeData, NodeDef, NodeFlags, NodeType, ProviderData, ProviderDef, RootData, Services, TextDef, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewHandleEventFn, ViewState, ViewUpdateFn, asElementData, asProviderData, asPureExpressionData, asQueryList, asTextData} from './types';
import {checkBindingNoChanges, isComponentView, resolveViewDefinition, viewParentEl} from './util';
const NOOP = (): any => undefined;
export function viewDef(
flags: ViewFlags, nodes: NodeDef[], updateDirectives?: ViewUpdateFn,
updateRenderer?: ViewUpdateFn, handleEvent?: ViewHandleEventFn, compId?: string,
encapsulation?: ViewEncapsulation, styles?: string[]): ViewDefinition {
updateRenderer?: ViewUpdateFn, handleEvent?: ViewHandleEventFn): ViewDefinition {
// clone nodes and set auto calculated values
if (nodes.length === 0) {
throw new Error(`Illegal State: Views without nodes are not allowed!`);
@ -57,8 +57,11 @@ export function viewDef(
let currentRenderParent: NodeDef;
if (currentParent &&
!(currentParent.type === NodeType.Element && currentParent.element.component)) {
// children of components should never be attached!
(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 {
@ -134,8 +137,6 @@ export function viewDef(
}
currentParent = newParent;
}
const componentDef =
compId ? <ComponentDefinition>{id: compId, encapsulation, styles} : undefined;
return {
nodeFlags: viewNodeFlags,
nodeMatchedQueries: viewMatchedQueries, flags,
@ -143,7 +144,6 @@ export function viewDef(
updateDirectives: updateDirectives || NOOP,
updateRenderer: updateRenderer || NOOP,
handleEvent: handleEvent || NOOP,
component: componentDef,
bindingCount: viewBindingCount,
disposableCount: viewDisposableCount, lastRootNode
};
@ -222,21 +222,23 @@ function validateNode(parent: NodeDef, node: NodeDef, nodeCount: number) {
export function createEmbeddedView(parent: ViewData, anchorDef: NodeDef, context?: any): ViewData {
// embedded views are seen as siblings to the anchor, so we need
// to get the parent of the anchor and use it as parentIndex.
const view = createView(parent.root, parent, anchorDef, anchorDef.element.template);
const view =
createView(parent.root, parent.renderer, parent, anchorDef, anchorDef.element.template);
initView(view, parent.component, context);
createViewNodes(view);
return view;
}
export function createRootView(root: RootData, def: ViewDefinition, context?: any): ViewData {
const view = createView(root, null, null, def);
const view = createView(root, root.renderer, null, null, def);
initView(view, context, context);
createViewNodes(view);
return view;
}
function createView(
root: RootData, parent: ViewData, parentNodeDef: NodeDef, def: ViewDefinition): ViewData {
root: RootData, renderer: RendererV2, parent: ViewData, parentNodeDef: NodeDef,
def: ViewDefinition): ViewData {
const nodes: NodeData[] = new Array(def.nodes.length);
const disposables = def.disposableCount ? new Array(def.disposableCount) : undefined;
const view: ViewData = {
@ -245,7 +247,7 @@ function createView(
parentNodeDef,
context: undefined,
component: undefined, nodes,
state: ViewState.FirstCheck | ViewState.ChecksEnabled, root,
state: ViewState.FirstCheck | ViewState.ChecksEnabled, root, renderer,
oldValues: new Array(def.bindingCount), disposables
};
return view;
@ -259,9 +261,9 @@ function initView(view: ViewData, component: any, context: any) {
function createViewNodes(view: ViewData) {
let renderHost: any;
if (isComponentView(view)) {
renderHost = asElementData(view.parent, viewParentEl(view).index).renderElement;
const hostDef = view.parentNodeDef;
renderHost = asElementData(view.parent, hostDef.parent.index).renderElement;
}
const def = view.def;
const nodes = view.nodes;
for (let i = 0; i < def.nodes.length; i++) {
@ -291,8 +293,16 @@ function createViewNodes(view: ViewData) {
// Components can inject a ChangeDetectorRef that needs a references to
// the component view. Therefore, we create the component view first
// and set the ProviderData in ViewData, and then instantiate the provider.
const componentView = createView(
view.root, view, nodeDef, resolveViewDefinition(nodeDef.provider.component));
const compViewDef = resolveViewDefinition(nodeDef.provider.component);
const compRenderType = nodeDef.provider.componentRenderType;
let compRenderer: RendererV2;
if (!compRenderType) {
compRenderer = view.root.renderer;
} else {
const hostEl = asElementData(view, nodeDef.parent.index).renderElement;
compRenderer = view.root.rendererFactory.createRenderer(hostEl, compRenderType);
}
const componentView = createView(view.root, compRenderer, view, nodeDef, compViewDef);
const providerData = <ProviderData>{componentView, instance: undefined};
nodes[i] = providerData as any;
const instance = providerData.instance = createDirectiveInstance(view, nodeDef);
@ -473,9 +483,27 @@ export function destroyView(view: ViewData) {
view.disposables[i]();
}
}
if (view.renderer.destroyNode) {
destroyViewNodes(view);
}
if (view.parentNodeDef && view.parentNodeDef.flags & NodeFlags.HasComponent) {
view.renderer.destroy();
}
view.state |= ViewState.Destroyed;
}
function destroyViewNodes(view: ViewData) {
const len = view.def.nodes.length;
for (let i = 0; i < len; i++) {
const def = view.def.nodes[i];
if (def.type === NodeType.Element) {
view.renderer.destroyNode(asElementData(view, i).renderElement);
} else if (def.type === NodeType.Text) {
view.renderer.destroyNode(asTextData(view, i).renderText);
}
}
}
enum ViewAction {
CreateViewNodes,
CheckNoChanges,

View File

@ -77,15 +77,15 @@ export function moveEmbeddedView(
function renderAttachEmbeddedView(elementData: ElementData, prevView: ViewData, view: ViewData) {
const prevRenderNode =
prevView ? renderNode(prevView, prevView.def.lastRootNode) : elementData.renderElement;
const parentNode = view.root.renderer.parentNode(prevRenderNode);
const nextSibling = view.root.renderer.nextSibling(prevRenderNode);
const parentNode = view.renderer.parentNode(prevRenderNode);
const nextSibling = view.renderer.nextSibling(prevRenderNode);
// Note: We can't check if `nextSibling` is present, as on WebWorkers it will always be!
// However, browsers automatically do `appendChild` when there is no `nextSibling`.
visitRootRenderNodes(view, RenderNodeAction.InsertBefore, parentNode, nextSibling, undefined);
}
function renderDetachEmbeddedView(elementData: ElementData, view: ViewData) {
const parentNode = view.root.renderer.parentNode(elementData.renderElement);
const parentNode = view.renderer.parentNode(elementData.renderElement);
visitRootRenderNodes(view, RenderNodeAction.RemoveChild, parentNode, null, undefined);
}