refactor(ivy): revert onChanges change back to a feature (#28187)

- adds fixmeIvy annotation to tests that should remain updated so we can resolve those issues in the subsequent commits

PR Close #28187
This commit is contained in:
Ben Lesh
2019-01-14 17:39:21 -08:00
committed by Alex Rickabaugh
parent 030350f53e
commit 5552661fd7
45 changed files with 1229 additions and 898 deletions

View File

@ -64,6 +64,20 @@ export class WrappedValue {
static isWrapped(value: any): value is WrappedValue { return value instanceof WrappedValue; }
}
/**
* Represents a basic change from a previous to a new value.
*
* @publicApi
*/
export class SimpleChange {
constructor(public previousValue: any, public currentValue: any, public firstChange: boolean) {}
/**
* Check whether the new value is the first value assigned.
*/
isFirstChange(): boolean { return this.firstChange; }
}
export function isListLikeIterable(obj: any): boolean {
if (!isJsObject(obj)) return false;
return Array.isArray(obj) ||

View File

@ -115,6 +115,7 @@ export interface R3DirectiveMetadataFacade {
queries: R3QueryMetadataFacade[];
host: {[key: string]: string};
propMetadata: {[key: string]: any[]};
lifecycle: {usesOnChanges: boolean;};
inputs: string[];
outputs: string[];
usesInheritance: boolean;

View File

@ -28,6 +28,7 @@ export {
templateRefExtractor as ɵtemplateRefExtractor,
ProvidersFeature as ɵProvidersFeature,
InheritDefinitionFeature as ɵInheritDefinitionFeature,
NgOnChangesFeature as ɵNgOnChangesFeature,
LifecycleHooksFeature as ɵLifecycleHooksFeature,
NgModuleType as ɵNgModuleType,
NgModuleRef as ɵRender3NgModuleRef,

View File

@ -5,9 +5,19 @@
* 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 {SimpleChanges} from './simple_change';
import {SimpleChanges, SimpleChange} from './simple_change';
/**
* Defines an object that associates properties with
* instances of `SimpleChange`.
*
* @see `OnChanges`
*
* @publicApi
*/
export interface SimpleChanges { [propName: string]: SimpleChange; }
/**
* @description
* A lifecycle hook that is called when any data-bound property of a directive changes.

View File

@ -9,12 +9,13 @@
import {ChangeDetectionStrategy} from '../change_detection/constants';
import {Provider} from '../di';
import {Type} from '../interface/type';
import {NG_BASE_DEF, NG_COMPONENT_DEF, NG_DIRECTIVE_DEF} from '../render3/fields';
import {NG_BASE_DEF} from '../render3/fields';
import {compileComponent as render3CompileComponent, compileDirective as render3CompileDirective} from '../render3/jit/directive';
import {compilePipe as render3CompilePipe} from '../render3/jit/pipe';
import {TypeDecorator, makeDecorator, makePropDecorator} from '../util/decorators';
import {noop} from '../util/noop';
import {fillProperties} from '../util/property';
import {ViewEncapsulation} from './view';
@ -714,46 +715,21 @@ const initializeBaseDef = (target: any): void => {
};
/**
* Returns a function that will update the static definition on a class to have the
* appropriate input or output mapping.
*
* Will also add an {@link ngBaseDef} property to a directive if no `ngDirectiveDef`
* or `ngComponentDef` is present. This is done because a class may have {@link InputDecorator}s and
* {@link OutputDecorator}s without having a {@link ComponentDecorator} or {@link DirectiveDecorator},
* and those inputs and outputs should still be inheritable, we need to add an
* `ngBaseDef` property if there are no existing `ngComponentDef` or `ngDirectiveDef`
* properties, so that we can track the inputs and outputs for inheritance purposes.
*
* @param getPropertyToUpdate A function that maps to either the `inputs` property or the
* `outputs` property of a definition.
* @returns A function that, the called, will add a `ngBaseDef` if no other definition is present,
* then update the `inputs` or `outputs` on it, depending on what was selected by `getPropertyToUpdate`
*
*
* @see InputDecorator
* @see OutputDecorator
* @see InheritenceFeature
* Does the work of creating the `ngBaseDef` property for the @Input and @Output decorators.
* @param key "inputs" or "outputs"
*/
function getOrCreateDefinitionAndUpdateMappingFor(
getPropertyToUpdate: (baseDef: {inputs?: any, outputs?: any}) => any) {
return function updateIOProp(target: any, name: string, ...args: any[]) {
const constructor = target.constructor;
const updateBaseDefFromIOProp = (getProp: (baseDef: {inputs?: any, outputs?: any}) => any) =>
(target: any, name: string, ...args: any[]) => {
const constructor = target.constructor;
let def: any =
constructor[NG_COMPONENT_DEF] || constructor[NG_DIRECTIVE_DEF] || constructor[NG_BASE_DEF];
if (!constructor.hasOwnProperty(NG_BASE_DEF)) {
initializeBaseDef(target);
}
if (!def) {
initializeBaseDef(target);
def = constructor[NG_BASE_DEF];
}
const defProp = getPropertyToUpdate(def);
// Use of `in` because we *do* want to check the prototype chain here.
if (!(name in defProp)) {
const baseDef = constructor.ngBaseDef;
const defProp = getProp(baseDef);
defProp[name] = args[0];
}
};
}
};
/**
* @Annotation
@ -761,7 +737,7 @@ function getOrCreateDefinitionAndUpdateMappingFor(
*/
export const Input: InputDecorator = makePropDecorator(
'Input', (bindingPropertyName?: string) => ({bindingPropertyName}), undefined,
getOrCreateDefinitionAndUpdateMappingFor(def => def.inputs || {}));
updateBaseDefFromIOProp(baseDef => baseDef.inputs || {}));
/**
* Type of the Output decorator / constructor function.
@ -801,7 +777,7 @@ export interface Output { bindingPropertyName?: string; }
*/
export const Output: OutputDecorator = makePropDecorator(
'Output', (bindingPropertyName?: string) => ({bindingPropertyName}), undefined,
getOrCreateDefinitionAndUpdateMappingFor(def => def.outputs || {}));
updateBaseDefFromIOProp(baseDef => baseDef.outputs || {}));

View File

@ -17,7 +17,6 @@ import {assertComponentType} from './assert';
import {getComponentDef} from './definition';
import {diPublicInInjector, getOrCreateNodeInjectorForNode} from './di';
import {publishDefaultGlobalUtils} from './global_utils';
import {registerPostOrderHooks, registerPreOrderHooks} from './hooks';
import {CLEAN_PROMISE, createLView, createNodeAtIndex, createTNode, createTView, getOrCreateTView, initNodeFlags, instantiateRootComponent, locateHostElement, queueComponentIndexForCheck, refreshDescendantViews} from './instructions';
import {ComponentDef, ComponentType, RenderFlags} from './interfaces/definition';
import {TElementNode, TNode, TNodeFlags, TNodeType} from './interfaces/node';
@ -26,6 +25,7 @@ import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './inte
import {CONTEXT, FLAGS, HEADER_OFFSET, HOST, HOST_NODE, LView, LViewFlags, RootContext, RootContextFlags, TVIEW} from './interfaces/view';
import {enterView, getPreviousOrParentTNode, leaveView, resetComponentState, setCurrentDirectiveDef} from './state';
import {defaultScheduler, getRootView, readPatchedLView, renderStringify} from './util';
import { registerPreOrderHooks, registerPostOrderHooks } from './hooks';
@ -240,8 +240,7 @@ export function LifecycleHooksFeature(component: any, def: ComponentDef<any>): v
registerPreOrderHooks(dirIndex, def, rootTView);
// TODO(misko): replace `as TNode` with createTNode call. (needs refactoring to lose dep on
// LNode).
registerPostOrderHooks(
rootTView, { directiveStart: dirIndex, directiveEnd: dirIndex + 1 } as TNode);
registerPostOrderHooks(rootTView, { directiveStart: dirIndex, directiveEnd: dirIndex + 1 } as TNode);
}
/**

View File

@ -12,7 +12,6 @@ import {LContext, MONKEY_PATCH_KEY_NAME} from './interfaces/context';
import {TNode, TNodeFlags} from './interfaces/node';
import {RElement} from './interfaces/renderer';
import {CONTEXT, HEADER_OFFSET, HOST, LView, TVIEW} from './interfaces/view';
import {unwrapOnChangesDirectiveWrapper} from './onchanges_util';
import {getComponentViewByIndex, getNativeByTNode, readElementValue, readPatchedData} from './util';
@ -258,7 +257,7 @@ function findViaDirective(lView: LView, directiveInstance: {}): number {
const directiveIndexStart = tNode.directiveStart;
const directiveIndexEnd = tNode.directiveEnd;
for (let i = directiveIndexStart; i < directiveIndexEnd; i++) {
if (unwrapOnChangesDirectiveWrapper(lView[i]) === directiveInstance) {
if (lView[i] === directiveInstance) {
return tNode.index;
}
}

View File

@ -194,7 +194,7 @@ export function defineComponent<T>(componentDefinition: {
/**
* A list of optional features to apply.
*
* See: {@link ProvidersFeature}
* See: {@link NgOnChangesFeature}, {@link ProvidersFeature}
*/
features?: ComponentDefFeature[];
@ -256,7 +256,6 @@ export function defineComponent<T>(componentDefinition: {
inputs: null !, // assigned in noSideEffects
outputs: null !, // assigned in noSideEffects
exportAs: componentDefinition.exportAs || null,
onChanges: typePrototype.ngOnChanges || null,
onInit: typePrototype.ngOnInit || null,
doCheck: typePrototype.ngDoCheck || null,
afterContentInit: typePrototype.ngAfterContentInit || null,
@ -567,7 +566,7 @@ export const defineDirective = defineComponent as any as<T>(directiveDefinition:
/**
* A list of optional features to apply.
*
* See: {@link ProvidersFeature}, {@link InheritDefinitionFeature}
* See: {@link NgOnChangesFeature}, {@link ProvidersFeature}, {@link InheritDefinitionFeature}
*/
features?: DirectiveDefFeature[];

View File

@ -20,7 +20,6 @@ import {NO_PARENT_INJECTOR, NodeInjectorFactory, PARENT_INJECTOR, RelativeInject
import {AttributeMarker, TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType} from './interfaces/node';
import {DECLARATION_VIEW, HOST_NODE, INJECTOR, LView, TData, TVIEW, TView} from './interfaces/view';
import {assertNodeOfPossibleTypes} from './node_assert';
import {unwrapOnChangesDirectiveWrapper} from './onchanges_util';
import {getLView, getPreviousOrParentTNode, setTNodeAndViewData} from './state';
import {findComponentView, getParentInjectorIndex, getParentInjectorView, hasParentInjector, isComponent, isComponentDef, renderStringify} from './util';
@ -523,8 +522,6 @@ export function getNodeInjectable(
factory.resolving = false;
setTNodeAndViewData(savePreviousOrParentTNode, saveLView);
}
} else {
value = unwrapOnChangesDirectiveWrapper(value);
}
return value;
}

View File

@ -10,6 +10,7 @@ import {Type} from '../../interface/type';
import {fillProperties} from '../../util/property';
import {EMPTY_ARRAY, EMPTY_OBJ} from '../empty';
import {ComponentDef, DirectiveDef, DirectiveDefFeature, RenderFlags} from '../interfaces/definition';
import { Component } from '../../metadata/directives';
@ -60,6 +61,7 @@ export function InheritDefinitionFeature(definition: DirectiveDef<any>| Componen
}
if (baseDef) {
// Merge inputs and outputs
fillProperties(definition.inputs, baseDef.inputs);
fillProperties(definition.declaredInputs, baseDef.declaredInputs);
fillProperties(definition.outputs, baseDef.outputs);
@ -124,6 +126,7 @@ export function InheritDefinitionFeature(definition: DirectiveDef<any>| Componen
}
}
// Merge inputs and outputs
fillProperties(definition.inputs, superDef.inputs);
fillProperties(definition.declaredInputs, superDef.declaredInputs);
@ -139,7 +142,6 @@ export function InheritDefinitionFeature(definition: DirectiveDef<any>| Componen
definition.doCheck = definition.doCheck || superDef.doCheck;
definition.onDestroy = definition.onDestroy || superDef.onDestroy;
definition.onInit = definition.onInit || superDef.onInit;
definition.onChanges = definition.onChanges || superDef.onChanges;
// Run parent features
const features = superDef.features;
@ -166,7 +168,6 @@ export function InheritDefinitionFeature(definition: DirectiveDef<any>| Componen
definition.doCheck = definition.doCheck || superPrototype.ngDoCheck;
definition.onDestroy = definition.onDestroy || superPrototype.ngOnDestroy;
definition.onInit = definition.onInit || superPrototype.ngOnInit;
definition.onChanges = definition.onChanges || superPrototype.ngOnChanges;
}
}

View File

@ -0,0 +1,125 @@
/**
* @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} from '../../change_detection/change_detection_util';
import {SimpleChanges} from '../../interface/simple_change';
import {OnChanges} from '../../interface/lifecycle_hooks';
import {DirectiveDef, DirectiveDefFeature} from '../interfaces/definition';
const PRIVATE_PREFIX = '__ngOnChanges_';
type OnChangesExpando = OnChanges & {
__ngOnChanges_: SimpleChanges|null|undefined;
// tslint:disable-next-line:no-any Can hold any value
[key: string]: any;
};
/**
* The NgOnChangesFeature decorates a component with support for the ngOnChanges
* lifecycle hook, so it should be included in any component that implements
* that hook.
*
* If the component or directive uses inheritance, the NgOnChangesFeature MUST
* be included as a feature AFTER {@link InheritDefinitionFeature}, otherwise
* inherited properties will not be propagated to the ngOnChanges lifecycle
* hook.
*
* Example usage:
*
* ```
* static ngComponentDef = defineComponent({
* ...
* inputs: {name: 'publicName'},
* features: [NgOnChangesFeature]
* });
* ```
*/
export function NgOnChangesFeature<T>(definition: DirectiveDef<T>): void {
const publicToDeclaredInputs = definition.declaredInputs;
const publicToMinifiedInputs = definition.inputs;
const proto = definition.type.prototype;
for (const publicName in publicToDeclaredInputs) {
if (publicToDeclaredInputs.hasOwnProperty(publicName)) {
const minifiedKey = publicToMinifiedInputs[publicName];
const declaredKey = publicToDeclaredInputs[publicName];
const privateMinKey = PRIVATE_PREFIX + minifiedKey;
// Walk the prototype chain to see if we find a property descriptor
// That way we can honor setters and getters that were inherited.
let originalProperty: PropertyDescriptor|undefined = undefined;
let checkProto = proto;
while (!originalProperty && checkProto &&
Object.getPrototypeOf(checkProto) !== Object.getPrototypeOf(Object.prototype)) {
originalProperty = Object.getOwnPropertyDescriptor(checkProto, minifiedKey);
checkProto = Object.getPrototypeOf(checkProto);
}
const getter = originalProperty && originalProperty.get;
const setter = originalProperty && originalProperty.set;
// create a getter and setter for property
Object.defineProperty(proto, minifiedKey, {
get: getter ||
(setter ? undefined : function(this: OnChangesExpando) { return this[privateMinKey]; }),
set<T>(this: OnChangesExpando, value: T) {
let simpleChanges = this[PRIVATE_PREFIX];
if (!simpleChanges) {
simpleChanges = {};
// Place where we will store SimpleChanges if there is a change
Object.defineProperty(this, PRIVATE_PREFIX, {value: simpleChanges, writable: true});
}
const isFirstChange = !this.hasOwnProperty(privateMinKey);
const currentChange = simpleChanges[declaredKey];
if (currentChange) {
currentChange.currentValue = value;
} else {
simpleChanges[declaredKey] =
new SimpleChange(this[privateMinKey], value, isFirstChange);
}
if (isFirstChange) {
// Create a place where the actual value will be stored and make it non-enumerable
Object.defineProperty(this, privateMinKey, {value, writable: true});
} else {
this[privateMinKey] = value;
}
if (setter) setter.call(this, value);
},
// Make the property configurable in dev mode to allow overriding in tests
configurable: !!ngDevMode
});
}
}
// If an onInit hook is defined, it will need to wrap the ngOnChanges call
// so the call order is changes-init-check in creation mode. In subsequent
// change detection runs, only the check wrapper will be called.
if (definition.onInit != null) {
definition.onInit = onChangesWrapper(definition.onInit);
}
definition.doCheck = onChangesWrapper(definition.doCheck);
}
// This option ensures that the ngOnChanges lifecycle hook will be inherited
// from superclasses (in InheritDefinitionFeature).
(NgOnChangesFeature as DirectiveDefFeature).ngInherit = true;
function onChangesWrapper(delegateHook: (() => void) | null) {
return function(this: OnChangesExpando) {
const simpleChanges = this[PRIVATE_PREFIX];
if (simpleChanges != null) {
this.ngOnChanges(simpleChanges);
this[PRIVATE_PREFIX] = null;
}
if (delegateHook) delegateHook.apply(this);
};
}

View File

@ -12,7 +12,6 @@ import {assertEqual} from '../util/assert';
import {DirectiveDef} from './interfaces/definition';
import {TNode} from './interfaces/node';
import {FLAGS, HookData, LView, LViewFlags, TView} from './interfaces/view';
import {OnChangesDirectiveWrapper, unwrapOnChangesDirectiveWrapper} from './onchanges_util';
@ -35,12 +34,7 @@ export function registerPreOrderHooks(
ngDevMode &&
assertEqual(tView.firstTemplatePass, true, 'Should only be called on first template pass');
const {onChanges, onInit, doCheck} = directiveDef;
if (onChanges) {
(tView.initHooks || (tView.initHooks = [])).push(-directiveIndex, onChanges);
(tView.checkHooks || (tView.checkHooks = [])).push(-directiveIndex, onChanges);
}
const {onInit, doCheck} = directiveDef;
if (onInit) {
(tView.initHooks || (tView.initHooks = [])).push(directiveIndex, onInit);
@ -148,31 +142,13 @@ export function executeHooks(
/**
* Calls lifecycle hooks with their contexts, skipping init hooks if it's not
* the first LView pass, and skipping onChanges hooks if there are no changes present.
* the first LView pass
*
* @param currentView The current view
* @param arr The array in which the hooks are found
*/
export function callHooks(currentView: LView, arr: HookData): void {
for (let i = 0; i < arr.length; i += 2) {
const directiveIndex = arr[i] as number;
const hook = arr[i + 1] as((() => void) | ((changes: SimpleChanges) => void));
// Negative indices signal that we're dealing with an `onChanges` hook.
const isOnChangesHook = directiveIndex < 0;
const directiveOrWrappedDirective =
currentView[isOnChangesHook ? -directiveIndex : directiveIndex];
const directive = unwrapOnChangesDirectiveWrapper(directiveOrWrappedDirective);
if (isOnChangesHook) {
const onChanges: OnChangesDirectiveWrapper = directiveOrWrappedDirective;
const changes = onChanges.changes;
if (changes) {
onChanges.previous = changes;
onChanges.changes = null;
hook.call(onChanges.instance, changes);
}
} else {
hook.call(directive);
}
(arr[i + 1] as() => void).call(currentView[arr[i] as number]);
}
}

View File

@ -9,6 +9,7 @@ import {LifecycleHooksFeature, renderComponent, whenRendered} from './component'
import {defineBase, defineComponent, defineDirective, defineNgModule, definePipe} from './definition';
import {getComponent, getDirectives, getHostElement, getRenderedText} from './discovery_utils';
import {InheritDefinitionFeature} from './features/inherit_definition_feature';
import {NgOnChangesFeature} from './features/ng_onchanges_feature';
import {ProvidersFeature} from './features/providers_feature';
import {BaseDef, ComponentDef, ComponentDefWithMeta, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFlags, DirectiveDefWithMeta, DirectiveType, PipeDef, PipeDefWithMeta} from './interfaces/definition';
@ -158,6 +159,7 @@ export {
DirectiveDefFlags,
DirectiveDefWithMeta,
DirectiveType,
NgOnChangesFeature,
InheritDefinitionFeature,
ProvidersFeature,
PipeDef,

View File

@ -36,7 +36,6 @@ import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTENT_QUERIES, CONTEXT, DECLA
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
import {appendChild, appendProjectedNode, createTextNode, getLViewChild, insertView, removeView} from './node_manipulation';
import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher';
import {OnChangesDirectiveWrapper, isOnChangesDirectiveWrapper, recordChange, unwrapOnChangesDirectiveWrapper} from './onchanges_util';
import {decreaseElementDepthCount, enterView, getBindingsEnabled, getCheckNoChangesMode, getContextLView, getCurrentDirectiveDef, getElementDepthCount, getFirstTemplatePass, getIsParent, getLView, getPreviousOrParentTNode, increaseElementDepthCount, isCreationMode, leaveView, nextContextImpl, resetComponentState, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setFirstTemplatePass, setIsParent, setPreviousOrParentTNode} from './state';
import {getInitialClassNameValue, initializeStaticContext as initializeStaticStylingContext, patchContextWithStaticAttrs, renderInitialStylesAndClasses, renderStyling, updateClassProp as updateElementClassProp, updateContextWithBindings, updateStyleProp as updateElementStyleProp, updateStylingMap} from './styling/class_and_style_bindings';
import {BoundPlayerFactory} from './styling/player_factory';
@ -121,7 +120,7 @@ export function setHostBindings(tView: TView, viewData: LView): void {
if (instruction !== null) {
viewData[BINDING_INDEX] = bindingRootIndex;
instruction(
RenderFlags.Update, unwrapOnChangesDirectiveWrapper(viewData[currentDirectiveIndex]),
RenderFlags.Update, readElementValue(viewData[currentDirectiveIndex]),
currentElementIndex);
}
currentDirectiveIndex++;
@ -726,7 +725,6 @@ export function createTView(
expandoStartIndex: initialViewLength,
expandoInstructions: null,
firstTemplatePass: true,
changesHooks: null,
initHooks: null,
checkHooks: null,
contentHooks: null,
@ -961,18 +959,16 @@ function listenerInternal(
const propsLength = props.length;
if (propsLength) {
const lCleanup = getCleanup(lView);
// Subscribe to listeners for each output, and setup clean up for each.
for (let i = 0; i < propsLength;) {
const directiveIndex = props[i++] as number;
const minifiedName = props[i++] as string;
const declaredName = props[i++] as string;
ngDevMode && assertDataInRange(lView, directiveIndex as number);
const directive = unwrapOnChangesDirectiveWrapper(lView[directiveIndex]);
const output = directive[minifiedName];
for (let i = 0; i < propsLength; i += 2) {
const index = props[i] as number;
ngDevMode && assertDataInRange(lView, index);
const minifiedName = props[i + 1];
const directiveInstance = lView[index];
const output = directiveInstance[minifiedName];
if (ngDevMode && !isObservable(output)) {
throw new Error(
`@Output ${minifiedName} not initialized in '${directive.constructor.name}'.`);
`@Output ${minifiedName} not initialized in '${directiveInstance.constructor.name}'.`);
}
const subscription = output.subscribe(listenerFn);
@ -1042,7 +1038,7 @@ export function elementEnd(): void {
if (hasClassInput(previousOrParentTNode)) {
const stylingContext = getStylingContext(previousOrParentTNode.index, lView);
setInputsForProperty(
lView, previousOrParentTNode.inputs !, 'class', getInitialClassNameValue(stylingContext));
lView, previousOrParentTNode.inputs !['class'] !, getInitialClassNameValue(stylingContext));
}
}
@ -1137,7 +1133,7 @@ function elementPropertyInternal<T>(
let dataValue: PropertyAliasValue|undefined;
if (!nativeOnly && (inputData = initializeTNodeInputs(tNode)) &&
(dataValue = inputData[propName])) {
setInputsForProperty(lView, inputData, propName, value);
setInputsForProperty(lView, dataValue, value);
if (isComponent(tNode)) markDirtyIfOnPush(lView, index + HEADER_OFFSET);
if (ngDevMode) {
if (tNode.type === TNodeType.Element || tNode.type === TNodeType.Container) {
@ -1215,30 +1211,21 @@ export function createTNode(
* @param lView the `LView` which contains the directives.
* @param inputAliases mapping between the public "input" name and privately-known,
* possibly minified, property names to write to.
* @param publicName public binding name. (This is the `<div [publicName]=value>`)
* @param value Value to set.
*/
function setInputsForProperty(
lView: LView, inputAliases: PropertyAliases, publicName: string, value: any): void {
const inputs = inputAliases[publicName];
for (let i = 0; i < inputs.length;) {
const directiveIndex = inputs[i++] as number;
const privateName = inputs[i++] as string;
const declaredName = inputs[i++] as string;
ngDevMode && assertDataInRange(lView, directiveIndex);
recordChangeAndUpdateProperty(lView[directiveIndex], declaredName, privateName, value);
function setInputsForProperty(lView: LView, inputs: PropertyAliasValue, value: any): void {
for (let i = 0; i < inputs.length; i += 2) {
ngDevMode && assertDataInRange(lView, inputs[i] as number);
lView[inputs[i] as number][inputs[i + 1]] = value;
}
}
function setNgReflectProperties(
lView: LView, element: RElement | RComment, type: TNodeType, inputs: PropertyAliasValue,
value: any) {
for (let i = 0; i < inputs.length;) {
const directiveIndex = inputs[i++] as number;
const privateName = inputs[i++] as string;
const declaredName = inputs[i++] as string;
for (let i = 0; i < inputs.length; i += 2) {
const renderer = lView[RENDERER];
const attrName = normalizeDebugBindingName(privateName);
const attrName = normalizeDebugBindingName(inputs[i + 1] as string);
const debugValue = normalizeDebugBindingValue(value);
if (type === TNodeType.Element) {
isProceduralRenderer(renderer) ?
@ -1274,20 +1261,15 @@ function generatePropertyAliases(tNode: TNode, direction: BindingDirection): Pro
for (let i = start; i < end; i++) {
const directiveDef = defs[i] as DirectiveDef<any>;
const publicToMinifiedNames: {[publicName: string]: string} =
const propertyAliasMap: {[publicName: string]: string} =
isInput ? directiveDef.inputs : directiveDef.outputs;
const publicToDeclaredNames: {[publicName: string]: string}|null =
isInput ? directiveDef.declaredInputs : null;
for (let publicName in publicToMinifiedNames) {
if (publicToMinifiedNames.hasOwnProperty(publicName)) {
for (let publicName in propertyAliasMap) {
if (propertyAliasMap.hasOwnProperty(publicName)) {
propStore = propStore || {};
const minifiedName = publicToMinifiedNames[publicName];
const declaredName =
publicToDeclaredNames ? publicToDeclaredNames[publicName] : minifiedName;
const aliases: PropertyAliasValue = propStore.hasOwnProperty(publicName) ?
propStore[publicName] :
propStore[publicName] = [];
aliases.push(i, minifiedName, declaredName);
const internalName = propertyAliasMap[publicName];
const hasProperty = propStore.hasOwnProperty(publicName);
hasProperty ? propStore[publicName].push(i, internalName) :
(propStore[publicName] = [i, internalName]);
}
}
}
@ -1514,7 +1496,7 @@ export function elementStylingMap<T>(
const initialClasses = getInitialClassNameValue(stylingContext);
const classInputVal =
(initialClasses.length ? (initialClasses + ' ') : '') + (classes as string);
setInputsForProperty(lView, tNode.inputs !, 'class', classInputVal);
setInputsForProperty(lView, tNode.inputs !['class'] !, classInputVal);
} else {
updateStylingMap(stylingContext, classes, styles);
}
@ -1647,7 +1629,6 @@ function instantiateAllDirectives(tView: TView, lView: LView, tNode: TNode) {
addComponentLogic(lView, tNode, def as ComponentDef<any>);
}
const directive = getNodeInjectable(tView.data, lView !, i, tNode as TElementNode);
postProcessDirective(lView, directive, def, i);
}
}
@ -1659,7 +1640,7 @@ function invokeDirectivesHostBindings(tView: TView, viewData: LView, tNode: TNod
const firstTemplatePass = getFirstTemplatePass();
for (let i = start; i < end; i++) {
const def = tView.data[i] as DirectiveDef<any>;
const directive = unwrapOnChangesDirectiveWrapper(viewData[i]);
const directive = viewData[i];
if (def.hostBindings) {
const previousExpandoLength = expando.length;
setCurrentDirectiveDef(def);
@ -1716,17 +1697,12 @@ function prefillHostVars(tView: TView, lView: LView, totalHostVars: number): voi
* Process a directive on the current node after its creation.
*/
function postProcessDirective<T>(
lView: LView, directive: T, def: DirectiveDef<T>, directiveDefIdx: number): void {
if (def.onChanges) {
// We have onChanges, wrap it so that we can track changes.
lView[directiveDefIdx] = new OnChangesDirectiveWrapper(lView[directiveDefIdx]);
}
viewData: LView, directive: T, def: DirectiveDef<T>, directiveDefIdx: number): void {
const previousOrParentTNode = getPreviousOrParentTNode();
postProcessBaseDirective(lView, previousOrParentTNode, directive, def);
postProcessBaseDirective(viewData, previousOrParentTNode, directive, def);
ngDevMode && assertDefined(previousOrParentTNode, 'previousOrParentTNode');
if (previousOrParentTNode && previousOrParentTNode.attrs) {
setInputsFromAttrs(lView, directiveDefIdx, def, previousOrParentTNode);
setInputsFromAttrs(directiveDefIdx, directive, def.inputs, previousOrParentTNode);
}
if (def.contentQueries) {
@ -1734,7 +1710,7 @@ function postProcessDirective<T>(
}
if (isComponentDef(def)) {
const componentView = getComponentViewByIndex(previousOrParentTNode.index, lView);
const componentView = getComponentViewByIndex(previousOrParentTNode.index, viewData);
componentView[CONTEXT] = directive;
}
}
@ -1927,53 +1903,20 @@ function addComponentLogic<T>(
* @param tNode The static data for this node
*/
function setInputsFromAttrs<T>(
lView: LView, directiveIndex: number, def: DirectiveDef<any>, tNode: TNode): void {
directiveIndex: number, instance: T, inputs: {[P in keyof T]: string;}, tNode: TNode): void {
let initialInputData = tNode.initialInputs as InitialInputData | undefined;
if (initialInputData === undefined || directiveIndex >= initialInputData.length) {
initialInputData = generateInitialInputs(directiveIndex, def, tNode);
initialInputData = generateInitialInputs(directiveIndex, inputs, tNode);
}
const initialInputs: InitialInputs|null = initialInputData[directiveIndex];
if (initialInputs) {
const directiveOrWrappedDirective = lView[directiveIndex];
for (let i = 0; i < initialInputs.length;) {
const privateName = initialInputs[i++];
const declaredName = initialInputs[i++];
const attrValue = initialInputs[i++];
recordChangeAndUpdateProperty(
directiveOrWrappedDirective, declaredName, privateName, attrValue);
for (let i = 0; i < initialInputs.length; i += 2) {
(instance as any)[initialInputs[i]] = initialInputs[i + 1];
}
}
}
/**
* Checks to see if the instanced passed as `directiveOrWrappedDirective` is wrapped in {@link
* OnChangesDirectiveWrapper} or not.
* If it is, it will update the related {@link SimpleChanges} object with the change to signal
* `ngOnChanges` hook
* should fire, then it will unwrap the instance. After that, it will set the property with the key
* provided
* in `privateName` on the instance with the passed value.
* @param directiveOrWrappedDirective The directive instance or a directive instance wrapped in
* {@link OnChangesDirectiveWrapper}
* @param declaredName The original, declared name of the property to update.
* @param privateName The private, possibly minified name of the property to update.
* @param value The value to update the property with.
*/
function recordChangeAndUpdateProperty<T, K extends keyof T>(
directiveOrWrappedDirective: OnChangesDirectiveWrapper<T>| T, declaredName: string,
privateName: K, value: any) {
let instance: T;
if (isOnChangesDirectiveWrapper(directiveOrWrappedDirective)) {
instance = unwrapOnChangesDirectiveWrapper(directiveOrWrappedDirective);
recordChange(directiveOrWrappedDirective, declaredName, value);
} else {
instance = directiveOrWrappedDirective;
}
instance[privateName] = value;
}
/**
* Generates initialInputData for a node and stores it in the template's static storage
* so subsequent template invocations don't have to recalculate it.
@ -1990,7 +1933,7 @@ function recordChangeAndUpdateProperty<T, K extends keyof T>(
* @param tNode The static data on this node
*/
function generateInitialInputs(
directiveIndex: number, directiveDef: DirectiveDef<any>, tNode: TNode): InitialInputData {
directiveIndex: number, inputs: {[key: string]: string}, tNode: TNode): InitialInputData {
const initialInputData: InitialInputData = tNode.initialInputs || (tNode.initialInputs = []);
initialInputData[directiveIndex] = null;
@ -2007,14 +1950,13 @@ function generateInitialInputs(
i += 4;
continue;
}
const privateName = directiveDef.inputs[attrName];
const declaredName = directiveDef.declaredInputs[attrName];
const minifiedInputName = inputs[attrName];
const attrValue = attrs[i + 1];
if (privateName !== undefined) {
if (minifiedInputName !== undefined) {
const inputsToStore: InitialInputs =
initialInputData[directiveIndex] || (initialInputData[directiveIndex] = []);
inputsToStore.push(privateName, declaredName, attrValue as string);
inputsToStore.push(minifiedInputName, attrValue as string);
}
i += 2;

View File

@ -6,9 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import {SimpleChanges, ViewEncapsulation} from '../../core';
import {ViewEncapsulation} from '../../core';
import {Type} from '../../interface/type';
import {CssSelectorList} from './projection';
@ -143,7 +142,6 @@ export interface DirectiveDef<T> extends BaseDef<T> {
/* The following are lifecycle hooks for this component */
onInit: (() => void)|null;
doCheck: (() => void)|null;
onChanges: ((changes: SimpleChanges) => void)|null;
afterContentInit: (() => void)|null;
afterContentChecked: (() => void)|null;
afterViewInit: (() => void)|null;

View File

@ -468,12 +468,10 @@ export type PropertyAliases = {
/**
* Store the runtime input or output names for all the directives.
*
* Values are stored in triplets:
* - i + 0: directive index
* - i + 1: minified / internal name
* - i + 2: declared name
* - Even indices: directive index
* - Odd indices: minified / internal name
*
* e.g. [0, 'minifiedName', 'declaredPropertyName']
* e.g. [0, 'change-minified']
*/
export type PropertyAliasValue = (number | string)[];
@ -501,12 +499,10 @@ export type InitialInputData = (InitialInputs | null)[];
* Used by InitialInputData to store input properties
* that should be set once from attributes.
*
* The inputs come in triplets of:
* i + 0: minified/internal input name
* i + 1: declared input name (needed for OnChanges)
* i + 2: initial value
* Even indices: minified/internal input name
* Odd indices: initial value
*
* e.g. ['minifiedName', 'declaredName', 'value']
* e.g. ['role-min', 'button']
*/
export type InitialInputs = string[];

View File

@ -534,7 +534,7 @@ export interface RootContext {
* Even indices: Directive index
* Odd indices: Hook function
*/
export type HookData = (number | (() => void) | ((changes: SimpleChanges) => void))[];
export type HookData = (number | (() => void))[];
/**
* Static data that corresponds to the instance-specific data array on an LView.

View File

@ -145,6 +145,9 @@ function directiveMetadata(type: Type<any>, metadata: Directive): R3DirectiveMet
inputs: metadata.inputs || EMPTY_ARRAY,
outputs: metadata.outputs || EMPTY_ARRAY,
queries: extractQueriesMetadata(type, propMetadata, isContentQuery),
lifecycle: {
usesOnChanges: type.prototype.ngOnChanges !== undefined,
},
typeSourceSpan: null !,
usesInheritance: !extendsDirectlyFromObject(type),
exportAs: extractExportAs(metadata.exportAs),

View File

@ -31,6 +31,7 @@ export const angularCoreEnv: {[name: string]: Function} = {
'inject': inject,
'ɵinjectAttribute': r3.injectAttribute,
'ɵtemplateRefExtractor': r3.templateRefExtractor,
'ɵNgOnChangesFeature': r3.NgOnChangesFeature,
'ɵProvidersFeature': r3.ProvidersFeature,
'ɵInheritDefinitionFeature': r3.InheritDefinitionFeature,
'ɵelementAttribute': r3.elementAttribute,

View File

@ -1,71 +0,0 @@
/**
* @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 '../interface/simple_change';
type Constructor<T> = new (...args: any[]) => T;
/**
* Checks an object to see if it's an exact instance of a particular type
* without traversing the inheritance hierarchy like `instanceof` does.
* @param obj The object to check
* @param type The type to check the object against
*/
export function isExactInstanceOf<T>(obj: any, type: Constructor<T>): obj is T {
return obj != null && typeof obj == 'object' && Object.getPrototypeOf(obj) == type.prototype;
}
/**
* Checks to see if an object is an instance of {@link OnChangesDirectiveWrapper}
* @param obj the object to check (generally from `LView`)
*/
export function isOnChangesDirectiveWrapper(obj: any): obj is OnChangesDirectiveWrapper<any> {
return isExactInstanceOf(obj, OnChangesDirectiveWrapper);
}
/**
* Removes the `OnChangesDirectiveWrapper` if present.
*
* @param obj to unwrap.
*/
export function unwrapOnChangesDirectiveWrapper<T>(obj: T | OnChangesDirectiveWrapper<T>): T {
return isOnChangesDirectiveWrapper(obj) ? obj.instance : obj;
}
/**
* A class that wraps directive instances for storage in LView when directives
* have onChanges hooks to deal with.
*/
export class OnChangesDirectiveWrapper<T = any> {
seenProps = new Set<string>();
previous: SimpleChanges = {};
changes: SimpleChanges|null = null;
constructor(public instance: T) {}
}
/**
* Updates the `changes` property on the `wrapper` instance, such that when it's
* checked in {@link callHooks} it will fire the related `onChanges` hook.
* @param wrapper the wrapper for the directive instance
* @param declaredName the declared name to be used in `SimpleChange`
* @param value The new value for the property
*/
export function recordChange(wrapper: OnChangesDirectiveWrapper, declaredName: string, value: any) {
const simpleChanges = wrapper.changes || (wrapper.changes = {});
const firstChange = !wrapper.seenProps.has(declaredName);
if (firstChange) {
wrapper.seenProps.add(declaredName);
}
const previous = wrapper.previous;
const previousValue: SimpleChange|undefined = previous[declaredName];
simpleChanges[declaredName] = new SimpleChange(
firstChange ? undefined : previousValue && previousValue.currentValue, value, firstChange);
}

View File

@ -17,7 +17,6 @@ import {TContainerNode, TElementNode, TNode, TNodeFlags, TNodeType} from './inte
import {GlobalTargetName, GlobalTargetResolver, RComment, RElement, RText} from './interfaces/renderer';
import {StylingContext} from './interfaces/styling';
import {CONTEXT, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, HOST_NODE, LView, LViewFlags, PARENT, RootContext, TData, TVIEW, TView} from './interfaces/view';
import {isOnChangesDirectiveWrapper} from './onchanges_util';
/**
@ -71,14 +70,9 @@ export function flatten(list: any[]): any[] {
/** Retrieves a value from any `LView` or `TData`. */
export function loadInternal<T>(view: LView | TData, index: number): T {
ngDevMode && assertDataInRange(view, index + HEADER_OFFSET);
const record = view[index + HEADER_OFFSET];
// If we're storing an array because of a directive or component with ngOnChanges,
// return the directive or component instance.
return isOnChangesDirectiveWrapper(record) ? record.instance : record;
return view[index + HEADER_OFFSET];
}
/**
* Takes the value of a slot in `LView` and returns the element node.
*
@ -297,4 +291,4 @@ export function resolveDocument(element: RElement & {ownerDocument: Document}) {
export function resolveBody(element: RElement & {ownerDocument: Document}) {
return {name: 'body', target: element.ownerDocument.body};
}
}

View File

@ -11,11 +11,10 @@ import {ChangeDetectorRef as viewEngine_ChangeDetectorRef} from '../change_detec
import {ViewContainerRef as viewEngine_ViewContainerRef} from '../linker/view_container_ref';
import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, InternalViewRef as viewEngine_InternalViewRef} from '../linker/view_ref';
import {checkNoChanges, checkNoChangesInRootView, detectChangesInRootView, detectChangesInternal, markViewDirty, storeCleanupFn, viewAttached} from './instructions';
import {checkNoChanges, checkNoChangesInRootView, checkView, detectChangesInRootView, detectChangesInternal, markViewDirty, storeCleanupFn, viewAttached} from './instructions';
import {TNode, TNodeType, TViewNode} from './interfaces/node';
import {FLAGS, HOST, HOST_NODE, LView, LViewFlags, PARENT} from './interfaces/view';
import {FLAGS, HOST, HOST_NODE, LView, LViewFlags, PARENT, RENDERER_FACTORY} from './interfaces/view';
import {destroyLView} from './node_manipulation';
import {unwrapOnChangesDirectiveWrapper} from './onchanges_util';
import {getNativeByTNode} from './util';
@ -272,8 +271,7 @@ export class ViewRef<T> implements viewEngine_EmbeddedViewRef<T>, viewEngine_Int
}
private _lookUpContext(): T {
return this._context =
unwrapOnChangesDirectiveWrapper(this._lView[PARENT] ![this._componentIndex] as T);
return this._context = this._lView[PARENT] ![this._componentIndex] as T;
}
}