/** * @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 {ChangeDetectorRef, SimpleChange, SimpleChanges} from '../change_detection/change_detection'; import {Injector} from '../di'; import {ElementRef} from '../linker/element_ref'; import {TemplateRef} from '../linker/template_ref'; import {ViewContainerRef} from '../linker/view_container_ref'; import {ViewEncapsulation} from '../metadata/view'; import {Renderer as RendererV1, Renderer2, RendererFactory2, RendererType2} from '../render/api'; import {createChangeDetectorRef, createInjector, createRendererV1} from './refs'; import {BindingDef, BindingType, DepDef, DepFlags, DisposableFn, NodeData, NodeDef, NodeFlags, OutputDef, OutputType, ProviderData, QueryBindingType, QueryDef, QueryValueType, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewState, asElementData, asProviderData} from './types'; import {checkBinding, dispatchEvent, filterQueryId, isComponentView, splitMatchedQueriesDsl, tokenKey, viewParentEl} from './util'; const RendererV1TokenKey = tokenKey(RendererV1); const Renderer2TokenKey = tokenKey(Renderer2); const ElementRefTokenKey = tokenKey(ElementRef); const ViewContainerRefTokenKey = tokenKey(ViewContainerRef); const TemplateRefTokenKey = tokenKey(TemplateRef); const ChangeDetectorRefTokenKey = tokenKey(ChangeDetectorRef); const InjectorRefTokenKey = tokenKey(Injector); 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}): NodeDef { const bindings: BindingDef[] = []; if (props) { for (let prop in props) { const [bindingIndex, nonMinifiedName] = props[prop]; bindings[bindingIndex] = { type: BindingType.DirectiveProperty, name: prop, nonMinifiedName, ns: undefined, securityContext: undefined, suffix: undefined }; } } const outputDefs: OutputDef[] = []; if (outputs) { for (let propName in outputs) { outputDefs.push( {type: OutputType.DirectiveOutput, propName, target: null, eventName: outputs[propName]}); } } flags |= NodeFlags.TypeDirective; return _def(flags, matchedQueries, childCount, ctor, ctor, deps, bindings, outputDefs); } export function pipeDef(flags: NodeFlags, ctor: any, deps: ([DepFlags, any] | any)[]): NodeDef { flags |= NodeFlags.TypePipe; return _def(flags, null, 0, ctor, ctor, deps); } export function providerDef( flags: NodeFlags, matchedQueries: [string | number, QueryValueType][], token: any, value: any, deps: ([DepFlags, any] | any)[]): NodeDef { return _def(flags, matchedQueries, 0, token, value, deps); } export function _def( flags: NodeFlags, matchedQueriesDsl: [string | number, QueryValueType][], childCount: number, token: any, value: any, deps: ([DepFlags, any] | any)[], bindings?: BindingDef[], outputs?: OutputDef[]): NodeDef { const {matchedQueries, references, matchedQueryIds} = splitMatchedQueriesDsl(matchedQueriesDsl); if (!outputs) { outputs = []; } if (!bindings) { bindings = []; } 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)}; }); return { // will bet set by the view definition index: undefined, parent: undefined, renderParent: undefined, bindingIndex: undefined, outputIndex: undefined, // regular values flags, childFlags: 0, directChildFlags: 0, childMatchedQueries: 0, matchedQueries, matchedQueryIds, references, ngContentIndex: undefined, childCount, bindings, outputs, element: undefined, provider: {token, tokenKey: tokenKey(token), value, deps: depDefs}, text: undefined, query: undefined, ngContent: undefined }; } export function createProviderInstance(view: ViewData, def: NodeDef): any { return def.flags & NodeFlags.LazyProvider ? NOT_CREATED : _createProviderInstance(view, def); } export function createPipeInstance(view: ViewData, def: NodeDef): any { // deps are looked up from component. let compView = view; while (compView.parent && !isComponentView(compView)) { compView = compView.parent; } // pipes can see the private services of the component const allowPrivateServices = true; // pipes are always eager and classes! return createClass( compView.parent, viewParentEl(compView), allowPrivateServices, def.provider.value, def.provider.deps); } export function createDirectiveInstance(view: ViewData, def: NodeDef): any { // components can see other private services, other directives can't. const allowPrivateServices = (def.flags & NodeFlags.Component) > 0; const providerDef = def.provider; // directives are always eager and classes! const instance = createClass(view, def.parent, allowPrivateServices, def.provider.value, def.provider.deps); if (def.outputs.length) { for (let i = 0; i < def.outputs.length; i++) { const output = def.outputs[i]; const subscription = instance[output.propName].subscribe( eventHandlerClosure(view, def.parent.index, output.eventName)); view.disposables[def.outputIndex + i] = subscription.unsubscribe.bind(subscription); } } return instance; } function eventHandlerClosure(view: ViewData, index: number, eventName: string) { return (event: any) => dispatchEvent(view, index, eventName, event); } export function checkAndUpdateDirectiveInline( view: ViewData, def: NodeDef, v0: any, v1: any, v2: any, v3: any, v4: any, v5: any, v6: any, v7: any, v8: any, v9: any): boolean { const providerData = asProviderData(view, def.index); const directive = providerData.instance; let changed = false; let changes: SimpleChanges; const bindLen = def.bindings.length; if (bindLen > 0 && checkBinding(view, def, 0, v0)) { changed = true; changes = updateProp(view, providerData, def, 0, v0, changes); }; if (bindLen > 1 && checkBinding(view, def, 1, v1)) { changed = true; changes = updateProp(view, providerData, def, 1, v1, changes); }; if (bindLen > 2 && checkBinding(view, def, 2, v2)) { changed = true; changes = updateProp(view, providerData, def, 2, v2, changes); }; if (bindLen > 3 && checkBinding(view, def, 3, v3)) { changed = true; changes = updateProp(view, providerData, def, 3, v3, changes); }; if (bindLen > 4 && checkBinding(view, def, 4, v4)) { changed = true; changes = updateProp(view, providerData, def, 4, v4, changes); }; if (bindLen > 5 && checkBinding(view, def, 5, v5)) { changed = true; changes = updateProp(view, providerData, def, 5, v5, changes); }; if (bindLen > 6 && checkBinding(view, def, 6, v6)) { changed = true; changes = updateProp(view, providerData, def, 6, v6, changes); }; if (bindLen > 7 && checkBinding(view, def, 7, v7)) { changed = true; changes = updateProp(view, providerData, def, 7, v7, changes); }; if (bindLen > 8 && checkBinding(view, def, 8, v8)) { changed = true; changes = updateProp(view, providerData, def, 8, v8, changes); }; if (bindLen > 9 && checkBinding(view, def, 9, v9)) { changed = true; changes = updateProp(view, providerData, def, 9, v9, changes); }; if (changes) { directive.ngOnChanges(changes); } if ((view.state & ViewState.FirstCheck) && (def.flags & NodeFlags.OnInit)) { directive.ngOnInit(); } if (def.flags & NodeFlags.DoCheck) { directive.ngDoCheck(); } return changed; } export function checkAndUpdateDirectiveDynamic( view: ViewData, def: NodeDef, values: any[]): boolean { const providerData = asProviderData(view, def.index); const directive = providerData.instance; let changed = false; let changes: SimpleChanges; for (let i = 0; i < values.length; i++) { if (checkBinding(view, def, i, values[i])) { changed = true; changes = updateProp(view, providerData, def, i, values[i], changes); } } if (changes) { directive.ngOnChanges(changes); } if ((view.state & ViewState.FirstCheck) && (def.flags & NodeFlags.OnInit)) { directive.ngOnInit(); } if (def.flags & NodeFlags.DoCheck) { directive.ngDoCheck(); } return changed; } function _createProviderInstance(view: ViewData, def: NodeDef): any { // private services can see other private services const allowPrivateServices = (def.flags & NodeFlags.PrivateProvider) > 0; const providerDef = def.provider; let injectable: any; switch (def.flags & NodeFlags.Types) { case NodeFlags.TypeClassProvider: injectable = createClass(view, def.parent, allowPrivateServices, providerDef.value, providerDef.deps); break; case NodeFlags.TypeFactoryProvider: injectable = callFactory(view, def.parent, allowPrivateServices, providerDef.value, providerDef.deps); break; case NodeFlags.TypeUseExistingProvider: injectable = resolveDep(view, def.parent, allowPrivateServices, providerDef.deps[0]); break; case NodeFlags.TypeValueProvider: injectable = providerDef.value; break; } return injectable; } function createClass( view: ViewData, elDef: NodeDef, allowPrivateServices: boolean, 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, elDef, allowPrivateServices, deps[0])); break; case 2: injectable = new ctor( resolveDep(view, elDef, allowPrivateServices, deps[0]), resolveDep(view, elDef, allowPrivateServices, deps[1])); break; case 3: injectable = new ctor( resolveDep(view, elDef, allowPrivateServices, deps[0]), resolveDep(view, elDef, allowPrivateServices, deps[1]), resolveDep(view, elDef, allowPrivateServices, deps[2])); break; default: const depValues = new Array(len); for (let i = 0; i < len; i++) { depValues[i] = resolveDep(view, elDef, allowPrivateServices, deps[i]); } injectable = new ctor(...depValues); } return injectable; } function callFactory( view: ViewData, elDef: NodeDef, allowPrivateServices: boolean, factory: any, deps: DepDef[]): any { const len = deps.length; let injectable: any; switch (len) { case 0: injectable = factory(); break; case 1: injectable = factory(resolveDep(view, elDef, allowPrivateServices, deps[0])); break; case 2: injectable = factory( resolveDep(view, elDef, allowPrivateServices, deps[0]), resolveDep(view, elDef, allowPrivateServices, deps[1])); break; case 3: injectable = factory( resolveDep(view, elDef, allowPrivateServices, deps[0]), resolveDep(view, elDef, allowPrivateServices, deps[1]), resolveDep(view, elDef, allowPrivateServices, deps[2])); break; default: const depValues = Array(len); for (let i = 0; i < len; i++) { depValues[i] = resolveDep(view, elDef, allowPrivateServices, deps[i]); } injectable = factory(...depValues); } return injectable; } export function resolveDep( view: ViewData, elDef: NodeDef, allowPrivateServices: boolean, depDef: DepDef, notFoundValue = Injector.THROW_IF_NOT_FOUND): any { if (depDef.flags & DepFlags.Value) { return depDef.token; } const startView = view; if (depDef.flags & DepFlags.Optional) { notFoundValue = null; } const tokenKey = depDef.tokenKey; if (depDef.flags & DepFlags.SkipSelf) { allowPrivateServices = false; elDef = elDef.parent; } while (view) { if (elDef) { switch (tokenKey) { case RendererV1TokenKey: { const compView = findCompView(view, elDef, allowPrivateServices); return createRendererV1(compView); } case Renderer2TokenKey: { const compView = findCompView(view, elDef, allowPrivateServices); return compView.renderer; } case ElementRefTokenKey: return new ElementRef(asElementData(view, elDef.index).renderElement); case ViewContainerRefTokenKey: return asElementData(view, elDef.index).viewContainer; case TemplateRefTokenKey: { if (elDef.element.template) { return asElementData(view, elDef.index).template; } break; } case ChangeDetectorRefTokenKey: { let cdView = findCompView(view, elDef, allowPrivateServices); return createChangeDetectorRef(cdView); } case InjectorRefTokenKey: return createInjector(view, elDef); default: const providerDef = (allowPrivateServices ? elDef.element.allProviders : elDef.element.publicProviders)[tokenKey]; if (providerDef) { const providerData = asProviderData(view, providerDef.index); if (providerData.instance === NOT_CREATED) { providerData.instance = _createProviderInstance(view, providerDef); } return providerData.instance; } } } allowPrivateServices = isComponentView(view); elDef = viewParentEl(view); view = view.parent; } return startView.root.injector.get(depDef.token, notFoundValue); } function findCompView(view: ViewData, elDef: NodeDef, allowPrivateServices: boolean) { let compView: ViewData; if (allowPrivateServices) { compView = asElementData(view, elDef.index).componentView; } else { compView = view; while (compView.parent && !isComponentView(compView)) { compView = compView.parent; } } return compView; } function updateProp( view: ViewData, providerData: ProviderData, def: NodeDef, bindingIdx: number, value: any, changes: SimpleChanges): SimpleChanges { if (def.flags & NodeFlags.Component) { const compView = asElementData(view, def.parent.index).componentView; if (compView.def.flags & ViewFlags.OnPush) { compView.state |= ViewState.ChecksEnabled; } } 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. providerData.instance[propName] = value; if (def.flags & NodeFlags.OnChanges) { changes = changes || {}; const oldValue = view.oldValues[def.bindingIndex + bindingIdx]; const binding = def.bindings[bindingIdx]; changes[binding.nonMinifiedName] = new SimpleChange(oldValue, value, (view.state & ViewState.FirstCheck) !== 0); } view.oldValues[def.bindingIndex + bindingIdx] = value; return changes; } export function callLifecycleHooksChildrenFirst(view: ViewData, lifecycles: NodeFlags) { if (!(view.def.nodeFlags & lifecycles)) { return; } const nodes = view.def.nodes; for (let i = 0; i < nodes.length; i++) { const nodeDef = nodes[i]; let parent = nodeDef.parent; if (!parent && nodeDef.flags & lifecycles) { // matching root node (e.g. a pipe) callProviderLifecycles(view, i, nodeDef.flags & lifecycles); } if ((nodeDef.childFlags & lifecycles) === 0) { // no child matches one of the lifecycles i += nodeDef.childCount; } while (parent && (parent.flags & NodeFlags.TypeElement) && i === parent.index + parent.childCount) { // last child of an element if (parent.directChildFlags & lifecycles) { callElementProvidersLifecycles(view, parent, lifecycles); } parent = parent.parent; } } } function callElementProvidersLifecycles(view: ViewData, elDef: NodeDef, lifecycles: NodeFlags) { for (let i = elDef.index + 1; i <= elDef.index + elDef.childCount; i++) { const nodeDef = view.def.nodes[i]; if (nodeDef.flags & lifecycles) { callProviderLifecycles(view, i, nodeDef.flags & lifecycles); } // only visit direct children i += nodeDef.childCount; } } function callProviderLifecycles(view: ViewData, index: number, lifecycles: NodeFlags) { const provider = asProviderData(view, index).instance; if (provider === NOT_CREATED) { return; } Services.setCurrentNode(view, index); 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(); } }