refactor(ivy): Add newer, smaller NgOnChangesFeature (#28187)

PR Close #28187
This commit is contained in:
Ben Lesh 2019-01-16 09:35:35 -08:00 committed by Alex Rickabaugh
parent 5552661fd7
commit a95e81978b
22 changed files with 752 additions and 1071 deletions

View File

@ -34,14 +34,20 @@ import {Directive, EmbeddedViewRef, Input, OnChanges, SimpleChange, SimpleChange
*/ */
@Directive({selector: '[ngTemplateOutlet]'}) @Directive({selector: '[ngTemplateOutlet]'})
export class NgTemplateOutlet implements OnChanges { export class NgTemplateOutlet implements OnChanges {
// TODO(issue/24571): remove '!'. private _viewRef: EmbeddedViewRef<any>|null = null;
private _viewRef !: EmbeddedViewRef<any>;
// TODO(issue/24571): remove '!'. /**
@Input() public ngTemplateOutletContext !: Object; * A context object to attach to the {@link EmbeddedViewRef}. This should be an
* object, the object's keys will be available for binding by the local template `let`
* declarations.
* Using the key `$implicit` in the context object will set its value as default.
*/
@Input() public ngTemplateOutletContext: Object|null = null;
// TODO(issue/24571): remove '!'. /**
@Input() public ngTemplateOutlet !: TemplateRef<any>; * A string defining the template reference and optionally the context object for the template.
*/
@Input() public ngTemplateOutlet: TemplateRef<any>|null = null;
constructor(private _viewContainerRef: ViewContainerRef) {} constructor(private _viewContainerRef: ViewContainerRef) {}
@ -97,7 +103,7 @@ export class NgTemplateOutlet implements OnChanges {
private _updateExistingContext(ctx: Object): void { private _updateExistingContext(ctx: Object): void {
for (let propName of Object.keys(ctx)) { for (let propName of Object.keys(ctx)) {
(<any>this._viewRef.context)[propName] = (<any>this.ngTemplateOutletContext)[propName]; (<any>this._viewRef !.context)[propName] = (<any>this.ngTemplateOutletContext)[propName];
} }
} }
} }

View File

@ -64,20 +64,6 @@ export class WrappedValue {
static isWrapped(value: any): value is WrappedValue { return value instanceof 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 { export function isListLikeIterable(obj: any): boolean {
if (!isJsObject(obj)) return false; if (!isJsObject(obj)) return false;
return Array.isArray(obj) || return Array.isArray(obj) ||

View File

@ -5,19 +5,9 @@
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
import {SimpleChanges, SimpleChange} from './simple_change'; import {SimpleChanges} from './simple_change';
/**
* Defines an object that associates properties with
* instances of `SimpleChange`.
*
* @see `OnChanges`
*
* @publicApi
*/
export interface SimpleChanges { [propName: string]: SimpleChange; }
/** /**
* @description * @description
* A lifecycle hook that is called when any data-bound property of a directive changes. * A lifecycle hook that is called when any data-bound property of a directive changes.

View File

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

View File

@ -276,6 +276,7 @@ export function defineComponent<T>(componentDefinition: {
id: 'c', id: 'c',
styles: componentDefinition.styles || EMPTY_ARRAY, styles: componentDefinition.styles || EMPTY_ARRAY,
_: null as never, _: null as never,
setInput: null,
}; };
def._ = noSideEffects(() => { def._ = noSideEffects(() => {
const directiveTypes = componentDefinition.directives !; const directiveTypes = componentDefinition.directives !;
@ -380,12 +381,14 @@ export function defineNgModule<T>(def: {type: T} & Partial<NgModuleDef<T>>): nev
* *
*/ */
function invertObject(obj: any, secondary?: any): any { function invertObject<T>(
if (obj == null) return EMPTY_OBJ; obj?: {[P in keyof T]?: string | [string, string]},
secondary?: {[key: string]: string}): {[P in keyof T]: string} {
if (obj == null) return EMPTY_OBJ as any;
const newLookup: any = {}; const newLookup: any = {};
for (const minifiedKey in obj) { for (const minifiedKey in obj) {
if (obj.hasOwnProperty(minifiedKey)) { if (obj.hasOwnProperty(minifiedKey)) {
let publicName: string = obj[minifiedKey]; let publicName: string|[string, string] = obj[minifiedKey] !;
let declaredName = publicName; let declaredName = publicName;
if (Array.isArray(publicName)) { if (Array.isArray(publicName)) {
declaredName = publicName[1]; declaredName = publicName[1];
@ -393,7 +396,7 @@ function invertObject(obj: any, secondary?: any): any {
} }
newLookup[publicName] = minifiedKey; newLookup[publicName] = minifiedKey;
if (secondary) { if (secondary) {
(secondary[publicName] = declaredName); (secondary[publicName] = declaredName as string);
} }
} }
} }
@ -470,11 +473,11 @@ export function defineBase<T>(baseDefinition: {
*/ */
outputs?: {[P in keyof T]?: string}; outputs?: {[P in keyof T]?: string};
}): BaseDef<T> { }): BaseDef<T> {
const declaredInputs: {[P in keyof T]: P} = {} as any; const declaredInputs: {[P in keyof T]: string} = {} as any;
return { return {
inputs: invertObject(baseDefinition.inputs, declaredInputs), inputs: invertObject<T>(baseDefinition.inputs as any, declaredInputs),
declaredInputs: declaredInputs, declaredInputs: declaredInputs,
outputs: invertObject(baseDefinition.outputs), outputs: invertObject<T>(baseDefinition.outputs as any),
}; };
} }

View File

@ -7,10 +7,10 @@
*/ */
import {Type} from '../../interface/type'; import {Type} from '../../interface/type';
import {Component} from '../../metadata/directives';
import {fillProperties} from '../../util/property'; import {fillProperties} from '../../util/property';
import {EMPTY_ARRAY, EMPTY_OBJ} from '../empty'; import {EMPTY_ARRAY, EMPTY_OBJ} from '../empty';
import {ComponentDef, DirectiveDef, DirectiveDefFeature, RenderFlags} from '../interfaces/definition'; import {ComponentDef, DirectiveDef, DirectiveDefFeature, RenderFlags} from '../interfaces/definition';
import { Component } from '../../metadata/directives';

View File

@ -6,9 +6,9 @@
* found in the LICENSE file at https://angular.io/license * 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 {OnChanges} from '../../interface/lifecycle_hooks';
import {SimpleChange, SimpleChanges} from '../../interface/simple_change';
import {EMPTY_OBJ} from '../empty';
import {DirectiveDef, DirectiveDefFeature} from '../interfaces/definition'; import {DirectiveDef, DirectiveDefFeature} from '../interfaces/definition';
const PRIVATE_PREFIX = '__ngOnChanges_'; const PRIVATE_PREFIX = '__ngOnChanges_';
@ -40,86 +40,63 @@ type OnChangesExpando = OnChanges & {
* ``` * ```
*/ */
export function NgOnChangesFeature<T>(definition: DirectiveDef<T>): void { export function NgOnChangesFeature<T>(definition: DirectiveDef<T>): void {
const publicToDeclaredInputs = definition.declaredInputs; if (definition.type.prototype.ngOnChanges) {
const publicToMinifiedInputs = definition.inputs; definition.setInput = ngOnChangesSetInput;
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 const prevDoCheck = definition.doCheck;
// That way we can honor setters and getters that were inherited. const prevOnInit = definition.onInit;
let originalProperty: PropertyDescriptor|undefined = undefined;
let checkProto = proto; definition.onInit = wrapOnChanges(prevOnInit);
while (!originalProperty && checkProto && definition.doCheck = wrapOnChanges(prevDoCheck);
Object.getPrototypeOf(checkProto) !== Object.getPrototypeOf(Object.prototype)) { }
originalProperty = Object.getOwnPropertyDescriptor(checkProto, minifiedKey); }
checkProto = Object.getPrototypeOf(checkProto);
function wrapOnChanges(hook: (() => void) | null) {
return function(this: OnChanges) {
const simpleChangesStore = getSimpleChangesStore(this);
const current = simpleChangesStore && simpleChangesStore.current;
if (current) {
simpleChangesStore !.previous = current;
simpleChangesStore !.current = null;
this.ngOnChanges(current);
} }
const getter = originalProperty && originalProperty.get; hook && hook.call(this);
const setter = originalProperty && originalProperty.set; };
}
// create a getter and setter for property function ngOnChangesSetInput<T>(
Object.defineProperty(proto, minifiedKey, { this: DirectiveDef<T>, instance: T, value: any, publicName: string, privateName: string): void {
get: getter || const simpleChangesStore = getSimpleChangesStore(instance) ||
(setter ? undefined : function(this: OnChangesExpando) { return this[privateMinKey]; }), setSimpleChangesStore(instance, {previous: EMPTY_OBJ, current: null});
set<T>(this: OnChangesExpando, value: T) { const current = simpleChangesStore.current || (simpleChangesStore.current = {});
let simpleChanges = this[PRIVATE_PREFIX]; const previous = simpleChangesStore.previous;
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 declaredName = (this.declaredInputs as{[key: string]: string})[publicName];
const currentChange = simpleChanges[declaredKey]; const previousChange = previous[declaredName];
current[declaredName] = new SimpleChange(
previousChange && previousChange.currentValue, value, previous === EMPTY_OBJ);
if (currentChange) { (instance as any)[privateName] = value;
currentChange.currentValue = value; }
} else {
simpleChanges[declaredKey] =
new SimpleChange(this[privateMinKey], value, isFirstChange);
}
if (isFirstChange) { const SIMPLE_CHANGES_STORE = '__ngSimpleChanges__';
// 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); function getSimpleChangesStore(instance: any): null|NgSimpleChangesStore {
}, return instance[SIMPLE_CHANGES_STORE] || null;
// 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 function setSimpleChangesStore(instance: any, store: NgSimpleChangesStore): NgSimpleChangesStore {
// so the call order is changes-init-check in creation mode. In subsequent return instance[SIMPLE_CHANGES_STORE] = store;
// 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 // This option ensures that the ngOnChanges lifecycle hook will be inherited
// from superclasses (in InheritDefinitionFeature). // from superclasses (in InheritDefinitionFeature).
(NgOnChangesFeature as DirectiveDefFeature).ngInherit = true; (NgOnChangesFeature as DirectiveDefFeature).ngInherit = true;
function onChangesWrapper(delegateHook: (() => void) | null) {
return function(this: OnChangesExpando) { interface NgSimpleChangesStore {
const simpleChanges = this[PRIVATE_PREFIX]; previous: SimpleChanges;
if (simpleChanges != null) { current: SimpleChanges|null;
this.ngOnChanges(simpleChanges);
this[PRIVATE_PREFIX] = null;
}
if (delegateHook) delegateHook.apply(this);
};
} }

View File

@ -959,10 +959,10 @@ function listenerInternal(
const propsLength = props.length; const propsLength = props.length;
if (propsLength) { if (propsLength) {
const lCleanup = getCleanup(lView); const lCleanup = getCleanup(lView);
for (let i = 0; i < propsLength; i += 2) { for (let i = 0; i < propsLength; i += 3) {
const index = props[i] as number; const index = props[i] as number;
ngDevMode && assertDataInRange(lView, index); ngDevMode && assertDataInRange(lView, index);
const minifiedName = props[i + 1]; const minifiedName = props[i + 2];
const directiveInstance = lView[index]; const directiveInstance = lView[index];
const output = directiveInstance[minifiedName]; const output = directiveInstance[minifiedName];
@ -1214,18 +1214,29 @@ export function createTNode(
* @param value Value to set. * @param value Value to set.
*/ */
function setInputsForProperty(lView: LView, inputs: PropertyAliasValue, value: any): void { function setInputsForProperty(lView: LView, inputs: PropertyAliasValue, value: any): void {
for (let i = 0; i < inputs.length; i += 2) { const tView = lView[TVIEW];
ngDevMode && assertDataInRange(lView, inputs[i] as number); for (let i = 0; i < inputs.length;) {
lView[inputs[i] as number][inputs[i + 1]] = value; const index = inputs[i++] as number;
const publicName = inputs[i++] as string;
const privateName = inputs[i++] as string;
const instance = lView[index];
ngDevMode && assertDataInRange(lView, index);
const def = tView.data[index] as DirectiveDef<any>;
const setInput = def.setInput;
if (setInput) {
def.setInput !(instance, value, publicName, privateName);
} else {
instance[privateName] = value;
}
} }
} }
function setNgReflectProperties( function setNgReflectProperties(
lView: LView, element: RElement | RComment, type: TNodeType, inputs: PropertyAliasValue, lView: LView, element: RElement | RComment, type: TNodeType, inputs: PropertyAliasValue,
value: any) { value: any) {
for (let i = 0; i < inputs.length; i += 2) { for (let i = 0; i < inputs.length; i += 3) {
const renderer = lView[RENDERER]; const renderer = lView[RENDERER];
const attrName = normalizeDebugBindingName(inputs[i + 1] as string); const attrName = normalizeDebugBindingName(inputs[i + 2] as string);
const debugValue = normalizeDebugBindingValue(value); const debugValue = normalizeDebugBindingValue(value);
if (type === TNodeType.Element) { if (type === TNodeType.Element) {
isProceduralRenderer(renderer) ? isProceduralRenderer(renderer) ?
@ -1268,8 +1279,8 @@ function generatePropertyAliases(tNode: TNode, direction: BindingDirection): Pro
propStore = propStore || {}; propStore = propStore || {};
const internalName = propertyAliasMap[publicName]; const internalName = propertyAliasMap[publicName];
const hasProperty = propStore.hasOwnProperty(publicName); const hasProperty = propStore.hasOwnProperty(publicName);
hasProperty ? propStore[publicName].push(i, internalName) : hasProperty ? propStore[publicName].push(i, publicName, internalName) :
(propStore[publicName] = [i, internalName]); (propStore[publicName] = [i, publicName, internalName]);
} }
} }
} }
@ -1702,7 +1713,7 @@ function postProcessDirective<T>(
postProcessBaseDirective(viewData, previousOrParentTNode, directive, def); postProcessBaseDirective(viewData, previousOrParentTNode, directive, def);
ngDevMode && assertDefined(previousOrParentTNode, 'previousOrParentTNode'); ngDevMode && assertDefined(previousOrParentTNode, 'previousOrParentTNode');
if (previousOrParentTNode && previousOrParentTNode.attrs) { if (previousOrParentTNode && previousOrParentTNode.attrs) {
setInputsFromAttrs(directiveDefIdx, directive, def.inputs, previousOrParentTNode); setInputsFromAttrs(directiveDefIdx, directive, def, previousOrParentTNode);
} }
if (def.contentQueries) { if (def.contentQueries) {
@ -1903,16 +1914,24 @@ function addComponentLogic<T>(
* @param tNode The static data for this node * @param tNode The static data for this node
*/ */
function setInputsFromAttrs<T>( function setInputsFromAttrs<T>(
directiveIndex: number, instance: T, inputs: {[P in keyof T]: string;}, tNode: TNode): void { directiveIndex: number, instance: T, def: DirectiveDef<T>, tNode: TNode): void {
let initialInputData = tNode.initialInputs as InitialInputData | undefined; let initialInputData = tNode.initialInputs as InitialInputData | undefined;
if (initialInputData === undefined || directiveIndex >= initialInputData.length) { if (initialInputData === undefined || directiveIndex >= initialInputData.length) {
initialInputData = generateInitialInputs(directiveIndex, inputs, tNode); initialInputData = generateInitialInputs(directiveIndex, def.inputs, tNode);
} }
const initialInputs: InitialInputs|null = initialInputData[directiveIndex]; const initialInputs: InitialInputs|null = initialInputData[directiveIndex];
if (initialInputs) { if (initialInputs) {
for (let i = 0; i < initialInputs.length; i += 2) { const setInput = def.setInput;
(instance as any)[initialInputs[i]] = initialInputs[i + 1]; for (let i = 0; i < initialInputs.length;) {
const publicName = initialInputs[i++];
const privateName = initialInputs[i++];
const value = initialInputs[i++];
if (setInput) {
def.setInput !(instance, value, publicName, privateName);
} else {
(instance as any)[privateName] = value;
}
} }
} }
} }
@ -1956,7 +1975,7 @@ function generateInitialInputs(
if (minifiedInputName !== undefined) { if (minifiedInputName !== undefined) {
const inputsToStore: InitialInputs = const inputsToStore: InitialInputs =
initialInputData[directiveIndex] || (initialInputData[directiveIndex] = []); initialInputData[directiveIndex] || (initialInputData[directiveIndex] = []);
inputsToStore.push(minifiedInputName, attrValue as string); inputsToStore.push(attrName, minifiedInputName, attrValue as string);
} }
i += 2; i += 2;

View File

@ -84,14 +84,14 @@ export interface BaseDef<T> {
* @deprecated This is only here because `NgOnChanges` incorrectly uses declared name instead of * @deprecated This is only here because `NgOnChanges` incorrectly uses declared name instead of
* public or minified name. * public or minified name.
*/ */
readonly declaredInputs: {[P in keyof T]: P}; readonly declaredInputs: {[P in keyof T]: string};
/** /**
* A dictionary mapping the outputs' minified property names to their public API names, which * A dictionary mapping the outputs' minified property names to their public API names, which
* are their aliases if any, or their original unminified property names * are their aliases if any, or their original unminified property names
* (as in `@Output('alias') propertyName: any;`). * (as in `@Output('alias') propertyName: any;`).
*/ */
readonly outputs: {[P in keyof T]: P}; readonly outputs: {[P in keyof T]: string};
} }
/** /**
@ -152,6 +152,10 @@ export interface DirectiveDef<T> extends BaseDef<T> {
* The features applied to this directive * The features applied to this directive
*/ */
readonly features: DirectiveDefFeature[]|null; readonly features: DirectiveDefFeature[]|null;
setInput:
((this: DirectiveDef<T>, instance: T, value: any, publicName: string,
privateName: string) => void)|null;
} }
export type ComponentDefWithMeta< export type ComponentDefWithMeta<

View File

@ -468,10 +468,11 @@ export type PropertyAliases = {
/** /**
* Store the runtime input or output names for all the directives. * Store the runtime input or output names for all the directives.
* *
* - Even indices: directive index * i+0: directive instance index
* - Odd indices: minified / internal name * i+1: publicName
* i+2: privateName
* *
* e.g. [0, 'change-minified'] * e.g. [0, 'change', 'change-minified']
*/ */
export type PropertyAliasValue = (number | string)[]; export type PropertyAliasValue = (number | string)[];
@ -484,14 +485,15 @@ export type PropertyAliasValue = (number | string)[];
* *
* Within each sub-array: * Within each sub-array:
* *
* Even indices: minified/internal input name * i+0: attribute name
* Odd indices: initial value * i+1: minified/internal input name
* i+2: initial value
* *
* If a directive on a node does not have any input properties * If a directive on a node does not have any input properties
* that should be set from attributes, its index is set to null * that should be set from attributes, its index is set to null
* to avoid a sparse array. * to avoid a sparse array.
* *
* e.g. [null, ['role-min', 'button']] * e.g. [null, ['role-min', 'minified-input', 'button']]
*/ */
export type InitialInputData = (InitialInputs | null)[]; export type InitialInputData = (InitialInputs | null)[];
@ -499,10 +501,11 @@ export type InitialInputData = (InitialInputs | null)[];
* Used by InitialInputData to store input properties * Used by InitialInputData to store input properties
* that should be set once from attributes. * that should be set once from attributes.
* *
* Even indices: minified/internal input name * i+0: attribute name
* Odd indices: initial value * i+1: minified/internal input name
* i+2: initial value
* *
* e.g. ['role-min', 'button'] * e.g. ['role-min', 'minified-input', 'button']
*/ */
export type InitialInputs = string[]; export type InitialInputs = string[];

View File

@ -8,7 +8,6 @@
import {InjectionToken} from '../../di/injection_token'; import {InjectionToken} from '../../di/injection_token';
import {Injector} from '../../di/injector'; import {Injector} from '../../di/injector';
import {SimpleChanges} from '../../interface/simple_change';
import {Type} from '../../interface/type'; import {Type} from '../../interface/type';
import {QueryList} from '../../linker'; import {QueryList} from '../../linker';
import {Sanitizer} from '../../sanitization/security'; import {Sanitizer} from '../../sanitization/security';

View File

@ -95,9 +95,6 @@
{ {
"name": "PARENT_INJECTOR" "name": "PARENT_INJECTOR"
}, },
{
"name": "PRIVATE_PREFIX"
},
{ {
"name": "RENDERER" "name": "RENDERER"
}, },
@ -107,6 +104,9 @@
{ {
"name": "SANITIZER" "name": "SANITIZER"
}, },
{
"name": "SIMPLE_CHANGES_STORE"
},
{ {
"name": "SimpleChange" "name": "SimpleChange"
}, },
@ -308,6 +308,9 @@
{ {
"name": "getRootView" "name": "getRootView"
}, },
{
"name": "getSimpleChangesStore"
},
{ {
"name": "hasParentInjector" "name": "hasParentInjector"
}, },
@ -366,10 +369,10 @@
"name": "nextNgElementId" "name": "nextNgElementId"
}, },
{ {
"name": "noSideEffects" "name": "ngOnChangesSetInput"
}, },
{ {
"name": "onChangesWrapper" "name": "noSideEffects"
}, },
{ {
"name": "postProcessBaseDirective" "name": "postProcessBaseDirective"
@ -437,6 +440,9 @@
{ {
"name": "setPreviousOrParentTNode" "name": "setPreviousOrParentTNode"
}, },
{
"name": "setSimpleChangesStore"
},
{ {
"name": "setTNodeAndViewData" "name": "setTNodeAndViewData"
}, },
@ -454,5 +460,8 @@
}, },
{ {
"name": "viewAttached" "name": "viewAttached"
},
{
"name": "wrapOnChanges"
} }
] ]

View File

@ -8,6 +8,9 @@
{ {
"name": "EMPTY_ARRAY" "name": "EMPTY_ARRAY"
}, },
{
"name": "EMPTY_OBJ"
},
{ {
"name": "EmptyErrorImpl" "name": "EmptyErrorImpl"
}, },
@ -51,10 +54,10 @@
"name": "PARAMETERS" "name": "PARAMETERS"
}, },
{ {
"name": "PRIVATE_PREFIX" "name": "R3Injector"
}, },
{ {
"name": "R3Injector" "name": "SIMPLE_CHANGES_STORE"
}, },
{ {
"name": "ScopedService" "name": "ScopedService"
@ -122,6 +125,9 @@
{ {
"name": "getNullInjector" "name": "getNullInjector"
}, },
{
"name": "getSimpleChangesStore"
},
{ {
"name": "hasDeps" "name": "hasDeps"
}, },
@ -165,7 +171,7 @@
"name": "makeRecord" "name": "makeRecord"
}, },
{ {
"name": "onChangesWrapper" "name": "ngOnChangesSetInput"
}, },
{ {
"name": "providerToFactory" "name": "providerToFactory"
@ -179,7 +185,13 @@
{ {
"name": "setCurrentInjector" "name": "setCurrentInjector"
}, },
{
"name": "setSimpleChangesStore"
},
{ {
"name": "stringify" "name": "stringify"
},
{
"name": "wrapOnChanges"
} }
] ]

View File

@ -167,9 +167,6 @@
{ {
"name": "PARENT_INJECTOR" "name": "PARENT_INJECTOR"
}, },
{
"name": "PRIVATE_PREFIX"
},
{ {
"name": "QUERIES" "name": "QUERIES"
}, },
@ -188,6 +185,9 @@
{ {
"name": "SANITIZER" "name": "SANITIZER"
}, },
{
"name": "SIMPLE_CHANGES_STORE"
},
{ {
"name": "SWITCH_ELEMENT_REF_FACTORY" "name": "SWITCH_ELEMENT_REF_FACTORY"
}, },
@ -785,6 +785,9 @@
{ {
"name": "getRootView" "name": "getRootView"
}, },
{
"name": "getSimpleChangesStore"
},
{ {
"name": "getSinglePropIndexValue" "name": "getSinglePropIndexValue"
}, },
@ -1014,10 +1017,10 @@
"name": "nextNgElementId" "name": "nextNgElementId"
}, },
{ {
"name": "noSideEffects" "name": "ngOnChangesSetInput"
}, },
{ {
"name": "onChangesWrapper" "name": "noSideEffects"
}, },
{ {
"name": "pointers" "name": "pointers"
@ -1184,6 +1187,9 @@
{ {
"name": "setSanitizeFlag" "name": "setSanitizeFlag"
}, },
{
"name": "setSimpleChangesStore"
},
{ {
"name": "setStyle" "name": "setStyle"
}, },
@ -1249,5 +1255,8 @@
}, },
{ {
"name": "wrapListenerWithPreventDefault" "name": "wrapListenerWithPreventDefault"
},
{
"name": "wrapOnChanges"
} }
] ]

View File

@ -536,7 +536,6 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
expect(renderLog.log).toEqual(['someProp=Megatron']); expect(renderLog.log).toEqual(['someProp=Megatron']);
})); }));
fixmeIvy('FW-956: refactor onChanges').
it('should record unwrapped values via ngOnChanges', fakeAsync(() => { it('should record unwrapped values via ngOnChanges', fakeAsync(() => {
const ctx = createCompFixture( const ctx = createCompFixture(
'<div [testDirective]="\'aName\' | wrappedPipe" [a]="1" [b]="2 | wrappedPipe"></div>'); '<div [testDirective]="\'aName\' | wrappedPipe" [a]="1" [b]="2 | wrappedPipe"></div>');
@ -739,7 +738,6 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
}); });
describe('ngOnChanges', () => { describe('ngOnChanges', () => {
fixmeIvy('FW-956: refactor onChanges').
it('should notify the directive when a group of records changes', fakeAsync(() => { it('should notify the directive when a group of records changes', fakeAsync(() => {
const ctx = createCompFixture( const ctx = createCompFixture(
'<div [testDirective]="\'aName\'" [a]="1" [b]="2"></div><div [testDirective]="\'bName\'" [a]="4"></div>'); '<div [testDirective]="\'aName\'" [a]="1" [b]="2"></div><div [testDirective]="\'bName\'" [a]="4"></div>');

View File

@ -519,7 +519,10 @@ describe('InheritDefinitionFeature', () => {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
element(0, 'div', ['subDir', '']); element(0, 'div', ['subDir', '']);
} }
}, 1, 0, [SubDirective]); if (rf & RenderFlags.Update) {
elementProperty(0, 'someInput', bind(1));
}
}, 1, 1, [SubDirective]);
const fixture = new ComponentFixture(App); const fixture = new ComponentFixture(App);
expect(log).toEqual(['on changes!']); expect(log).toEqual(['on changes!']);

View File

@ -7,14 +7,14 @@
*/ */
import {ComponentFactoryResolver, OnDestroy, SimpleChange, SimpleChanges, ViewContainerRef} from '../../src/core'; import {ComponentFactoryResolver, OnDestroy, SimpleChange, SimpleChanges, ViewContainerRef} from '../../src/core';
import {AttributeMarker, ComponentTemplate, LifecycleHooksFeature, NO_CHANGE, defineComponent, defineDirective, injectComponentFactoryResolver} from '../../src/render3/index'; import {AttributeMarker, ComponentTemplate, LifecycleHooksFeature, NO_CHANGE, NgOnChangesFeature, defineComponent, defineDirective, injectComponentFactoryResolver} from '../../src/render3/index';
import {bind, container, containerRefreshEnd, containerRefreshStart, directiveInject, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, listener, markDirty, projection, projectionDef, store, template, text} from '../../src/render3/instructions'; import {bind, container, containerRefreshEnd, containerRefreshStart, directiveInject, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, listener, markDirty, projection, projectionDef, store, template, text} from '../../src/render3/instructions';
import {RenderFlags} from '../../src/render3/interfaces/definition'; import {RenderFlags} from '../../src/render3/interfaces/definition';
import {NgIf} from './common_with_def'; import {NgIf} from './common_with_def';
import {ComponentFixture, containerEl, createComponent, renderComponent, renderToHtml, requestAnimationFrame} from './render_util'; import {ComponentFixture, containerEl, createComponent, renderComponent, renderToHtml, requestAnimationFrame} from './render_util';
import { fixmeIvy } from '@angular/private/testing'; import {fixmeIvy} from '@angular/private/testing';
describe('lifecycles', () => { describe('lifecycles', () => {
@ -1941,7 +1941,6 @@ describe('lifecycles', () => {
}); });
fixmeIvy('FW-956: refactor onChanges').
describe('onChanges', () => { describe('onChanges', () => {
let events: ({type: string, name: string, [key: string]: any})[]; let events: ({type: string, name: string, [key: string]: any})[];
@ -2008,7 +2007,8 @@ describe('lifecycles', () => {
consts: consts, consts: consts,
vars: vars, vars: vars,
inputs: {a: 'val1', b: ['publicVal2', 'val2']}, template, inputs: {a: 'val1', b: ['publicVal2', 'val2']}, template,
directives: directives directives: directives,
features: [NgOnChangesFeature],
}); });
}; };
} }
@ -2026,7 +2026,8 @@ describe('lifecycles', () => {
type: Directive, type: Directive,
selectors: [['', 'dir', '']], selectors: [['', 'dir', '']],
factory: () => new Directive(), factory: () => new Directive(),
inputs: {a: 'val1', b: ['publicVal2', 'val2']} inputs: {a: 'val1', b: ['publicVal2', 'val2']},
features: [NgOnChangesFeature],
}); });
} }
@ -2701,7 +2702,6 @@ describe('lifecycles', () => {
}); });
fixmeIvy('FW-956: refactor onChanges').
describe('hook order', () => { describe('hook order', () => {
let events: string[]; let events: string[];
@ -2731,7 +2731,8 @@ describe('lifecycles', () => {
consts: consts, consts: consts,
vars: vars, vars: vars,
inputs: {val: 'val'}, template, inputs: {val: 'val'}, template,
directives: directives directives: directives,
features: [NgOnChangesFeature],
}); });
}; };
} }

View File

@ -1,327 +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 {DoCheck, OnChanges, SimpleChange, SimpleChanges} from '../../src/core';
import {InheritDefinitionFeature} from '../../src/render3/features/inherit_definition_feature';
import {DirectiveDef, NgOnChangesFeature, defineDirective} from '../../src/render3/index';
import { fixmeIvy } from '@angular/private/testing';
fixmeIvy('FW-956: refactor onChanges').
describe('NgOnChangesFeature', () => {
it('should patch class', () => {
class MyDirective implements OnChanges, DoCheck {
public log: Array<string|SimpleChange> = [];
public valA: string = 'initValue';
public set valB(value: string) { this.log.push(value); }
public get valB() { return 'works'; }
ngDoCheck(): void { this.log.push('ngDoCheck'); }
ngOnChanges(changes: SimpleChanges): void {
this.log.push('ngOnChanges');
this.log.push('valA', changes['valA']);
this.log.push('valB', changes['valB']);
}
static ngDirectiveDef = defineDirective({
type: MyDirective,
selectors: [['', 'myDir', '']],
factory: () => new MyDirective(),
features: [NgOnChangesFeature],
inputs: {valA: 'valA', valB: 'valB'}
});
}
const myDir =
(MyDirective.ngDirectiveDef as DirectiveDef<MyDirective>).factory(null) as MyDirective;
myDir.valA = 'first';
expect(myDir.valA).toEqual('first');
myDir.valB = 'second';
expect(myDir.log).toEqual(['second']);
expect(myDir.valB).toEqual('works');
myDir.log.length = 0;
(MyDirective.ngDirectiveDef as DirectiveDef<MyDirective>).doCheck !.call(myDir);
const changeA = new SimpleChange(undefined, 'first', true);
const changeB = new SimpleChange(undefined, 'second', true);
expect(myDir.log).toEqual(['ngOnChanges', 'valA', changeA, 'valB', changeB, 'ngDoCheck']);
});
it('should inherit the behavior from super class', () => {
const log: any[] = [];
class SuperDirective implements OnChanges, DoCheck {
valA = 'initValue';
set valB(value: string) { log.push(value); }
get valB() { return 'works'; }
ngDoCheck(): void { log.push('ngDoCheck'); }
ngOnChanges(changes: SimpleChanges): void {
log.push('ngOnChanges');
log.push('valA', changes['valA']);
log.push('valB', changes['valB']);
log.push('valC', changes['valC']);
}
static ngDirectiveDef = defineDirective({
type: SuperDirective,
selectors: [['', 'superDir', '']],
factory: () => new SuperDirective(),
features: [NgOnChangesFeature],
inputs: {valA: 'valA', valB: 'valB'},
});
}
class SubDirective extends SuperDirective {
valC = 'initValue';
static ngDirectiveDef = defineDirective({
type: SubDirective,
selectors: [['', 'subDir', '']],
factory: () => new SubDirective(),
features: [InheritDefinitionFeature],
inputs: {valC: 'valC'},
});
}
const myDir =
(SubDirective.ngDirectiveDef as DirectiveDef<SubDirective>).factory(null) as SubDirective;
myDir.valA = 'first';
expect(myDir.valA).toEqual('first');
myDir.valB = 'second';
expect(myDir.valB).toEqual('works');
myDir.valC = 'third';
expect(myDir.valC).toEqual('third');
log.length = 0;
(SubDirective.ngDirectiveDef as DirectiveDef<SubDirective>).doCheck !.call(myDir);
const changeA = new SimpleChange(undefined, 'first', true);
const changeB = new SimpleChange(undefined, 'second', true);
const changeC = new SimpleChange(undefined, 'third', true);
expect(log).toEqual(
['ngOnChanges', 'valA', changeA, 'valB', changeB, 'valC', changeC, 'ngDoCheck']);
});
it('should not run the parent doCheck if it is not called explicitly on super class', () => {
const log: any[] = [];
class SuperDirective implements OnChanges, DoCheck {
valA = 'initValue';
ngDoCheck(): void { log.push('ERROR: Child overrides it without super call'); }
ngOnChanges(changes: SimpleChanges): void { log.push(changes.valA, changes.valB); }
static ngDirectiveDef = defineDirective({
type: SuperDirective,
selectors: [['', 'superDir', '']],
factory: () => new SuperDirective(),
features: [NgOnChangesFeature],
inputs: {valA: 'valA'},
});
}
class SubDirective extends SuperDirective implements DoCheck {
valB = 'initValue';
ngDoCheck(): void { log.push('sub ngDoCheck'); }
static ngDirectiveDef = defineDirective({
type: SubDirective,
selectors: [['', 'subDir', '']],
factory: () => new SubDirective(),
features: [InheritDefinitionFeature],
inputs: {valB: 'valB'},
});
}
const myDir =
(SubDirective.ngDirectiveDef as DirectiveDef<SubDirective>).factory(null) as SubDirective;
myDir.valA = 'first';
myDir.valB = 'second';
(SubDirective.ngDirectiveDef as DirectiveDef<SubDirective>).doCheck !.call(myDir);
const changeA = new SimpleChange(undefined, 'first', true);
const changeB = new SimpleChange(undefined, 'second', true);
expect(log).toEqual([changeA, changeB, 'sub ngDoCheck']);
});
it('should run the parent doCheck if it is inherited from super class', () => {
const log: any[] = [];
class SuperDirective implements OnChanges, DoCheck {
valA = 'initValue';
ngDoCheck(): void { log.push('super ngDoCheck'); }
ngOnChanges(changes: SimpleChanges): void { log.push(changes.valA, changes.valB); }
static ngDirectiveDef = defineDirective({
type: SuperDirective,
selectors: [['', 'superDir', '']],
factory: () => new SuperDirective(),
features: [NgOnChangesFeature],
inputs: {valA: 'valA'},
});
}
class SubDirective extends SuperDirective implements DoCheck {
valB = 'initValue';
static ngDirectiveDef = defineDirective({
type: SubDirective,
selectors: [['', 'subDir', '']],
factory: () => new SubDirective(),
features: [InheritDefinitionFeature],
inputs: {valB: 'valB'},
});
}
const myDir =
(SubDirective.ngDirectiveDef as DirectiveDef<SubDirective>).factory(null) as SubDirective;
myDir.valA = 'first';
myDir.valB = 'second';
(SubDirective.ngDirectiveDef as DirectiveDef<SubDirective>).doCheck !.call(myDir);
const changeA = new SimpleChange(undefined, 'first', true);
const changeB = new SimpleChange(undefined, 'second', true);
expect(log).toEqual([changeA, changeB, 'super ngDoCheck']);
});
it('should apply the feature to inherited properties if on sub class', () => {
const log: any[] = [];
class SuperDirective {
valC = 'initValue';
static ngDirectiveDef = defineDirective({
type: SuperDirective,
selectors: [['', 'subDir', '']],
factory: () => new SuperDirective(),
features: [],
inputs: {valC: 'valC'},
});
}
class SubDirective extends SuperDirective implements OnChanges, DoCheck {
valA = 'initValue';
set valB(value: string) { log.push(value); }
get valB() { return 'works'; }
ngDoCheck(): void { log.push('ngDoCheck'); }
ngOnChanges(changes: SimpleChanges): void {
log.push('ngOnChanges');
log.push('valA', changes['valA']);
log.push('valB', changes['valB']);
log.push('valC', changes['valC']);
}
static ngDirectiveDef = defineDirective({
type: SubDirective,
selectors: [['', 'superDir', '']],
factory: () => new SubDirective(),
// Inheritance must always be before OnChanges feature.
features: [
InheritDefinitionFeature,
NgOnChangesFeature,
],
inputs: {valA: 'valA', valB: 'valB'}
});
}
const myDir =
(SubDirective.ngDirectiveDef as DirectiveDef<SubDirective>).factory(null) as SubDirective;
myDir.valA = 'first';
expect(myDir.valA).toEqual('first');
myDir.valB = 'second';
expect(log).toEqual(['second']);
expect(myDir.valB).toEqual('works');
myDir.valC = 'third';
expect(myDir.valC).toEqual('third');
log.length = 0;
(SubDirective.ngDirectiveDef as DirectiveDef<SubDirective>).doCheck !.call(myDir);
const changeA = new SimpleChange(undefined, 'first', true);
const changeB = new SimpleChange(undefined, 'second', true);
const changeC = new SimpleChange(undefined, 'third', true);
expect(log).toEqual(
['ngOnChanges', 'valA', changeA, 'valB', changeB, 'valC', changeC, 'ngDoCheck']);
});
it('correctly computes firstChange', () => {
class MyDirective implements OnChanges {
public log: Array<string|SimpleChange|undefined> = [];
public valA: string = 'initValue';
// TODO(issue/24571): remove '!'.
public valB !: string;
ngOnChanges(changes: SimpleChanges): void {
this.log.push('valA', changes['valA']);
this.log.push('valB', changes['valB']);
}
static ngDirectiveDef = defineDirective({
type: MyDirective,
selectors: [['', 'myDir', '']],
factory: () => new MyDirective(),
features: [NgOnChangesFeature],
inputs: {valA: 'valA', valB: 'valB'}
});
}
const myDir =
(MyDirective.ngDirectiveDef as DirectiveDef<MyDirective>).factory(null) as MyDirective;
myDir.valA = 'first';
myDir.valB = 'second';
(MyDirective.ngDirectiveDef as DirectiveDef<MyDirective>).doCheck !.call(myDir);
const changeA1 = new SimpleChange(undefined, 'first', true);
const changeB1 = new SimpleChange(undefined, 'second', true);
expect(myDir.log).toEqual(['valA', changeA1, 'valB', changeB1]);
myDir.log.length = 0;
myDir.valA = 'third';
(MyDirective.ngDirectiveDef as DirectiveDef<MyDirective>).doCheck !.call(myDir);
const changeA2 = new SimpleChange('first', 'third', false);
expect(myDir.log).toEqual(['valA', changeA2, 'valB', undefined]);
});
it('should not create a getter when only a setter is originally defined', () => {
class MyDirective implements OnChanges {
public log: Array<string|SimpleChange> = [];
public set onlySetter(value: string) { this.log.push(value); }
ngOnChanges(changes: SimpleChanges): void {
this.log.push('ngOnChanges');
this.log.push('onlySetter', changes['onlySetter']);
}
static ngDirectiveDef = defineDirective({
type: MyDirective,
selectors: [['', 'myDir', '']],
factory: () => new MyDirective(),
features: [NgOnChangesFeature],
inputs: {onlySetter: 'onlySetter'}
});
}
const myDir =
(MyDirective.ngDirectiveDef as DirectiveDef<MyDirective>).factory(null) as MyDirective;
myDir.onlySetter = 'someValue';
expect(myDir.onlySetter).toBeUndefined();
(MyDirective.ngDirectiveDef as DirectiveDef<MyDirective>).doCheck !.call(myDir);
const changeSetter = new SimpleChange(undefined, 'someValue', true);
expect(myDir.log).toEqual(['someValue', 'ngOnChanges', 'onlySetter', changeSetter]);
});
});

View File

@ -1677,7 +1677,8 @@ describe('ViewContainerRef', () => {
elementProperty(3, 'name', bind('B')); elementProperty(3, 'name', bind('B'));
} }
}, },
directives: [ComponentWithHooks, DirectiveWithVCRef] directives: [ComponentWithHooks, DirectiveWithVCRef],
features: [NgOnChangesFeature],
}); });
} }
@ -1769,7 +1770,8 @@ describe('ViewContainerRef', () => {
elementProperty(1, 'name', bind('B')); elementProperty(1, 'name', bind('B'));
} }
}, },
directives: [ComponentWithHooks, DirectiveWithVCRef] directives: [ComponentWithHooks, DirectiveWithVCRef],
features: [NgOnChangesFeature],
}); });
} }
@ -1801,7 +1803,7 @@ describe('ViewContainerRef', () => {
fixture.update(); fixture.update();
expect(fixture.html).toEqual('<hooks vcref="">A</hooks><hooks>D</hooks><hooks>B</hooks>'); expect(fixture.html).toEqual('<hooks vcref="">A</hooks><hooks>D</hooks><hooks>B</hooks>');
expect(log).toEqual([ expect(log).toEqual([
'doCheck-A', 'doCheck-B', 'onChanges-D', 'onInit-D', 'doCheck-D', 'afterContentInit-D', 'doCheck-A', 'doCheck-B', 'onInit-D', 'doCheck-D', 'afterContentInit-D',
'afterContentChecked-D', 'afterViewInit-D', 'afterViewChecked-D', 'afterContentChecked-A', 'afterContentChecked-D', 'afterViewInit-D', 'afterViewChecked-D', 'afterContentChecked-A',
'afterContentChecked-B', 'afterViewChecked-A', 'afterViewChecked-B' 'afterContentChecked-B', 'afterViewChecked-A', 'afterViewChecked-B'
]); ]);

View File

@ -315,11 +315,10 @@ withEachNg1Version(() => {
}); });
})); }));
fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly') it('should bind properties, events', async(() => {
.it('should bind properties, events', async(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const ng1Module = angular.module('ng1', []).value( const ng1Module =
$EXCEPTION_HANDLER, (err: any) => { throw err; }); angular.module('ng1', []).value($EXCEPTION_HANDLER, (err: any) => { throw err; });
ng1Module.run(($rootScope: any) => { ng1Module.run(($rootScope: any) => {
$rootScope.name = 'world'; $rootScope.name = 'world';
@ -334,8 +333,7 @@ withEachNg1Version(() => {
selector: 'ng2', selector: 'ng2',
inputs: ['literal', 'interpolate', 'oneWayA', 'oneWayB', 'twoWayA', 'twoWayB'], inputs: ['literal', 'interpolate', 'oneWayA', 'oneWayB', 'twoWayA', 'twoWayB'],
outputs: [ outputs: [
'eventA', 'eventB', 'twoWayAEmitter: twoWayAChange', 'eventA', 'eventB', 'twoWayAEmitter: twoWayAChange', 'twoWayBEmitter: twoWayBChange'
'twoWayBEmitter: twoWayBChange'
], ],
template: 'ignore: {{ignore}}; ' + template: 'ignore: {{ignore}}; ' +
'literal: {{literal}}; interpolate: {{interpolate}}; ' + 'literal: {{literal}}; interpolate: {{interpolate}}; ' +
@ -439,8 +437,7 @@ withEachNg1Version(() => {
})); }));
fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly') it('should support two-way binding and event listener', async(() => {
.it('should support two-way binding and event listener', async(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const listenerSpy = jasmine.createSpy('$rootScope.listener'); const listenerSpy = jasmine.createSpy('$rootScope.listener');
const ng1Module = angular.module('ng1', []).run(($rootScope: angular.IScope) => { const ng1Module = angular.module('ng1', []).run(($rootScope: angular.IScope) => {
@ -489,8 +486,7 @@ withEachNg1Version(() => {
}); });
})); }));
fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly') it('should initialize inputs in time for `ngOnChanges`', async(() => {
.it('should initialize inputs in time for `ngOnChanges`', async(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
@Component({ @Component({
@ -1872,7 +1868,6 @@ withEachNg1Version(() => {
}); });
})); }));
fixmeIvy('FW-956: refactor onChanges').
it('should call `$onChanges()` on binding destination', fakeAsync(() => { it('should call `$onChanges()` on binding destination', fakeAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const $onChangesControllerSpyA = jasmine.createSpy('$onChangesControllerA'); const $onChangesControllerSpyA = jasmine.createSpy('$onChangesControllerA');

View File

@ -22,8 +22,7 @@ withEachNg1Version(() => {
beforeEach(() => destroyPlatform()); beforeEach(() => destroyPlatform());
afterEach(() => destroyPlatform()); afterEach(() => destroyPlatform());
fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly') it('should bind properties, events', async(() => {
.it('should bind properties, events', async(() => {
const ng1Module = angular.module('ng1', []).run(($rootScope: angular.IScope) => { const ng1Module = angular.module('ng1', []).run(($rootScope: angular.IScope) => {
$rootScope['name'] = 'world'; $rootScope['name'] = 'world';
$rootScope['dataA'] = 'A'; $rootScope['dataA'] = 'A';
@ -38,8 +37,7 @@ withEachNg1Version(() => {
selector: 'ng2', selector: 'ng2',
inputs: ['literal', 'interpolate', 'oneWayA', 'oneWayB', 'twoWayA', 'twoWayB'], inputs: ['literal', 'interpolate', 'oneWayA', 'oneWayB', 'twoWayA', 'twoWayB'],
outputs: [ outputs: [
'eventA', 'eventB', 'twoWayAEmitter: twoWayAChange', 'eventA', 'eventB', 'twoWayAEmitter: twoWayAChange', 'twoWayBEmitter: twoWayBChange'
'twoWayBEmitter: twoWayBChange'
], ],
template: 'ignore: {{ignore}}; ' + template: 'ignore: {{ignore}}; ' +
'literal: {{literal}}; interpolate: {{interpolate}}; ' + 'literal: {{literal}}; interpolate: {{interpolate}}; ' +
@ -189,8 +187,7 @@ withEachNg1Version(() => {
}); });
})); }));
fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly') it('should support two-way binding and event listener', async(() => {
.it('should support two-way binding and event listener', async(() => {
const listenerSpy = jasmine.createSpy('$rootScope.listener'); const listenerSpy = jasmine.createSpy('$rootScope.listener');
const ng1Module = angular.module('ng1', []).run(($rootScope: angular.IScope) => { const ng1Module = angular.module('ng1', []).run(($rootScope: angular.IScope) => {
$rootScope['value'] = 'world'; $rootScope['value'] = 'world';
@ -404,8 +401,7 @@ withEachNg1Version(() => {
}); });
})); }));
fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly') it('should initialize inputs in time for `ngOnChanges`', async(() => {
.it('should initialize inputs in time for `ngOnChanges`', async(() => {
@Component({ @Component({
selector: 'ng2', selector: 'ng2',
template: ` template: `

View File

@ -721,8 +721,7 @@ withEachNg1Version(() => {
}); });
})); }));
fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly') it('should propagate input changes inside the Angular zone', async(() => {
.it('should propagate input changes inside the Angular zone', async(() => {
let ng2Component: Ng2Component; let ng2Component: Ng2Component;
@Component({selector: 'ng2', template: ''}) @Component({selector: 'ng2', template: ''})
@ -748,15 +747,13 @@ withEachNg1Version(() => {
const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn); const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
const ng1Module = const ng1Module =
angular.module('ng1', [lazyModuleName]) angular.module('ng1', [lazyModuleName])
.directive( .directive('ng2', downgradeComponent({component: Ng2Component, propagateDigest}))
'ng2', downgradeComponent({component: Ng2Component, propagateDigest}))
.run(($rootScope: angular.IRootScopeService) => { .run(($rootScope: angular.IRootScopeService) => {
$rootScope.attrVal = 'bar'; $rootScope.attrVal = 'bar';
$rootScope.propVal = 'bar'; $rootScope.propVal = 'bar';
}); });
const element = const element = html('<ng2 attr-input="{{ attrVal }}" [prop-input]="propVal"></ng2>');
html('<ng2 attr-input="{{ attrVal }}" [prop-input]="propVal"></ng2>');
const $injector = angular.bootstrap(element, [ng1Module.name]); const $injector = angular.bootstrap(element, [ng1Module.name]);
const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService; const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService;
@ -943,8 +940,7 @@ withEachNg1Version(() => {
}); });
})); }));
fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly') it('should run the lifecycle hooks in the correct order', async(() => {
.it('should run the lifecycle hooks in the correct order', async(() => {
const logs: string[] = []; const logs: string[] = [];
let rootScope: angular.IRootScopeService; let rootScope: angular.IRootScopeService;
@ -957,8 +953,8 @@ withEachNg1Version(() => {
` `
}) })
class Ng2Component implements AfterContentChecked, class Ng2Component implements AfterContentChecked,
AfterContentInit, AfterViewChecked, AfterViewInit, DoCheck, OnChanges, AfterContentInit, AfterViewChecked, AfterViewInit, DoCheck, OnChanges, OnDestroy,
OnDestroy, OnInit { OnInit {
@Input() value = 'foo'; @Input() value = 'foo';
ngAfterContentChecked() { this.log('AfterContentChecked'); } ngAfterContentChecked() { this.log('AfterContentChecked'); }
@ -987,8 +983,7 @@ withEachNg1Version(() => {
const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn); const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
const ng1Module = const ng1Module =
angular.module('ng1', [lazyModuleName]) angular.module('ng1', [lazyModuleName])
.directive( .directive('ng2', downgradeComponent({component: Ng2Component, propagateDigest}))
'ng2', downgradeComponent({component: Ng2Component, propagateDigest}))
.run(($rootScope: angular.IRootScopeService) => { .run(($rootScope: angular.IRootScopeService) => {
rootScope = $rootScope; rootScope = $rootScope;
rootScope.value = 'bar'; rootScope.value = 'bar';