473 lines
14 KiB
TypeScript
Raw Normal View History

/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {ApplicationRef} from '../application_ref';
import {ChangeDetectorRef, ChangeDetectorStatus} from '../change_detection/change_detection';
import {Injector, THROW_IF_NOT_FOUND} from '../di/injector';
import {getType} from '../errors';
2016-07-13 11:01:32 -07:00
import {isPresent} from '../facade/lang';
import {WtfScopeFn, wtfCreateScope, wtfLeave} from '../profile/profile';
2017-01-04 13:59:43 -08:00
import {DirectRenderer, RenderComponentType, Renderer} from '../render/api';
import {AnimationViewContext} from './animation_view_context';
import {ComponentRef} from './component_factory';
import {DebugContext, StaticNodeDebugInfo} from './debug_context';
import {ElementInjector} from './element_injector';
import {expressionChangedAfterItHasBeenCheckedError, viewDestroyedError, viewWrappedError} from './errors';
import {ViewContainer} from './view_container';
import {ViewRef_} from './view_ref';
import {ViewType} from './view_type';
import {ViewUtils, addToArray} from './view_utils';
const _scope_check: WtfScopeFn = wtfCreateScope(`AppView#check(ascii id)`);
/**
* @experimental
*/
const EMPTY_CONTEXT = new Object();
const UNDEFINED = new Object();
/**
* Cost of making objects: http://jsperf.com/instantiate-size-of-object
*
2014-10-10 20:44:55 -07:00
*/
export abstract class AppView<T> {
ref: ViewRef_<T>;
lastRootNode: any;
allNodes: any[];
disposables: Function[];
viewContainer: ViewContainer;
// This will be set if a view is directly attached to an ApplicationRef
// and not to a view container.
appRef: ApplicationRef;
numberOfChecks: number = 0;
refactor(compiler): generate less code for bindings to DOM elements Detailed changes: - remove `UNINITIALIZED`, initialize change detection fields with `undefined`. * we use `view.numberOfChecks === 0` now everywhere as indicator whether we are in the first change detection cycle (previously we used this only in a couple of places). * we keep the initialization itself as change detection get slower without it. - remove passing around `throwOnChange` in various generated calls, and store it on the view as property instead. - change generated code for bindings to DOM elements as follows: Before: ``` var currVal_10 = self.context.bgColor; if (jit_checkBinding15(self.throwOnChange,self._expr_10,currVal_10)) { self.renderer.setElementStyle(self._el_0,'backgroundColor',((self.viewUtils.sanitizer.sanitize(jit_21,currVal_10) == null)? null: self.viewUtils.sanitizer.sanitize(jit_21,currVal_10).toString())); self._expr_10 = currVal_10; } var currVal_11 = jit_inlineInterpolate16(1,' ',self.context.data.value,' '); if (jit_checkBinding15(self.throwOnChange,self._expr_11,currVal_11)) { self.renderer.setText(self._text_1,currVal_11); self._expr_11 = currVal_11; } ```, After: ``` var currVal_10 = self.context.bgColor; jit_checkRenderStyle14(self,self._el_0,'backgroundColor',null,self._expr_10,self._expr_10=currVal_10,false,jit_21); var currVal_11 = jit_inlineInterpolate15(1,' ',self.context.data.value,' '); jit_checkRenderText16(self,self._text_1,self._expr_11,self._expr_11=currVal_11,false); ``` Performance impact: - None seen (checked against internal latency lab) Part of #13651
2016-12-29 15:03:55 -08:00
throwOnChange: boolean = false;
feat(compiler): attach components and project light dom during compilation. Closes #2529 BREAKING CHANGES: - shadow dom emulation no longer supports the `<content>` tag. Use the new `<ng-content>` instead (works with all shadow dom strategies). - removed `DomRenderer.setViewRootNodes` and `AppViewManager.getComponentView` -> use `DomRenderer.getNativeElementSync(elementRef)` and change shadow dom directly - the `Renderer` interface has changed: * `createView` now also has to support sub views * the notion of a container has been removed. Instead, the renderer has to implement methods to attach views next to elements or other views. * a RenderView now contains multiple RenderFragments. Fragments are used to move DOM nodes around. Internal changes / design changes: - Introduce notion of view fragments on render side - DomProtoViews and DomViews on render side are merged, AppProtoViews are not merged, AppViews are partially merged (they share arrays with the other merged AppViews but we keep individual AppView instances for now). - DomProtoViews always have a `<template>` element as root * needed for storing subviews * we have less chunks of DOM to clone now - remove fake ElementBinder / Bound element for root text bindings and model them explicitly. This removes a lot of special cases we had! - AppView shares data with nested component views - some methods in AppViewManager (create, hydrate, dehydrate) are iterative now * now possible as we have all child AppViews / ElementRefs already in an array!
2015-06-24 13:46:39 -07:00
renderer: Renderer;
private _hasExternalHostElement: boolean;
private _hostInjector: Injector;
private _hostProjectableNodes: any[][];
private _animationContext: AnimationViewContext;
private _directRenderer: DirectRenderer;
public context: T;
constructor(
public clazz: any, public componentType: RenderComponentType, public type: ViewType,
public viewUtils: ViewUtils, public parentView: AppView<any>, public parentIndex: number,
public parentElement: any, public cdMode: ChangeDetectorStatus,
public declaredViewContainer: ViewContainer = null) {
this.ref = new ViewRef_(this, viewUtils.animationQueue);
if (type === ViewType.COMPONENT || type === ViewType.HOST) {
this.renderer = viewUtils.renderComponent(componentType);
} else {
this.renderer = parentView.renderer;
}
this._directRenderer = (this.renderer as any).directRenderer;
}
get animationContext(): AnimationViewContext {
if (!this._animationContext) {
this._animationContext = new AnimationViewContext(this.viewUtils.animationQueue);
}
return this._animationContext;
}
get destroyed(): boolean { return this.cdMode === ChangeDetectorStatus.Destroyed; }
create(context: T) {
this.context = context;
return this.createInternal(null);
}
createHostView(rootSelectorOrNode: string|any, hostInjector: Injector, projectableNodes: any[][]):
ComponentRef<any> {
this.context = <any>EMPTY_CONTEXT;
this._hasExternalHostElement = isPresent(rootSelectorOrNode);
this._hostInjector = hostInjector;
this._hostProjectableNodes = projectableNodes;
return this.createInternal(rootSelectorOrNode);
}
feat(compiler): attach components and project light dom during compilation. Closes #2529 BREAKING CHANGES: - shadow dom emulation no longer supports the `<content>` tag. Use the new `<ng-content>` instead (works with all shadow dom strategies). - removed `DomRenderer.setViewRootNodes` and `AppViewManager.getComponentView` -> use `DomRenderer.getNativeElementSync(elementRef)` and change shadow dom directly - the `Renderer` interface has changed: * `createView` now also has to support sub views * the notion of a container has been removed. Instead, the renderer has to implement methods to attach views next to elements or other views. * a RenderView now contains multiple RenderFragments. Fragments are used to move DOM nodes around. Internal changes / design changes: - Introduce notion of view fragments on render side - DomProtoViews and DomViews on render side are merged, AppProtoViews are not merged, AppViews are partially merged (they share arrays with the other merged AppViews but we keep individual AppView instances for now). - DomProtoViews always have a `<template>` element as root * needed for storing subviews * we have less chunks of DOM to clone now - remove fake ElementBinder / Bound element for root text bindings and model them explicitly. This removes a lot of special cases we had! - AppView shares data with nested component views - some methods in AppViewManager (create, hydrate, dehydrate) are iterative now * now possible as we have all child AppViews / ElementRefs already in an array!
2015-06-24 13:46:39 -07:00
/**
* Overwritten by implementations.
* Returns the ComponentRef for the host element for ViewType.HOST.
*/
createInternal(rootSelectorOrNode: string|any): ComponentRef<any> { return null; }
/**
* Overwritten by implementations.
*/
createEmbeddedViewInternal(templateNodeIndex: number): AppView<any> { return null; }
init(lastRootNode: any, allNodes: any[], disposables: Function[]) {
this.lastRootNode = lastRootNode;
this.allNodes = allNodes;
this.disposables = disposables;
if (this.type === ViewType.COMPONENT) {
this.dirtyParentQueriesInternal();
}
}
injectorGet(token: any, nodeIndex: number, notFoundValue: any = THROW_IF_NOT_FOUND): any {
let result = UNDEFINED;
let view: AppView<any> = this;
while (result === UNDEFINED) {
if (isPresent(nodeIndex)) {
result = view.injectorGetInternal(token, nodeIndex, UNDEFINED);
}
if (result === UNDEFINED && view.type === ViewType.HOST) {
result = view._hostInjector.get(token, notFoundValue);
}
nodeIndex = view.parentIndex;
view = view.parentView;
}
return result;
}
/**
* Overwritten by implementations
*/
injectorGetInternal(token: any, nodeIndex: number, notFoundResult: any): any {
return notFoundResult;
}
injector(nodeIndex: number): Injector { return new ElementInjector(this, nodeIndex); }
detachAndDestroy() {
if (this.viewContainer) {
this.viewContainer.detachView(this.viewContainer.nestedViews.indexOf(this));
} else if (this.appRef) {
this.appRef.detachView(this.ref);
} else if (this._hasExternalHostElement) {
this.detach();
}
this.destroy();
}
destroy() {
if (this.cdMode === ChangeDetectorStatus.Destroyed) {
return;
}
const hostElement = this.type === ViewType.COMPONENT ? this.parentElement : null;
if (this.disposables) {
for (let i = 0; i < this.disposables.length; i++) {
this.disposables[i]();
}
}
this.destroyInternal();
this.dirtyParentQueriesInternal();
if (this._animationContext) {
this._animationContext.onAllActiveAnimationsDone(
() => this.renderer.destroyView(hostElement, this.allNodes));
} else {
this.renderer.destroyView(hostElement, this.allNodes);
}
this.cdMode = ChangeDetectorStatus.Destroyed;
}
/**
* Overwritten by implementations
*/
destroyInternal(): void {}
/**
* Overwritten by implementations
*/
detachInternal(): void {}
detach(): void {
this.detachInternal();
if (this._animationContext) {
this._animationContext.onAllActiveAnimationsDone(() => this._renderDetach());
} else {
this._renderDetach();
}
if (this.declaredViewContainer && this.declaredViewContainer !== this.viewContainer &&
this.declaredViewContainer.projectedViews) {
const projectedViews = this.declaredViewContainer.projectedViews;
const index = projectedViews.indexOf(this);
// perf: pop is faster than splice!
if (index >= projectedViews.length - 1) {
projectedViews.pop();
} else {
projectedViews.splice(index, 1);
}
}
this.appRef = null;
this.viewContainer = null;
this.dirtyParentQueriesInternal();
}
private _renderDetach() {
if (this._directRenderer) {
this.visitRootNodesInternal(this._directRenderer.remove, null);
} else {
this.renderer.detachView(this.flatRootNodes);
}
}
attachToAppRef(appRef: ApplicationRef) {
if (this.viewContainer) {
throw new Error('This view is already attached to a ViewContainer!');
}
this.appRef = appRef;
this.dirtyParentQueriesInternal();
}
attachAfter(viewContainer: ViewContainer, prevView: AppView<any>) {
if (this.appRef) {
throw new Error('This view is already attached directly to the ApplicationRef!');
}
this._renderAttach(viewContainer, prevView);
this.viewContainer = viewContainer;
if (this.declaredViewContainer && this.declaredViewContainer !== viewContainer) {
if (!this.declaredViewContainer.projectedViews) {
this.declaredViewContainer.projectedViews = [];
}
this.declaredViewContainer.projectedViews.push(this);
}
this.dirtyParentQueriesInternal();
}
moveAfter(viewContainer: ViewContainer, prevView: AppView<any>) {
this._renderAttach(viewContainer, prevView);
this.dirtyParentQueriesInternal();
}
private _renderAttach(viewContainer: ViewContainer, prevView: AppView<any>) {
const prevNode = prevView ? prevView.lastRootNode : viewContainer.nativeElement;
if (this._directRenderer) {
const nextSibling = this._directRenderer.nextSibling(prevNode);
if (nextSibling) {
this.visitRootNodesInternal(this._directRenderer.insertBefore, nextSibling);
} else {
const parentElement = this._directRenderer.parentElement(prevNode);
if (parentElement) {
this.visitRootNodesInternal(this._directRenderer.appendChild, parentElement);
}
}
} else {
this.renderer.attachViewAfter(prevNode, this.flatRootNodes);
}
}
get changeDetectorRef(): ChangeDetectorRef { return this.ref; }
get flatRootNodes(): any[] {
const nodes: any[] = [];
this.visitRootNodesInternal(addToArray, nodes);
return nodes;
}
projectNodes(parentElement: any, ngContentIndex: number) {
if (this._directRenderer) {
this.visitProjectedNodes(ngContentIndex, this._directRenderer.appendChild, parentElement);
} else {
const nodes: any[] = [];
this.visitProjectedNodes(ngContentIndex, addToArray, nodes);
this.renderer.projectNodes(parentElement, nodes);
}
}
visitProjectedNodes<C>(ngContentIndex: number, cb: (node: any, ctx: C) => void, c: C): void {
switch (this.type) {
case ViewType.EMBEDDED:
this.parentView.visitProjectedNodes(ngContentIndex, cb, c);
break;
case ViewType.COMPONENT:
if (this.parentView.type === ViewType.HOST) {
const nodes = this.parentView._hostProjectableNodes[ngContentIndex] || [];
for (let i = 0; i < nodes.length; i++) {
cb(nodes[i], c);
}
} else {
this.parentView.visitProjectableNodesInternal(this.parentIndex, ngContentIndex, cb, c);
}
break;
}
}
/**
* Overwritten by implementations
*/
visitRootNodesInternal<C>(cb: (node: any, ctx: C) => void, c: C): void {}
/**
* Overwritten by implementations
*/
visitProjectableNodesInternal<C>(
nodeIndex: number, ngContentIndex: number, cb: (node: any, ctx: C) => void, c: C): void {}
/**
* Overwritten by implementations
*/
dirtyParentQueriesInternal(): void {}
internalDetectChanges(throwOnChange: boolean): void {
if (this.cdMode !== ChangeDetectorStatus.Detached) {
this.detectChanges(throwOnChange);
}
}
detectChanges(throwOnChange: boolean): void {
const s = _scope_check(this.clazz);
if (this.cdMode === ChangeDetectorStatus.Checked ||
this.cdMode === ChangeDetectorStatus.Errored)
return;
if (this.cdMode === ChangeDetectorStatus.Destroyed) {
this.throwDestroyedError('detectChanges');
}
refactor(compiler): generate less code for bindings to DOM elements Detailed changes: - remove `UNINITIALIZED`, initialize change detection fields with `undefined`. * we use `view.numberOfChecks === 0` now everywhere as indicator whether we are in the first change detection cycle (previously we used this only in a couple of places). * we keep the initialization itself as change detection get slower without it. - remove passing around `throwOnChange` in various generated calls, and store it on the view as property instead. - change generated code for bindings to DOM elements as follows: Before: ``` var currVal_10 = self.context.bgColor; if (jit_checkBinding15(self.throwOnChange,self._expr_10,currVal_10)) { self.renderer.setElementStyle(self._el_0,'backgroundColor',((self.viewUtils.sanitizer.sanitize(jit_21,currVal_10) == null)? null: self.viewUtils.sanitizer.sanitize(jit_21,currVal_10).toString())); self._expr_10 = currVal_10; } var currVal_11 = jit_inlineInterpolate16(1,' ',self.context.data.value,' '); if (jit_checkBinding15(self.throwOnChange,self._expr_11,currVal_11)) { self.renderer.setText(self._text_1,currVal_11); self._expr_11 = currVal_11; } ```, After: ``` var currVal_10 = self.context.bgColor; jit_checkRenderStyle14(self,self._el_0,'backgroundColor',null,self._expr_10,self._expr_10=currVal_10,false,jit_21); var currVal_11 = jit_inlineInterpolate15(1,' ',self.context.data.value,' '); jit_checkRenderText16(self,self._text_1,self._expr_11,self._expr_11=currVal_11,false); ``` Performance impact: - None seen (checked against internal latency lab) Part of #13651
2016-12-29 15:03:55 -08:00
this.throwOnChange = throwOnChange;
this.detectChangesInternal();
if (this.cdMode === ChangeDetectorStatus.CheckOnce) this.cdMode = ChangeDetectorStatus.Checked;
this.numberOfChecks++;
wtfLeave(s);
}
/**
* Overwritten by implementations
*/
refactor(compiler): generate less code for bindings to DOM elements Detailed changes: - remove `UNINITIALIZED`, initialize change detection fields with `undefined`. * we use `view.numberOfChecks === 0` now everywhere as indicator whether we are in the first change detection cycle (previously we used this only in a couple of places). * we keep the initialization itself as change detection get slower without it. - remove passing around `throwOnChange` in various generated calls, and store it on the view as property instead. - change generated code for bindings to DOM elements as follows: Before: ``` var currVal_10 = self.context.bgColor; if (jit_checkBinding15(self.throwOnChange,self._expr_10,currVal_10)) { self.renderer.setElementStyle(self._el_0,'backgroundColor',((self.viewUtils.sanitizer.sanitize(jit_21,currVal_10) == null)? null: self.viewUtils.sanitizer.sanitize(jit_21,currVal_10).toString())); self._expr_10 = currVal_10; } var currVal_11 = jit_inlineInterpolate16(1,' ',self.context.data.value,' '); if (jit_checkBinding15(self.throwOnChange,self._expr_11,currVal_11)) { self.renderer.setText(self._text_1,currVal_11); self._expr_11 = currVal_11; } ```, After: ``` var currVal_10 = self.context.bgColor; jit_checkRenderStyle14(self,self._el_0,'backgroundColor',null,self._expr_10,self._expr_10=currVal_10,false,jit_21); var currVal_11 = jit_inlineInterpolate15(1,' ',self.context.data.value,' '); jit_checkRenderText16(self,self._text_1,self._expr_11,self._expr_11=currVal_11,false); ``` Performance impact: - None seen (checked against internal latency lab) Part of #13651
2016-12-29 15:03:55 -08:00
detectChangesInternal(): void {}
2015-06-12 09:45:31 -07:00
markAsCheckOnce(): void { this.cdMode = ChangeDetectorStatus.CheckOnce; }
feat(compiler): attach components and project light dom during compilation. Closes #2529 BREAKING CHANGES: - shadow dom emulation no longer supports the `<content>` tag. Use the new `<ng-content>` instead (works with all shadow dom strategies). - removed `DomRenderer.setViewRootNodes` and `AppViewManager.getComponentView` -> use `DomRenderer.getNativeElementSync(elementRef)` and change shadow dom directly - the `Renderer` interface has changed: * `createView` now also has to support sub views * the notion of a container has been removed. Instead, the renderer has to implement methods to attach views next to elements or other views. * a RenderView now contains multiple RenderFragments. Fragments are used to move DOM nodes around. Internal changes / design changes: - Introduce notion of view fragments on render side - DomProtoViews and DomViews on render side are merged, AppProtoViews are not merged, AppViews are partially merged (they share arrays with the other merged AppViews but we keep individual AppView instances for now). - DomProtoViews always have a `<template>` element as root * needed for storing subviews * we have less chunks of DOM to clone now - remove fake ElementBinder / Bound element for root text bindings and model them explicitly. This removes a lot of special cases we had! - AppView shares data with nested component views - some methods in AppViewManager (create, hydrate, dehydrate) are iterative now * now possible as we have all child AppViews / ElementRefs already in an array!
2015-06-24 13:46:39 -07:00
markPathToRootAsCheckOnce(): void {
let c: AppView<any> = this;
while (isPresent(c) && c.cdMode !== ChangeDetectorStatus.Detached) {
if (c.cdMode === ChangeDetectorStatus.Checked) {
c.cdMode = ChangeDetectorStatus.CheckOnce;
}
if (c.type === ViewType.COMPONENT) {
c = c.parentView;
} else {
c = c.viewContainer ? c.viewContainer.parentView : null;
}
}
}
2014-09-28 16:29:11 -07:00
eventHandler<E, R>(cb: (eventName: string, event?: E) => R): (eventName: string, event?: E) => R {
return cb;
}
throwDestroyedError(details: string): void { throw viewDestroyedError(details); }
}
export class DebugAppView<T> extends AppView<T> {
private _currentDebugContext: DebugContext = null;
constructor(
clazz: any, componentType: RenderComponentType, type: ViewType, viewUtils: ViewUtils,
parentView: AppView<any>, parentIndex: number, parentNode: any, cdMode: ChangeDetectorStatus,
public staticNodeDebugInfos: StaticNodeDebugInfo[],
declaredViewContainer: ViewContainer = null) {
super(
clazz, componentType, type, viewUtils, parentView, parentIndex, parentNode, cdMode,
declaredViewContainer);
}
create(context: T) {
this._resetDebug();
try {
return super.create(context);
} catch (e) {
this._rethrowWithContext(e);
throw e;
}
}
createHostView(
rootSelectorOrNode: string|any, injector: Injector,
projectableNodes: any[][] = null): ComponentRef<any> {
this._resetDebug();
try {
return super.createHostView(rootSelectorOrNode, injector, projectableNodes);
} catch (e) {
this._rethrowWithContext(e);
throw e;
}
}
2016-11-02 07:36:31 -07:00
injectorGet(token: any, nodeIndex: number, notFoundResult?: any): any {
this._resetDebug();
try {
return super.injectorGet(token, nodeIndex, notFoundResult);
} catch (e) {
this._rethrowWithContext(e);
throw e;
}
}
detach(): void {
this._resetDebug();
try {
super.detach();
} catch (e) {
this._rethrowWithContext(e);
throw e;
}
}
destroy() {
this._resetDebug();
try {
super.destroy();
} catch (e) {
this._rethrowWithContext(e);
throw e;
}
}
detectChanges(throwOnChange: boolean): void {
this._resetDebug();
try {
super.detectChanges(throwOnChange);
} catch (e) {
this._rethrowWithContext(e);
throw e;
}
}
private _resetDebug() { this._currentDebugContext = null; }
debug(nodeIndex: number, rowNum: number, colNum: number): DebugContext {
return this._currentDebugContext = new DebugContext(this, nodeIndex, rowNum, colNum);
}
private _rethrowWithContext(e: any) {
if (!(getType(e) == viewWrappedError)) {
if (!(getType(e) == expressionChangedAfterItHasBeenCheckedError)) {
this.cdMode = ChangeDetectorStatus.Errored;
}
if (isPresent(this._currentDebugContext)) {
throw viewWrappedError(e, this._currentDebugContext);
}
}
}
eventHandler<E, R>(cb: (eventName: string, event?: E) => R): (eventName: string, event?: E) => R {
const superHandler = super.eventHandler(cb);
return (eventName: string, event?: any) => {
this._resetDebug();
try {
return superHandler.call(this, eventName, event);
} catch (e) {
this._rethrowWithContext(e);
throw e;
}
};
}
}