306 lines
9.9 KiB
TypeScript
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();
|
|
}
|
|
}
|