Tobias Bosch fc8694ed11 refactor(core): view engine, refactor runtime data
Structure in a better way, in preparation for queries.
2017-01-25 17:44:42 -08:00

306 lines
9.9 KiB
TypeScript

/**
* @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 {SimpleChange, SimpleChanges} from '../change_detection/change_detection';
import {Injector} from '../di';
import {stringify} from '../facade/lang';
import {ElementRef} from '../linker/element_ref';
import {TemplateRef} from '../linker/template_ref';
import {ViewContainerRef} from '../linker/view_container_ref';
import {Renderer} from '../render/api';
import {BindingDef, BindingType, DepDef, DepFlags, DisposableFn, NodeData, NodeDef, NodeFlags, NodeType, ProviderOutputDef, Services, ViewData, ViewDefinition, ViewFlags} from './types';
import {checkAndUpdateBinding, checkAndUpdateBindingWithChange, setBindingDebugInfo} from './util';
const _tokenKeyCache = new Map<any, string>();
const RendererTokenKey = tokenKey(Renderer);
const ElementRefTokenKey = tokenKey(ElementRef);
const ViewContainerRefTokenKey = tokenKey(ViewContainerRef);
const TemplateRefTokenKey = tokenKey(TemplateRef);
export function providerDef(
flags: NodeFlags, ctor: any, deps: ([DepFlags, any] | any)[],
props?: {[name: string]: [number, string]}, outputs?: {[name: string]: string},
component?: () => ViewDefinition): NodeDef {
const bindings: BindingDef[] = [];
if (props) {
for (let prop in props) {
const [bindingIndex, nonMinifiedName] = props[prop];
bindings[bindingIndex] = {
type: BindingType.ProviderProperty,
name: prop, nonMinifiedName,
securityContext: undefined,
suffix: undefined
};
}
}
const outputDefs: ProviderOutputDef[] = [];
if (outputs) {
for (let propName in outputs) {
outputDefs.push({propName, eventName: outputs[propName]});
}
}
const depDefs: DepDef[] = deps.map(value => {
let token: any;
let flags: DepFlags;
if (Array.isArray(value)) {
[flags, token] = value;
} else {
flags = DepFlags.None;
token = value;
}
return {flags, token, tokenKey: tokenKey(token)};
});
if (component) {
flags = flags | NodeFlags.HasComponent;
}
return {
type: NodeType.Provider,
// will bet set by the view definition
index: undefined,
reverseChildIndex: undefined,
parent: undefined,
childFlags: undefined,
bindingIndex: undefined,
disposableIndex: undefined,
providerIndices: undefined,
// regular values
flags,
childCount: 0, bindings,
disposableCount: outputDefs.length,
element: undefined,
provider: {tokenKey: tokenKey(ctor), ctor, deps: depDefs, outputs: outputDefs, component},
text: undefined,
pureExpression: undefined
};
}
export function tokenKey(token: any): string {
let key = _tokenKeyCache.get(token);
if (!key) {
key = stringify(token) + '_' + _tokenKeyCache.size;
_tokenKeyCache.set(token, key);
}
return key;
}
export function createProvider(view: ViewData, def: NodeDef, componentView: ViewData): NodeData {
const providerDef = def.provider;
const provider = createInstance(view, def.parent, providerDef.ctor, providerDef.deps);
if (providerDef.outputs.length) {
for (let i = 0; i < providerDef.outputs.length; i++) {
const output = providerDef.outputs[i];
const subscription = provider[output.propName].subscribe(
view.def.handleEvent.bind(null, view, def.parent, output.eventName));
view.disposables[def.disposableIndex + i] = subscription.unsubscribe.bind(subscription);
}
}
return {
elementOrText: undefined,
provider: {instance: provider, componentView: componentView},
pureExpression: undefined,
};
}
export function checkAndUpdateProviderInline(
view: ViewData, def: NodeDef, v0: any, v1: any, v2: any, v3: any, v4: any, v5: any, v6: any,
v7: any, v8: any, v9: any) {
const provider = view.nodes[def.index].provider.instance;
let changes: SimpleChanges;
// Note: fallthrough is intended!
switch (def.bindings.length) {
case 10:
changes = checkAndUpdateProp(view, provider, def, 9, v9, changes);
case 9:
changes = checkAndUpdateProp(view, provider, def, 8, v8, changes);
case 8:
changes = checkAndUpdateProp(view, provider, def, 7, v7, changes);
case 7:
changes = checkAndUpdateProp(view, provider, def, 6, v6, changes);
case 6:
changes = checkAndUpdateProp(view, provider, def, 5, v5, changes);
case 5:
changes = checkAndUpdateProp(view, provider, def, 4, v4, changes);
case 4:
changes = checkAndUpdateProp(view, provider, def, 3, v3, changes);
case 3:
changes = checkAndUpdateProp(view, provider, def, 2, v2, changes);
case 2:
changes = checkAndUpdateProp(view, provider, def, 1, v1, changes);
case 1:
changes = checkAndUpdateProp(view, provider, def, 0, v0, changes);
}
if (changes) {
provider.ngOnChanges(changes);
}
if (view.firstChange && (def.flags & NodeFlags.OnInit)) {
provider.ngOnInit();
}
if (def.flags & NodeFlags.DoCheck) {
provider.ngDoCheck();
}
}
export function checkAndUpdateProviderDynamic(view: ViewData, def: NodeDef, values: any[]) {
const provider = view.nodes[def.index].provider.instance;
let changes: SimpleChanges;
for (let i = 0; i < values.length; i++) {
changes = checkAndUpdateProp(view, provider, def, i, values[i], changes);
}
if (changes) {
provider.ngOnChanges(changes);
}
if (view.firstChange && (def.flags & NodeFlags.OnInit)) {
provider.ngOnInit();
}
if (def.flags & NodeFlags.DoCheck) {
provider.ngDoCheck();
}
}
function createInstance(view: ViewData, elIndex: number, ctor: any, deps: DepDef[]): any {
const len = deps.length;
let injectable: any;
switch (len) {
case 0:
injectable = new ctor();
break;
case 1:
injectable = new ctor(resolveDep(view, elIndex, deps[0]));
break;
case 2:
injectable = new ctor(resolveDep(view, elIndex, deps[0]), resolveDep(view, elIndex, deps[1]));
break;
case 3:
injectable = new ctor(
resolveDep(view, elIndex, deps[0]), resolveDep(view, elIndex, deps[1]),
resolveDep(view, elIndex, deps[2]));
break;
default:
const depValues = new Array(len);
for (let i = 0; i < len; i++) {
depValues[i] = resolveDep(view, elIndex, deps[i]);
}
injectable = new ctor(...depValues);
}
return injectable;
}
export function resolveDep(
view: ViewData, elIndex: number, depDef: DepDef,
notFoundValue: any = Injector.THROW_IF_NOT_FOUND): any {
const tokenKey = depDef.tokenKey;
if (depDef.flags & DepFlags.SkipSelf) {
const elDef = view.def.nodes[elIndex];
if (elDef.parent != null) {
elIndex = elDef.parent;
} else {
elIndex = view.parentIndex;
view = view.parent;
}
}
while (view) {
const elDef = view.def.nodes[elIndex];
switch (tokenKey) {
case RendererTokenKey:
if (view.renderer) {
return view.renderer;
} else {
return Injector.NULL.get(depDef.token, notFoundValue);
}
case ElementRefTokenKey:
return new ElementRef(view.nodes[elIndex].elementOrText.node);
case ViewContainerRefTokenKey:
return view.services.createViewContainerRef(view.nodes[elIndex]);
case TemplateRefTokenKey:
return view.services.createTemplateRef(view, elDef);
default:
const providerIndex = elDef.providerIndices[tokenKey];
if (providerIndex != null) {
return view.nodes[providerIndex].provider.instance;
}
}
elIndex = view.parentIndex;
view = view.parent;
}
return Injector.NULL.get(depDef.token, notFoundValue);
}
function checkAndUpdateProp(
view: ViewData, provider: any, def: NodeDef, bindingIdx: number, value: any,
changes: SimpleChanges): SimpleChanges {
let change: SimpleChange;
let changed: boolean;
if (def.flags & NodeFlags.OnChanges) {
change = checkAndUpdateBindingWithChange(view, def, bindingIdx, value);
changed = !!change;
} else {
changed = checkAndUpdateBinding(view, def, bindingIdx, value);
}
if (changed) {
const binding = def.bindings[bindingIdx];
const propName = binding.name;
// Note: This is still safe with Closure Compiler as
// the user passed in the property name as an object has to `providerDef`,
// so Closure Compiler will have renamed the property correctly already.
provider[propName] = value;
if (view.def.flags & ViewFlags.LogBindingUpdate) {
setBindingDebugInfo(
view.renderer, view.nodes[def.parent].elementOrText.node, binding.nonMinifiedName, value);
}
if (change) {
changes = changes || {};
changes[binding.nonMinifiedName] = change;
}
}
return changes;
}
export function callLifecycleHooksChildrenFirst(view: ViewData, lifecycles: NodeFlags) {
if (!(view.def.nodeFlags & lifecycles)) {
return;
}
const len = view.def.nodes.length;
for (let i = 0; i < len; i++) {
// We use the provider post order to call providers of children first.
const nodeDef = view.def.reverseChildNodes[i];
const nodeIndex = nodeDef.index;
if (nodeDef.flags & lifecycles) {
// a leaf
callProviderLifecycles(view.nodes[nodeIndex].provider.instance, nodeDef.flags & lifecycles);
} else if ((nodeDef.childFlags & lifecycles) === 0) {
// a parent with leafs
// no child matches one of the lifecycles,
// then skip the children
i += nodeDef.childCount;
}
}
}
function callProviderLifecycles(provider: any, lifecycles: NodeFlags) {
if (lifecycles & NodeFlags.AfterContentInit) {
provider.ngAfterContentInit();
}
if (lifecycles & NodeFlags.AfterContentChecked) {
provider.ngAfterContentChecked();
}
if (lifecycles & NodeFlags.AfterViewInit) {
provider.ngAfterViewInit();
}
if (lifecycles & NodeFlags.AfterViewChecked) {
provider.ngAfterViewChecked();
}
if (lifecycles & NodeFlags.OnDestroy) {
provider.ngOnDestroy();
}
}