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:
parent
030350f53e
commit
5552661fd7
|
@ -34,20 +34,14 @@ import {Directive, EmbeddedViewRef, Input, OnChanges, SimpleChange, SimpleChange
|
||||||
*/
|
*/
|
||||||
@Directive({selector: '[ngTemplateOutlet]'})
|
@Directive({selector: '[ngTemplateOutlet]'})
|
||||||
export class NgTemplateOutlet implements OnChanges {
|
export class NgTemplateOutlet implements OnChanges {
|
||||||
private _viewRef: EmbeddedViewRef<any>|null = null;
|
// TODO(issue/24571): remove '!'.
|
||||||
|
private _viewRef !: EmbeddedViewRef<any>;
|
||||||
|
|
||||||
/**
|
// TODO(issue/24571): remove '!'.
|
||||||
* A context object to attach to the {@link EmbeddedViewRef}. This should be an
|
@Input() public ngTemplateOutletContext !: Object;
|
||||||
* 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 '!'.
|
||||||
* A string defining the template reference and optionally the context object for the template.
|
@Input() public ngTemplateOutlet !: TemplateRef<any>;
|
||||||
*/
|
|
||||||
@Input() public ngTemplateOutlet: TemplateRef<any>|null = null;
|
|
||||||
|
|
||||||
constructor(private _viewContainerRef: ViewContainerRef) {}
|
constructor(private _viewContainerRef: ViewContainerRef) {}
|
||||||
|
|
||||||
|
@ -103,7 +97,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];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -174,6 +174,11 @@ export function extractDirectiveMetadata(
|
||||||
const providers: Expression|null =
|
const providers: Expression|null =
|
||||||
directive.has('providers') ? new WrappedNodeExpr(directive.get('providers') !) : null;
|
directive.has('providers') ? new WrappedNodeExpr(directive.get('providers') !) : null;
|
||||||
|
|
||||||
|
// Determine if `ngOnChanges` is a lifecycle hook defined on the component.
|
||||||
|
const usesOnChanges = members.some(
|
||||||
|
member => !member.isStatic && member.kind === ClassMemberKind.Method &&
|
||||||
|
member.name === 'ngOnChanges');
|
||||||
|
|
||||||
// Parse exportAs.
|
// Parse exportAs.
|
||||||
let exportAs: string[]|null = null;
|
let exportAs: string[]|null = null;
|
||||||
if (directive.has('exportAs')) {
|
if (directive.has('exportAs')) {
|
||||||
|
@ -192,6 +197,9 @@ export function extractDirectiveMetadata(
|
||||||
const metadata: R3DirectiveMetadata = {
|
const metadata: R3DirectiveMetadata = {
|
||||||
name: clazz.name !.text,
|
name: clazz.name !.text,
|
||||||
deps: getConstructorDependencies(clazz, reflector, isCore), host,
|
deps: getConstructorDependencies(clazz, reflector, isCore), host,
|
||||||
|
lifecycle: {
|
||||||
|
usesOnChanges,
|
||||||
|
},
|
||||||
inputs: {...inputsFromMeta, ...inputsFromFields},
|
inputs: {...inputsFromMeta, ...inputsFromFields},
|
||||||
outputs: {...outputsFromMeta, ...outputsFromFields}, queries, selector,
|
outputs: {...outputsFromMeta, ...outputsFromFields}, queries, selector,
|
||||||
type: new WrappedNodeExpr(clazz.name !),
|
type: new WrappedNodeExpr(clazz.name !),
|
||||||
|
|
|
@ -2117,6 +2117,7 @@ describe('compiler compliance', () => {
|
||||||
selectors: [["lifecycle-comp"]],
|
selectors: [["lifecycle-comp"]],
|
||||||
factory: function LifecycleComp_Factory(t) { return new (t || LifecycleComp)(); },
|
factory: function LifecycleComp_Factory(t) { return new (t || LifecycleComp)(); },
|
||||||
inputs: {nameMin: ["name", "nameMin"]},
|
inputs: {nameMin: ["name", "nameMin"]},
|
||||||
|
features: [$r3$.ɵNgOnChangesFeature],
|
||||||
consts: 0,
|
consts: 0,
|
||||||
vars: 0,
|
vars: 0,
|
||||||
template: function LifecycleComp_Template(rf, ctx) {},
|
template: function LifecycleComp_Template(rf, ctx) {},
|
||||||
|
@ -2238,6 +2239,7 @@ describe('compiler compliance', () => {
|
||||||
factory: function ForOfDirective_Factory(t) {
|
factory: function ForOfDirective_Factory(t) {
|
||||||
return new (t || ForOfDirective)($r3$.ɵdirectiveInject(ViewContainerRef), $r3$.ɵdirectiveInject(TemplateRef));
|
return new (t || ForOfDirective)($r3$.ɵdirectiveInject(ViewContainerRef), $r3$.ɵdirectiveInject(TemplateRef));
|
||||||
},
|
},
|
||||||
|
features: [$r3$.ɵNgOnChangesFeature],
|
||||||
inputs: {forOf: "forOf"}
|
inputs: {forOf: "forOf"}
|
||||||
});
|
});
|
||||||
`;
|
`;
|
||||||
|
@ -2313,6 +2315,7 @@ describe('compiler compliance', () => {
|
||||||
factory: function ForOfDirective_Factory(t) {
|
factory: function ForOfDirective_Factory(t) {
|
||||||
return new (t || ForOfDirective)($r3$.ɵdirectiveInject(ViewContainerRef), $r3$.ɵdirectiveInject(TemplateRef));
|
return new (t || ForOfDirective)($r3$.ɵdirectiveInject(ViewContainerRef), $r3$.ɵdirectiveInject(TemplateRef));
|
||||||
},
|
},
|
||||||
|
features: [$r3$.ɵNgOnChangesFeature],
|
||||||
inputs: {forOf: "forOf"}
|
inputs: {forOf: "forOf"}
|
||||||
});
|
});
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -115,6 +115,7 @@ export interface R3DirectiveMetadataFacade {
|
||||||
queries: R3QueryMetadataFacade[];
|
queries: R3QueryMetadataFacade[];
|
||||||
host: {[key: string]: string};
|
host: {[key: string]: string};
|
||||||
propMetadata: {[key: string]: any[]};
|
propMetadata: {[key: string]: any[]};
|
||||||
|
lifecycle: {usesOnChanges: boolean;};
|
||||||
inputs: string[];
|
inputs: string[];
|
||||||
outputs: string[];
|
outputs: string[];
|
||||||
usesInheritance: boolean;
|
usesInheritance: boolean;
|
||||||
|
|
|
@ -188,6 +188,8 @@ export class Identifiers {
|
||||||
static registerContentQuery:
|
static registerContentQuery:
|
||||||
o.ExternalReference = {name: 'ɵregisterContentQuery', moduleName: CORE};
|
o.ExternalReference = {name: 'ɵregisterContentQuery', moduleName: CORE};
|
||||||
|
|
||||||
|
static NgOnChangesFeature: o.ExternalReference = {name: 'ɵNgOnChangesFeature', moduleName: CORE};
|
||||||
|
|
||||||
static InheritDefinitionFeature:
|
static InheritDefinitionFeature:
|
||||||
o.ExternalReference = {name: 'ɵInheritDefinitionFeature', moduleName: CORE};
|
o.ExternalReference = {name: 'ɵInheritDefinitionFeature', moduleName: CORE};
|
||||||
|
|
||||||
|
|
|
@ -74,6 +74,17 @@ export interface R3DirectiveMetadata {
|
||||||
properties: {[key: string]: string};
|
properties: {[key: string]: string};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Information about usage of specific lifecycle events which require special treatment in the
|
||||||
|
* code generator.
|
||||||
|
*/
|
||||||
|
lifecycle: {
|
||||||
|
/**
|
||||||
|
* Whether the directive uses NgOnChanges.
|
||||||
|
*/
|
||||||
|
usesOnChanges: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A mapping of input field names to the property names.
|
* A mapping of input field names to the property names.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -125,6 +125,7 @@ function baseDirectiveFields(
|
||||||
*/
|
*/
|
||||||
function addFeatures(
|
function addFeatures(
|
||||||
definitionMap: DefinitionMap, meta: R3DirectiveMetadata | R3ComponentMetadata) {
|
definitionMap: DefinitionMap, meta: R3DirectiveMetadata | R3ComponentMetadata) {
|
||||||
|
// e.g. `features: [NgOnChangesFeature]`
|
||||||
const features: o.Expression[] = [];
|
const features: o.Expression[] = [];
|
||||||
|
|
||||||
const providers = meta.providers;
|
const providers = meta.providers;
|
||||||
|
@ -140,7 +141,9 @@ function addFeatures(
|
||||||
if (meta.usesInheritance) {
|
if (meta.usesInheritance) {
|
||||||
features.push(o.importExpr(R3.InheritDefinitionFeature));
|
features.push(o.importExpr(R3.InheritDefinitionFeature));
|
||||||
}
|
}
|
||||||
|
if (meta.lifecycle.usesOnChanges) {
|
||||||
|
features.push(o.importExpr(R3.NgOnChangesFeature));
|
||||||
|
}
|
||||||
if (features.length) {
|
if (features.length) {
|
||||||
definitionMap.set('features', o.literalArr(features));
|
definitionMap.set('features', o.literalArr(features));
|
||||||
}
|
}
|
||||||
|
@ -421,6 +424,10 @@ function directiveMetadataFromGlobalMetadata(
|
||||||
selector: directive.selector,
|
selector: directive.selector,
|
||||||
deps: dependenciesFromGlobalMetadata(directive.type, outputCtx, reflector),
|
deps: dependenciesFromGlobalMetadata(directive.type, outputCtx, reflector),
|
||||||
queries: queriesFromGlobalMetadata(directive.queries, outputCtx),
|
queries: queriesFromGlobalMetadata(directive.queries, outputCtx),
|
||||||
|
lifecycle: {
|
||||||
|
usesOnChanges:
|
||||||
|
directive.type.lifecycleHooks.some(lifecycle => lifecycle == LifecycleHooks.OnChanges),
|
||||||
|
},
|
||||||
host: {
|
host: {
|
||||||
attributes: directive.hostAttributes,
|
attributes: directive.hostAttributes,
|
||||||
listeners: summary.hostListeners,
|
listeners: summary.hostListeners,
|
||||||
|
|
|
@ -64,6 +64,20 @@ 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) ||
|
||||||
|
|
|
@ -115,6 +115,7 @@ export interface R3DirectiveMetadataFacade {
|
||||||
queries: R3QueryMetadataFacade[];
|
queries: R3QueryMetadataFacade[];
|
||||||
host: {[key: string]: string};
|
host: {[key: string]: string};
|
||||||
propMetadata: {[key: string]: any[]};
|
propMetadata: {[key: string]: any[]};
|
||||||
|
lifecycle: {usesOnChanges: boolean;};
|
||||||
inputs: string[];
|
inputs: string[];
|
||||||
outputs: string[];
|
outputs: string[];
|
||||||
usesInheritance: boolean;
|
usesInheritance: boolean;
|
||||||
|
|
|
@ -28,6 +28,7 @@ export {
|
||||||
templateRefExtractor as ɵtemplateRefExtractor,
|
templateRefExtractor as ɵtemplateRefExtractor,
|
||||||
ProvidersFeature as ɵProvidersFeature,
|
ProvidersFeature as ɵProvidersFeature,
|
||||||
InheritDefinitionFeature as ɵInheritDefinitionFeature,
|
InheritDefinitionFeature as ɵInheritDefinitionFeature,
|
||||||
|
NgOnChangesFeature as ɵNgOnChangesFeature,
|
||||||
LifecycleHooksFeature as ɵLifecycleHooksFeature,
|
LifecycleHooksFeature as ɵLifecycleHooksFeature,
|
||||||
NgModuleType as ɵNgModuleType,
|
NgModuleType as ɵNgModuleType,
|
||||||
NgModuleRef as ɵRender3NgModuleRef,
|
NgModuleRef as ɵRender3NgModuleRef,
|
||||||
|
|
|
@ -5,9 +5,19 @@
|
||||||
* 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} 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
|
* @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.
|
||||||
|
|
|
@ -9,12 +9,13 @@
|
||||||
import {ChangeDetectionStrategy} from '../change_detection/constants';
|
import {ChangeDetectionStrategy} from '../change_detection/constants';
|
||||||
import {Provider} from '../di';
|
import {Provider} from '../di';
|
||||||
import {Type} from '../interface/type';
|
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 {compileComponent as render3CompileComponent, compileDirective as render3CompileDirective} from '../render3/jit/directive';
|
||||||
import {compilePipe as render3CompilePipe} from '../render3/jit/pipe';
|
import {compilePipe as render3CompilePipe} from '../render3/jit/pipe';
|
||||||
import {TypeDecorator, makeDecorator, makePropDecorator} from '../util/decorators';
|
import {TypeDecorator, makeDecorator, makePropDecorator} from '../util/decorators';
|
||||||
import {noop} from '../util/noop';
|
import {noop} from '../util/noop';
|
||||||
import {fillProperties} from '../util/property';
|
import {fillProperties} from '../util/property';
|
||||||
|
|
||||||
import {ViewEncapsulation} from './view';
|
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
|
* Does the work of creating the `ngBaseDef` property for the @Input and @Output decorators.
|
||||||
* appropriate input or output mapping.
|
* @param key "inputs" or "outputs"
|
||||||
*
|
|
||||||
* 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
|
|
||||||
*/
|
*/
|
||||||
function getOrCreateDefinitionAndUpdateMappingFor(
|
const updateBaseDefFromIOProp = (getProp: (baseDef: {inputs?: any, outputs?: any}) => any) =>
|
||||||
getPropertyToUpdate: (baseDef: {inputs?: any, outputs?: any}) => any) {
|
(target: any, name: string, ...args: any[]) => {
|
||||||
return function updateIOProp(target: any, name: string, ...args: any[]) {
|
const constructor = target.constructor;
|
||||||
const constructor = target.constructor;
|
|
||||||
|
|
||||||
let def: any =
|
if (!constructor.hasOwnProperty(NG_BASE_DEF)) {
|
||||||
constructor[NG_COMPONENT_DEF] || constructor[NG_DIRECTIVE_DEF] || constructor[NG_BASE_DEF];
|
initializeBaseDef(target);
|
||||||
|
}
|
||||||
|
|
||||||
if (!def) {
|
const baseDef = constructor.ngBaseDef;
|
||||||
initializeBaseDef(target);
|
const defProp = getProp(baseDef);
|
||||||
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)) {
|
|
||||||
defProp[name] = args[0];
|
defProp[name] = args[0];
|
||||||
}
|
};
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Annotation
|
* @Annotation
|
||||||
|
@ -761,7 +737,7 @@ function getOrCreateDefinitionAndUpdateMappingFor(
|
||||||
*/
|
*/
|
||||||
export const Input: InputDecorator = makePropDecorator(
|
export const Input: InputDecorator = makePropDecorator(
|
||||||
'Input', (bindingPropertyName?: string) => ({bindingPropertyName}), undefined,
|
'Input', (bindingPropertyName?: string) => ({bindingPropertyName}), undefined,
|
||||||
getOrCreateDefinitionAndUpdateMappingFor(def => def.inputs || {}));
|
updateBaseDefFromIOProp(baseDef => baseDef.inputs || {}));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type of the Output decorator / constructor function.
|
* Type of the Output decorator / constructor function.
|
||||||
|
@ -801,7 +777,7 @@ export interface Output { bindingPropertyName?: string; }
|
||||||
*/
|
*/
|
||||||
export const Output: OutputDecorator = makePropDecorator(
|
export const Output: OutputDecorator = makePropDecorator(
|
||||||
'Output', (bindingPropertyName?: string) => ({bindingPropertyName}), undefined,
|
'Output', (bindingPropertyName?: string) => ({bindingPropertyName}), undefined,
|
||||||
getOrCreateDefinitionAndUpdateMappingFor(def => def.outputs || {}));
|
updateBaseDefFromIOProp(baseDef => baseDef.outputs || {}));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,6 @@ 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';
|
||||||
|
@ -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 {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,8 +240,7 @@ 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(
|
registerPostOrderHooks(rootTView, { directiveStart: dirIndex, directiveEnd: dirIndex + 1 } as TNode);
|
||||||
rootTView, { directiveStart: dirIndex, directiveEnd: dirIndex + 1 } as TNode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -12,7 +12,6 @@ import {LContext, MONKEY_PATCH_KEY_NAME} from './interfaces/context';
|
||||||
import {TNode, TNodeFlags} from './interfaces/node';
|
import {TNode, TNodeFlags} from './interfaces/node';
|
||||||
import {RElement} from './interfaces/renderer';
|
import {RElement} from './interfaces/renderer';
|
||||||
import {CONTEXT, HEADER_OFFSET, HOST, LView, TVIEW} from './interfaces/view';
|
import {CONTEXT, HEADER_OFFSET, HOST, LView, TVIEW} from './interfaces/view';
|
||||||
import {unwrapOnChangesDirectiveWrapper} from './onchanges_util';
|
|
||||||
import {getComponentViewByIndex, getNativeByTNode, readElementValue, readPatchedData} from './util';
|
import {getComponentViewByIndex, getNativeByTNode, readElementValue, readPatchedData} from './util';
|
||||||
|
|
||||||
|
|
||||||
|
@ -258,7 +257,7 @@ function findViaDirective(lView: LView, directiveInstance: {}): number {
|
||||||
const directiveIndexStart = tNode.directiveStart;
|
const directiveIndexStart = tNode.directiveStart;
|
||||||
const directiveIndexEnd = tNode.directiveEnd;
|
const directiveIndexEnd = tNode.directiveEnd;
|
||||||
for (let i = directiveIndexStart; i < directiveIndexEnd; i++) {
|
for (let i = directiveIndexStart; i < directiveIndexEnd; i++) {
|
||||||
if (unwrapOnChangesDirectiveWrapper(lView[i]) === directiveInstance) {
|
if (lView[i] === directiveInstance) {
|
||||||
return tNode.index;
|
return tNode.index;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -194,7 +194,7 @@ export function defineComponent<T>(componentDefinition: {
|
||||||
/**
|
/**
|
||||||
* A list of optional features to apply.
|
* A list of optional features to apply.
|
||||||
*
|
*
|
||||||
* See: {@link ProvidersFeature}
|
* See: {@link NgOnChangesFeature}, {@link ProvidersFeature}
|
||||||
*/
|
*/
|
||||||
features?: ComponentDefFeature[];
|
features?: ComponentDefFeature[];
|
||||||
|
|
||||||
|
@ -256,7 +256,6 @@ export function defineComponent<T>(componentDefinition: {
|
||||||
inputs: null !, // assigned in noSideEffects
|
inputs: null !, // assigned in noSideEffects
|
||||||
outputs: null !, // assigned in noSideEffects
|
outputs: null !, // assigned in noSideEffects
|
||||||
exportAs: componentDefinition.exportAs || null,
|
exportAs: componentDefinition.exportAs || null,
|
||||||
onChanges: typePrototype.ngOnChanges || null,
|
|
||||||
onInit: typePrototype.ngOnInit || null,
|
onInit: typePrototype.ngOnInit || null,
|
||||||
doCheck: typePrototype.ngDoCheck || null,
|
doCheck: typePrototype.ngDoCheck || null,
|
||||||
afterContentInit: typePrototype.ngAfterContentInit || 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.
|
* A list of optional features to apply.
|
||||||
*
|
*
|
||||||
* See: {@link ProvidersFeature}, {@link InheritDefinitionFeature}
|
* See: {@link NgOnChangesFeature}, {@link ProvidersFeature}, {@link InheritDefinitionFeature}
|
||||||
*/
|
*/
|
||||||
features?: DirectiveDefFeature[];
|
features?: DirectiveDefFeature[];
|
||||||
|
|
||||||
|
|
|
@ -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 {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 {DECLARATION_VIEW, HOST_NODE, INJECTOR, LView, TData, TVIEW, TView} from './interfaces/view';
|
||||||
import {assertNodeOfPossibleTypes} from './node_assert';
|
import {assertNodeOfPossibleTypes} from './node_assert';
|
||||||
import {unwrapOnChangesDirectiveWrapper} from './onchanges_util';
|
|
||||||
import {getLView, getPreviousOrParentTNode, setTNodeAndViewData} from './state';
|
import {getLView, getPreviousOrParentTNode, setTNodeAndViewData} from './state';
|
||||||
import {findComponentView, getParentInjectorIndex, getParentInjectorView, hasParentInjector, isComponent, isComponentDef, renderStringify} from './util';
|
import {findComponentView, getParentInjectorIndex, getParentInjectorView, hasParentInjector, isComponent, isComponentDef, renderStringify} from './util';
|
||||||
|
|
||||||
|
@ -523,8 +522,6 @@ export function getNodeInjectable(
|
||||||
factory.resolving = false;
|
factory.resolving = false;
|
||||||
setTNodeAndViewData(savePreviousOrParentTNode, saveLView);
|
setTNodeAndViewData(savePreviousOrParentTNode, saveLView);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
value = unwrapOnChangesDirectiveWrapper(value);
|
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import {Type} from '../../interface/type';
|
||||||
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';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -60,6 +61,7 @@ export function InheritDefinitionFeature(definition: DirectiveDef<any>| Componen
|
||||||
}
|
}
|
||||||
|
|
||||||
if (baseDef) {
|
if (baseDef) {
|
||||||
|
// Merge inputs and outputs
|
||||||
fillProperties(definition.inputs, baseDef.inputs);
|
fillProperties(definition.inputs, baseDef.inputs);
|
||||||
fillProperties(definition.declaredInputs, baseDef.declaredInputs);
|
fillProperties(definition.declaredInputs, baseDef.declaredInputs);
|
||||||
fillProperties(definition.outputs, baseDef.outputs);
|
fillProperties(definition.outputs, baseDef.outputs);
|
||||||
|
@ -124,6 +126,7 @@ export function InheritDefinitionFeature(definition: DirectiveDef<any>| Componen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Merge inputs and outputs
|
// Merge inputs and outputs
|
||||||
fillProperties(definition.inputs, superDef.inputs);
|
fillProperties(definition.inputs, superDef.inputs);
|
||||||
fillProperties(definition.declaredInputs, superDef.declaredInputs);
|
fillProperties(definition.declaredInputs, superDef.declaredInputs);
|
||||||
|
@ -139,7 +142,6 @@ export function InheritDefinitionFeature(definition: DirectiveDef<any>| Componen
|
||||||
definition.doCheck = definition.doCheck || superDef.doCheck;
|
definition.doCheck = definition.doCheck || superDef.doCheck;
|
||||||
definition.onDestroy = definition.onDestroy || superDef.onDestroy;
|
definition.onDestroy = definition.onDestroy || superDef.onDestroy;
|
||||||
definition.onInit = definition.onInit || superDef.onInit;
|
definition.onInit = definition.onInit || superDef.onInit;
|
||||||
definition.onChanges = definition.onChanges || superDef.onChanges;
|
|
||||||
|
|
||||||
// Run parent features
|
// Run parent features
|
||||||
const features = superDef.features;
|
const features = superDef.features;
|
||||||
|
@ -166,7 +168,6 @@ export function InheritDefinitionFeature(definition: DirectiveDef<any>| Componen
|
||||||
definition.doCheck = definition.doCheck || superPrototype.ngDoCheck;
|
definition.doCheck = definition.doCheck || superPrototype.ngDoCheck;
|
||||||
definition.onDestroy = definition.onDestroy || superPrototype.ngOnDestroy;
|
definition.onDestroy = definition.onDestroy || superPrototype.ngOnDestroy;
|
||||||
definition.onInit = definition.onInit || superPrototype.ngOnInit;
|
definition.onInit = definition.onInit || superPrototype.ngOnInit;
|
||||||
definition.onChanges = definition.onChanges || superPrototype.ngOnChanges;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
};
|
||||||
|
}
|
|
@ -12,7 +12,6 @@ import {assertEqual} from '../util/assert';
|
||||||
import {DirectiveDef} from './interfaces/definition';
|
import {DirectiveDef} from './interfaces/definition';
|
||||||
import {TNode} from './interfaces/node';
|
import {TNode} from './interfaces/node';
|
||||||
import {FLAGS, HookData, LView, LViewFlags, TView} from './interfaces/view';
|
import {FLAGS, HookData, LView, LViewFlags, TView} from './interfaces/view';
|
||||||
import {OnChangesDirectiveWrapper, unwrapOnChangesDirectiveWrapper} from './onchanges_util';
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -35,12 +34,7 @@ export function registerPreOrderHooks(
|
||||||
ngDevMode &&
|
ngDevMode &&
|
||||||
assertEqual(tView.firstTemplatePass, true, 'Should only be called on first template pass');
|
assertEqual(tView.firstTemplatePass, true, 'Should only be called on first template pass');
|
||||||
|
|
||||||
const {onChanges, onInit, doCheck} = directiveDef;
|
const {onInit, doCheck} = directiveDef;
|
||||||
|
|
||||||
if (onChanges) {
|
|
||||||
(tView.initHooks || (tView.initHooks = [])).push(-directiveIndex, onChanges);
|
|
||||||
(tView.checkHooks || (tView.checkHooks = [])).push(-directiveIndex, onChanges);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (onInit) {
|
if (onInit) {
|
||||||
(tView.initHooks || (tView.initHooks = [])).push(directiveIndex, 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
|
* 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 currentView The current view
|
||||||
* @param arr The array in which the hooks are found
|
* @param arr The array in which the hooks are found
|
||||||
*/
|
*/
|
||||||
export function callHooks(currentView: LView, arr: HookData): void {
|
export function callHooks(currentView: LView, arr: HookData): void {
|
||||||
for (let i = 0; i < arr.length; i += 2) {
|
for (let i = 0; i < arr.length; i += 2) {
|
||||||
const directiveIndex = arr[i] as number;
|
(arr[i + 1] as() => void).call(currentView[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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {LifecycleHooksFeature, renderComponent, whenRendered} from './component'
|
||||||
import {defineBase, defineComponent, defineDirective, defineNgModule, definePipe} from './definition';
|
import {defineBase, defineComponent, defineDirective, defineNgModule, definePipe} from './definition';
|
||||||
import {getComponent, getDirectives, getHostElement, getRenderedText} from './discovery_utils';
|
import {getComponent, getDirectives, getHostElement, getRenderedText} from './discovery_utils';
|
||||||
import {InheritDefinitionFeature} from './features/inherit_definition_feature';
|
import {InheritDefinitionFeature} from './features/inherit_definition_feature';
|
||||||
|
import {NgOnChangesFeature} from './features/ng_onchanges_feature';
|
||||||
import {ProvidersFeature} from './features/providers_feature';
|
import {ProvidersFeature} from './features/providers_feature';
|
||||||
import {BaseDef, ComponentDef, ComponentDefWithMeta, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFlags, DirectiveDefWithMeta, DirectiveType, PipeDef, PipeDefWithMeta} from './interfaces/definition';
|
import {BaseDef, ComponentDef, ComponentDefWithMeta, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFlags, DirectiveDefWithMeta, DirectiveType, PipeDef, PipeDefWithMeta} from './interfaces/definition';
|
||||||
|
|
||||||
|
@ -158,6 +159,7 @@ export {
|
||||||
DirectiveDefFlags,
|
DirectiveDefFlags,
|
||||||
DirectiveDefWithMeta,
|
DirectiveDefWithMeta,
|
||||||
DirectiveType,
|
DirectiveType,
|
||||||
|
NgOnChangesFeature,
|
||||||
InheritDefinitionFeature,
|
InheritDefinitionFeature,
|
||||||
ProvidersFeature,
|
ProvidersFeature,
|
||||||
PipeDef,
|
PipeDef,
|
||||||
|
|
|
@ -36,7 +36,6 @@ import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTENT_QUERIES, CONTEXT, DECLA
|
||||||
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
|
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
|
||||||
import {appendChild, appendProjectedNode, createTextNode, getLViewChild, insertView, removeView} from './node_manipulation';
|
import {appendChild, appendProjectedNode, createTextNode, getLViewChild, insertView, removeView} from './node_manipulation';
|
||||||
import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher';
|
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 {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 {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';
|
import {BoundPlayerFactory} from './styling/player_factory';
|
||||||
|
@ -121,7 +120,7 @@ export function setHostBindings(tView: TView, viewData: LView): void {
|
||||||
if (instruction !== null) {
|
if (instruction !== null) {
|
||||||
viewData[BINDING_INDEX] = bindingRootIndex;
|
viewData[BINDING_INDEX] = bindingRootIndex;
|
||||||
instruction(
|
instruction(
|
||||||
RenderFlags.Update, unwrapOnChangesDirectiveWrapper(viewData[currentDirectiveIndex]),
|
RenderFlags.Update, readElementValue(viewData[currentDirectiveIndex]),
|
||||||
currentElementIndex);
|
currentElementIndex);
|
||||||
}
|
}
|
||||||
currentDirectiveIndex++;
|
currentDirectiveIndex++;
|
||||||
|
@ -726,7 +725,6 @@ export function createTView(
|
||||||
expandoStartIndex: initialViewLength,
|
expandoStartIndex: initialViewLength,
|
||||||
expandoInstructions: null,
|
expandoInstructions: null,
|
||||||
firstTemplatePass: true,
|
firstTemplatePass: true,
|
||||||
changesHooks: null,
|
|
||||||
initHooks: null,
|
initHooks: null,
|
||||||
checkHooks: null,
|
checkHooks: null,
|
||||||
contentHooks: null,
|
contentHooks: null,
|
||||||
|
@ -961,18 +959,16 @@ function listenerInternal(
|
||||||
const propsLength = props.length;
|
const propsLength = props.length;
|
||||||
if (propsLength) {
|
if (propsLength) {
|
||||||
const lCleanup = getCleanup(lView);
|
const lCleanup = getCleanup(lView);
|
||||||
// Subscribe to listeners for each output, and setup clean up for each.
|
for (let i = 0; i < propsLength; i += 2) {
|
||||||
for (let i = 0; i < propsLength;) {
|
const index = props[i] as number;
|
||||||
const directiveIndex = props[i++] as number;
|
ngDevMode && assertDataInRange(lView, index);
|
||||||
const minifiedName = props[i++] as string;
|
const minifiedName = props[i + 1];
|
||||||
const declaredName = props[i++] as string;
|
const directiveInstance = lView[index];
|
||||||
ngDevMode && assertDataInRange(lView, directiveIndex as number);
|
const output = directiveInstance[minifiedName];
|
||||||
const directive = unwrapOnChangesDirectiveWrapper(lView[directiveIndex]);
|
|
||||||
const output = directive[minifiedName];
|
|
||||||
|
|
||||||
if (ngDevMode && !isObservable(output)) {
|
if (ngDevMode && !isObservable(output)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`@Output ${minifiedName} not initialized in '${directive.constructor.name}'.`);
|
`@Output ${minifiedName} not initialized in '${directiveInstance.constructor.name}'.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const subscription = output.subscribe(listenerFn);
|
const subscription = output.subscribe(listenerFn);
|
||||||
|
@ -1042,7 +1038,7 @@ export function elementEnd(): void {
|
||||||
if (hasClassInput(previousOrParentTNode)) {
|
if (hasClassInput(previousOrParentTNode)) {
|
||||||
const stylingContext = getStylingContext(previousOrParentTNode.index, lView);
|
const stylingContext = getStylingContext(previousOrParentTNode.index, lView);
|
||||||
setInputsForProperty(
|
setInputsForProperty(
|
||||||
lView, previousOrParentTNode.inputs !, 'class', getInitialClassNameValue(stylingContext));
|
lView, previousOrParentTNode.inputs !['class'] !, getInitialClassNameValue(stylingContext));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1137,7 +1133,7 @@ function elementPropertyInternal<T>(
|
||||||
let dataValue: PropertyAliasValue|undefined;
|
let dataValue: PropertyAliasValue|undefined;
|
||||||
if (!nativeOnly && (inputData = initializeTNodeInputs(tNode)) &&
|
if (!nativeOnly && (inputData = initializeTNodeInputs(tNode)) &&
|
||||||
(dataValue = inputData[propName])) {
|
(dataValue = inputData[propName])) {
|
||||||
setInputsForProperty(lView, inputData, propName, value);
|
setInputsForProperty(lView, dataValue, value);
|
||||||
if (isComponent(tNode)) markDirtyIfOnPush(lView, index + HEADER_OFFSET);
|
if (isComponent(tNode)) markDirtyIfOnPush(lView, index + HEADER_OFFSET);
|
||||||
if (ngDevMode) {
|
if (ngDevMode) {
|
||||||
if (tNode.type === TNodeType.Element || tNode.type === TNodeType.Container) {
|
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 lView the `LView` which contains the directives.
|
||||||
* @param inputAliases mapping between the public "input" name and privately-known,
|
* @param inputAliases mapping between the public "input" name and privately-known,
|
||||||
* possibly minified, property names to write to.
|
* possibly minified, property names to write to.
|
||||||
* @param publicName public binding name. (This is the `<div [publicName]=value>`)
|
|
||||||
* @param value Value to set.
|
* @param value Value to set.
|
||||||
*/
|
*/
|
||||||
function setInputsForProperty(
|
function setInputsForProperty(lView: LView, inputs: PropertyAliasValue, value: any): void {
|
||||||
lView: LView, inputAliases: PropertyAliases, publicName: string, value: any): void {
|
for (let i = 0; i < inputs.length; i += 2) {
|
||||||
const inputs = inputAliases[publicName];
|
ngDevMode && assertDataInRange(lView, inputs[i] as number);
|
||||||
for (let i = 0; i < inputs.length;) {
|
lView[inputs[i] as number][inputs[i + 1]] = value;
|
||||||
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 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;) {
|
for (let i = 0; i < inputs.length; i += 2) {
|
||||||
const directiveIndex = inputs[i++] as number;
|
|
||||||
const privateName = inputs[i++] as string;
|
|
||||||
const declaredName = inputs[i++] as string;
|
|
||||||
const renderer = lView[RENDERER];
|
const renderer = lView[RENDERER];
|
||||||
const attrName = normalizeDebugBindingName(privateName);
|
const attrName = normalizeDebugBindingName(inputs[i + 1] as string);
|
||||||
const debugValue = normalizeDebugBindingValue(value);
|
const debugValue = normalizeDebugBindingValue(value);
|
||||||
if (type === TNodeType.Element) {
|
if (type === TNodeType.Element) {
|
||||||
isProceduralRenderer(renderer) ?
|
isProceduralRenderer(renderer) ?
|
||||||
|
@ -1274,20 +1261,15 @@ function generatePropertyAliases(tNode: TNode, direction: BindingDirection): Pro
|
||||||
|
|
||||||
for (let i = start; i < end; i++) {
|
for (let i = start; i < end; i++) {
|
||||||
const directiveDef = defs[i] as DirectiveDef<any>;
|
const directiveDef = defs[i] as DirectiveDef<any>;
|
||||||
const publicToMinifiedNames: {[publicName: string]: string} =
|
const propertyAliasMap: {[publicName: string]: string} =
|
||||||
isInput ? directiveDef.inputs : directiveDef.outputs;
|
isInput ? directiveDef.inputs : directiveDef.outputs;
|
||||||
const publicToDeclaredNames: {[publicName: string]: string}|null =
|
for (let publicName in propertyAliasMap) {
|
||||||
isInput ? directiveDef.declaredInputs : null;
|
if (propertyAliasMap.hasOwnProperty(publicName)) {
|
||||||
for (let publicName in publicToMinifiedNames) {
|
|
||||||
if (publicToMinifiedNames.hasOwnProperty(publicName)) {
|
|
||||||
propStore = propStore || {};
|
propStore = propStore || {};
|
||||||
const minifiedName = publicToMinifiedNames[publicName];
|
const internalName = propertyAliasMap[publicName];
|
||||||
const declaredName =
|
const hasProperty = propStore.hasOwnProperty(publicName);
|
||||||
publicToDeclaredNames ? publicToDeclaredNames[publicName] : minifiedName;
|
hasProperty ? propStore[publicName].push(i, internalName) :
|
||||||
const aliases: PropertyAliasValue = propStore.hasOwnProperty(publicName) ?
|
(propStore[publicName] = [i, internalName]);
|
||||||
propStore[publicName] :
|
|
||||||
propStore[publicName] = [];
|
|
||||||
aliases.push(i, minifiedName, declaredName);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1514,7 +1496,7 @@ export function elementStylingMap<T>(
|
||||||
const initialClasses = getInitialClassNameValue(stylingContext);
|
const initialClasses = getInitialClassNameValue(stylingContext);
|
||||||
const classInputVal =
|
const classInputVal =
|
||||||
(initialClasses.length ? (initialClasses + ' ') : '') + (classes as string);
|
(initialClasses.length ? (initialClasses + ' ') : '') + (classes as string);
|
||||||
setInputsForProperty(lView, tNode.inputs !, 'class', classInputVal);
|
setInputsForProperty(lView, tNode.inputs !['class'] !, classInputVal);
|
||||||
} else {
|
} else {
|
||||||
updateStylingMap(stylingContext, classes, styles);
|
updateStylingMap(stylingContext, classes, styles);
|
||||||
}
|
}
|
||||||
|
@ -1647,7 +1629,6 @@ function instantiateAllDirectives(tView: TView, lView: LView, tNode: TNode) {
|
||||||
addComponentLogic(lView, tNode, def as ComponentDef<any>);
|
addComponentLogic(lView, tNode, def as ComponentDef<any>);
|
||||||
}
|
}
|
||||||
const directive = getNodeInjectable(tView.data, lView !, i, tNode as TElementNode);
|
const directive = getNodeInjectable(tView.data, lView !, i, tNode as TElementNode);
|
||||||
|
|
||||||
postProcessDirective(lView, directive, def, i);
|
postProcessDirective(lView, directive, def, i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1659,7 +1640,7 @@ function invokeDirectivesHostBindings(tView: TView, viewData: LView, tNode: TNod
|
||||||
const firstTemplatePass = getFirstTemplatePass();
|
const firstTemplatePass = getFirstTemplatePass();
|
||||||
for (let i = start; i < end; i++) {
|
for (let i = start; i < end; i++) {
|
||||||
const def = tView.data[i] as DirectiveDef<any>;
|
const def = tView.data[i] as DirectiveDef<any>;
|
||||||
const directive = unwrapOnChangesDirectiveWrapper(viewData[i]);
|
const directive = viewData[i];
|
||||||
if (def.hostBindings) {
|
if (def.hostBindings) {
|
||||||
const previousExpandoLength = expando.length;
|
const previousExpandoLength = expando.length;
|
||||||
setCurrentDirectiveDef(def);
|
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.
|
* Process a directive on the current node after its creation.
|
||||||
*/
|
*/
|
||||||
function postProcessDirective<T>(
|
function postProcessDirective<T>(
|
||||||
lView: LView, directive: T, def: DirectiveDef<T>, directiveDefIdx: number): void {
|
viewData: 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]);
|
|
||||||
}
|
|
||||||
|
|
||||||
const previousOrParentTNode = getPreviousOrParentTNode();
|
const previousOrParentTNode = getPreviousOrParentTNode();
|
||||||
postProcessBaseDirective(lView, 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(lView, directiveDefIdx, def, previousOrParentTNode);
|
setInputsFromAttrs(directiveDefIdx, directive, def.inputs, previousOrParentTNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (def.contentQueries) {
|
if (def.contentQueries) {
|
||||||
|
@ -1734,7 +1710,7 @@ function postProcessDirective<T>(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isComponentDef(def)) {
|
if (isComponentDef(def)) {
|
||||||
const componentView = getComponentViewByIndex(previousOrParentTNode.index, lView);
|
const componentView = getComponentViewByIndex(previousOrParentTNode.index, viewData);
|
||||||
componentView[CONTEXT] = directive;
|
componentView[CONTEXT] = directive;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1927,53 +1903,20 @@ 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>(
|
||||||
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;
|
let initialInputData = tNode.initialInputs as InitialInputData | undefined;
|
||||||
if (initialInputData === undefined || directiveIndex >= initialInputData.length) {
|
if (initialInputData === undefined || directiveIndex >= initialInputData.length) {
|
||||||
initialInputData = generateInitialInputs(directiveIndex, def, tNode);
|
initialInputData = generateInitialInputs(directiveIndex, inputs, tNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialInputs: InitialInputs|null = initialInputData[directiveIndex];
|
const initialInputs: InitialInputs|null = initialInputData[directiveIndex];
|
||||||
if (initialInputs) {
|
if (initialInputs) {
|
||||||
const directiveOrWrappedDirective = lView[directiveIndex];
|
for (let i = 0; i < initialInputs.length; i += 2) {
|
||||||
|
(instance as any)[initialInputs[i]] = initialInputs[i + 1];
|
||||||
for (let i = 0; i < initialInputs.length;) {
|
|
||||||
const privateName = initialInputs[i++];
|
|
||||||
const declaredName = initialInputs[i++];
|
|
||||||
const attrValue = initialInputs[i++];
|
|
||||||
recordChangeAndUpdateProperty(
|
|
||||||
directiveOrWrappedDirective, declaredName, privateName, attrValue);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
* Generates initialInputData for a node and stores it in the template's static storage
|
||||||
* so subsequent template invocations don't have to recalculate it.
|
* 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
|
* @param tNode The static data on this node
|
||||||
*/
|
*/
|
||||||
function generateInitialInputs(
|
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 = []);
|
const initialInputData: InitialInputData = tNode.initialInputs || (tNode.initialInputs = []);
|
||||||
initialInputData[directiveIndex] = null;
|
initialInputData[directiveIndex] = null;
|
||||||
|
|
||||||
|
@ -2007,14 +1950,13 @@ function generateInitialInputs(
|
||||||
i += 4;
|
i += 4;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const privateName = directiveDef.inputs[attrName];
|
const minifiedInputName = inputs[attrName];
|
||||||
const declaredName = directiveDef.declaredInputs[attrName];
|
|
||||||
const attrValue = attrs[i + 1];
|
const attrValue = attrs[i + 1];
|
||||||
|
|
||||||
if (privateName !== undefined) {
|
if (minifiedInputName !== undefined) {
|
||||||
const inputsToStore: InitialInputs =
|
const inputsToStore: InitialInputs =
|
||||||
initialInputData[directiveIndex] || (initialInputData[directiveIndex] = []);
|
initialInputData[directiveIndex] || (initialInputData[directiveIndex] = []);
|
||||||
inputsToStore.push(privateName, declaredName, attrValue as string);
|
inputsToStore.push(minifiedInputName, attrValue as string);
|
||||||
}
|
}
|
||||||
|
|
||||||
i += 2;
|
i += 2;
|
||||||
|
|
|
@ -6,9 +6,8 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* 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 {Type} from '../../interface/type';
|
||||||
|
|
||||||
import {CssSelectorList} from './projection';
|
import {CssSelectorList} from './projection';
|
||||||
|
|
||||||
|
|
||||||
|
@ -143,7 +142,6 @@ export interface DirectiveDef<T> extends BaseDef<T> {
|
||||||
/* The following are lifecycle hooks for this component */
|
/* The following are lifecycle hooks for this component */
|
||||||
onInit: (() => void)|null;
|
onInit: (() => void)|null;
|
||||||
doCheck: (() => void)|null;
|
doCheck: (() => void)|null;
|
||||||
onChanges: ((changes: SimpleChanges) => void)|null;
|
|
||||||
afterContentInit: (() => void)|null;
|
afterContentInit: (() => void)|null;
|
||||||
afterContentChecked: (() => void)|null;
|
afterContentChecked: (() => void)|null;
|
||||||
afterViewInit: (() => void)|null;
|
afterViewInit: (() => void)|null;
|
||||||
|
|
|
@ -468,12 +468,10 @@ 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.
|
||||||
*
|
*
|
||||||
* Values are stored in triplets:
|
* - Even indices: directive index
|
||||||
* - i + 0: directive index
|
* - Odd indices: minified / internal name
|
||||||
* - i + 1: minified / internal name
|
|
||||||
* - i + 2: declared name
|
|
||||||
*
|
*
|
||||||
* e.g. [0, 'minifiedName', 'declaredPropertyName']
|
* e.g. [0, 'change-minified']
|
||||||
*/
|
*/
|
||||||
export type PropertyAliasValue = (number | string)[];
|
export type PropertyAliasValue = (number | string)[];
|
||||||
|
|
||||||
|
@ -501,12 +499,10 @@ 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.
|
||||||
*
|
*
|
||||||
* The inputs come in triplets of:
|
* Even indices: minified/internal input name
|
||||||
* i + 0: minified/internal input name
|
* Odd indices: initial value
|
||||||
* i + 1: declared input name (needed for OnChanges)
|
|
||||||
* i + 2: initial value
|
|
||||||
*
|
*
|
||||||
* e.g. ['minifiedName', 'declaredName', 'value']
|
* e.g. ['role-min', 'button']
|
||||||
*/
|
*/
|
||||||
export type InitialInputs = string[];
|
export type InitialInputs = string[];
|
||||||
|
|
||||||
|
|
|
@ -534,7 +534,7 @@ export interface RootContext {
|
||||||
* Even indices: Directive index
|
* Even indices: Directive index
|
||||||
* Odd indices: Hook function
|
* 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.
|
* Static data that corresponds to the instance-specific data array on an LView.
|
||||||
|
|
|
@ -145,6 +145,9 @@ function directiveMetadata(type: Type<any>, metadata: Directive): R3DirectiveMet
|
||||||
inputs: metadata.inputs || EMPTY_ARRAY,
|
inputs: metadata.inputs || EMPTY_ARRAY,
|
||||||
outputs: metadata.outputs || EMPTY_ARRAY,
|
outputs: metadata.outputs || EMPTY_ARRAY,
|
||||||
queries: extractQueriesMetadata(type, propMetadata, isContentQuery),
|
queries: extractQueriesMetadata(type, propMetadata, isContentQuery),
|
||||||
|
lifecycle: {
|
||||||
|
usesOnChanges: type.prototype.ngOnChanges !== undefined,
|
||||||
|
},
|
||||||
typeSourceSpan: null !,
|
typeSourceSpan: null !,
|
||||||
usesInheritance: !extendsDirectlyFromObject(type),
|
usesInheritance: !extendsDirectlyFromObject(type),
|
||||||
exportAs: extractExportAs(metadata.exportAs),
|
exportAs: extractExportAs(metadata.exportAs),
|
||||||
|
|
|
@ -31,6 +31,7 @@ export const angularCoreEnv: {[name: string]: Function} = {
|
||||||
'inject': inject,
|
'inject': inject,
|
||||||
'ɵinjectAttribute': r3.injectAttribute,
|
'ɵinjectAttribute': r3.injectAttribute,
|
||||||
'ɵtemplateRefExtractor': r3.templateRefExtractor,
|
'ɵtemplateRefExtractor': r3.templateRefExtractor,
|
||||||
|
'ɵNgOnChangesFeature': r3.NgOnChangesFeature,
|
||||||
'ɵProvidersFeature': r3.ProvidersFeature,
|
'ɵProvidersFeature': r3.ProvidersFeature,
|
||||||
'ɵInheritDefinitionFeature': r3.InheritDefinitionFeature,
|
'ɵInheritDefinitionFeature': r3.InheritDefinitionFeature,
|
||||||
'ɵelementAttribute': r3.elementAttribute,
|
'ɵelementAttribute': r3.elementAttribute,
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -17,7 +17,6 @@ import {TContainerNode, TElementNode, TNode, TNodeFlags, TNodeType} from './inte
|
||||||
import {GlobalTargetName, GlobalTargetResolver, RComment, RElement, RText} from './interfaces/renderer';
|
import {GlobalTargetName, GlobalTargetResolver, RComment, RElement, RText} from './interfaces/renderer';
|
||||||
import {StylingContext} from './interfaces/styling';
|
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 {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`. */
|
/** Retrieves a value from any `LView` or `TData`. */
|
||||||
export function loadInternal<T>(view: LView | TData, index: number): T {
|
export function loadInternal<T>(view: LView | TData, index: number): T {
|
||||||
ngDevMode && assertDataInRange(view, index + HEADER_OFFSET);
|
ngDevMode && assertDataInRange(view, index + HEADER_OFFSET);
|
||||||
const record = view[index + HEADER_OFFSET];
|
return 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Takes the value of a slot in `LView` and returns the element node.
|
* 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}) {
|
export function resolveBody(element: RElement & {ownerDocument: Document}) {
|
||||||
return {name: 'body', target: element.ownerDocument.body};
|
return {name: 'body', target: element.ownerDocument.body};
|
||||||
}
|
}
|
|
@ -11,11 +11,10 @@ import {ChangeDetectorRef as viewEngine_ChangeDetectorRef} from '../change_detec
|
||||||
import {ViewContainerRef as viewEngine_ViewContainerRef} from '../linker/view_container_ref';
|
import {ViewContainerRef as viewEngine_ViewContainerRef} from '../linker/view_container_ref';
|
||||||
import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, InternalViewRef as viewEngine_InternalViewRef} from '../linker/view_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 {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 {destroyLView} from './node_manipulation';
|
||||||
import {unwrapOnChangesDirectiveWrapper} from './onchanges_util';
|
|
||||||
import {getNativeByTNode} from './util';
|
import {getNativeByTNode} from './util';
|
||||||
|
|
||||||
|
|
||||||
|
@ -272,8 +271,7 @@ export class ViewRef<T> implements viewEngine_EmbeddedViewRef<T>, viewEngine_Int
|
||||||
}
|
}
|
||||||
|
|
||||||
private _lookUpContext(): T {
|
private _lookUpContext(): T {
|
||||||
return this._context =
|
return this._context = this._lView[PARENT] ![this._componentIndex] as T;
|
||||||
unwrapOnChangesDirectiveWrapper(this._lView[PARENT] ![this._componentIndex] as T);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -80,21 +80,24 @@
|
||||||
{
|
{
|
||||||
"name": "NO_PARENT_INJECTOR"
|
"name": "NO_PARENT_INJECTOR"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "NgOnChangesFeature"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "NodeInjectorFactory"
|
"name": "NodeInjectorFactory"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "ObjectUnsubscribedErrorImpl"
|
"name": "ObjectUnsubscribedErrorImpl"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "OnChangesDirectiveWrapper"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "PARENT"
|
"name": "PARENT"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "PARENT_INJECTOR"
|
"name": "PARENT_INJECTOR"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "PRIVATE_PREFIX"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "RENDERER"
|
"name": "RENDERER"
|
||||||
},
|
},
|
||||||
|
@ -104,6 +107,9 @@
|
||||||
{
|
{
|
||||||
"name": "SANITIZER"
|
"name": "SANITIZER"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "SimpleChange"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "TVIEW"
|
"name": "TVIEW"
|
||||||
},
|
},
|
||||||
|
@ -326,15 +332,9 @@
|
||||||
{
|
{
|
||||||
"name": "isCreationMode"
|
"name": "isCreationMode"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "isExactInstanceOf"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "isFactory"
|
"name": "isFactory"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "isOnChangesDirectiveWrapper"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "isProceduralRenderer"
|
"name": "isProceduralRenderer"
|
||||||
},
|
},
|
||||||
|
@ -368,6 +368,9 @@
|
||||||
{
|
{
|
||||||
"name": "noSideEffects"
|
"name": "noSideEffects"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "onChangesWrapper"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "postProcessBaseDirective"
|
"name": "postProcessBaseDirective"
|
||||||
},
|
},
|
||||||
|
@ -446,9 +449,6 @@
|
||||||
{
|
{
|
||||||
"name": "tickRootContext"
|
"name": "tickRootContext"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "unwrapOnChangesDirectiveWrapper"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "updateViewQuery"
|
"name": "updateViewQuery"
|
||||||
},
|
},
|
||||||
|
|
|
@ -35,6 +35,9 @@
|
||||||
{
|
{
|
||||||
"name": "NULL_INJECTOR"
|
"name": "NULL_INJECTOR"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "NgOnChangesFeature"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "NullInjector"
|
"name": "NullInjector"
|
||||||
},
|
},
|
||||||
|
@ -47,6 +50,9 @@
|
||||||
{
|
{
|
||||||
"name": "PARAMETERS"
|
"name": "PARAMETERS"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "PRIVATE_PREFIX"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "R3Injector"
|
"name": "R3Injector"
|
||||||
},
|
},
|
||||||
|
@ -56,6 +62,9 @@
|
||||||
{
|
{
|
||||||
"name": "Self"
|
"name": "Self"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "SimpleChange"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "SkipSelf"
|
"name": "SkipSelf"
|
||||||
},
|
},
|
||||||
|
@ -155,6 +164,9 @@
|
||||||
{
|
{
|
||||||
"name": "makeRecord"
|
"name": "makeRecord"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "onChangesWrapper"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "providerToFactory"
|
"name": "providerToFactory"
|
||||||
},
|
},
|
||||||
|
|
|
@ -143,6 +143,9 @@
|
||||||
{
|
{
|
||||||
"name": "NgModuleRef"
|
"name": "NgModuleRef"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "NgOnChangesFeature"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "NodeInjector"
|
"name": "NodeInjector"
|
||||||
},
|
},
|
||||||
|
@ -152,9 +155,6 @@
|
||||||
{
|
{
|
||||||
"name": "ObjectUnsubscribedErrorImpl"
|
"name": "ObjectUnsubscribedErrorImpl"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "OnChangesDirectiveWrapper"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "Optional"
|
"name": "Optional"
|
||||||
},
|
},
|
||||||
|
@ -167,6 +167,9 @@
|
||||||
{
|
{
|
||||||
"name": "PARENT_INJECTOR"
|
"name": "PARENT_INJECTOR"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "PRIVATE_PREFIX"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "QUERIES"
|
"name": "QUERIES"
|
||||||
},
|
},
|
||||||
|
@ -908,9 +911,6 @@
|
||||||
{
|
{
|
||||||
"name": "isDirty"
|
"name": "isDirty"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "isExactInstanceOf"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "isFactory"
|
"name": "isFactory"
|
||||||
},
|
},
|
||||||
|
@ -929,9 +929,6 @@
|
||||||
{
|
{
|
||||||
"name": "isNodeMatchingSelectorList"
|
"name": "isNodeMatchingSelectorList"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "isOnChangesDirectiveWrapper"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "isPositive"
|
"name": "isPositive"
|
||||||
},
|
},
|
||||||
|
@ -1019,6 +1016,9 @@
|
||||||
{
|
{
|
||||||
"name": "noSideEffects"
|
"name": "noSideEffects"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "onChangesWrapper"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "pointers"
|
"name": "pointers"
|
||||||
},
|
},
|
||||||
|
@ -1049,12 +1049,6 @@
|
||||||
{
|
{
|
||||||
"name": "readPatchedLView"
|
"name": "readPatchedLView"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "recordChange"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "recordChangeAndUpdateProperty"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "reference"
|
"name": "reference"
|
||||||
},
|
},
|
||||||
|
@ -1232,9 +1226,6 @@
|
||||||
{
|
{
|
||||||
"name": "trackByIdentity"
|
"name": "trackByIdentity"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "unwrapOnChangesDirectiveWrapper"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "updateClassProp"
|
"name": "updateClassProp"
|
||||||
},
|
},
|
||||||
|
|
|
@ -536,6 +536,7 @@ 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>');
|
||||||
|
@ -738,6 +739,7 @@ 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>');
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
import {NgForOf as NgForOfDef, NgIf as NgIfDef, NgTemplateOutlet as NgTemplateOutletDef} from '@angular/common';
|
import {NgForOf as NgForOfDef, NgIf as NgIfDef, NgTemplateOutlet as NgTemplateOutletDef} from '@angular/common';
|
||||||
import {IterableDiffers, TemplateRef, ViewContainerRef} from '@angular/core';
|
import {IterableDiffers, TemplateRef, ViewContainerRef} from '@angular/core';
|
||||||
|
|
||||||
import {DirectiveType, defineDirective, directiveInject} from '../../src/render3/index';
|
import {DirectiveType, NgOnChangesFeature, defineDirective, directiveInject} from '../../src/render3/index';
|
||||||
|
|
||||||
export const NgForOf: DirectiveType<NgForOfDef<any>> = NgForOfDef as any;
|
export const NgForOf: DirectiveType<NgForOfDef<any>> = NgForOfDef as any;
|
||||||
export const NgIf: DirectiveType<NgIfDef> = NgIfDef as any;
|
export const NgIf: DirectiveType<NgIfDef> = NgIfDef as any;
|
||||||
|
@ -40,6 +40,7 @@ NgForOf.ngDirectiveDef = defineDirective({
|
||||||
type: NgTemplateOutletDef,
|
type: NgTemplateOutletDef,
|
||||||
selectors: [['', 'ngTemplateOutlet', '']],
|
selectors: [['', 'ngTemplateOutlet', '']],
|
||||||
factory: () => new NgTemplateOutletDef(directiveInject(ViewContainerRef as any)),
|
factory: () => new NgTemplateOutletDef(directiveInject(ViewContainerRef as any)),
|
||||||
|
features: [NgOnChangesFeature],
|
||||||
inputs:
|
inputs:
|
||||||
{ngTemplateOutlet: 'ngTemplateOutlet', ngTemplateOutletContext: 'ngTemplateOutletContext'}
|
{ngTemplateOutlet: 'ngTemplateOutlet', ngTemplateOutletContext: 'ngTemplateOutletContext'}
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
import {ElementRef, QueryList, ViewContainerRef} from '@angular/core';
|
import {ElementRef, QueryList, ViewContainerRef} from '@angular/core';
|
||||||
|
|
||||||
import {AttributeMarker, defineComponent, template, defineDirective, InheritDefinitionFeature, ProvidersFeature} from '../../src/render3/index';
|
import {AttributeMarker, defineComponent, template, defineDirective, InheritDefinitionFeature, ProvidersFeature, NgOnChangesFeature} from '../../src/render3/index';
|
||||||
import {allocHostVars, bind, directiveInject, element, elementAttribute, elementEnd, elementProperty, elementStyleProp, elementStyling, elementStylingApply, elementStart, listener, load, text, textBinding, loadQueryList, registerContentQuery, elementHostAttrs} from '../../src/render3/instructions';
|
import {allocHostVars, bind, directiveInject, element, elementAttribute, elementEnd, elementProperty, elementStyleProp, elementStyling, elementStylingApply, elementStart, listener, load, text, textBinding, loadQueryList, registerContentQuery, elementHostAttrs} from '../../src/render3/instructions';
|
||||||
import {query, queryRefresh} from '../../src/render3/query';
|
import {query, queryRefresh} from '../../src/render3/query';
|
||||||
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
||||||
|
@ -357,6 +357,7 @@ describe('host bindings', () => {
|
||||||
template: (rf: RenderFlags, ctx: InitHookComp) => {},
|
template: (rf: RenderFlags, ctx: InitHookComp) => {},
|
||||||
consts: 0,
|
consts: 0,
|
||||||
vars: 0,
|
vars: 0,
|
||||||
|
features: [NgOnChangesFeature],
|
||||||
hostBindings: (rf: RenderFlags, ctx: InitHookComp, elIndex: number) => {
|
hostBindings: (rf: RenderFlags, ctx: InitHookComp, elIndex: number) => {
|
||||||
if (rf & RenderFlags.Create) {
|
if (rf & RenderFlags.Create) {
|
||||||
allocHostVars(1);
|
allocHostVars(1);
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Inject, InjectionToken} from '../../src/core';
|
import {Inject, InjectionToken} from '../../src/core';
|
||||||
import {ComponentDef, DirectiveDef, InheritDefinitionFeature, ProvidersFeature, RenderFlags, allocHostVars, bind, defineBase, defineComponent, defineDirective, directiveInject, element, elementProperty} from '../../src/render3/index';
|
import {ComponentDef, DirectiveDef, InheritDefinitionFeature, NgOnChangesFeature, ProvidersFeature, RenderFlags, allocHostVars, bind, defineBase, defineComponent, defineDirective, directiveInject, element, elementProperty, load} from '../../src/render3/index';
|
||||||
|
|
||||||
import {ComponentFixture, createComponent} from './render_util';
|
import {ComponentFixture, createComponent} from './render_util';
|
||||||
|
|
||||||
|
@ -501,7 +501,8 @@ describe('InheritDefinitionFeature', () => {
|
||||||
type: SuperDirective,
|
type: SuperDirective,
|
||||||
selectors: [['', 'superDir', '']],
|
selectors: [['', 'superDir', '']],
|
||||||
factory: () => new SuperDirective(),
|
factory: () => new SuperDirective(),
|
||||||
inputs: {someInput: 'someInput'},
|
features: [NgOnChangesFeature],
|
||||||
|
inputs: {someInput: 'someInput'}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -518,9 +519,6 @@ describe('InheritDefinitionFeature', () => {
|
||||||
if (rf & RenderFlags.Create) {
|
if (rf & RenderFlags.Create) {
|
||||||
element(0, 'div', ['subDir', '']);
|
element(0, 'div', ['subDir', '']);
|
||||||
}
|
}
|
||||||
if (rf & RenderFlags.Update) {
|
|
||||||
elementProperty(0, 'someInput', bind('Weee'));
|
|
||||||
}
|
|
||||||
}, 1, 0, [SubDirective]);
|
}, 1, 0, [SubDirective]);
|
||||||
|
|
||||||
const fixture = new ComponentFixture(App);
|
const fixture = new ComponentFixture(App);
|
||||||
|
|
|
@ -14,6 +14,7 @@ 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';
|
||||||
|
|
||||||
describe('lifecycles', () => {
|
describe('lifecycles', () => {
|
||||||
|
|
||||||
|
@ -1940,6 +1941,7 @@ 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})[];
|
||||||
|
|
||||||
|
@ -2699,6 +2701,7 @@ describe('lifecycles', () => {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
fixmeIvy('FW-956: refactor onChanges').
|
||||||
describe('hook order', () => {
|
describe('hook order', () => {
|
||||||
let events: string[];
|
let events: string[];
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,327 @@
|
||||||
|
/**
|
||||||
|
* @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]);
|
||||||
|
});
|
||||||
|
});
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
import {ChangeDetectorRef, Component as _Component, ComponentFactoryResolver, ElementRef, EmbeddedViewRef, NgModuleRef, Pipe, PipeTransform, QueryList, RendererFactory2, TemplateRef, ViewContainerRef, createInjector, defineInjector, ɵAPP_ROOT as APP_ROOT, ɵNgModuleDef as NgModuleDef} from '../../src/core';
|
import {ChangeDetectorRef, Component as _Component, ComponentFactoryResolver, ElementRef, EmbeddedViewRef, NgModuleRef, Pipe, PipeTransform, QueryList, RendererFactory2, TemplateRef, ViewContainerRef, createInjector, defineInjector, ɵAPP_ROOT as APP_ROOT, ɵNgModuleDef as NgModuleDef} from '../../src/core';
|
||||||
import {ViewEncapsulation} from '../../src/metadata';
|
import {ViewEncapsulation} from '../../src/metadata';
|
||||||
import {AttributeMarker, defineComponent, defineDirective, definePipe, injectComponentFactoryResolver, load, query, queryRefresh} from '../../src/render3/index';
|
import {AttributeMarker, NO_CHANGE, NgOnChangesFeature, defineComponent, defineDirective, definePipe, injectComponentFactoryResolver, load, query, queryRefresh} from '../../src/render3/index';
|
||||||
|
|
||||||
import {allocHostVars, bind, container, containerRefreshEnd, containerRefreshStart, directiveInject, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation3, nextContext, projection, projectionDef, reference, template, text, textBinding, elementHostAttrs} from '../../src/render3/instructions';
|
import {allocHostVars, bind, container, containerRefreshEnd, containerRefreshStart, directiveInject, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation3, nextContext, projection, projectionDef, reference, template, text, textBinding, elementHostAttrs} from '../../src/render3/instructions';
|
||||||
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
||||||
|
@ -1631,6 +1631,7 @@ describe('ViewContainerRef', () => {
|
||||||
textBinding(0, interpolation1('', cmp.name, ''));
|
textBinding(0, interpolation1('', cmp.name, ''));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
features: [NgOnChangesFeature],
|
||||||
inputs: {name: 'name'}
|
inputs: {name: 'name'}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1795,13 +1796,12 @@ describe('ViewContainerRef', () => {
|
||||||
expect(fixture.html).toEqual('<hooks vcref="">A</hooks><hooks></hooks><hooks>B</hooks>');
|
expect(fixture.html).toEqual('<hooks vcref="">A</hooks><hooks></hooks><hooks>B</hooks>');
|
||||||
expect(log).toEqual([]);
|
expect(log).toEqual([]);
|
||||||
|
|
||||||
// Below will *NOT* cause onChanges to fire, because only bindings trigger onChanges
|
|
||||||
componentRef.instance.name = 'D';
|
componentRef.instance.name = 'D';
|
||||||
log.length = 0;
|
log.length = 0;
|
||||||
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', 'onInit-D', 'doCheck-D', 'afterContentInit-D',
|
'doCheck-A', 'doCheck-B', 'onChanges-D', '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'
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
import {SecurityContext} from '@angular/core';
|
import {SecurityContext} from '@angular/core';
|
||||||
import {ArgumentType, BindingFlags, NodeCheckFn, NodeFlags, Services, ViewData, ViewFlags, ViewState, asElementData, directiveDef, elementDef, rootRenderNodes} from '@angular/core/src/view/index';
|
import {ArgumentType, BindingFlags, NodeCheckFn, NodeFlags, Services, ViewData, ViewFlags, ViewState, asElementData, directiveDef, elementDef, rootRenderNodes} from '@angular/core/src/view/index';
|
||||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||||
import {fixmeIvy} from '@angular/private/testing';
|
|
||||||
|
|
||||||
import {callMostRecentEventListenerHandler, compViewDef, createAndGetRootNodes, createRootView, isBrowser, recordNodeToRemove} from './helper';
|
import {callMostRecentEventListenerHandler, compViewDef, createAndGetRootNodes, createRootView, isBrowser, recordNodeToRemove} from './helper';
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,6 @@ import {getDebugContext} from '@angular/core/src/errors';
|
||||||
import {BindingFlags, NodeFlags, Services, ViewData, ViewDefinition, asElementData, elementDef} from '@angular/core/src/view/index';
|
import {BindingFlags, NodeFlags, Services, ViewData, ViewDefinition, asElementData, elementDef} from '@angular/core/src/view/index';
|
||||||
import {TestBed} from '@angular/core/testing';
|
import {TestBed} from '@angular/core/testing';
|
||||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||||
import {fixmeIvy} from '@angular/private/testing';
|
|
||||||
|
|
||||||
import {ARG_TYPE_VALUES, callMostRecentEventListenerHandler, checkNodeInlineOrDynamic, compViewDef, createAndGetRootNodes, isBrowser, recordNodeToRemove} from './helper';
|
import {ARG_TYPE_VALUES, callMostRecentEventListenerHandler, checkNodeInlineOrDynamic, compViewDef, createAndGetRootNodes, isBrowser, recordNodeToRemove} from './helper';
|
||||||
|
|
||||||
|
@ -185,7 +184,6 @@ const removeEventListener = '__zone_symbol__removeEventListener' as 'removeEvent
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
it('should listen to DOM events', () => {
|
it('should listen to DOM events', () => {
|
||||||
const handleEventSpy = jasmine.createSpy('handleEvent');
|
const handleEventSpy = jasmine.createSpy('handleEvent');
|
||||||
const removeListenerSpy =
|
const removeListenerSpy =
|
||||||
|
@ -253,7 +251,6 @@ const removeEventListener = '__zone_symbol__removeEventListener' as 'removeEvent
|
||||||
expect(removeListenerSpy).toHaveBeenCalled();
|
expect(removeListenerSpy).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should preventDefault only if the handler returns false', () => {
|
it('should preventDefault only if the handler returns false', () => {
|
||||||
let eventHandlerResult: any;
|
let eventHandlerResult: any;
|
||||||
let preventDefaultSpy: jasmine.Spy = undefined !;
|
let preventDefaultSpy: jasmine.Spy = undefined !;
|
||||||
|
@ -282,7 +279,6 @@ const removeEventListener = '__zone_symbol__removeEventListener' as 'removeEvent
|
||||||
expect(preventDefaultSpy).toHaveBeenCalled();
|
expect(preventDefaultSpy).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should report debug info on event errors', () => {
|
it('should report debug info on event errors', () => {
|
||||||
const handleErrorSpy = spyOn(TestBed.get(ErrorHandler), 'handleError');
|
const handleErrorSpy = spyOn(TestBed.get(ErrorHandler), 'handleError');
|
||||||
const addListenerSpy = spyOn(HTMLElement.prototype, addEventListener).and.callThrough();
|
const addListenerSpy = spyOn(HTMLElement.prototype, addEventListener).and.callThrough();
|
||||||
|
|
|
@ -315,235 +315,239 @@ withEachNg1Version(() => {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should bind properties, events', async(() => {
|
fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly')
|
||||||
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
.it('should bind properties, events', async(() => {
|
||||||
const ng1Module =
|
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
||||||
angular.module('ng1', []).value($EXCEPTION_HANDLER, (err: any) => { throw err; });
|
const ng1Module = angular.module('ng1', []).value(
|
||||||
|
$EXCEPTION_HANDLER, (err: any) => { throw err; });
|
||||||
|
|
||||||
ng1Module.run(($rootScope: any) => {
|
ng1Module.run(($rootScope: any) => {
|
||||||
$rootScope.name = 'world';
|
$rootScope.name = 'world';
|
||||||
$rootScope.dataA = 'A';
|
$rootScope.dataA = 'A';
|
||||||
$rootScope.dataB = 'B';
|
$rootScope.dataB = 'B';
|
||||||
$rootScope.modelA = 'initModelA';
|
$rootScope.modelA = 'initModelA';
|
||||||
$rootScope.modelB = 'initModelB';
|
$rootScope.modelB = 'initModelB';
|
||||||
$rootScope.eventA = '?';
|
$rootScope.eventA = '?';
|
||||||
$rootScope.eventB = '?';
|
$rootScope.eventB = '?';
|
||||||
});
|
});
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ng2',
|
selector: 'ng2',
|
||||||
inputs: ['literal', 'interpolate', 'oneWayA', 'oneWayB', 'twoWayA', 'twoWayB'],
|
inputs: ['literal', 'interpolate', 'oneWayA', 'oneWayB', 'twoWayA', 'twoWayB'],
|
||||||
outputs: [
|
outputs: [
|
||||||
'eventA', 'eventB', 'twoWayAEmitter: twoWayAChange', 'twoWayBEmitter: twoWayBChange'
|
'eventA', 'eventB', 'twoWayAEmitter: twoWayAChange',
|
||||||
],
|
'twoWayBEmitter: twoWayBChange'
|
||||||
template: 'ignore: {{ignore}}; ' +
|
],
|
||||||
'literal: {{literal}}; interpolate: {{interpolate}}; ' +
|
template: 'ignore: {{ignore}}; ' +
|
||||||
'oneWayA: {{oneWayA}}; oneWayB: {{oneWayB}}; ' +
|
'literal: {{literal}}; interpolate: {{interpolate}}; ' +
|
||||||
'twoWayA: {{twoWayA}}; twoWayB: {{twoWayB}}; ({{ngOnChangesCount}})'
|
'oneWayA: {{oneWayA}}; oneWayB: {{oneWayB}}; ' +
|
||||||
})
|
'twoWayA: {{twoWayA}}; twoWayB: {{twoWayB}}; ({{ngOnChangesCount}})'
|
||||||
class Ng2 {
|
})
|
||||||
ngOnChangesCount = 0;
|
class Ng2 {
|
||||||
ignore = '-';
|
ngOnChangesCount = 0;
|
||||||
literal = '?';
|
ignore = '-';
|
||||||
interpolate = '?';
|
literal = '?';
|
||||||
oneWayA = '?';
|
interpolate = '?';
|
||||||
oneWayB = '?';
|
oneWayA = '?';
|
||||||
twoWayA = '?';
|
oneWayB = '?';
|
||||||
twoWayB = '?';
|
twoWayA = '?';
|
||||||
eventA = new EventEmitter();
|
twoWayB = '?';
|
||||||
eventB = new EventEmitter();
|
eventA = new EventEmitter();
|
||||||
twoWayAEmitter = new EventEmitter();
|
eventB = new EventEmitter();
|
||||||
twoWayBEmitter = new EventEmitter();
|
twoWayAEmitter = new EventEmitter();
|
||||||
ngOnChanges(changes: SimpleChanges) {
|
twoWayBEmitter = new EventEmitter();
|
||||||
const assert = (prop: string, value: any) => {
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
if ((this as any)[prop] != value) {
|
const assert = (prop: string, value: any) => {
|
||||||
throw new Error(
|
if ((this as any)[prop] != value) {
|
||||||
`Expected: '${prop}' to be '${value}' but was '${(this as any)[prop]}'`);
|
throw new Error(
|
||||||
}
|
`Expected: '${prop}' to be '${value}' but was '${(this as any)[prop]}'`);
|
||||||
};
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const assertChange = (prop: string, value: any) => {
|
const assertChange = (prop: string, value: any) => {
|
||||||
assert(prop, value);
|
assert(prop, value);
|
||||||
if (!changes[prop]) {
|
if (!changes[prop]) {
|
||||||
throw new Error(`Changes record for '${prop}' not found.`);
|
throw new Error(`Changes record for '${prop}' not found.`);
|
||||||
}
|
}
|
||||||
const actValue = changes[prop].currentValue;
|
const actValue = changes[prop].currentValue;
|
||||||
if (actValue != value) {
|
if (actValue != value) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Expected changes record for'${prop}' to be '${value}' but was '${actValue}'`);
|
`Expected changes record for'${prop}' to be '${value}' but was '${actValue}'`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
switch (this.ngOnChangesCount++) {
|
switch (this.ngOnChangesCount++) {
|
||||||
case 0:
|
case 0:
|
||||||
assert('ignore', '-');
|
assert('ignore', '-');
|
||||||
assertChange('literal', 'Text');
|
assertChange('literal', 'Text');
|
||||||
assertChange('interpolate', 'Hello world');
|
assertChange('interpolate', 'Hello world');
|
||||||
assertChange('oneWayA', 'A');
|
assertChange('oneWayA', 'A');
|
||||||
assertChange('oneWayB', 'B');
|
assertChange('oneWayB', 'B');
|
||||||
assertChange('twoWayA', 'initModelA');
|
assertChange('twoWayA', 'initModelA');
|
||||||
assertChange('twoWayB', 'initModelB');
|
assertChange('twoWayB', 'initModelB');
|
||||||
|
|
||||||
this.twoWayAEmitter.emit('newA');
|
this.twoWayAEmitter.emit('newA');
|
||||||
this.twoWayBEmitter.emit('newB');
|
this.twoWayBEmitter.emit('newB');
|
||||||
this.eventA.emit('aFired');
|
this.eventA.emit('aFired');
|
||||||
this.eventB.emit('bFired');
|
this.eventB.emit('bFired');
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
assertChange('twoWayA', 'newA');
|
assertChange('twoWayA', 'newA');
|
||||||
assertChange('twoWayB', 'newB');
|
assertChange('twoWayB', 'newB');
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
assertChange('interpolate', 'Hello everyone');
|
assertChange('interpolate', 'Hello everyone');
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error('Called too many times! ' + JSON.stringify(changes));
|
throw new Error('Called too many times! ' + JSON.stringify(changes));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [Ng2],
|
declarations: [Ng2],
|
||||||
imports: [BrowserModule],
|
imports: [BrowserModule],
|
||||||
})
|
})
|
||||||
class Ng2Module {
|
class Ng2Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
const element = html(`<div>
|
const element = html(`<div>
|
||||||
<ng2 literal="Text" interpolate="Hello {{name}}"
|
<ng2 literal="Text" interpolate="Hello {{name}}"
|
||||||
bind-one-way-a="dataA" [one-way-b]="dataB"
|
bind-one-way-a="dataA" [one-way-b]="dataB"
|
||||||
bindon-two-way-a="modelA" [(two-way-b)]="modelB"
|
bindon-two-way-a="modelA" [(two-way-b)]="modelB"
|
||||||
on-event-a='eventA=$event' (event-b)="eventB=$event"></ng2>
|
on-event-a='eventA=$event' (event-b)="eventB=$event"></ng2>
|
||||||
| modelA: {{modelA}}; modelB: {{modelB}}; eventA: {{eventA}}; eventB: {{eventB}};
|
| modelA: {{modelA}}; modelB: {{modelB}}; eventA: {{eventA}}; eventB: {{eventB}};
|
||||||
</div>`);
|
</div>`);
|
||||||
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
||||||
expect(multiTrim(document.body.textContent !))
|
expect(multiTrim(document.body.textContent !))
|
||||||
.toEqual(
|
.toEqual(
|
||||||
'ignore: -; ' +
|
'ignore: -; ' +
|
||||||
'literal: Text; interpolate: Hello world; ' +
|
'literal: Text; interpolate: Hello world; ' +
|
||||||
'oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (2) | ' +
|
'oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (2) | ' +
|
||||||
'modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;');
|
'modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;');
|
||||||
|
|
||||||
ref.ng1RootScope.$apply('name = "everyone"');
|
ref.ng1RootScope.$apply('name = "everyone"');
|
||||||
expect(multiTrim(document.body.textContent !))
|
expect(multiTrim(document.body.textContent !))
|
||||||
.toEqual(
|
.toEqual(
|
||||||
'ignore: -; ' +
|
'ignore: -; ' +
|
||||||
'literal: Text; interpolate: Hello everyone; ' +
|
'literal: Text; interpolate: Hello everyone; ' +
|
||||||
'oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (3) | ' +
|
'oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (3) | ' +
|
||||||
'modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;');
|
'modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;');
|
||||||
|
|
||||||
ref.dispose();
|
ref.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should support two-way binding and event listener', async(() => {
|
fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly')
|
||||||
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
.it('should support two-way binding and event listener', async(() => {
|
||||||
const listenerSpy = jasmine.createSpy('$rootScope.listener');
|
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
||||||
const ng1Module = angular.module('ng1', []).run(($rootScope: angular.IScope) => {
|
const listenerSpy = jasmine.createSpy('$rootScope.listener');
|
||||||
$rootScope['value'] = 'world';
|
const ng1Module = angular.module('ng1', []).run(($rootScope: angular.IScope) => {
|
||||||
$rootScope['listener'] = listenerSpy;
|
$rootScope['value'] = 'world';
|
||||||
});
|
$rootScope['listener'] = listenerSpy;
|
||||||
|
});
|
||||||
|
|
||||||
@Component({selector: 'ng2', template: `model: {{model}};`})
|
@Component({selector: 'ng2', template: `model: {{model}};`})
|
||||||
class Ng2Component implements OnChanges {
|
class Ng2Component implements OnChanges {
|
||||||
ngOnChangesCount = 0;
|
ngOnChangesCount = 0;
|
||||||
@Input() model = '?';
|
@Input() model = '?';
|
||||||
@Output() modelChange = new EventEmitter();
|
@Output() modelChange = new EventEmitter();
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges) {
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
switch (this.ngOnChangesCount++) {
|
switch (this.ngOnChangesCount++) {
|
||||||
case 0:
|
case 0:
|
||||||
expect(changes.model.currentValue).toBe('world');
|
expect(changes.model.currentValue).toBe('world');
|
||||||
this.modelChange.emit('newC');
|
this.modelChange.emit('newC');
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
expect(changes.model.currentValue).toBe('newC');
|
expect(changes.model.currentValue).toBe('newC');
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error('Called too many times! ' + JSON.stringify(changes));
|
throw new Error('Called too many times! ' + JSON.stringify(changes));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2Component));
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2Component));
|
||||||
|
|
||||||
@NgModule({declarations: [Ng2Component], imports: [BrowserModule]})
|
@NgModule({declarations: [Ng2Component], imports: [BrowserModule]})
|
||||||
class Ng2Module {
|
class Ng2Module {
|
||||||
ngDoBootstrap() {}
|
ngDoBootstrap() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
const element = html(`
|
const element = html(`
|
||||||
<div>
|
<div>
|
||||||
<ng2 [(model)]="value" (model-change)="listener($event)"></ng2>
|
<ng2 [(model)]="value" (model-change)="listener($event)"></ng2>
|
||||||
| value: {{value}}
|
| value: {{value}}
|
||||||
</div>
|
</div>
|
||||||
`);
|
`);
|
||||||
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
||||||
expect(multiTrim(element.textContent)).toEqual('model: newC; | value: newC');
|
expect(multiTrim(element.textContent)).toEqual('model: newC; | value: newC');
|
||||||
expect(listenerSpy).toHaveBeenCalledWith('newC');
|
expect(listenerSpy).toHaveBeenCalledWith('newC');
|
||||||
ref.dispose();
|
ref.dispose();
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should initialize inputs in time for `ngOnChanges`', async(() => {
|
fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly')
|
||||||
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
.it('should initialize inputs in time for `ngOnChanges`', async(() => {
|
||||||
|
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ng2',
|
selector: 'ng2',
|
||||||
template: `
|
template: `
|
||||||
ngOnChangesCount: {{ ngOnChangesCount }} |
|
ngOnChangesCount: {{ ngOnChangesCount }} |
|
||||||
firstChangesCount: {{ firstChangesCount }} |
|
firstChangesCount: {{ firstChangesCount }} |
|
||||||
initialValue: {{ initialValue }}`
|
initialValue: {{ initialValue }}`
|
||||||
})
|
})
|
||||||
class Ng2Component implements OnChanges {
|
class Ng2Component implements OnChanges {
|
||||||
ngOnChangesCount = 0;
|
ngOnChangesCount = 0;
|
||||||
firstChangesCount = 0;
|
firstChangesCount = 0;
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
initialValue !: string;
|
initialValue !: string;
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
@Input() foo !: string;
|
@Input() foo !: string;
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges) {
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
this.ngOnChangesCount++;
|
this.ngOnChangesCount++;
|
||||||
|
|
||||||
if (this.ngOnChangesCount === 1) {
|
if (this.ngOnChangesCount === 1) {
|
||||||
this.initialValue = this.foo;
|
this.initialValue = this.foo;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changes['foo'] && changes['foo'].isFirstChange()) {
|
if (changes['foo'] && changes['foo'].isFirstChange()) {
|
||||||
this.firstChangesCount++;
|
this.firstChangesCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@NgModule({imports: [BrowserModule], declarations: [Ng2Component]})
|
@NgModule({imports: [BrowserModule], declarations: [Ng2Component]})
|
||||||
class Ng2Module {
|
class Ng2Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
const ng1Module = angular.module('ng1', []).directive(
|
const ng1Module = angular.module('ng1', []).directive(
|
||||||
'ng2', adapter.downgradeNg2Component(Ng2Component));
|
'ng2', adapter.downgradeNg2Component(Ng2Component));
|
||||||
|
|
||||||
const element = html(`
|
const element = html(`
|
||||||
<ng2 [foo]="'foo'"></ng2>
|
<ng2 [foo]="'foo'"></ng2>
|
||||||
<ng2 foo="bar"></ng2>
|
<ng2 foo="bar"></ng2>
|
||||||
<ng2 [foo]="'baz'" ng-if="true"></ng2>
|
<ng2 [foo]="'baz'" ng-if="true"></ng2>
|
||||||
<ng2 foo="qux" ng-if="true"></ng2>
|
<ng2 foo="qux" ng-if="true"></ng2>
|
||||||
`);
|
`);
|
||||||
|
|
||||||
adapter.bootstrap(element, ['ng1']).ready(ref => {
|
adapter.bootstrap(element, ['ng1']).ready(ref => {
|
||||||
const nodes = element.querySelectorAll('ng2');
|
const nodes = element.querySelectorAll('ng2');
|
||||||
const expectedTextWith = (value: string) =>
|
const expectedTextWith = (value: string) =>
|
||||||
`ngOnChangesCount: 1 | firstChangesCount: 1 | initialValue: ${value}`;
|
`ngOnChangesCount: 1 | firstChangesCount: 1 | initialValue: ${value}`;
|
||||||
|
|
||||||
expect(multiTrim(nodes[0].textContent)).toBe(expectedTextWith('foo'));
|
expect(multiTrim(nodes[0].textContent)).toBe(expectedTextWith('foo'));
|
||||||
expect(multiTrim(nodes[1].textContent)).toBe(expectedTextWith('bar'));
|
expect(multiTrim(nodes[1].textContent)).toBe(expectedTextWith('bar'));
|
||||||
expect(multiTrim(nodes[2].textContent)).toBe(expectedTextWith('baz'));
|
expect(multiTrim(nodes[2].textContent)).toBe(expectedTextWith('baz'));
|
||||||
expect(multiTrim(nodes[3].textContent)).toBe(expectedTextWith('qux'));
|
expect(multiTrim(nodes[3].textContent)).toBe(expectedTextWith('qux'));
|
||||||
|
|
||||||
ref.dispose();
|
ref.dispose();
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should bind to ng-model', async(() => {
|
it('should bind to ng-model', async(() => {
|
||||||
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
||||||
|
@ -1868,6 +1872,7 @@ 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');
|
||||||
|
|
|
@ -22,104 +22,106 @@ withEachNg1Version(() => {
|
||||||
beforeEach(() => destroyPlatform());
|
beforeEach(() => destroyPlatform());
|
||||||
afterEach(() => destroyPlatform());
|
afterEach(() => destroyPlatform());
|
||||||
|
|
||||||
it('should bind properties, events', async(() => {
|
fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly')
|
||||||
const ng1Module = angular.module('ng1', []).run(($rootScope: angular.IScope) => {
|
.it('should bind properties, events', async(() => {
|
||||||
$rootScope['name'] = 'world';
|
const ng1Module = angular.module('ng1', []).run(($rootScope: angular.IScope) => {
|
||||||
$rootScope['dataA'] = 'A';
|
$rootScope['name'] = 'world';
|
||||||
$rootScope['dataB'] = 'B';
|
$rootScope['dataA'] = 'A';
|
||||||
$rootScope['modelA'] = 'initModelA';
|
$rootScope['dataB'] = 'B';
|
||||||
$rootScope['modelB'] = 'initModelB';
|
$rootScope['modelA'] = 'initModelA';
|
||||||
$rootScope['eventA'] = '?';
|
$rootScope['modelB'] = 'initModelB';
|
||||||
$rootScope['eventB'] = '?';
|
$rootScope['eventA'] = '?';
|
||||||
});
|
$rootScope['eventB'] = '?';
|
||||||
|
});
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ng2',
|
selector: 'ng2',
|
||||||
inputs: ['literal', 'interpolate', 'oneWayA', 'oneWayB', 'twoWayA', 'twoWayB'],
|
inputs: ['literal', 'interpolate', 'oneWayA', 'oneWayB', 'twoWayA', 'twoWayB'],
|
||||||
outputs: [
|
outputs: [
|
||||||
'eventA', 'eventB', 'twoWayAEmitter: twoWayAChange', 'twoWayBEmitter: twoWayBChange'
|
'eventA', 'eventB', 'twoWayAEmitter: twoWayAChange',
|
||||||
],
|
'twoWayBEmitter: twoWayBChange'
|
||||||
template: 'ignore: {{ignore}}; ' +
|
],
|
||||||
'literal: {{literal}}; interpolate: {{interpolate}}; ' +
|
template: 'ignore: {{ignore}}; ' +
|
||||||
'oneWayA: {{oneWayA}}; oneWayB: {{oneWayB}}; ' +
|
'literal: {{literal}}; interpolate: {{interpolate}}; ' +
|
||||||
'twoWayA: {{twoWayA}}; twoWayB: {{twoWayB}}; ({{ngOnChangesCount}})'
|
'oneWayA: {{oneWayA}}; oneWayB: {{oneWayB}}; ' +
|
||||||
})
|
'twoWayA: {{twoWayA}}; twoWayB: {{twoWayB}}; ({{ngOnChangesCount}})'
|
||||||
class Ng2Component implements OnChanges {
|
})
|
||||||
ngOnChangesCount = 0;
|
class Ng2Component implements OnChanges {
|
||||||
ignore = '-';
|
ngOnChangesCount = 0;
|
||||||
literal = '?';
|
ignore = '-';
|
||||||
interpolate = '?';
|
literal = '?';
|
||||||
oneWayA = '?';
|
interpolate = '?';
|
||||||
oneWayB = '?';
|
oneWayA = '?';
|
||||||
twoWayA = '?';
|
oneWayB = '?';
|
||||||
twoWayB = '?';
|
twoWayA = '?';
|
||||||
eventA = new EventEmitter();
|
twoWayB = '?';
|
||||||
eventB = new EventEmitter();
|
eventA = new EventEmitter();
|
||||||
twoWayAEmitter = new EventEmitter();
|
eventB = new EventEmitter();
|
||||||
twoWayBEmitter = new EventEmitter();
|
twoWayAEmitter = new EventEmitter();
|
||||||
|
twoWayBEmitter = new EventEmitter();
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges) {
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
const assert = (prop: string, value: any) => {
|
const assert = (prop: string, value: any) => {
|
||||||
const propVal = (this as any)[prop];
|
const propVal = (this as any)[prop];
|
||||||
if (propVal != value) {
|
if (propVal != value) {
|
||||||
throw new Error(`Expected: '${prop}' to be '${value}' but was '${propVal}'`);
|
throw new Error(`Expected: '${prop}' to be '${value}' but was '${propVal}'`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const assertChange = (prop: string, value: any) => {
|
const assertChange = (prop: string, value: any) => {
|
||||||
assert(prop, value);
|
assert(prop, value);
|
||||||
if (!changes[prop]) {
|
if (!changes[prop]) {
|
||||||
throw new Error(`Changes record for '${prop}' not found.`);
|
throw new Error(`Changes record for '${prop}' not found.`);
|
||||||
}
|
}
|
||||||
const actualValue = changes[prop].currentValue;
|
const actualValue = changes[prop].currentValue;
|
||||||
if (actualValue != value) {
|
if (actualValue != value) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Expected changes record for'${prop}' to be '${value}' but was '${actualValue}'`);
|
`Expected changes record for'${prop}' to be '${value}' but was '${actualValue}'`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
switch (this.ngOnChangesCount++) {
|
switch (this.ngOnChangesCount++) {
|
||||||
case 0:
|
case 0:
|
||||||
assert('ignore', '-');
|
assert('ignore', '-');
|
||||||
assertChange('literal', 'Text');
|
assertChange('literal', 'Text');
|
||||||
assertChange('interpolate', 'Hello world');
|
assertChange('interpolate', 'Hello world');
|
||||||
assertChange('oneWayA', 'A');
|
assertChange('oneWayA', 'A');
|
||||||
assertChange('oneWayB', 'B');
|
assertChange('oneWayB', 'B');
|
||||||
assertChange('twoWayA', 'initModelA');
|
assertChange('twoWayA', 'initModelA');
|
||||||
assertChange('twoWayB', 'initModelB');
|
assertChange('twoWayB', 'initModelB');
|
||||||
|
|
||||||
this.twoWayAEmitter.emit('newA');
|
this.twoWayAEmitter.emit('newA');
|
||||||
this.twoWayBEmitter.emit('newB');
|
this.twoWayBEmitter.emit('newB');
|
||||||
this.eventA.emit('aFired');
|
this.eventA.emit('aFired');
|
||||||
this.eventB.emit('bFired');
|
this.eventB.emit('bFired');
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
assertChange('twoWayA', 'newA');
|
assertChange('twoWayA', 'newA');
|
||||||
assertChange('twoWayB', 'newB');
|
assertChange('twoWayB', 'newB');
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
assertChange('interpolate', 'Hello everyone');
|
assertChange('interpolate', 'Hello everyone');
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error('Called too many times! ' + JSON.stringify(changes));
|
throw new Error('Called too many times! ' + JSON.stringify(changes));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ng1Module.directive('ng2', downgradeComponent({
|
ng1Module.directive('ng2', downgradeComponent({
|
||||||
component: Ng2Component,
|
component: Ng2Component,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [Ng2Component],
|
declarations: [Ng2Component],
|
||||||
entryComponents: [Ng2Component],
|
entryComponents: [Ng2Component],
|
||||||
imports: [BrowserModule, UpgradeModule]
|
imports: [BrowserModule, UpgradeModule]
|
||||||
})
|
})
|
||||||
class Ng2Module {
|
class Ng2Module {
|
||||||
ngDoBootstrap() {}
|
ngDoBootstrap() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
const element = html(`
|
const element = html(`
|
||||||
<div>
|
<div>
|
||||||
<ng2 literal="Text" interpolate="Hello {{name}}"
|
<ng2 literal="Text" interpolate="Hello {{name}}"
|
||||||
bind-one-way-a="dataA" [one-way-b]="dataB"
|
bind-one-way-a="dataA" [one-way-b]="dataB"
|
||||||
|
@ -128,23 +130,23 @@ withEachNg1Version(() => {
|
||||||
| modelA: {{modelA}}; modelB: {{modelB}}; eventA: {{eventA}}; eventB: {{eventB}};
|
| modelA: {{modelA}}; modelB: {{modelB}}; eventA: {{eventA}}; eventB: {{eventB}};
|
||||||
</div>`);
|
</div>`);
|
||||||
|
|
||||||
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => {
|
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => {
|
||||||
expect(multiTrim(document.body.textContent))
|
expect(multiTrim(document.body.textContent))
|
||||||
.toEqual(
|
.toEqual(
|
||||||
'ignore: -; ' +
|
'ignore: -; ' +
|
||||||
'literal: Text; interpolate: Hello world; ' +
|
'literal: Text; interpolate: Hello world; ' +
|
||||||
'oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (2) | ' +
|
'oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (2) | ' +
|
||||||
'modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;');
|
'modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;');
|
||||||
|
|
||||||
$apply(upgrade, 'name = "everyone"');
|
$apply(upgrade, 'name = "everyone"');
|
||||||
expect(multiTrim(document.body.textContent))
|
expect(multiTrim(document.body.textContent))
|
||||||
.toEqual(
|
.toEqual(
|
||||||
'ignore: -; ' +
|
'ignore: -; ' +
|
||||||
'literal: Text; interpolate: Hello everyone; ' +
|
'literal: Text; interpolate: Hello everyone; ' +
|
||||||
'oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (3) | ' +
|
'oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (3) | ' +
|
||||||
'modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;');
|
'modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;');
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should bind properties to onpush components', async(() => {
|
it('should bind properties to onpush components', async(() => {
|
||||||
const ng1Module = angular.module('ng1', []).run(
|
const ng1Module = angular.module('ng1', []).run(
|
||||||
|
@ -187,57 +189,58 @@ withEachNg1Version(() => {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should support two-way binding and event listener', async(() => {
|
fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly')
|
||||||
const listenerSpy = jasmine.createSpy('$rootScope.listener');
|
.it('should support two-way binding and event listener', async(() => {
|
||||||
const ng1Module = angular.module('ng1', []).run(($rootScope: angular.IScope) => {
|
const listenerSpy = jasmine.createSpy('$rootScope.listener');
|
||||||
$rootScope['value'] = 'world';
|
const ng1Module = angular.module('ng1', []).run(($rootScope: angular.IScope) => {
|
||||||
$rootScope['listener'] = listenerSpy;
|
$rootScope['value'] = 'world';
|
||||||
});
|
$rootScope['listener'] = listenerSpy;
|
||||||
|
});
|
||||||
|
|
||||||
@Component({selector: 'ng2', template: `model: {{model}};`})
|
@Component({selector: 'ng2', template: `model: {{model}};`})
|
||||||
class Ng2Component implements OnChanges {
|
class Ng2Component implements OnChanges {
|
||||||
ngOnChangesCount = 0;
|
ngOnChangesCount = 0;
|
||||||
@Input() model = '?';
|
@Input() model = '?';
|
||||||
@Output() modelChange = new EventEmitter();
|
@Output() modelChange = new EventEmitter();
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges) {
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
switch (this.ngOnChangesCount++) {
|
switch (this.ngOnChangesCount++) {
|
||||||
case 0:
|
case 0:
|
||||||
expect(changes.model.currentValue).toBe('world');
|
expect(changes.model.currentValue).toBe('world');
|
||||||
this.modelChange.emit('newC');
|
this.modelChange.emit('newC');
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
expect(changes.model.currentValue).toBe('newC');
|
expect(changes.model.currentValue).toBe('newC');
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error('Called too many times! ' + JSON.stringify(changes));
|
throw new Error('Called too many times! ' + JSON.stringify(changes));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ng1Module.directive('ng2', downgradeComponent({component: Ng2Component}));
|
ng1Module.directive('ng2', downgradeComponent({component: Ng2Component}));
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [Ng2Component],
|
declarations: [Ng2Component],
|
||||||
entryComponents: [Ng2Component],
|
entryComponents: [Ng2Component],
|
||||||
imports: [BrowserModule, UpgradeModule]
|
imports: [BrowserModule, UpgradeModule]
|
||||||
})
|
})
|
||||||
class Ng2Module {
|
class Ng2Module {
|
||||||
ngDoBootstrap() {}
|
ngDoBootstrap() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
const element = html(`
|
const element = html(`
|
||||||
<div>
|
<div>
|
||||||
<ng2 [(model)]="value" (model-change)="listener($event)"></ng2>
|
<ng2 [(model)]="value" (model-change)="listener($event)"></ng2>
|
||||||
| value: {{value}}
|
| value: {{value}}
|
||||||
</div>
|
</div>
|
||||||
`);
|
`);
|
||||||
|
|
||||||
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => {
|
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => {
|
||||||
expect(multiTrim(element.textContent)).toEqual('model: newC; | value: newC');
|
expect(multiTrim(element.textContent)).toEqual('model: newC; | value: newC');
|
||||||
expect(listenerSpy).toHaveBeenCalledWith('newC');
|
expect(listenerSpy).toHaveBeenCalledWith('newC');
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should run change-detection on every digest (by default)', async(() => {
|
it('should run change-detection on every digest (by default)', async(() => {
|
||||||
let ng2Component: Ng2Component;
|
let ng2Component: Ng2Component;
|
||||||
|
@ -401,65 +404,66 @@ withEachNg1Version(() => {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should initialize inputs in time for `ngOnChanges`', async(() => {
|
fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly')
|
||||||
@Component({
|
.it('should initialize inputs in time for `ngOnChanges`', async(() => {
|
||||||
selector: 'ng2',
|
@Component({
|
||||||
template: `
|
selector: 'ng2',
|
||||||
|
template: `
|
||||||
ngOnChangesCount: {{ ngOnChangesCount }} |
|
ngOnChangesCount: {{ ngOnChangesCount }} |
|
||||||
firstChangesCount: {{ firstChangesCount }} |
|
firstChangesCount: {{ firstChangesCount }} |
|
||||||
initialValue: {{ initialValue }}`
|
initialValue: {{ initialValue }}`
|
||||||
})
|
})
|
||||||
class Ng2Component implements OnChanges {
|
class Ng2Component implements OnChanges {
|
||||||
ngOnChangesCount = 0;
|
ngOnChangesCount = 0;
|
||||||
firstChangesCount = 0;
|
firstChangesCount = 0;
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
initialValue !: string;
|
initialValue !: string;
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
@Input() foo !: string;
|
@Input() foo !: string;
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges) {
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
this.ngOnChangesCount++;
|
this.ngOnChangesCount++;
|
||||||
|
|
||||||
if (this.ngOnChangesCount === 1) {
|
if (this.ngOnChangesCount === 1) {
|
||||||
this.initialValue = this.foo;
|
this.initialValue = this.foo;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changes['foo'] && changes['foo'].isFirstChange()) {
|
if (changes['foo'] && changes['foo'].isFirstChange()) {
|
||||||
this.firstChangesCount++;
|
this.firstChangesCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [BrowserModule, UpgradeModule],
|
imports: [BrowserModule, UpgradeModule],
|
||||||
declarations: [Ng2Component],
|
declarations: [Ng2Component],
|
||||||
entryComponents: [Ng2Component]
|
entryComponents: [Ng2Component]
|
||||||
})
|
})
|
||||||
class Ng2Module {
|
class Ng2Module {
|
||||||
ngDoBootstrap() {}
|
ngDoBootstrap() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ng1Module = angular.module('ng1', []).directive(
|
const ng1Module = angular.module('ng1', []).directive(
|
||||||
'ng2', downgradeComponent({component: Ng2Component}));
|
'ng2', downgradeComponent({component: Ng2Component}));
|
||||||
|
|
||||||
const element = html(`
|
const element = html(`
|
||||||
<ng2 [foo]="'foo'"></ng2>
|
<ng2 [foo]="'foo'"></ng2>
|
||||||
<ng2 foo="bar"></ng2>
|
<ng2 foo="bar"></ng2>
|
||||||
<ng2 [foo]="'baz'" ng-if="true"></ng2>
|
<ng2 [foo]="'baz'" ng-if="true"></ng2>
|
||||||
<ng2 foo="qux" ng-if="true"></ng2>
|
<ng2 foo="qux" ng-if="true"></ng2>
|
||||||
`);
|
`);
|
||||||
|
|
||||||
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => {
|
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => {
|
||||||
const nodes = element.querySelectorAll('ng2');
|
const nodes = element.querySelectorAll('ng2');
|
||||||
const expectedTextWith = (value: string) =>
|
const expectedTextWith = (value: string) =>
|
||||||
`ngOnChangesCount: 1 | firstChangesCount: 1 | initialValue: ${value}`;
|
`ngOnChangesCount: 1 | firstChangesCount: 1 | initialValue: ${value}`;
|
||||||
|
|
||||||
expect(multiTrim(nodes[0].textContent)).toBe(expectedTextWith('foo'));
|
expect(multiTrim(nodes[0].textContent)).toBe(expectedTextWith('foo'));
|
||||||
expect(multiTrim(nodes[1].textContent)).toBe(expectedTextWith('bar'));
|
expect(multiTrim(nodes[1].textContent)).toBe(expectedTextWith('bar'));
|
||||||
expect(multiTrim(nodes[2].textContent)).toBe(expectedTextWith('baz'));
|
expect(multiTrim(nodes[2].textContent)).toBe(expectedTextWith('baz'));
|
||||||
expect(multiTrim(nodes[3].textContent)).toBe(expectedTextWith('qux'));
|
expect(multiTrim(nodes[3].textContent)).toBe(expectedTextWith('qux'));
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should bind to ng-model', async(() => {
|
it('should bind to ng-model', async(() => {
|
||||||
const ng1Module = angular.module('ng1', []).run(
|
const ng1Module = angular.module('ng1', []).run(
|
||||||
|
|
|
@ -721,63 +721,66 @@ withEachNg1Version(() => {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should propagate input changes inside the Angular zone', async(() => {
|
fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly')
|
||||||
let ng2Component: Ng2Component;
|
.it('should propagate input changes inside the Angular zone', async(() => {
|
||||||
|
let ng2Component: Ng2Component;
|
||||||
|
|
||||||
@Component({selector: 'ng2', template: ''})
|
@Component({selector: 'ng2', template: ''})
|
||||||
class Ng2Component implements OnChanges {
|
class Ng2Component implements OnChanges {
|
||||||
@Input() attrInput = 'foo';
|
@Input() attrInput = 'foo';
|
||||||
@Input() propInput = 'foo';
|
@Input() propInput = 'foo';
|
||||||
|
|
||||||
constructor() { ng2Component = this; }
|
constructor() { ng2Component = this; }
|
||||||
ngOnChanges() {}
|
ngOnChanges() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [Ng2Component],
|
declarations: [Ng2Component],
|
||||||
entryComponents: [Ng2Component],
|
entryComponents: [Ng2Component],
|
||||||
imports: [BrowserModule],
|
imports: [BrowserModule],
|
||||||
})
|
})
|
||||||
class Ng2Module {
|
class Ng2Module {
|
||||||
ngDoBootstrap() {}
|
ngDoBootstrap() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
const bootstrapFn = (extraProviders: StaticProvider[]) =>
|
const bootstrapFn = (extraProviders: StaticProvider[]) =>
|
||||||
platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
|
platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
|
||||||
const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
|
const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
|
||||||
const ng1Module =
|
const ng1Module =
|
||||||
angular.module('ng1', [lazyModuleName])
|
angular.module('ng1', [lazyModuleName])
|
||||||
.directive('ng2', downgradeComponent({component: Ng2Component, propagateDigest}))
|
.directive(
|
||||||
.run(($rootScope: angular.IRootScopeService) => {
|
'ng2', downgradeComponent({component: Ng2Component, propagateDigest}))
|
||||||
$rootScope.attrVal = 'bar';
|
.run(($rootScope: angular.IRootScopeService) => {
|
||||||
$rootScope.propVal = 'bar';
|
$rootScope.attrVal = 'bar';
|
||||||
});
|
$rootScope.propVal = 'bar';
|
||||||
|
});
|
||||||
|
|
||||||
const element = html('<ng2 attr-input="{{ attrVal }}" [prop-input]="propVal"></ng2>');
|
const element =
|
||||||
const $injector = angular.bootstrap(element, [ng1Module.name]);
|
html('<ng2 attr-input="{{ attrVal }}" [prop-input]="propVal"></ng2>');
|
||||||
const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService;
|
const $injector = angular.bootstrap(element, [ng1Module.name]);
|
||||||
|
const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService;
|
||||||
|
|
||||||
setTimeout(() => { // Wait for the module to be bootstrapped.
|
setTimeout(() => { // Wait for the module to be bootstrapped.
|
||||||
setTimeout(() => { // Wait for `$evalAsync()` to propagate inputs.
|
setTimeout(() => { // Wait for `$evalAsync()` to propagate inputs.
|
||||||
const expectToBeInNgZone = () => expect(NgZone.isInAngularZone()).toBe(true);
|
const expectToBeInNgZone = () => expect(NgZone.isInAngularZone()).toBe(true);
|
||||||
const changesSpy =
|
const changesSpy =
|
||||||
spyOn(ng2Component, 'ngOnChanges').and.callFake(expectToBeInNgZone);
|
spyOn(ng2Component, 'ngOnChanges').and.callFake(expectToBeInNgZone);
|
||||||
|
|
||||||
expect(ng2Component.attrInput).toBe('bar');
|
expect(ng2Component.attrInput).toBe('bar');
|
||||||
expect(ng2Component.propInput).toBe('bar');
|
expect(ng2Component.propInput).toBe('bar');
|
||||||
|
|
||||||
$rootScope.$apply('attrVal = "baz"');
|
$rootScope.$apply('attrVal = "baz"');
|
||||||
expect(ng2Component.attrInput).toBe('baz');
|
expect(ng2Component.attrInput).toBe('baz');
|
||||||
expect(ng2Component.propInput).toBe('bar');
|
expect(ng2Component.propInput).toBe('bar');
|
||||||
expect(changesSpy).toHaveBeenCalledTimes(1);
|
expect(changesSpy).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
$rootScope.$apply('propVal = "qux"');
|
$rootScope.$apply('propVal = "qux"');
|
||||||
expect(ng2Component.attrInput).toBe('baz');
|
expect(ng2Component.attrInput).toBe('baz');
|
||||||
expect(ng2Component.propInput).toBe('qux');
|
expect(ng2Component.propInput).toBe('qux');
|
||||||
expect(changesSpy).toHaveBeenCalledTimes(2);
|
expect(changesSpy).toHaveBeenCalledTimes(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should create and destroy nested, asynchronously instantiated components inside the Angular zone',
|
it('should create and destroy nested, asynchronously instantiated components inside the Angular zone',
|
||||||
async(() => {
|
async(() => {
|
||||||
|
@ -940,165 +943,167 @@ withEachNg1Version(() => {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should run the lifecycle hooks in the correct order', async(() => {
|
fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly')
|
||||||
const logs: string[] = [];
|
.it('should run the lifecycle hooks in the correct order', async(() => {
|
||||||
let rootScope: angular.IRootScopeService;
|
const logs: string[] = [];
|
||||||
|
let rootScope: angular.IRootScopeService;
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ng2',
|
selector: 'ng2',
|
||||||
template: `
|
template: `
|
||||||
{{ value }}
|
{{ value }}
|
||||||
<button (click)="value = 'qux'"></button>
|
<button (click)="value = 'qux'"></button>
|
||||||
<ng-content></ng-content>
|
<ng-content></ng-content>
|
||||||
`
|
`
|
||||||
})
|
})
|
||||||
class Ng2Component implements AfterContentChecked,
|
class Ng2Component implements AfterContentChecked,
|
||||||
AfterContentInit, AfterViewChecked, AfterViewInit, DoCheck, OnChanges, OnDestroy,
|
AfterContentInit, AfterViewChecked, AfterViewInit, DoCheck, OnChanges,
|
||||||
OnInit {
|
OnDestroy, OnInit {
|
||||||
@Input() value = 'foo';
|
@Input() value = 'foo';
|
||||||
|
|
||||||
ngAfterContentChecked() { this.log('AfterContentChecked'); }
|
ngAfterContentChecked() { this.log('AfterContentChecked'); }
|
||||||
ngAfterContentInit() { this.log('AfterContentInit'); }
|
ngAfterContentInit() { this.log('AfterContentInit'); }
|
||||||
ngAfterViewChecked() { this.log('AfterViewChecked'); }
|
ngAfterViewChecked() { this.log('AfterViewChecked'); }
|
||||||
ngAfterViewInit() { this.log('AfterViewInit'); }
|
ngAfterViewInit() { this.log('AfterViewInit'); }
|
||||||
ngDoCheck() { this.log('DoCheck'); }
|
ngDoCheck() { this.log('DoCheck'); }
|
||||||
ngOnChanges() { this.log('OnChanges'); }
|
ngOnChanges() { this.log('OnChanges'); }
|
||||||
ngOnDestroy() { this.log('OnDestroy'); }
|
ngOnDestroy() { this.log('OnDestroy'); }
|
||||||
ngOnInit() { this.log('OnInit'); }
|
ngOnInit() { this.log('OnInit'); }
|
||||||
|
|
||||||
private log(hook: string) { logs.push(`${hook}(${this.value})`); }
|
private log(hook: string) { logs.push(`${hook}(${this.value})`); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [Ng2Component],
|
declarations: [Ng2Component],
|
||||||
entryComponents: [Ng2Component],
|
entryComponents: [Ng2Component],
|
||||||
imports: [BrowserModule],
|
imports: [BrowserModule],
|
||||||
})
|
})
|
||||||
class Ng2Module {
|
class Ng2Module {
|
||||||
ngDoBootstrap() {}
|
ngDoBootstrap() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
const bootstrapFn = (extraProviders: StaticProvider[]) =>
|
const bootstrapFn = (extraProviders: StaticProvider[]) =>
|
||||||
platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
|
platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
|
||||||
const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
|
const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
|
||||||
const ng1Module =
|
const ng1Module =
|
||||||
angular.module('ng1', [lazyModuleName])
|
angular.module('ng1', [lazyModuleName])
|
||||||
.directive('ng2', downgradeComponent({component: Ng2Component, propagateDigest}))
|
.directive(
|
||||||
.run(($rootScope: angular.IRootScopeService) => {
|
'ng2', downgradeComponent({component: Ng2Component, propagateDigest}))
|
||||||
rootScope = $rootScope;
|
.run(($rootScope: angular.IRootScopeService) => {
|
||||||
rootScope.value = 'bar';
|
rootScope = $rootScope;
|
||||||
});
|
rootScope.value = 'bar';
|
||||||
|
});
|
||||||
|
|
||||||
const element =
|
const element =
|
||||||
html('<div><ng2 value="{{ value }}" ng-if="!hideNg2">Content</ng2></div>');
|
html('<div><ng2 value="{{ value }}" ng-if="!hideNg2">Content</ng2></div>');
|
||||||
angular.bootstrap(element, [ng1Module.name]);
|
angular.bootstrap(element, [ng1Module.name]);
|
||||||
|
|
||||||
setTimeout(() => { // Wait for the module to be bootstrapped.
|
setTimeout(() => { // Wait for the module to be bootstrapped.
|
||||||
setTimeout(() => { // Wait for `$evalAsync()` to propagate inputs.
|
setTimeout(() => { // Wait for `$evalAsync()` to propagate inputs.
|
||||||
const button = element.querySelector('button') !;
|
const button = element.querySelector('button') !;
|
||||||
|
|
||||||
// Once initialized.
|
// Once initialized.
|
||||||
expect(multiTrim(element.textContent)).toBe('bar Content');
|
expect(multiTrim(element.textContent)).toBe('bar Content');
|
||||||
expect(logs).toEqual([
|
expect(logs).toEqual([
|
||||||
// `ngOnChanges()` call triggered directly through the `inputChanges`
|
// `ngOnChanges()` call triggered directly through the `inputChanges`
|
||||||
// $watcher.
|
// $watcher.
|
||||||
'OnChanges(bar)',
|
'OnChanges(bar)',
|
||||||
// Initial CD triggered directly through the `detectChanges()` or
|
// Initial CD triggered directly through the `detectChanges()` or
|
||||||
// `inputChanges`
|
// `inputChanges`
|
||||||
// $watcher (for `propagateDigest` true/false respectively).
|
// $watcher (for `propagateDigest` true/false respectively).
|
||||||
'OnInit(bar)',
|
'OnInit(bar)',
|
||||||
'DoCheck(bar)',
|
'DoCheck(bar)',
|
||||||
'AfterContentInit(bar)',
|
'AfterContentInit(bar)',
|
||||||
'AfterContentChecked(bar)',
|
'AfterContentChecked(bar)',
|
||||||
'AfterViewInit(bar)',
|
'AfterViewInit(bar)',
|
||||||
'AfterViewChecked(bar)',
|
'AfterViewChecked(bar)',
|
||||||
...(propagateDigest ?
|
...(propagateDigest ?
|
||||||
[
|
[
|
||||||
// CD triggered directly through the `detectChanges()` $watcher (2nd
|
// CD triggered directly through the `detectChanges()` $watcher (2nd
|
||||||
// $digest).
|
// $digest).
|
||||||
'DoCheck(bar)',
|
'DoCheck(bar)',
|
||||||
'AfterContentChecked(bar)',
|
'AfterContentChecked(bar)',
|
||||||
'AfterViewChecked(bar)',
|
'AfterViewChecked(bar)',
|
||||||
] :
|
] :
|
||||||
[]),
|
[]),
|
||||||
// CD triggered due to entering/leaving the NgZone (in `downgradeFn()`).
|
// CD triggered due to entering/leaving the NgZone (in `downgradeFn()`).
|
||||||
'DoCheck(bar)',
|
'DoCheck(bar)',
|
||||||
'AfterContentChecked(bar)',
|
'AfterContentChecked(bar)',
|
||||||
'AfterViewChecked(bar)',
|
'AfterViewChecked(bar)',
|
||||||
]);
|
]);
|
||||||
logs.length = 0;
|
logs.length = 0;
|
||||||
|
|
||||||
// Change inputs and run `$digest`.
|
// Change inputs and run `$digest`.
|
||||||
rootScope.$apply('value = "baz"');
|
rootScope.$apply('value = "baz"');
|
||||||
expect(multiTrim(element.textContent)).toBe('baz Content');
|
expect(multiTrim(element.textContent)).toBe('baz Content');
|
||||||
expect(logs).toEqual([
|
expect(logs).toEqual([
|
||||||
// `ngOnChanges()` call triggered directly through the `inputChanges`
|
// `ngOnChanges()` call triggered directly through the `inputChanges`
|
||||||
// $watcher.
|
// $watcher.
|
||||||
'OnChanges(baz)',
|
'OnChanges(baz)',
|
||||||
// `propagateDigest: true` (3 CD runs):
|
// `propagateDigest: true` (3 CD runs):
|
||||||
// - CD triggered due to entering/leaving the NgZone (in `inputChanges`
|
// - CD triggered due to entering/leaving the NgZone (in `inputChanges`
|
||||||
// $watcher).
|
// $watcher).
|
||||||
// - CD triggered directly through the `detectChanges()` $watcher.
|
// - CD triggered directly through the `detectChanges()` $watcher.
|
||||||
// - CD triggered due to entering/leaving the NgZone (in `detectChanges`
|
// - CD triggered due to entering/leaving the NgZone (in `detectChanges`
|
||||||
// $watcher).
|
// $watcher).
|
||||||
// `propagateDigest: false` (2 CD runs):
|
// `propagateDigest: false` (2 CD runs):
|
||||||
// - CD triggered directly through the `inputChanges` $watcher.
|
// - CD triggered directly through the `inputChanges` $watcher.
|
||||||
// - CD triggered due to entering/leaving the NgZone (in `inputChanges`
|
// - CD triggered due to entering/leaving the NgZone (in `inputChanges`
|
||||||
// $watcher).
|
// $watcher).
|
||||||
'DoCheck(baz)',
|
'DoCheck(baz)',
|
||||||
'AfterContentChecked(baz)',
|
'AfterContentChecked(baz)',
|
||||||
'AfterViewChecked(baz)',
|
'AfterViewChecked(baz)',
|
||||||
'DoCheck(baz)',
|
'DoCheck(baz)',
|
||||||
'AfterContentChecked(baz)',
|
'AfterContentChecked(baz)',
|
||||||
'AfterViewChecked(baz)',
|
'AfterViewChecked(baz)',
|
||||||
...(propagateDigest ?
|
...(propagateDigest ?
|
||||||
[
|
[
|
||||||
'DoCheck(baz)',
|
'DoCheck(baz)',
|
||||||
'AfterContentChecked(baz)',
|
'AfterContentChecked(baz)',
|
||||||
'AfterViewChecked(baz)',
|
'AfterViewChecked(baz)',
|
||||||
] :
|
] :
|
||||||
[]),
|
[]),
|
||||||
]);
|
]);
|
||||||
logs.length = 0;
|
logs.length = 0;
|
||||||
|
|
||||||
// Run `$digest` (without changing inputs).
|
// Run `$digest` (without changing inputs).
|
||||||
rootScope.$digest();
|
rootScope.$digest();
|
||||||
expect(multiTrim(element.textContent)).toBe('baz Content');
|
expect(multiTrim(element.textContent)).toBe('baz Content');
|
||||||
expect(logs).toEqual(
|
expect(logs).toEqual(
|
||||||
propagateDigest ?
|
propagateDigest ?
|
||||||
[
|
[
|
||||||
// CD triggered directly through the `detectChanges()` $watcher.
|
// CD triggered directly through the `detectChanges()` $watcher.
|
||||||
'DoCheck(baz)',
|
'DoCheck(baz)',
|
||||||
'AfterContentChecked(baz)',
|
'AfterContentChecked(baz)',
|
||||||
'AfterViewChecked(baz)',
|
'AfterViewChecked(baz)',
|
||||||
// CD triggered due to entering/leaving the NgZone (in the above
|
// CD triggered due to entering/leaving the NgZone (in the above
|
||||||
// $watcher).
|
// $watcher).
|
||||||
'DoCheck(baz)',
|
'DoCheck(baz)',
|
||||||
'AfterContentChecked(baz)',
|
'AfterContentChecked(baz)',
|
||||||
'AfterViewChecked(baz)',
|
'AfterViewChecked(baz)',
|
||||||
] :
|
] :
|
||||||
[]);
|
[]);
|
||||||
logs.length = 0;
|
logs.length = 0;
|
||||||
|
|
||||||
// Trigger change detection (without changing inputs).
|
// Trigger change detection (without changing inputs).
|
||||||
button.click();
|
button.click();
|
||||||
expect(multiTrim(element.textContent)).toBe('qux Content');
|
expect(multiTrim(element.textContent)).toBe('qux Content');
|
||||||
expect(logs).toEqual([
|
expect(logs).toEqual([
|
||||||
'DoCheck(qux)',
|
'DoCheck(qux)',
|
||||||
'AfterContentChecked(qux)',
|
'AfterContentChecked(qux)',
|
||||||
'AfterViewChecked(qux)',
|
'AfterViewChecked(qux)',
|
||||||
]);
|
]);
|
||||||
logs.length = 0;
|
logs.length = 0;
|
||||||
|
|
||||||
// Destroy the component.
|
// Destroy the component.
|
||||||
rootScope.$apply('hideNg2 = true');
|
rootScope.$apply('hideNg2 = true');
|
||||||
expect(logs).toEqual([
|
expect(logs).toEqual([
|
||||||
'OnDestroy(qux)',
|
'OnDestroy(qux)',
|
||||||
]);
|
]);
|
||||||
logs.length = 0;
|
logs.length = 0;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should detach hostViews from the ApplicationRef once destroyed', async(() => {
|
it('should detach hostViews from the ApplicationRef once destroyed', async(() => {
|
||||||
let ng2Component: Ng2Component;
|
let ng2Component: Ng2Component;
|
||||||
|
|
|
@ -306,8 +306,8 @@ export declare class NgSwitchDefault {
|
||||||
}
|
}
|
||||||
|
|
||||||
export declare class NgTemplateOutlet implements OnChanges {
|
export declare class NgTemplateOutlet implements OnChanges {
|
||||||
ngTemplateOutlet: TemplateRef<any> | null;
|
ngTemplateOutlet: TemplateRef<any>;
|
||||||
ngTemplateOutletContext: Object | null;
|
ngTemplateOutletContext: Object;
|
||||||
constructor(_viewContainerRef: ViewContainerRef);
|
constructor(_viewContainerRef: ViewContainerRef);
|
||||||
ngOnChanges(changes: SimpleChanges): void;
|
ngOnChanges(changes: SimpleChanges): void;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue