refactor(core): remove unused embedded view instructions (#34715)
This commit performs a cleanup and removes unused embedded view instructions and corresponding tests. PR Close #34715
This commit is contained in:
parent
ed1b4a8f19
commit
0c8adbc4ec
|
@ -1601,16 +1601,6 @@
|
|||
"packages/core/src/render3/component.ts",
|
||||
"packages/core/src/render3/styling/static_styling.ts"
|
||||
],
|
||||
[
|
||||
"packages/core/src/render3/assert.ts",
|
||||
"packages/core/src/render3/interfaces/type_checks.ts",
|
||||
"packages/core/src/render3/index.ts",
|
||||
"packages/core/src/render3/component.ts",
|
||||
"packages/core/src/render3/util/global_utils.ts",
|
||||
"packages/core/src/render3/util/change_detection_utils.ts",
|
||||
"packages/core/src/render3/instructions/all.ts",
|
||||
"packages/core/src/render3/instructions/container.ts"
|
||||
],
|
||||
[
|
||||
"packages/core/src/render3/assert.ts",
|
||||
"packages/core/src/render3/interfaces/type_checks.ts",
|
||||
|
@ -1631,16 +1621,6 @@
|
|||
"packages/core/src/render3/instructions/all.ts",
|
||||
"packages/core/src/render3/instructions/element.ts"
|
||||
],
|
||||
[
|
||||
"packages/core/src/render3/assert.ts",
|
||||
"packages/core/src/render3/interfaces/type_checks.ts",
|
||||
"packages/core/src/render3/index.ts",
|
||||
"packages/core/src/render3/component.ts",
|
||||
"packages/core/src/render3/util/global_utils.ts",
|
||||
"packages/core/src/render3/util/change_detection_utils.ts",
|
||||
"packages/core/src/render3/instructions/all.ts",
|
||||
"packages/core/src/render3/instructions/embedded_view.ts"
|
||||
],
|
||||
[
|
||||
"packages/core/src/render3/assert.ts",
|
||||
"packages/core/src/render3/interfaces/type_checks.ts",
|
||||
|
@ -1662,6 +1642,16 @@
|
|||
"packages/core/src/render3/instructions/styling.ts",
|
||||
"packages/core/src/render3/styling/style_binding_list.ts"
|
||||
],
|
||||
[
|
||||
"packages/core/src/render3/assert.ts",
|
||||
"packages/core/src/render3/interfaces/type_checks.ts",
|
||||
"packages/core/src/render3/index.ts",
|
||||
"packages/core/src/render3/component.ts",
|
||||
"packages/core/src/render3/util/global_utils.ts",
|
||||
"packages/core/src/render3/util/change_detection_utils.ts",
|
||||
"packages/core/src/render3/instructions/all.ts",
|
||||
"packages/core/src/render3/instructions/template.ts"
|
||||
],
|
||||
[
|
||||
"packages/core/src/render3/assert.ts",
|
||||
"packages/core/src/render3/interfaces/type_checks.ts",
|
||||
|
@ -1715,15 +1705,6 @@
|
|||
"packages/core/src/render3/interfaces/type_checks.ts",
|
||||
"packages/core/src/render3/index.ts"
|
||||
],
|
||||
[
|
||||
"packages/core/src/render3/component.ts",
|
||||
"packages/core/src/render3/util/global_utils.ts",
|
||||
"packages/core/src/render3/util/change_detection_utils.ts",
|
||||
"packages/core/src/render3/instructions/all.ts",
|
||||
"packages/core/src/render3/instructions/container.ts",
|
||||
"packages/core/src/render3/interfaces/type_checks.ts",
|
||||
"packages/core/src/render3/index.ts"
|
||||
],
|
||||
[
|
||||
"packages/core/src/render3/component.ts",
|
||||
"packages/core/src/render3/util/global_utils.ts",
|
||||
|
@ -1751,6 +1732,15 @@
|
|||
"packages/core/src/render3/interfaces/type_checks.ts",
|
||||
"packages/core/src/render3/index.ts"
|
||||
],
|
||||
[
|
||||
"packages/core/src/render3/component.ts",
|
||||
"packages/core/src/render3/util/global_utils.ts",
|
||||
"packages/core/src/render3/util/change_detection_utils.ts",
|
||||
"packages/core/src/render3/instructions/all.ts",
|
||||
"packages/core/src/render3/instructions/template.ts",
|
||||
"packages/core/src/render3/interfaces/type_checks.ts",
|
||||
"packages/core/src/render3/index.ts"
|
||||
],
|
||||
[
|
||||
"packages/core/src/render3/di_setup.ts",
|
||||
"packages/core/src/render3/interfaces/type_checks.ts",
|
||||
|
|
|
@ -719,12 +719,6 @@ export declare type ɵɵComponentDefWithMeta<T, Selector extends String, ExportA
|
|||
|
||||
export declare function ɵɵcomponentHostSyntheticListener(eventName: string, listenerFn: (e?: any) => any, useCapture?: boolean, eventTargetResolver?: GlobalTargetResolver): typeof ɵɵcomponentHostSyntheticListener;
|
||||
|
||||
export declare function ɵɵcontainer(index: number): void;
|
||||
|
||||
export declare function ɵɵcontainerRefreshEnd(): void;
|
||||
|
||||
export declare function ɵɵcontainerRefreshStart(index: number): void;
|
||||
|
||||
export declare function ɵɵcontentQuery<T>(directiveIndex: number, predicate: Type<any> | string[], descend: boolean, read?: any): void;
|
||||
|
||||
export declare function ɵɵCopyDefinitionFeature(definition: ɵDirectiveDef<any> | ɵComponentDef<any>): void;
|
||||
|
@ -828,10 +822,6 @@ export declare function ɵɵelementEnd(): void;
|
|||
|
||||
export declare function ɵɵelementStart(index: number, name: string, attrsIndex?: number | null, localRefsIndex?: number): void;
|
||||
|
||||
export declare function ɵɵembeddedViewEnd(): void;
|
||||
|
||||
export declare function ɵɵembeddedViewStart(viewBlockId: number, decls: number, vars: number): ɵRenderFlags;
|
||||
|
||||
export declare function ɵɵenableBindings(): void;
|
||||
|
||||
export declare type ɵɵFactoryDef<T, CtorDependencies extends CtorDependency[]> = () => T;
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
load("//tools:defaults.bzl", "ts_devserver", "ts_library")
|
||||
load("//modules/benchmarks:benchmark_test.bzl", "benchmark_test")
|
||||
load("//modules/benchmarks:e2e_test.bzl", "e2e_test")
|
||||
|
||||
package(default_visibility = ["//modules/benchmarks:__subpackages__"])
|
||||
|
||||
ts_library(
|
||||
name = "render3_function_lib",
|
||||
srcs = glob(["**/*.ts"]),
|
||||
deps = [
|
||||
"//modules/benchmarks/src:util_lib",
|
||||
"//modules/benchmarks/src/tree:util_lib",
|
||||
"//modules/benchmarks/src/tree/render3:tree_lib",
|
||||
"//packages/core",
|
||||
],
|
||||
)
|
||||
|
||||
ts_devserver(
|
||||
name = "devserver",
|
||||
entry_module = "angular/modules/benchmarks/src/tree/render3_function/index",
|
||||
port = 4200,
|
||||
scripts = [
|
||||
"@npm//:node_modules/tslib/tslib.js",
|
||||
"//tools/rxjs:rxjs_umd_modules",
|
||||
],
|
||||
static_files = ["index.html"],
|
||||
deps = [":render3_function_lib"],
|
||||
)
|
||||
|
||||
benchmark_test(
|
||||
name = "perf",
|
||||
server = ":devserver",
|
||||
deps = [
|
||||
"//modules/benchmarks/src/tree:detect_changes_perf_tests_lib",
|
||||
"//modules/benchmarks/src/tree:perf_tests_lib",
|
||||
],
|
||||
)
|
||||
|
||||
e2e_test(
|
||||
name = "e2e",
|
||||
server = ":devserver",
|
||||
deps = [
|
||||
"//modules/benchmarks/src/tree:detect_changes_e2e_tests_lib",
|
||||
"//modules/benchmarks/src/tree:e2e_tests_lib",
|
||||
],
|
||||
)
|
|
@ -1,42 +0,0 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<!-- Prevent the browser from requesting any favicon. -->
|
||||
<link rel="icon" href="data:,">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h2>Params</h2>
|
||||
<form>
|
||||
Depth:
|
||||
<input type="number" name="depth" placeholder="depth" value="10">
|
||||
<br>
|
||||
<button>Apply</button>
|
||||
</form>
|
||||
|
||||
<h2>Render3 Tree Benchmark with functions</h2>
|
||||
<p>
|
||||
<button id="destroyDom">destroyDom</button>
|
||||
<button id="createDom">createDom</button>
|
||||
<button id="detectChanges">detectChanges</button>
|
||||
<button id="updateDomProfile">profile updateDom</button>
|
||||
<button id="createDomProfile">profile createDom</button>
|
||||
<button id="detectChangesProfile">profile detectChanges</button>
|
||||
</p>
|
||||
|
||||
<div>
|
||||
Change detection runs:<span id="numberOfChecks"></span>
|
||||
</div>
|
||||
<div>
|
||||
<tree id="root"></tree>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// TODO(mlaval): remove once we have a proper solution
|
||||
ngDevMode = false;
|
||||
</script>
|
||||
|
||||
<!--load location for ts_devserver-->
|
||||
<script src="/app_bundle.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,90 +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 {ɵrenderComponent as renderComponent, ɵRenderFlags, ɵɵadvance, ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵdefineComponent, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵstyleProp, ɵɵtext, ɵɵtextInterpolate1} from '@angular/core';
|
||||
|
||||
import {bindAction, profile} from '../../util';
|
||||
import {createDom, destroyDom, detectChanges} from '../render3/tree';
|
||||
import {emptyTree, TreeNode} from '../util';
|
||||
|
||||
function noop() {}
|
||||
|
||||
export class TreeFunction {
|
||||
data: TreeNode = emptyTree;
|
||||
|
||||
/** @nocollapse */
|
||||
static ɵfac = () => new TreeFunction;
|
||||
|
||||
/** @nocollapse */
|
||||
static ɵcmp = ɵɵdefineComponent({
|
||||
type: TreeFunction,
|
||||
selectors: [['tree']],
|
||||
decls: 5,
|
||||
vars: 2,
|
||||
template:
|
||||
function(rf: ɵRenderFlags, ctx: TreeFunction) {
|
||||
// bit of a hack
|
||||
TreeTpl(rf, ctx.data);
|
||||
},
|
||||
inputs: {data: 'data'}
|
||||
});
|
||||
}
|
||||
|
||||
const TreeFunctionCmpDef = TreeFunction.ɵcmp as {decls: number, vars: number};
|
||||
export function TreeTpl(rf: ɵRenderFlags, ctx: TreeNode) {
|
||||
if (rf & ɵRenderFlags.Create) {
|
||||
ɵɵelementStart(0, 'tree');
|
||||
{
|
||||
ɵɵelementStart(1, 'span');
|
||||
{ ɵɵtext(2); }
|
||||
ɵɵelementEnd();
|
||||
ɵɵcontainer(3);
|
||||
ɵɵcontainer(4);
|
||||
}
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
if (rf & ɵRenderFlags.Update) {
|
||||
ɵɵadvance(1);
|
||||
ɵɵstyleProp('background-color', ctx.depth % 2 ? '' : 'grey');
|
||||
ɵɵadvance(1);
|
||||
ɵɵtextInterpolate1(' ', ctx.value, ' ');
|
||||
ɵɵcontainerRefreshStart(3);
|
||||
{
|
||||
if (ctx.left != null) {
|
||||
let rf0 = ɵɵembeddedViewStart(0, 5, 2);
|
||||
{ TreeTpl(rf0, ctx.left); }
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
ɵɵcontainerRefreshStart(4);
|
||||
{
|
||||
if (ctx.right != null) {
|
||||
let rf0 = ɵɵembeddedViewStart(0, TreeFunctionCmpDef.decls, TreeFunctionCmpDef.vars);
|
||||
{ TreeTpl(rf0, ctx.right); }
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
}
|
||||
|
||||
let component: TreeFunction;
|
||||
if (typeof window !== 'undefined') {
|
||||
component = renderComponent(TreeFunction);
|
||||
bindAction('#createDom', () => createDom(component as any));
|
||||
bindAction('#destroyDom', () => destroyDom(component as any));
|
||||
bindAction('#detectChanges', () => detectChanges(component as any));
|
||||
bindAction(
|
||||
'#detectChangesProfile',
|
||||
profile(() => detectChanges(component as any), noop, 'detectChanges'));
|
||||
bindAction('#updateDomProfile', profile(() => createDom(component as any), noop, 'update'));
|
||||
bindAction(
|
||||
'#createDomProfile',
|
||||
profile(() => createDom(component as any), () => destroyDom(component as any), 'create'));
|
||||
}
|
|
@ -132,8 +132,6 @@ export class Identifiers {
|
|||
static stylePropInterpolateV:
|
||||
o.ExternalReference = {name: 'ɵɵstylePropInterpolateV', moduleName: CORE};
|
||||
|
||||
static containerCreate: o.ExternalReference = {name: 'ɵɵcontainer', moduleName: CORE};
|
||||
|
||||
static nextContext: o.ExternalReference = {name: 'ɵɵnextContext', moduleName: CORE};
|
||||
|
||||
static templateCreate: o.ExternalReference = {name: 'ɵɵtemplate', moduleName: CORE};
|
||||
|
|
|
@ -125,9 +125,6 @@ export {
|
|||
ɵɵclassProp,
|
||||
ɵɵComponentDefWithMeta,
|
||||
ɵɵcomponentHostSyntheticListener,
|
||||
ɵɵcontainer,
|
||||
ɵɵcontainerRefreshEnd,
|
||||
ɵɵcontainerRefreshStart,
|
||||
ɵɵcontentQuery,
|
||||
ɵɵCopyDefinitionFeature,
|
||||
ɵɵdefineComponent,
|
||||
|
@ -143,8 +140,6 @@ export {
|
|||
ɵɵelementContainerStart,
|
||||
ɵɵelementEnd,
|
||||
ɵɵelementStart,
|
||||
ɵɵembeddedViewEnd,
|
||||
ɵɵembeddedViewStart,
|
||||
ɵɵenableBindings,
|
||||
ɵɵFactoryDef,
|
||||
ɵɵgetCurrentView,
|
||||
|
|
|
@ -57,11 +57,6 @@ export function assertDataNext(lView: LView, index: number, arr?: any[]) {
|
|||
arr.length, index, `index ${index} expected to be at the end of arr (length ${arr.length})`);
|
||||
}
|
||||
|
||||
export function assertLContainerOrUndefined(value: any): asserts value is LContainer|undefined|
|
||||
null {
|
||||
value && assertEqual(isLContainer(value), true, 'Expecting LContainer or undefined or null');
|
||||
}
|
||||
|
||||
export function assertLContainer(value: any): asserts value is LContainer {
|
||||
assertDefined(value, 'LContainer must be defined');
|
||||
assertEqual(isLContainer(value), true, 'Expecting LContainer');
|
||||
|
|
|
@ -50,23 +50,15 @@ export {
|
|||
ɵɵclassProp,
|
||||
ɵɵcomponentHostSyntheticListener,
|
||||
|
||||
ɵɵcontainer,
|
||||
ɵɵcontainerRefreshEnd,
|
||||
ɵɵcontainerRefreshStart,
|
||||
|
||||
ɵɵdirectiveInject,
|
||||
|
||||
ɵɵelement,
|
||||
|
||||
ɵɵelementContainer,
|
||||
ɵɵelementContainerEnd,
|
||||
|
||||
ɵɵelementContainerStart,
|
||||
ɵɵelementEnd,
|
||||
|
||||
ɵɵelementStart,
|
||||
ɵɵembeddedViewEnd,
|
||||
|
||||
ɵɵembeddedViewStart,
|
||||
|
||||
ɵɵgetCurrentView,
|
||||
ɵɵhostProperty,
|
||||
|
|
|
@ -33,7 +33,6 @@ export * from './storage';
|
|||
export * from './di';
|
||||
export * from './element';
|
||||
export * from './element_container';
|
||||
export * from './embedded_view';
|
||||
export * from './get_current_view';
|
||||
export * from './listener';
|
||||
export * from './namespace';
|
||||
|
|
|
@ -5,46 +5,20 @@
|
|||
* 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 {assertDataInRange, assertEqual} from '../../util/assert';
|
||||
import {assertFirstCreatePass, assertHasParent} from '../assert';
|
||||
import {assertFirstCreatePass} from '../assert';
|
||||
import {attachPatchData} from '../context_discovery';
|
||||
import {executeCheckHooks, executeInitAndCheckHooks, incrementInitPhaseFlags, registerPostOrderHooks} from '../hooks';
|
||||
import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer} from '../interfaces/container';
|
||||
import {registerPostOrderHooks} from '../hooks';
|
||||
import {ComponentTemplate} from '../interfaces/definition';
|
||||
import {LocalRefExtractor, TAttributes, TContainerNode, TNode, TNodeType, TViewNode} from '../interfaces/node';
|
||||
import {LocalRefExtractor, TAttributes, TContainerNode, TNodeType, TViewNode} from '../interfaces/node';
|
||||
import {isDirectiveHost} from '../interfaces/type_checks';
|
||||
import {FLAGS, HEADER_OFFSET, InitPhaseState, LView, LViewFlags, RENDERER, T_HOST, TView, TViewType} from '../interfaces/view';
|
||||
import {assertNodeType} from '../node_assert';
|
||||
import {appendChild, removeView} from '../node_manipulation';
|
||||
import {getBindingIndex, getCheckNoChangesMode, getIsParent, getLView, getPreviousOrParentTNode, getTView, setIsNotParent, setPreviousOrParentTNode} from '../state';
|
||||
import {getConstant, getLContainerActiveIndex, load} from '../util/view_utils';
|
||||
import {HEADER_OFFSET, LView, RENDERER, T_HOST, TView, TViewType} from '../interfaces/view';
|
||||
import {appendChild} from '../node_manipulation';
|
||||
import {getLView, getTView, setPreviousOrParentTNode} from '../state';
|
||||
import {getConstant} from '../util/view_utils';
|
||||
|
||||
import {addToViewTree, createDirectivesInstances, createLContainer, createTNode, createTView, getOrCreateTNode, resolveDirectives, saveResolvedLocalsInData} from './shared';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates an LContainer for inline views, e.g.
|
||||
*
|
||||
* % if (showing) {
|
||||
* <div></div>
|
||||
* % }
|
||||
*
|
||||
* @param index The index of the container in the data array
|
||||
*
|
||||
* @codeGenApi
|
||||
*/
|
||||
export function ɵɵcontainer(index: number): void {
|
||||
const lView = getLView();
|
||||
const tView = getTView();
|
||||
const tNode = containerInternal(tView, lView, index, null, null);
|
||||
|
||||
if (tView.firstCreatePass) {
|
||||
tNode.tViews = [];
|
||||
}
|
||||
setIsNotParent();
|
||||
}
|
||||
|
||||
function templateFirstCreatePass(
|
||||
index: number, tView: TView, lView: LView, templateFn: ComponentTemplate<any>|null,
|
||||
decls: number, vars: number, tagName?: string|null, attrsIndex?: number|null,
|
||||
|
@ -121,97 +95,4 @@ export function ɵɵtemplate(
|
|||
if (localRefsIndex != null) {
|
||||
saveResolvedLocalsInData(lView, tNode, localRefExtractor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a container up to receive views.
|
||||
*
|
||||
* @param index The index of the container in the data array
|
||||
*
|
||||
* @codeGenApi
|
||||
*/
|
||||
export function ɵɵcontainerRefreshStart(index: number): void {
|
||||
const lView = getLView();
|
||||
const tView = getTView();
|
||||
let previousOrParentTNode = load(tView.data, index) as TNode;
|
||||
ngDevMode && assertNodeType(previousOrParentTNode, TNodeType.Container);
|
||||
setPreviousOrParentTNode(previousOrParentTNode, true);
|
||||
|
||||
lView[index + HEADER_OFFSET][ACTIVE_INDEX] = 0;
|
||||
|
||||
// We need to execute init hooks here so ngOnInit hooks are called in top level views
|
||||
// before they are called in embedded views (for backwards compatibility).
|
||||
if (!getCheckNoChangesMode()) {
|
||||
const hooksInitPhaseCompleted =
|
||||
(lView[FLAGS] & LViewFlags.InitPhaseStateMask) === InitPhaseState.InitPhaseCompleted;
|
||||
if (hooksInitPhaseCompleted) {
|
||||
const preOrderCheckHooks = tView.preOrderCheckHooks;
|
||||
if (preOrderCheckHooks !== null) {
|
||||
executeCheckHooks(lView, preOrderCheckHooks, null);
|
||||
}
|
||||
} else {
|
||||
const preOrderHooks = tView.preOrderHooks;
|
||||
if (preOrderHooks !== null) {
|
||||
executeInitAndCheckHooks(lView, preOrderHooks, InitPhaseState.OnInitHooksToBeRun, null);
|
||||
}
|
||||
incrementInitPhaseFlags(lView, InitPhaseState.OnInitHooksToBeRun);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the end of the LContainer.
|
||||
*
|
||||
* Marking the end of LContainer is the time when to child views get inserted or removed.
|
||||
*
|
||||
* @codeGenApi
|
||||
*/
|
||||
export function ɵɵcontainerRefreshEnd(): void {
|
||||
let previousOrParentTNode = getPreviousOrParentTNode();
|
||||
if (getIsParent()) {
|
||||
setIsNotParent();
|
||||
} else {
|
||||
ngDevMode && assertNodeType(previousOrParentTNode, TNodeType.View);
|
||||
ngDevMode && assertHasParent(previousOrParentTNode);
|
||||
previousOrParentTNode = previousOrParentTNode.parent!;
|
||||
setPreviousOrParentTNode(previousOrParentTNode, false);
|
||||
}
|
||||
|
||||
ngDevMode && assertNodeType(previousOrParentTNode, TNodeType.Container);
|
||||
|
||||
const lContainer: LContainer = getLView()[previousOrParentTNode.index];
|
||||
const nextIndex = getLContainerActiveIndex(lContainer);
|
||||
|
||||
// remove extra views at the end of the container
|
||||
while (nextIndex < lContainer.length - CONTAINER_HEADER_OFFSET) {
|
||||
removeView(lContainer, nextIndex);
|
||||
}
|
||||
}
|
||||
|
||||
function containerInternal(
|
||||
tView: TView, lView: LView, nodeIndex: number, tagName: string|null,
|
||||
attrs: TAttributes|null): TContainerNode {
|
||||
ngDevMode &&
|
||||
assertEqual(
|
||||
getBindingIndex(), tView.bindingStartIndex,
|
||||
'container nodes should be created before any bindings');
|
||||
|
||||
const adjustedIndex = nodeIndex + HEADER_OFFSET;
|
||||
ngDevMode && assertDataInRange(lView, nodeIndex + HEADER_OFFSET);
|
||||
ngDevMode && ngDevMode.rendererCreateComment++;
|
||||
const comment = lView[adjustedIndex] =
|
||||
lView[RENDERER].createComment(ngDevMode ? 'container' : '');
|
||||
const tNode =
|
||||
getOrCreateTNode(tView, lView[T_HOST], nodeIndex, TNodeType.Container, tagName, attrs);
|
||||
const lContainer = lView[adjustedIndex] = createLContainer(comment, lView, comment, tNode);
|
||||
|
||||
appendChild(tView, lView, comment, tNode);
|
||||
attachPatchData(comment, lView);
|
||||
|
||||
// Containers are added to the current view tree instead of their embedded views
|
||||
// because views can be removed and re-inserted.
|
||||
addToViewTree(lView, lContainer);
|
||||
|
||||
ngDevMode && assertNodeType(getPreviousOrParentTNode(), TNodeType.Container);
|
||||
return tNode;
|
||||
}
|
|
@ -1,145 +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 {assertDefined, assertEqual} from '../../util/assert';
|
||||
import {assertLContainerOrUndefined} from '../assert';
|
||||
import {ACTIVE_INDEX, ActiveIndexFlag, CONTAINER_HEADER_OFFSET, LContainer} from '../interfaces/container';
|
||||
import {RenderFlags} from '../interfaces/definition';
|
||||
import {TContainerNode, TNodeType} from '../interfaces/node';
|
||||
import {CONTEXT, LView, LViewFlags, PARENT, T_HOST, TVIEW, TView, TViewType} from '../interfaces/view';
|
||||
import {assertNodeType} from '../node_assert';
|
||||
import {insertView, removeView} from '../node_manipulation';
|
||||
import {enterView, getIsParent, getLView, getPreviousOrParentTNode, getTView, leaveView, setIsParent, setPreviousOrParentTNode} from '../state';
|
||||
import {getLContainerActiveIndex, isCreationMode} from '../util/view_utils';
|
||||
|
||||
import {assignTViewNodeToLView, createLView, createTView, refreshView, renderView} from './shared';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Marks the start of an embedded view.
|
||||
*
|
||||
* @param viewBlockId The ID of this view
|
||||
* @return boolean Whether or not this view is in creation mode
|
||||
*
|
||||
* @codeGenApi
|
||||
*/
|
||||
export function ɵɵembeddedViewStart(viewBlockId: number, decls: number, vars: number): RenderFlags {
|
||||
const lView = getLView();
|
||||
const previousOrParentTNode = getPreviousOrParentTNode();
|
||||
// The previous node can be a view node if we are processing an inline for loop
|
||||
const containerTNode = previousOrParentTNode.type === TNodeType.View ?
|
||||
previousOrParentTNode.parent! :
|
||||
previousOrParentTNode;
|
||||
const lContainer = lView[containerTNode.index] as LContainer;
|
||||
|
||||
ngDevMode && assertNodeType(containerTNode, TNodeType.Container);
|
||||
let viewToRender = scanForView(lContainer, getLContainerActiveIndex(lContainer), viewBlockId);
|
||||
|
||||
if (viewToRender) {
|
||||
setIsParent();
|
||||
enterView(viewToRender, viewToRender[TVIEW].node);
|
||||
} else {
|
||||
// When we create a new LView, we always reset the state of the instructions.
|
||||
viewToRender = createLView(
|
||||
lView, getOrCreateEmbeddedTView(viewBlockId, decls, vars, containerTNode as TContainerNode),
|
||||
null, LViewFlags.CheckAlways, null, null);
|
||||
|
||||
const tParentNode = getIsParent() ? previousOrParentTNode :
|
||||
previousOrParentTNode && previousOrParentTNode.parent;
|
||||
assignTViewNodeToLView(viewToRender[TVIEW], tParentNode, viewBlockId, viewToRender);
|
||||
enterView(viewToRender, viewToRender[TVIEW].node);
|
||||
}
|
||||
if (lContainer) {
|
||||
if (isCreationMode(viewToRender)) {
|
||||
// it is a new view, insert it into collection of views for a given container
|
||||
insertView(
|
||||
viewToRender[TVIEW], viewToRender, lContainer, getLContainerActiveIndex(lContainer));
|
||||
}
|
||||
lContainer[ACTIVE_INDEX] += ActiveIndexFlag.INCREMENT;
|
||||
}
|
||||
return isCreationMode(viewToRender) ? RenderFlags.Create | RenderFlags.Update :
|
||||
RenderFlags.Update;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the TView (e.g. static data) for the active embedded view.
|
||||
*
|
||||
* Each embedded view block must create or retrieve its own TView. Otherwise, the embedded view's
|
||||
* static data for a particular node would overwrite the static data for a node in the view above
|
||||
* it with the same index (since it's in the same template).
|
||||
*
|
||||
* @param viewIndex The index of the TView in TNode.tViews
|
||||
* @param decls The number of nodes, local refs, and pipes in this template
|
||||
* @param vars The number of bindings and pure function bindings in this template
|
||||
* @param container The parent container in which to look for the view's static data
|
||||
* @returns TView
|
||||
*/
|
||||
function getOrCreateEmbeddedTView(
|
||||
viewIndex: number, decls: number, vars: number, parent: TContainerNode): TView {
|
||||
const tView = getLView()[TVIEW];
|
||||
ngDevMode && assertNodeType(parent, TNodeType.Container);
|
||||
const containerTViews = parent.tViews as TView[];
|
||||
ngDevMode && assertDefined(containerTViews, 'TView expected');
|
||||
ngDevMode && assertEqual(Array.isArray(containerTViews), true, 'TViews should be in an array');
|
||||
if (viewIndex >= containerTViews.length || containerTViews[viewIndex] == null) {
|
||||
containerTViews[viewIndex] = createTView(
|
||||
TViewType.Embedded, viewIndex, null, decls, vars, tView.directiveRegistry,
|
||||
tView.pipeRegistry, null, null, tView.consts);
|
||||
}
|
||||
return containerTViews[viewIndex];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Looks for a view with a given view block id inside a provided LContainer.
|
||||
* Removes views that need to be deleted in the process.
|
||||
*
|
||||
* @param lContainer to search for views
|
||||
* @param startIdx starting index in the views array to search from
|
||||
* @param viewBlockId exact view block id to look for
|
||||
*/
|
||||
function scanForView(lContainer: LContainer, startIdx: number, viewBlockId: number): LView|null {
|
||||
for (let i = startIdx + CONTAINER_HEADER_OFFSET; i < lContainer.length; i++) {
|
||||
const viewAtPositionId = lContainer[i][TVIEW].id;
|
||||
if (viewAtPositionId === viewBlockId) {
|
||||
return lContainer[i];
|
||||
} else if (viewAtPositionId < viewBlockId) {
|
||||
// found a view that should not be at this position - remove
|
||||
removeView(lContainer, i - CONTAINER_HEADER_OFFSET);
|
||||
} else {
|
||||
// found a view with id greater than the one we are searching for
|
||||
// which means that required view doesn't exist and can't be found at
|
||||
// later positions in the views array - stop the searchdef.cont here
|
||||
break;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the end of an embedded view.
|
||||
*
|
||||
* @codeGenApi
|
||||
*/
|
||||
export function ɵɵembeddedViewEnd(): void {
|
||||
const lView = getLView();
|
||||
const tView = getTView();
|
||||
const viewHost = lView[T_HOST];
|
||||
const context = lView[CONTEXT];
|
||||
|
||||
if (isCreationMode(lView)) {
|
||||
renderView(tView, lView, context); // creation mode pass
|
||||
}
|
||||
refreshView(tView, lView, tView.template, context); // update mode pass
|
||||
|
||||
const lContainer = lView[PARENT] as LContainer;
|
||||
ngDevMode && assertLContainerOrUndefined(lContainer);
|
||||
leaveView();
|
||||
setPreviousOrParentTNode(viewHost!, false);
|
||||
}
|
|
@ -20,6 +20,7 @@ import {HOST, LView, NEXT, PARENT, T_HOST, TRANSPLANTED_VIEWS_TO_REFRESH} from '
|
|||
* `LContainer`.
|
||||
*/
|
||||
export const TYPE = 1;
|
||||
|
||||
/**
|
||||
* Below are constants for LContainer indices to help us look up LContainer members
|
||||
* without having to remember the specific indices.
|
||||
|
@ -48,9 +49,7 @@ export const CONTAINER_HEADER_OFFSET = 10;
|
|||
|
||||
|
||||
/**
|
||||
* Used to track:
|
||||
* - Inline embedded views (see: `ɵɵembeddedViewStart`)
|
||||
* - Transplanted `LView`s (see: `LView[DECLARATION_COMPONENT_VIEW])`
|
||||
* Used to track Transplanted `LView`s (see: `LView[DECLARATION_COMPONENT_VIEW])`
|
||||
*/
|
||||
export const enum ActiveIndexFlag {
|
||||
/**
|
||||
|
@ -72,13 +71,6 @@ export const enum ActiveIndexFlag {
|
|||
* Number of bits to shift inline embedded views counter to make space for other flags.
|
||||
*/
|
||||
SHIFT = 1,
|
||||
|
||||
|
||||
/**
|
||||
* When incrementing the active index for inline embedded views, the amount to increment to leave
|
||||
* space for other flags.
|
||||
*/
|
||||
INCREMENT = 1 << SHIFT,
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -111,10 +103,6 @@ export interface LContainer extends Array<any> {
|
|||
* it is set to null to identify this scenario, as indices are "absolute" in that case,
|
||||
* i.e. provided directly by the user of the ViewContainerRef API.
|
||||
*
|
||||
* This is used by `ɵɵembeddedViewStart` to track which `LView` is currently active.
|
||||
* Because `ɵɵembeddedViewStart` is not generated by the compiler this feature is essentially
|
||||
* unused.
|
||||
*
|
||||
* The lowest bit signals that this `LContainer` has transplanted views which need to be change
|
||||
* detected as part of the declaration CD. (See `LView[DECLARATION_COMPONENT_VIEW]`)
|
||||
*/
|
||||
|
|
|
@ -49,10 +49,7 @@ export const angularCoreEnv: {[name: string]: Function} =
|
|||
'ɵɵProvidersFeature': r3.ɵɵProvidersFeature,
|
||||
'ɵɵCopyDefinitionFeature': r3.ɵɵCopyDefinitionFeature,
|
||||
'ɵɵInheritDefinitionFeature': r3.ɵɵInheritDefinitionFeature,
|
||||
'ɵɵcontainer': r3.ɵɵcontainer,
|
||||
'ɵɵnextContext': r3.ɵɵnextContext,
|
||||
'ɵɵcontainerRefreshStart': r3.ɵɵcontainerRefreshStart,
|
||||
'ɵɵcontainerRefreshEnd': r3.ɵɵcontainerRefreshEnd,
|
||||
'ɵɵnamespaceHTML': r3.ɵɵnamespaceHTML,
|
||||
'ɵɵnamespaceMathML': r3.ɵɵnamespaceMathML,
|
||||
'ɵɵnamespaceSVG': r3.ɵɵnamespaceSVG,
|
||||
|
@ -152,8 +149,6 @@ export const angularCoreEnv: {[name: string]: Function} =
|
|||
'ɵɵtextInterpolate7': r3.ɵɵtextInterpolate7,
|
||||
'ɵɵtextInterpolate8': r3.ɵɵtextInterpolate8,
|
||||
'ɵɵtextInterpolateV': r3.ɵɵtextInterpolateV,
|
||||
'ɵɵembeddedViewStart': r3.ɵɵembeddedViewStart,
|
||||
'ɵɵembeddedViewEnd': r3.ɵɵembeddedViewEnd,
|
||||
'ɵɵi18n': r3.ɵɵi18n,
|
||||
'ɵɵi18nAttributes': r3.ɵɵi18nAttributes,
|
||||
'ɵɵi18nExp': r3.ɵɵi18nExp,
|
||||
|
|
|
@ -289,4 +289,111 @@ describe('event listeners', () => {
|
|||
expect(log).toEqual(['dirA.click', 'dirB.click', 'component.click']);
|
||||
});
|
||||
});
|
||||
|
||||
it('should destroy listeners when view is removed', () => {
|
||||
@Component({
|
||||
selector: 'my-comp',
|
||||
template: `
|
||||
<button *ngIf="visible" (click)="count()">Click me!</button>
|
||||
`,
|
||||
})
|
||||
class MyComp {
|
||||
visible = true;
|
||||
counter = 0;
|
||||
count() {
|
||||
this.counter++;
|
||||
}
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [MyComp]});
|
||||
const fixture = TestBed.createComponent(MyComp);
|
||||
fixture.detectChanges();
|
||||
|
||||
const comp = fixture.componentInstance;
|
||||
|
||||
const button = fixture.nativeElement.querySelector('button');
|
||||
button.click();
|
||||
expect(comp.counter).toBe(1);
|
||||
|
||||
comp.visible = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
button.click();
|
||||
expect(comp.counter).toBe(1);
|
||||
});
|
||||
|
||||
it('should destroy listeners when views generated using *ngFor are removed', () => {
|
||||
let counter = 0;
|
||||
@Component({
|
||||
selector: 'my-comp',
|
||||
template: `
|
||||
<button *ngFor="let button of buttons" (click)="count()">Click me!</button>
|
||||
`,
|
||||
})
|
||||
class MyComp {
|
||||
buttons = [1, 2];
|
||||
count() {
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [MyComp]});
|
||||
const fixture = TestBed.createComponent(MyComp);
|
||||
fixture.detectChanges();
|
||||
|
||||
const comp = fixture.componentInstance;
|
||||
|
||||
const buttons = fixture.nativeElement.querySelectorAll('button');
|
||||
buttons[0].click();
|
||||
buttons[1].click();
|
||||
expect(counter).toBe(2);
|
||||
|
||||
comp.buttons = [];
|
||||
fixture.detectChanges();
|
||||
|
||||
buttons[0].click();
|
||||
buttons[1].click();
|
||||
expect(counter).toBe(2);
|
||||
});
|
||||
|
||||
it('should destroy listeners when nested view is removed', () => {
|
||||
@Component({
|
||||
selector: 'my-comp',
|
||||
template: `
|
||||
<ng-container *ngIf="isSectionVisible">
|
||||
Click to submit a form:
|
||||
<button *ngIf="isButtonVisible" (click)="count()">Click me!</button>
|
||||
</ng-container>
|
||||
`,
|
||||
})
|
||||
class MyComp {
|
||||
isSectionVisible = true;
|
||||
isButtonVisible = true;
|
||||
counter = 0;
|
||||
count() {
|
||||
this.counter++;
|
||||
}
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [MyComp]});
|
||||
const fixture = TestBed.createComponent(MyComp);
|
||||
fixture.detectChanges();
|
||||
|
||||
const comp = fixture.componentInstance;
|
||||
const button = fixture.nativeElement.querySelector('button');
|
||||
button.click();
|
||||
expect(comp.counter).toBe(1);
|
||||
|
||||
comp.isButtonVisible = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
button.click();
|
||||
expect(comp.counter).toBe(1);
|
||||
|
||||
comp.isSectionVisible = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
button.click();
|
||||
expect(comp.counter).toBe(1);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {Component, Directive, forwardRef, Inject, Injectable, InjectionToken, Injector, NgModule, Optional} from '@angular/core';
|
||||
import {async, inject, TestBed} from '@angular/core/testing';
|
||||
import {By} from '@angular/platform-browser';
|
||||
|
@ -506,6 +507,47 @@ describe('providers', () => {
|
|||
|
||||
expect(destroyCalls).toBe(0);
|
||||
});
|
||||
|
||||
it('should call ngOnDestroy if host component is destroyed', () => {
|
||||
const logs: string[] = [];
|
||||
|
||||
@Injectable()
|
||||
class InjectableWithDestroyHookToken {
|
||||
ngOnDestroy() {
|
||||
logs.push('OnDestroy Token');
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'comp-with-provider',
|
||||
template: '',
|
||||
providers: [InjectableWithDestroyHookToken],
|
||||
})
|
||||
class CompWithProvider {
|
||||
constructor(token: InjectableWithDestroyHookToken) {}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app',
|
||||
template: '<comp-with-provider *ngIf="condition"></comp-with-provider>',
|
||||
})
|
||||
class App {
|
||||
condition = true;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [App, CompWithProvider],
|
||||
imports: [CommonModule],
|
||||
});
|
||||
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.componentInstance.condition = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(logs).toEqual(['OnDestroy Token']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('components and directives', () => {
|
||||
|
@ -669,4 +711,69 @@ describe('providers', () => {
|
|||
expect(injector.get(MyService).value).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('view providers', () => {
|
||||
it('should have access to viewProviders within the same component', () => {
|
||||
@Component({
|
||||
selector: 'comp',
|
||||
template: '{{s}}-{{n}}',
|
||||
providers: [
|
||||
{provide: Number, useValue: 1, multi: true},
|
||||
],
|
||||
viewProviders: [
|
||||
{provide: String, useValue: 'bar'},
|
||||
{provide: Number, useValue: 2, multi: true},
|
||||
]
|
||||
})
|
||||
class Comp {
|
||||
constructor(private s: String, private n: Number) {}
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [Comp]});
|
||||
|
||||
const fixture = TestBed.createComponent(Comp);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.nativeElement.textContent).toBe('bar-1,2');
|
||||
});
|
||||
|
||||
it('should have access to viewProviders of the host component', () => {
|
||||
@Component({
|
||||
selector: 'repeated',
|
||||
template: '[{{s}}-{{n}}]',
|
||||
})
|
||||
class Repeated {
|
||||
constructor(private s: String, private n: Number) {}
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<div>
|
||||
<ng-container *ngFor="let item of items">
|
||||
<repeated></repeated>
|
||||
</ng-container>
|
||||
</div>
|
||||
`,
|
||||
providers: [
|
||||
{provide: Number, useValue: 1, multi: true},
|
||||
],
|
||||
viewProviders: [
|
||||
{provide: String, useValue: 'foo'},
|
||||
{provide: Number, useValue: 2, multi: true},
|
||||
],
|
||||
})
|
||||
class ComponentWithProviders {
|
||||
items = [1, 2, 3];
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ComponentWithProviders, Repeated],
|
||||
imports: [CommonModule],
|
||||
});
|
||||
|
||||
const fixture = TestBed.createComponent(ComponentWithProviders);
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement.textContent).toBe('[foo-1,2][foo-1,2][foo-1,2]');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {AfterViewInit, Component, ContentChild, ContentChildren, Directive, ElementRef, forwardRef, Input, QueryList, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef, ViewRef} from '@angular/core';
|
||||
import {AfterViewInit, Component, ContentChild, ContentChildren, Directive, ElementRef, EventEmitter, forwardRef, Input, QueryList, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef, ViewRef} from '@angular/core';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {By} from '@angular/platform-browser';
|
||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
|
@ -300,6 +300,51 @@ describe('query logic', () => {
|
|||
fixture.detectChanges();
|
||||
expect(fixture.componentInstance.viewChildAvailableInAfterViewInit).toBe(true);
|
||||
});
|
||||
|
||||
it('should destroy QueryList when the containing view is destroyed', () => {
|
||||
let queryInstance: QueryList<any>;
|
||||
|
||||
@Component({
|
||||
selector: 'comp-with-view-query',
|
||||
template: '<div #foo>Content</div>',
|
||||
})
|
||||
class ComponentWithViewQuery {
|
||||
@ViewChildren('foo')
|
||||
set foo(value: any) {
|
||||
queryInstance = value;
|
||||
}
|
||||
get foo() {
|
||||
return queryInstance;
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'root',
|
||||
template: `
|
||||
<ng-container *ngIf="condition">
|
||||
<comp-with-view-query></comp-with-view-query>
|
||||
</ng-container>
|
||||
`
|
||||
})
|
||||
class Root {
|
||||
condition = true;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [Root, ComponentWithViewQuery],
|
||||
imports: [CommonModule],
|
||||
});
|
||||
|
||||
const fixture = TestBed.createComponent(Root);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect((queryInstance!.changes as EventEmitter<any>).closed).toBeFalsy();
|
||||
|
||||
fixture.componentInstance.condition = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect((queryInstance!.changes as EventEmitter<any>).closed).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('content queries', () => {
|
||||
|
@ -676,6 +721,68 @@ describe('query logic', () => {
|
|||
fixture.detectChanges();
|
||||
expect(queryList.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should support content queries for directives within repeated embedded views', () => {
|
||||
const withContentInstances: DirWithContentQuery[] = [];
|
||||
|
||||
@Directive({
|
||||
selector: '[with-content]',
|
||||
})
|
||||
class DirWithContentQuery {
|
||||
constructor() {
|
||||
withContentInstances.push(this);
|
||||
}
|
||||
|
||||
@ContentChildren('foo', {descendants: false}) foos!: QueryList<ElementRef>;
|
||||
|
||||
contentInitQuerySnapshot = 0;
|
||||
contentCheckedQuerySnapshot = 0;
|
||||
|
||||
ngAfterContentInit() {
|
||||
this.contentInitQuerySnapshot = this.foos ? this.foos.length : 0;
|
||||
}
|
||||
|
||||
ngAfterContentChecked() {
|
||||
this.contentCheckedQuerySnapshot = this.foos ? this.foos.length : 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'comp',
|
||||
template: `
|
||||
<ng-container *ngFor="let item of items">
|
||||
<div with-content>
|
||||
<span #foo></span>
|
||||
</div>
|
||||
</ng-container>
|
||||
`,
|
||||
})
|
||||
class Root {
|
||||
items = [1, 2, 3];
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [Root, DirWithContentQuery],
|
||||
imports: [CommonModule],
|
||||
});
|
||||
const fixture = TestBed.createComponent(Root);
|
||||
fixture.detectChanges();
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
expect(withContentInstances[i].foos.length)
|
||||
.toBe(1, `Expected content query to match <span #foo>.`);
|
||||
|
||||
expect(withContentInstances[i].contentInitQuerySnapshot)
|
||||
.toBe(
|
||||
1,
|
||||
`Expected content query results to be available when ngAfterContentInit was called.`);
|
||||
|
||||
expect(withContentInstances[i].contentCheckedQuerySnapshot)
|
||||
.toBe(
|
||||
1,
|
||||
`Expected content query results to be available when ngAfterContentChecked was called.`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Some root components may have ContentChildren queries if they are also
|
||||
|
|
|
@ -11,6 +11,7 @@ import {ɵAnimationEngine, ɵNoopAnimationStyleNormalizer} from '@angular/animat
|
|||
import {MockAnimationDriver, MockAnimationPlayer} from '@angular/animations/browser/testing';
|
||||
import {DOCUMENT} from '@angular/common';
|
||||
import {Component, DoCheck, NgZone, RendererFactory2, RendererType2} from '@angular/core';
|
||||
import {ngDevModeResetPerfCounters} from '@angular/core/src/util/ng_dev_mode';
|
||||
import {NoopNgZone} from '@angular/core/src/zone/ng_zone';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {EventManager, ɵDomSharedStylesHost} from '@angular/platform-browser';
|
||||
|
@ -252,4 +253,80 @@ describe('custom renderer', () => {
|
|||
fixture.detectChanges();
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
onlyInIvy('access global ngDevMode').describe('Renderer2 destruction hooks', () => {
|
||||
@Component({
|
||||
selector: 'some-component',
|
||||
template: `
|
||||
<span *ngIf="isContentVisible">A</span>
|
||||
<span *ngIf="isContentVisible">B</span>
|
||||
<span *ngIf="isContentVisible">C</span>
|
||||
`,
|
||||
})
|
||||
class SimpleApp {
|
||||
isContentVisible = true;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'basic-comp',
|
||||
template: 'comp(<ng-content></ng-content>)',
|
||||
})
|
||||
class BasicComponent {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'some-component',
|
||||
template: `
|
||||
<basic-comp *ngIf="isContentVisible">A</basic-comp>
|
||||
<basic-comp *ngIf="isContentVisible">B</basic-comp>
|
||||
<basic-comp *ngIf="isContentVisible">C</basic-comp>
|
||||
`,
|
||||
})
|
||||
class AppWithComponents {
|
||||
isContentVisible = true;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
// Tests below depend on perf counters when running with Ivy. In order to have
|
||||
// clean perf counters at the beginning of a test, we reset those here.
|
||||
ngDevModeResetPerfCounters();
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [SimpleApp, AppWithComponents, BasicComponent],
|
||||
providers: [{
|
||||
provide: RendererFactory2,
|
||||
useFactory: (document: any) => getRendererFactory2(document),
|
||||
deps: [DOCUMENT]
|
||||
}]
|
||||
});
|
||||
});
|
||||
|
||||
it('should call renderer.destroyNode for each node destroyed', () => {
|
||||
const fixture = TestBed.createComponent(SimpleApp);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.nativeElement.textContent).toBe('ABC');
|
||||
|
||||
fixture.componentInstance.isContentVisible = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.nativeElement.textContent).toBe('');
|
||||
expect(ngDevMode!.rendererDestroy).toBe(0);
|
||||
expect(ngDevMode!.rendererDestroyNode).toBe(3);
|
||||
});
|
||||
|
||||
it('should call renderer.destroy for each component destroyed', () => {
|
||||
const fixture = TestBed.createComponent(AppWithComponents);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.nativeElement.textContent).toBe('comp(A)comp(B)comp(C)');
|
||||
|
||||
fixture.componentInstance.isContentVisible = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.nativeElement.textContent).toBe('');
|
||||
expect(ngDevMode!.rendererDestroy).toBe(3);
|
||||
expect(ngDevMode!.rendererDestroyNode).toBe(3);
|
||||
});
|
||||
});
|
|
@ -1,79 +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 {ɵɵdefineComponent} from '../../src/render3/index';
|
||||
import {ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵtext} from '../../src/render3/instructions/all';
|
||||
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
||||
|
||||
import {document, renderComponent} from './render_util';
|
||||
|
||||
describe('iv perf test', () => {
|
||||
const count = 100000;
|
||||
const noOfIterations = 10;
|
||||
|
||||
describe('render', () => {
|
||||
for (let iteration = 0; iteration < noOfIterations; iteration++) {
|
||||
it(`${iteration}. create ${count} divs in DOM`, () => {
|
||||
const start = new Date().getTime();
|
||||
const container = document.createElement('div');
|
||||
for (let i = 0; i < count; i++) {
|
||||
const div = document.createElement('div');
|
||||
div.appendChild(document.createTextNode('-'));
|
||||
container.appendChild(div);
|
||||
}
|
||||
const end = new Date().getTime();
|
||||
log(`${count} DIVs in DOM`, (end - start) / count);
|
||||
});
|
||||
|
||||
it(`${iteration}. create ${count} divs in Render3`, () => {
|
||||
class Component {
|
||||
static ɵfac = () => new Component;
|
||||
static ɵcmp = ɵɵdefineComponent({
|
||||
type: Component,
|
||||
selectors: [['div']],
|
||||
decls: 1,
|
||||
vars: 0,
|
||||
template:
|
||||
function Template(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵcontainer(0);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(0);
|
||||
{
|
||||
for (let i = 0; i < count; i++) {
|
||||
let rf0 = ɵɵembeddedViewStart(0, 2, 0);
|
||||
{
|
||||
if (rf0 & RenderFlags.Create) {
|
||||
ɵɵelementStart(0, 'div');
|
||||
ɵɵtext(1, '-');
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const start = new Date().getTime();
|
||||
renderComponent(Component);
|
||||
const end = new Date().getTime();
|
||||
log(`${count} DIVs in Render3`, (end - start) / count);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function log(text: string, duration: number) {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.log(text, duration * 1000, 'ns');
|
||||
}
|
|
@ -8,12 +8,12 @@
|
|||
|
||||
import {ViewEncapsulation, ɵɵdefineInjectable, ɵɵdefineInjector} from '../../src/core';
|
||||
import {createInjector} from '../../src/di/r3_injector';
|
||||
import {AttributeMarker, ComponentFactory, getRenderedText, LifecycleHooksFeature, markDirty, ɵɵadvance, ɵɵdefineComponent, ɵɵdirectiveInject, ɵɵproperty, ɵɵselect, ɵɵtemplate} from '../../src/render3/index';
|
||||
import {tick, ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵnextContext, ɵɵtext, ɵɵtextInterpolate} from '../../src/render3/instructions/all';
|
||||
import {ComponentDef, RenderFlags} from '../../src/render3/interfaces/definition';
|
||||
import {AttributeMarker, markDirty, ɵɵadvance, ɵɵdefineComponent, ɵɵdirectiveInject, ɵɵproperty, ɵɵtemplate} from '../../src/render3/index';
|
||||
import {ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵtext, ɵɵtextInterpolate} from '../../src/render3/instructions/all';
|
||||
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
||||
|
||||
import {NgIf} from './common_with_def';
|
||||
import {ComponentFixture, containerEl, createComponent, MockRendererFactory, renderComponent, renderToHtml, requestAnimationFrame, toHtml} from './render_util';
|
||||
import {ComponentFixture, containerEl, createComponent, MockRendererFactory, renderComponent, requestAnimationFrame, toHtml} from './render_util';
|
||||
|
||||
describe('component', () => {
|
||||
class CounterComponent {
|
||||
|
@ -219,349 +219,4 @@ it('should not invoke renderer destroy method for embedded views', () => {
|
|||
// we should never see `destroy` method being called
|
||||
// in case child views are created/removed
|
||||
expect(destroySpy.calls.count()).toBe(0);
|
||||
});
|
||||
|
||||
describe('component with a container', () => {
|
||||
function showItems(rf: RenderFlags, ctx: {items: string[]}) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵcontainer(0);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(0);
|
||||
{
|
||||
for (const item of ctx.items) {
|
||||
const rf0 = ɵɵembeddedViewStart(0, 1, 1);
|
||||
{
|
||||
if (rf0 & RenderFlags.Create) {
|
||||
ɵɵtext(0);
|
||||
}
|
||||
if (rf0 & RenderFlags.Update) {
|
||||
ɵɵselect(0);
|
||||
ɵɵtextInterpolate(item);
|
||||
}
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
}
|
||||
|
||||
class WrapperComponent {
|
||||
// TODO(issue/24571): remove '!'.
|
||||
items!: string[];
|
||||
static ɵfac = () => new WrapperComponent;
|
||||
static ɵcmp = ɵɵdefineComponent({
|
||||
type: WrapperComponent,
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
selectors: [['wrapper']],
|
||||
decls: 1,
|
||||
vars: 0,
|
||||
template:
|
||||
function ChildComponentTemplate(rf: RenderFlags, ctx: {items: string[]}) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵcontainer(0);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(0);
|
||||
{
|
||||
const rf0 = ɵɵembeddedViewStart(0, 1, 0);
|
||||
{ showItems(rf0, {items: ctx.items}); }
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
},
|
||||
inputs: {items: 'items'}
|
||||
});
|
||||
}
|
||||
|
||||
function template(rf: RenderFlags, ctx: {items: string[]}) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵelement(0, 'wrapper');
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵproperty('items', ctx.items);
|
||||
}
|
||||
}
|
||||
|
||||
const defs = [WrapperComponent];
|
||||
|
||||
it('should re-render on input change', () => {
|
||||
const ctx: {items: string[]} = {items: ['a']};
|
||||
expect(renderToHtml(template, ctx, 1, 1, defs)).toEqual('<wrapper>a</wrapper>');
|
||||
|
||||
ctx.items = [...ctx.items, 'b'];
|
||||
expect(renderToHtml(template, ctx, 1, 1, defs)).toEqual('<wrapper>ab</wrapper>');
|
||||
});
|
||||
});
|
||||
|
||||
describe('recursive components', () => {
|
||||
let events: string[];
|
||||
let count: number;
|
||||
|
||||
beforeEach(() => {
|
||||
events = [];
|
||||
count = 0;
|
||||
});
|
||||
|
||||
class TreeNode {
|
||||
constructor(
|
||||
public value: number, public depth: number, public left: TreeNode|null,
|
||||
public right: TreeNode|null) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* {{ data.value }}
|
||||
*
|
||||
* % if (data.left != null) {
|
||||
* <tree-comp [data]="data.left"></tree-comp>
|
||||
* % }
|
||||
* % if (data.right != null) {
|
||||
* <tree-comp [data]="data.right"></tree-comp>
|
||||
* % }
|
||||
*/
|
||||
class TreeComponent {
|
||||
data: TreeNode = _buildTree(0);
|
||||
|
||||
ngDoCheck() {
|
||||
events.push('check' + this.data.value);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
events.push('destroy' + this.data.value);
|
||||
}
|
||||
|
||||
static ɵfac = () => new TreeComponent();
|
||||
static ɵcmp = ɵɵdefineComponent({
|
||||
type: TreeComponent,
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
selectors: [['tree-comp']],
|
||||
decls: 3,
|
||||
vars: 1,
|
||||
template:
|
||||
(rf: RenderFlags, ctx: TreeComponent) => {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵtext(0);
|
||||
ɵɵcontainer(1);
|
||||
ɵɵcontainer(2);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵtextInterpolate(ctx.data.value);
|
||||
ɵɵcontainerRefreshStart(1);
|
||||
{
|
||||
if (ctx.data.left != null) {
|
||||
let rf0 = ɵɵembeddedViewStart(0, 1, 1);
|
||||
if (rf0 & RenderFlags.Create) {
|
||||
ɵɵelement(0, 'tree-comp');
|
||||
}
|
||||
if (rf0 & RenderFlags.Update) {
|
||||
ɵɵselect(0);
|
||||
ɵɵproperty('data', ctx.data.left);
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
ɵɵcontainerRefreshStart(2);
|
||||
{
|
||||
if (ctx.data.right != null) {
|
||||
let rf0 = ɵɵembeddedViewStart(0, 1, 1);
|
||||
if (rf0 & RenderFlags.Create) {
|
||||
ɵɵelement(0, 'tree-comp');
|
||||
}
|
||||
if (rf0 & RenderFlags.Update) {
|
||||
ɵɵselect(0);
|
||||
ɵɵproperty('data', ctx.data.right);
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
},
|
||||
inputs: {data: 'data'}
|
||||
});
|
||||
}
|
||||
|
||||
(TreeComponent.ɵcmp as ComponentDef<TreeComponent>).directiveDefs = () => [TreeComponent.ɵcmp];
|
||||
|
||||
/**
|
||||
* {{ data.value }}
|
||||
* <ng-if-tree [data]="data.left" *ngIf="data.left"></ng-if-tree>
|
||||
* <ng-if-tree [data]="data.right" *ngIf="data.right"></ng-if-tree>
|
||||
*/
|
||||
class NgIfTree {
|
||||
data: TreeNode = _buildTree(0);
|
||||
|
||||
ngDoCheck() {
|
||||
events.push('check' + this.data.value);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
events.push('destroy' + this.data.value);
|
||||
}
|
||||
|
||||
static ɵfac = () => new NgIfTree();
|
||||
static ɵcmp = ɵɵdefineComponent({
|
||||
type: NgIfTree,
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
selectors: [['ng-if-tree']],
|
||||
decls: 3,
|
||||
vars: 3,
|
||||
consts: [[AttributeMarker.Bindings, 'data', AttributeMarker.Template, 'ngIf']],
|
||||
template:
|
||||
(rf: RenderFlags, ctx: NgIfTree) => {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵtext(0);
|
||||
ɵɵtemplate(1, IfTemplate, 1, 1, 'ng-if-tree', 0);
|
||||
ɵɵtemplate(2, IfTemplate2, 1, 1, 'ng-if-tree', 0);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵtextInterpolate(ctx.data.value);
|
||||
ɵɵadvance(1);
|
||||
ɵɵproperty('ngIf', ctx.data.left);
|
||||
ɵɵadvance(1);
|
||||
ɵɵproperty('ngIf', ctx.data.right);
|
||||
}
|
||||
},
|
||||
inputs: {data: 'data'},
|
||||
});
|
||||
}
|
||||
|
||||
function IfTemplate(rf: RenderFlags, left: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵelementStart(0, 'ng-if-tree');
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
const parent = ɵɵnextContext();
|
||||
ɵɵproperty('data', parent.data.left);
|
||||
}
|
||||
}
|
||||
|
||||
function IfTemplate2(rf: RenderFlags, right: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵelementStart(0, 'ng-if-tree');
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
const parent = ɵɵnextContext();
|
||||
ɵɵproperty('data', parent.data.right);
|
||||
}
|
||||
}
|
||||
|
||||
(NgIfTree.ɵcmp as ComponentDef<NgIfTree>).directiveDefs = () => [NgIfTree.ɵcmp, NgIf.ɵdir];
|
||||
|
||||
function _buildTree(currDepth: number): TreeNode {
|
||||
const children = currDepth < 2 ? _buildTree(currDepth + 1) : null;
|
||||
const children2 = currDepth < 2 ? _buildTree(currDepth + 1) : null;
|
||||
return new TreeNode(count++, currDepth, children, children2);
|
||||
}
|
||||
|
||||
it('should check each component just once', () => {
|
||||
const comp = renderComponent(TreeComponent, {hostFeatures: [LifecycleHooksFeature]});
|
||||
expect(getRenderedText(comp)).toEqual('6201534');
|
||||
expect(events).toEqual(['check6', 'check2', 'check0', 'check1', 'check5', 'check3', 'check4']);
|
||||
|
||||
events = [];
|
||||
tick(comp);
|
||||
expect(events).toEqual(['check6', 'check2', 'check0', 'check1', 'check5', 'check3', 'check4']);
|
||||
});
|
||||
|
||||
// This tests that the view tree is set up properly for recursive components
|
||||
it('should call onDestroys properly', () => {
|
||||
/**
|
||||
* % if (!skipContent) {
|
||||
* <tree-comp></tree-comp>
|
||||
* % }
|
||||
*/
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵcontainer(0);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(0);
|
||||
if (!ctx.skipContent) {
|
||||
const rf0 = ɵɵembeddedViewStart(0, 1, 0);
|
||||
if (rf0 & RenderFlags.Create) {
|
||||
ɵɵelementStart(0, 'tree-comp');
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
}, 1, 0, [TreeComponent]);
|
||||
|
||||
const fixture = new ComponentFixture(App);
|
||||
expect(getRenderedText(fixture.component)).toEqual('6201534');
|
||||
|
||||
events = [];
|
||||
fixture.component.skipContent = true;
|
||||
fixture.update();
|
||||
expect(events).toEqual(
|
||||
['destroy0', 'destroy1', 'destroy2', 'destroy3', 'destroy4', 'destroy5', 'destroy6']);
|
||||
});
|
||||
|
||||
it('should call onDestroys properly with ngIf', () => {
|
||||
/**
|
||||
* % if (!skipContent) {
|
||||
* <ng-if-tree></ng-if-tree>
|
||||
* % }
|
||||
*/
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵcontainer(0);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(0);
|
||||
if (!ctx.skipContent) {
|
||||
const rf0 = ɵɵembeddedViewStart(0, 1, 0);
|
||||
if (rf0 & RenderFlags.Create) {
|
||||
ɵɵelementStart(0, 'ng-if-tree');
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
}, 1, 0, [NgIfTree]);
|
||||
|
||||
const fixture = new ComponentFixture(App);
|
||||
expect(getRenderedText(fixture.component)).toEqual('6201534');
|
||||
expect(events).toEqual(['check6', 'check2', 'check0', 'check1', 'check5', 'check3', 'check4']);
|
||||
|
||||
events = [];
|
||||
fixture.component.skipContent = true;
|
||||
fixture.update();
|
||||
expect(events).toEqual(
|
||||
['destroy0', 'destroy1', 'destroy2', 'destroy3', 'destroy4', 'destroy5', 'destroy6']);
|
||||
});
|
||||
|
||||
it('should map inputs minified & unminified names', async () => {
|
||||
class TestInputsComponent {
|
||||
// TODO(issue/24571): remove '!'.
|
||||
minifiedName!: string;
|
||||
static ɵfac = () => new TestInputsComponent();
|
||||
static ɵcmp = ɵɵdefineComponent({
|
||||
type: TestInputsComponent,
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
selectors: [['test-inputs']],
|
||||
inputs: {minifiedName: 'unminifiedName'},
|
||||
decls: 0,
|
||||
vars: 0,
|
||||
template: function(rf: RenderFlags, ctx: TestInputsComponent):
|
||||
void {
|
||||
// Template not needed for this test
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const testInputsComponentFactory = new ComponentFactory(TestInputsComponent.ɵcmp);
|
||||
|
||||
expect([
|
||||
{propName: 'minifiedName', templateName: 'unminifiedName'}
|
||||
]).toEqual(testInputsComponentFactory.inputs);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,961 +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 {ɵɵdefineComponent} from '../../src/render3/definition';
|
||||
import {ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵselect, ɵɵtext, ɵɵtextInterpolate} from '../../src/render3/instructions/all';
|
||||
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
||||
|
||||
import {ComponentFixture, createComponent, TemplateFixture} from './render_util';
|
||||
|
||||
describe('JS control flow', () => {
|
||||
it('should work with if block', () => {
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵelementStart(0, 'div');
|
||||
{ ɵɵcontainer(1); }
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(1);
|
||||
{
|
||||
if (ctx.condition) {
|
||||
let rf1 = ɵɵembeddedViewStart(1, 2, 1);
|
||||
{
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
ɵɵelementStart(0, 'span');
|
||||
{ ɵɵtext(1); }
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
if (rf1 & RenderFlags.Update) {
|
||||
ɵɵselect(1);
|
||||
ɵɵtextInterpolate(ctx.message);
|
||||
}
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
}, 2);
|
||||
|
||||
const fixture = new ComponentFixture(App);
|
||||
fixture.component.condition = true;
|
||||
fixture.component.message = 'Hello';
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<div><span>Hello</span></div>');
|
||||
|
||||
fixture.component.condition = false;
|
||||
fixture.component.message = 'Hi!';
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<div></div>');
|
||||
|
||||
fixture.component.condition = true;
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<div><span>Hi!</span></div>');
|
||||
});
|
||||
|
||||
it('should work with nested if blocks', () => {
|
||||
/**
|
||||
* <div>
|
||||
* % if(ctx.condition) {
|
||||
* <span>
|
||||
* % if(ctx.condition2) {
|
||||
* Hello
|
||||
* % }
|
||||
* </span>
|
||||
* % }
|
||||
* </div>
|
||||
*/
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵelementStart(0, 'div');
|
||||
{ ɵɵcontainer(1); }
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(1);
|
||||
{
|
||||
if (ctx.condition) {
|
||||
let rf1 = ɵɵembeddedViewStart(1, 2, 0);
|
||||
{
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
ɵɵelementStart(0, 'span');
|
||||
{ ɵɵcontainer(1); }
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
if (rf1 & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(1);
|
||||
{
|
||||
if (ctx.condition2) {
|
||||
let rf2 = ɵɵembeddedViewStart(2, 1, 0);
|
||||
{
|
||||
if (rf2 & RenderFlags.Create) {
|
||||
ɵɵtext(0, 'Hello');
|
||||
}
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
}, 2);
|
||||
|
||||
const fixture = new ComponentFixture(App);
|
||||
fixture.component.condition = true;
|
||||
fixture.component.condition2 = true;
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<div><span>Hello</span></div>');
|
||||
|
||||
fixture.component.condition = false;
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<div></div>');
|
||||
|
||||
fixture.component.condition = true;
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<div><span>Hello</span></div>');
|
||||
|
||||
fixture.component.condition2 = false;
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<div><span></span></div>');
|
||||
|
||||
fixture.component.condition2 = true;
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<div><span>Hello</span></div>');
|
||||
|
||||
fixture.component.condition2 = false;
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<div><span></span></div>');
|
||||
|
||||
fixture.component.condition = false;
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<div></div>');
|
||||
|
||||
fixture.component.condition = true;
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<div><span></span></div>');
|
||||
|
||||
fixture.component.condition2 = true;
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<div><span>Hello</span></div>');
|
||||
});
|
||||
|
||||
it('should work with nested adjacent if blocks', () => {
|
||||
const ctx:
|
||||
{condition: boolean,
|
||||
condition2: boolean,
|
||||
condition3: boolean} = {condition: true, condition2: false, condition3: true};
|
||||
|
||||
/**
|
||||
* % if(ctx.condition) {
|
||||
* % if(ctx.condition2) {
|
||||
* Hello
|
||||
* % }
|
||||
* % if(ctx.condition3) {
|
||||
* World
|
||||
* % }
|
||||
* % }
|
||||
*/
|
||||
function createTemplate() {
|
||||
ɵɵcontainer(0);
|
||||
}
|
||||
|
||||
function updateTemplate() {
|
||||
ɵɵcontainerRefreshStart(0);
|
||||
{
|
||||
if (ctx.condition) {
|
||||
let rf1 = ɵɵembeddedViewStart(1, 2, 0);
|
||||
{
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
{
|
||||
ɵɵcontainer(0);
|
||||
}
|
||||
{ ɵɵcontainer(1); }
|
||||
}
|
||||
if (rf1 & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(0);
|
||||
{
|
||||
if (ctx.condition2) {
|
||||
let rf2 = ɵɵembeddedViewStart(2, 1, 0);
|
||||
{
|
||||
if (rf2 & RenderFlags.Create) {
|
||||
ɵɵtext(0, 'Hello');
|
||||
}
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
ɵɵcontainerRefreshStart(1);
|
||||
{
|
||||
if (ctx.condition3) {
|
||||
let rf2 = ɵɵembeddedViewStart(2, 1, 0);
|
||||
{
|
||||
if (rf2 & RenderFlags.Create) {
|
||||
ɵɵtext(0, 'World');
|
||||
}
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
|
||||
const fixture = new TemplateFixture(createTemplate, updateTemplate, 1);
|
||||
expect(fixture.html).toEqual('World');
|
||||
|
||||
ctx.condition2 = true;
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('HelloWorld');
|
||||
});
|
||||
|
||||
it('should work with adjacent if blocks managing views in the same container', () => {
|
||||
/**
|
||||
* % if(ctx.condition1) {
|
||||
* 1
|
||||
* % }; if(ctx.condition2) {
|
||||
* 2
|
||||
* % }; if(ctx.condition3) {
|
||||
* 3
|
||||
* % }
|
||||
*/
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵcontainer(0);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(0);
|
||||
if (ctx.condition1) {
|
||||
const rf1 = ɵɵembeddedViewStart(1, 1, 0);
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
ɵɵtext(0, '1');
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
} // can't have ; here due linting rules
|
||||
if (ctx.condition2) {
|
||||
const rf2 = ɵɵembeddedViewStart(2, 1, 0);
|
||||
if (rf2 & RenderFlags.Create) {
|
||||
ɵɵtext(0, '2');
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
} // can't have ; here due linting rules
|
||||
if (ctx.condition3) {
|
||||
const rf3 = ɵɵembeddedViewStart(3, 1, 0);
|
||||
if (rf3 & RenderFlags.Create) {
|
||||
ɵɵtext(0, '3');
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
}, 1);
|
||||
|
||||
const fixture = new ComponentFixture(App);
|
||||
fixture.component.condition1 = true;
|
||||
fixture.component.condition2 = true;
|
||||
fixture.component.condition3 = true;
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('123');
|
||||
|
||||
fixture.component.condition2 = false;
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('13');
|
||||
});
|
||||
|
||||
it('should work with containers with views as parents', () => {
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵelementStart(0, 'div');
|
||||
{ ɵɵtext(1, 'hello'); }
|
||||
ɵɵelementEnd();
|
||||
ɵɵcontainer(2);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(2);
|
||||
{
|
||||
if (ctx.condition1) {
|
||||
let rf0 = ɵɵembeddedViewStart(0, 1, 0);
|
||||
{
|
||||
if (rf0 & RenderFlags.Create) {
|
||||
ɵɵcontainer(0);
|
||||
}
|
||||
if (rf0 & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(0);
|
||||
{
|
||||
if (ctx.condition2) {
|
||||
let rf0 = ɵɵembeddedViewStart(0, 1, 0);
|
||||
{
|
||||
if (rf0 & RenderFlags.Create) {
|
||||
ɵɵtext(0, 'world');
|
||||
}
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
}, 3);
|
||||
|
||||
const fixture = new ComponentFixture(App);
|
||||
fixture.component.condition1 = true;
|
||||
fixture.component.condition2 = true;
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<div>hello</div>world');
|
||||
|
||||
fixture.component.condition1 = false;
|
||||
fixture.component.condition2 = false;
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<div>hello</div>');
|
||||
});
|
||||
|
||||
it('should work with loop block', () => {
|
||||
let data: string[] = ['a', 'b', 'c'];
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵelementStart(0, 'ul');
|
||||
{ ɵɵcontainer(1); }
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(1);
|
||||
{
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
let rf1 = ɵɵembeddedViewStart(1, 2, 1);
|
||||
{
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
ɵɵelementStart(0, 'li');
|
||||
{ ɵɵtext(1); }
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
if (rf1 & RenderFlags.Update) {
|
||||
ɵɵselect(1);
|
||||
ɵɵtextInterpolate(data[i]);
|
||||
}
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
}, 2);
|
||||
|
||||
const fixture = new ComponentFixture(App);
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<ul><li>a</li><li>b</li><li>c</li></ul>');
|
||||
|
||||
data = ['e', 'f'];
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<ul><li>e</li><li>f</li></ul>');
|
||||
|
||||
data = [];
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<ul></ul>');
|
||||
|
||||
data = ['a', 'b', 'c'];
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<ul><li>a</li><li>b</li><li>c</li></ul>');
|
||||
|
||||
data.push('d');
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<ul><li>a</li><li>b</li><li>c</li><li>d</li></ul>');
|
||||
|
||||
data = ['e'];
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<ul><li>e</li></ul>');
|
||||
});
|
||||
|
||||
it('should work with nested loop blocks', () => {
|
||||
let data: string[][] = [['a', 'b', 'c'], ['m', 'n']];
|
||||
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵelementStart(0, 'ul');
|
||||
{ ɵɵcontainer(1); }
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(1);
|
||||
{
|
||||
for (let i = 0; i < data[0].length; i++) {
|
||||
let rf1 = ɵɵembeddedViewStart(1, 2, 0);
|
||||
{
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
ɵɵelementStart(0, 'li');
|
||||
{ ɵɵcontainer(1); }
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
if (rf1 & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(1);
|
||||
{
|
||||
data[1].forEach((value: string, ind: number) => {
|
||||
let rf2 = ɵɵembeddedViewStart(2, 1, 1);
|
||||
if (rf2 & RenderFlags.Create) {
|
||||
ɵɵtext(0);
|
||||
}
|
||||
if (rf2 & RenderFlags.Update) {
|
||||
ɵɵselect(0);
|
||||
ɵɵtextInterpolate(data[0][i] + value);
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
});
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
}, 2);
|
||||
|
||||
const fixture = new ComponentFixture(App);
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<ul><li>aman</li><li>bmbn</li><li>cmcn</li></ul>');
|
||||
|
||||
data = [[], []];
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<ul></ul>');
|
||||
});
|
||||
|
||||
it('should work with nested loop blocks where nested container is a root node', () => {
|
||||
let cafes = [
|
||||
{name: '1', entrees: ['a', 'b', 'c']}, {name: '2', entrees: ['d', 'e', 'f']},
|
||||
{name: '3', entrees: ['g', 'h', 'i']}
|
||||
];
|
||||
|
||||
/**
|
||||
* <div>
|
||||
* Before
|
||||
* % for (let i = 0; i < cafes.length; i++) {
|
||||
* <h2> {{ cafes[i].name }} </h2>
|
||||
* % for (let j = 0; j < cafes[i].entrees; j++) {
|
||||
* {{ cafes[i].entrees[j] }}
|
||||
* % }
|
||||
* -
|
||||
* % }
|
||||
* After
|
||||
* <div>
|
||||
*/
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵelementStart(0, 'div');
|
||||
{
|
||||
ɵɵtext(1, 'Before');
|
||||
ɵɵcontainer(2);
|
||||
ɵɵtext(3, 'After');
|
||||
}
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(2);
|
||||
{
|
||||
for (let i = 0; i < cafes.length; i++) {
|
||||
let rf1 = ɵɵembeddedViewStart(1, 4, 1);
|
||||
{
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
ɵɵelementStart(0, 'h2');
|
||||
{ ɵɵtext(1); }
|
||||
ɵɵelementEnd();
|
||||
ɵɵcontainer(2);
|
||||
ɵɵtext(3, '-');
|
||||
}
|
||||
if (rf1 & RenderFlags.Update) {
|
||||
ɵɵselect(1);
|
||||
ɵɵtextInterpolate(cafes[i].name);
|
||||
ɵɵcontainerRefreshStart(2);
|
||||
{
|
||||
for (let j = 0; j < cafes[i].entrees.length; j++) {
|
||||
let rf2 = ɵɵembeddedViewStart(2, 1, 1);
|
||||
if (rf2 & RenderFlags.Create) {
|
||||
ɵɵtext(0);
|
||||
}
|
||||
if (rf2 & RenderFlags.Update) {
|
||||
ɵɵselect(0);
|
||||
ɵɵtextInterpolate(cafes[i].entrees[j]);
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
}, 4);
|
||||
|
||||
const fixture = new ComponentFixture(App);
|
||||
fixture.update();
|
||||
expect(fixture.html)
|
||||
.toEqual('<div>Before<h2>1</h2>abc-<h2>2</h2>def-<h2>3</h2>ghi-After</div>');
|
||||
|
||||
cafes = [];
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<div>BeforeAfter</div>');
|
||||
|
||||
cafes = [
|
||||
{name: '1', entrees: ['a', 'c']},
|
||||
{name: '2', entrees: ['d', 'e']},
|
||||
];
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<div>Before<h2>1</h2>ac-<h2>2</h2>de-After</div>');
|
||||
});
|
||||
|
||||
it('should work with loop blocks nested three deep', () => {
|
||||
let cafes = [
|
||||
{
|
||||
name: '1',
|
||||
entrees:
|
||||
[{name: 'a', foods: [1, 2]}, {name: 'b', foods: [3, 4]}, {name: 'c', foods: [5, 6]}]
|
||||
},
|
||||
{
|
||||
name: '2',
|
||||
entrees:
|
||||
[{name: 'd', foods: [1, 2]}, {name: 'e', foods: [3, 4]}, {name: 'f', foods: [5, 6]}]
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* <div>
|
||||
* Before
|
||||
* % for (let i = 0; i < cafes.length; i++) {
|
||||
* <h2> {{ cafes[i].name }} </h2>
|
||||
* % for (let j = 0; j < cafes[i].entrees.length; j++) {
|
||||
* <h3> {{ cafes[i].entrees[j].name }} </h3>
|
||||
* % for (let k = 0; k < cafes[i].entrees[j].foods.length; k++) {
|
||||
* {{ cafes[i].entrees[j].foods[k] }}
|
||||
* % }
|
||||
* % }
|
||||
* -
|
||||
* % }
|
||||
* After
|
||||
* <div>
|
||||
*/
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵelementStart(0, 'div');
|
||||
{
|
||||
ɵɵtext(1, 'Before');
|
||||
ɵɵcontainer(2);
|
||||
ɵɵtext(3, 'After');
|
||||
}
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(2);
|
||||
{
|
||||
for (let i = 0; i < cafes.length; i++) {
|
||||
let rf1 = ɵɵembeddedViewStart(1, 4, 1);
|
||||
{
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
ɵɵelementStart(0, 'h2');
|
||||
{ ɵɵtext(1); }
|
||||
ɵɵelementEnd();
|
||||
ɵɵcontainer(2);
|
||||
ɵɵtext(3, '-');
|
||||
}
|
||||
if (rf1 & RenderFlags.Update) {
|
||||
ɵɵselect(1);
|
||||
ɵɵtextInterpolate(cafes[i].name);
|
||||
ɵɵcontainerRefreshStart(2);
|
||||
{
|
||||
for (let j = 0; j < cafes[i].entrees.length; j++) {
|
||||
let rf1 = ɵɵembeddedViewStart(1, 3, 1);
|
||||
{
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
ɵɵelementStart(0, 'h3');
|
||||
{ ɵɵtext(1); }
|
||||
ɵɵelementEnd();
|
||||
ɵɵcontainer(2);
|
||||
}
|
||||
if (rf1 & RenderFlags.Update) {
|
||||
ɵɵselect(1);
|
||||
ɵɵtextInterpolate(cafes[i].entrees[j].name);
|
||||
ɵɵcontainerRefreshStart(2);
|
||||
{
|
||||
for (let k = 0; k < cafes[i].entrees[j].foods.length; k++) {
|
||||
let rf2 = ɵɵembeddedViewStart(1, 1, 1);
|
||||
if (rf2 & RenderFlags.Create) {
|
||||
ɵɵtext(0);
|
||||
}
|
||||
if (rf2 & RenderFlags.Update) {
|
||||
ɵɵselect(0);
|
||||
ɵɵtextInterpolate(cafes[i].entrees[j].foods[k]);
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
}, 4);
|
||||
|
||||
const fixture = new ComponentFixture(App);
|
||||
fixture.update();
|
||||
expect(fixture.html)
|
||||
.toEqual(
|
||||
'<div>' +
|
||||
'Before' +
|
||||
'<h2>1</h2><h3>a</h3>12<h3>b</h3>34<h3>c</h3>56-' +
|
||||
'<h2>2</h2><h3>d</h3>12<h3>e</h3>34<h3>f</h3>56-' +
|
||||
'After' +
|
||||
'</div>');
|
||||
|
||||
cafes = [];
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<div>BeforeAfter</div>');
|
||||
});
|
||||
|
||||
it('should work with if/else blocks', () => {
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵelementStart(0, 'div');
|
||||
{ ɵɵcontainer(1); }
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(1);
|
||||
{
|
||||
if (ctx.condition) {
|
||||
let rf1 = ɵɵembeddedViewStart(1, 2, 0);
|
||||
{
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
ɵɵelementStart(0, 'span');
|
||||
{ ɵɵtext(1, 'Hello'); }
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
} else {
|
||||
let rf2 = ɵɵembeddedViewStart(2, 2, 0);
|
||||
{
|
||||
if (rf2) {
|
||||
ɵɵelementStart(0, 'div');
|
||||
{ ɵɵtext(1, 'Goodbye'); }
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
}, 2);
|
||||
|
||||
const fixture = new ComponentFixture(App);
|
||||
fixture.component.condition = true;
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<div><span>Hello</span></div>');
|
||||
|
||||
fixture.component.condition = false;
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<div><div>Goodbye</div></div>');
|
||||
|
||||
fixture.component.condition = true;
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<div><span>Hello</span></div>');
|
||||
});
|
||||
|
||||
it('should work with sibling if blocks with children', () => {
|
||||
let log: string[] = [];
|
||||
|
||||
// Intentionally duplicating the templates in test below so we are
|
||||
// testing the behavior on firstCreatePass for each of these tests
|
||||
class Comp {
|
||||
static ɵfac =
|
||||
() => {
|
||||
log.push('comp!');
|
||||
return new Comp();
|
||||
}
|
||||
|
||||
static ɵcmp = ɵɵdefineComponent({
|
||||
type: Comp,
|
||||
selectors: [['comp']],
|
||||
decls: 0,
|
||||
vars: 0,
|
||||
template: function(rf: RenderFlags, ctx: Comp) {}
|
||||
});
|
||||
}
|
||||
|
||||
class App {
|
||||
condition = true;
|
||||
condition2 = true;
|
||||
|
||||
static ɵfac = () => new App();
|
||||
static ɵcmp = ɵɵdefineComponent({
|
||||
type: App,
|
||||
selectors: [['app']],
|
||||
decls: 3,
|
||||
vars: 0,
|
||||
template:
|
||||
function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵelement(0, 'div');
|
||||
ɵɵcontainer(1);
|
||||
ɵɵcontainer(2);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(1);
|
||||
{
|
||||
if (ctx.condition) {
|
||||
let rf1 = ɵɵembeddedViewStart(0, 1, 0);
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
ɵɵelement(0, 'comp');
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
ɵɵcontainerRefreshStart(2);
|
||||
{
|
||||
if (ctx.condition2) {
|
||||
let rf1 = ɵɵembeddedViewStart(0, 1, 0);
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
ɵɵelement(0, 'comp');
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
},
|
||||
directives: () => [Comp]
|
||||
});
|
||||
}
|
||||
|
||||
const fixture = new ComponentFixture(App);
|
||||
expect(log).toEqual(['comp!', 'comp!']);
|
||||
});
|
||||
|
||||
it('should work with a sibling if block that starts closed', () => {
|
||||
let log: string[] = [];
|
||||
|
||||
// Intentionally duplicating the templates from above so we are
|
||||
// testing the behavior on firstCreatePass for each of these tests
|
||||
class Comp {
|
||||
static ɵfac =
|
||||
() => {
|
||||
log.push('comp!');
|
||||
return new Comp();
|
||||
}
|
||||
|
||||
static ɵcmp = ɵɵdefineComponent({
|
||||
type: Comp,
|
||||
selectors: [['comp']],
|
||||
decls: 0,
|
||||
vars: 0,
|
||||
template: function(rf: RenderFlags, ctx: Comp) {}
|
||||
});
|
||||
}
|
||||
|
||||
class App {
|
||||
condition = false;
|
||||
condition2 = true;
|
||||
|
||||
static ɵfac = () => new App();
|
||||
static ɵcmp = ɵɵdefineComponent({
|
||||
type: App,
|
||||
selectors: [['app']],
|
||||
decls: 3,
|
||||
vars: 0,
|
||||
template:
|
||||
function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵelement(0, 'div');
|
||||
ɵɵcontainer(1);
|
||||
ɵɵcontainer(2);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(1);
|
||||
{
|
||||
if (ctx.condition) {
|
||||
let rf1 = ɵɵembeddedViewStart(0, 1, 0);
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
ɵɵelement(0, 'comp');
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
ɵɵcontainerRefreshStart(2);
|
||||
{
|
||||
if (ctx.condition2) {
|
||||
let rf1 = ɵɵembeddedViewStart(0, 1, 0);
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
ɵɵelement(0, 'comp');
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
},
|
||||
directives: () => [Comp]
|
||||
});
|
||||
}
|
||||
|
||||
const fixture = new ComponentFixture(App);
|
||||
expect(log).toEqual(['comp!']);
|
||||
|
||||
fixture.component.condition = true;
|
||||
fixture.update();
|
||||
expect(log).toEqual(['comp!', 'comp!']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('JS for loop', () => {
|
||||
it('should work with sibling for blocks', () => {
|
||||
const config: {data1: string[], data2: number[]} = {data1: ['a', 'b', 'c'], data2: [1, 2]};
|
||||
|
||||
/**
|
||||
* <div>
|
||||
* % for (let i = 0; i < ctx.data1.length; i++) {
|
||||
* {{data1[i]}}
|
||||
* % } for (let j = 0; j < ctx.data2.length; j++) {
|
||||
* {{data1[j]}}
|
||||
* % }
|
||||
* </div>
|
||||
*/
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵelementStart(0, 'div');
|
||||
{ ɵɵcontainer(1); }
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(1);
|
||||
{
|
||||
for (let i = 0; i < config.data1.length; i++) {
|
||||
let rf2 = ɵɵembeddedViewStart(1, 1, 1);
|
||||
if (rf2 & RenderFlags.Create) {
|
||||
ɵɵtext(0);
|
||||
}
|
||||
if (rf2 & RenderFlags.Update) {
|
||||
ɵɵselect(0);
|
||||
ɵɵtextInterpolate(config.data1[i]);
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
for (let j = 0; j < config.data2.length; j++) {
|
||||
let rf2 = ɵɵembeddedViewStart(1, 1, 1);
|
||||
if (rf2 & RenderFlags.Create) {
|
||||
ɵɵtext(0);
|
||||
}
|
||||
if (rf2 & RenderFlags.Update) {
|
||||
ɵɵselect(0);
|
||||
ɵɵtextInterpolate(config.data2[j]);
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
}, 2);
|
||||
|
||||
const fixture = new ComponentFixture(App);
|
||||
expect(fixture.html).toEqual('<div>abc12</div>');
|
||||
|
||||
config.data1 = ['e', 'f'];
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<div>ef12</div>');
|
||||
|
||||
config.data2 = [8];
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<div>ef8</div>');
|
||||
|
||||
config.data1 = ['x', 'y'];
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<div>xy8</div>');
|
||||
});
|
||||
});
|
||||
|
||||
describe('function calls', () => {
|
||||
it('should work', () => {
|
||||
let data: string[] = ['foo', 'bar'];
|
||||
|
||||
function spanify(rf: RenderFlags, ctx: {message: string|null}) {
|
||||
const message = ctx.message;
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵelementStart(0, 'span');
|
||||
{ ɵɵtext(1); }
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵselect(1);
|
||||
ɵɵtextInterpolate(message);
|
||||
}
|
||||
}
|
||||
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵelementStart(0, 'div');
|
||||
{
|
||||
ɵɵtext(1, 'Before');
|
||||
ɵɵcontainer(2);
|
||||
ɵɵcontainer(3);
|
||||
ɵɵtext(4, 'After');
|
||||
}
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(2);
|
||||
{
|
||||
let rf0 = ɵɵembeddedViewStart(0, 2, 1);
|
||||
{ spanify(rf0, {message: data[0]}); }
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
ɵɵcontainerRefreshStart(3);
|
||||
{
|
||||
let rf0 = ɵɵembeddedViewStart(0, 2, 1);
|
||||
{ spanify(rf0, {message: data[1]}); }
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
}, 5);
|
||||
|
||||
const fixture = new ComponentFixture(App);
|
||||
expect(fixture.html).toEqual('<div>Before<span>foo</span><span>bar</span>After</div>');
|
||||
|
||||
data = [];
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<div>Before<span></span><span></span>After</div>');
|
||||
});
|
||||
});
|
|
@ -9,7 +9,7 @@
|
|||
import {RendererType2} from '../../src/render/api';
|
||||
import {getLContext} from '../../src/render3/context_discovery';
|
||||
import {AttributeMarker, ɵɵadvance, ɵɵattribute, ɵɵdefineComponent, ɵɵdefineDirective, ɵɵhostProperty, ɵɵproperty} from '../../src/render3/index';
|
||||
import {ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵprojection, ɵɵprojectionDef, ɵɵtemplate, ɵɵtext, ɵɵtextInterpolate} from '../../src/render3/instructions/all';
|
||||
import {ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵprojection, ɵɵprojectionDef, ɵɵtemplate, ɵɵtext} from '../../src/render3/instructions/all';
|
||||
import {MONKEY_PATCH_KEY_NAME} from '../../src/render3/interfaces/context';
|
||||
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
||||
import {domRendererFactory3, RElement, Renderer3, RendererFactory3} from '../../src/render3/interfaces/renderer';
|
||||
|
@ -41,156 +41,6 @@ describe('render3 integration test', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('tree', () => {
|
||||
interface Tree {
|
||||
beforeLabel?: string;
|
||||
subTrees?: Tree[];
|
||||
afterLabel?: string;
|
||||
}
|
||||
|
||||
interface ParentCtx {
|
||||
beforeTree: Tree;
|
||||
projectedTree: Tree;
|
||||
afterTree: Tree;
|
||||
}
|
||||
|
||||
function showLabel(rf: RenderFlags, ctx: {label: string|undefined}) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵcontainer(0);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(0);
|
||||
{
|
||||
if (ctx.label != null) {
|
||||
let rf1 = ɵɵembeddedViewStart(0, 1, 1);
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
ɵɵtext(0);
|
||||
}
|
||||
if (rf1 & RenderFlags.Update) {
|
||||
ɵɵtextInterpolate(ctx.label);
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
}
|
||||
|
||||
function showTree(rf: RenderFlags, ctx: {tree: Tree}) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵcontainer(0);
|
||||
ɵɵcontainer(1);
|
||||
ɵɵcontainer(2);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(0);
|
||||
{
|
||||
const rf0 = ɵɵembeddedViewStart(0, 1, 0);
|
||||
{ showLabel(rf0, {label: ctx.tree.beforeLabel}); }
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
ɵɵcontainerRefreshStart(1);
|
||||
{
|
||||
for (let subTree of ctx.tree.subTrees || []) {
|
||||
const rf0 = ɵɵembeddedViewStart(0, 3, 0);
|
||||
{ showTree(rf0, {tree: subTree}); }
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
ɵɵcontainerRefreshStart(2);
|
||||
{
|
||||
const rf0 = ɵɵembeddedViewStart(0, 1, 0);
|
||||
{ showLabel(rf0, {label: ctx.tree.afterLabel}); }
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
}
|
||||
|
||||
class ChildComponent {
|
||||
// TODO(issue/24571): remove '!'.
|
||||
beforeTree!: Tree;
|
||||
// TODO(issue/24571): remove '!'.
|
||||
afterTree!: Tree;
|
||||
|
||||
static ɵfac = () => new ChildComponent;
|
||||
static ɵcmp = ɵɵdefineComponent({
|
||||
selectors: [['child']],
|
||||
type: ChildComponent,
|
||||
decls: 3,
|
||||
vars: 0,
|
||||
template:
|
||||
function ChildComponentTemplate(
|
||||
rf: RenderFlags, ctx: {beforeTree: Tree, afterTree: Tree}) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵprojectionDef();
|
||||
ɵɵcontainer(0);
|
||||
ɵɵprojection(1);
|
||||
ɵɵcontainer(2);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(0);
|
||||
{
|
||||
const rf0 = ɵɵembeddedViewStart(0, 3, 0);
|
||||
{ showTree(rf0, {tree: ctx.beforeTree}); }
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
ɵɵcontainerRefreshStart(2);
|
||||
{
|
||||
const rf0 = ɵɵembeddedViewStart(0, 3, 0);
|
||||
{ showTree(rf0, {tree: ctx.afterTree}); }
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
},
|
||||
inputs: {beforeTree: 'beforeTree', afterTree: 'afterTree'}
|
||||
});
|
||||
}
|
||||
|
||||
function parentTemplate(rf: RenderFlags, ctx: ParentCtx) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵelementStart(0, 'child');
|
||||
{ ɵɵcontainer(1); }
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵproperty('beforeTree', ctx.beforeTree);
|
||||
ɵɵproperty('afterTree', ctx.afterTree);
|
||||
ɵɵcontainerRefreshStart(1);
|
||||
{
|
||||
const rf0 = ɵɵembeddedViewStart(0, 3, 0);
|
||||
{ showTree(rf0, {tree: ctx.projectedTree}); }
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
}
|
||||
|
||||
it('should work with a tree', () => {
|
||||
const ctx: ParentCtx = {
|
||||
beforeTree: {subTrees: [{beforeLabel: 'a'}]},
|
||||
projectedTree: {beforeLabel: 'p'},
|
||||
afterTree: {afterLabel: 'z'}
|
||||
};
|
||||
const defs = [ChildComponent];
|
||||
expect(renderToHtml(parentTemplate, ctx, 2, 2, defs)).toEqual('<child>apz</child>');
|
||||
ctx.projectedTree = {subTrees: [{}, {}, {subTrees: [{}, {}]}, {}]};
|
||||
ctx.beforeTree.subTrees!.push({afterLabel: 'b'});
|
||||
expect(renderToHtml(parentTemplate, ctx, 2, 2, defs)).toEqual('<child>abz</child>');
|
||||
ctx.projectedTree.subTrees![1].afterLabel = 'h';
|
||||
expect(renderToHtml(parentTemplate, ctx, 2, 2, defs)).toEqual('<child>abhz</child>');
|
||||
ctx.beforeTree.subTrees!.push({beforeLabel: 'c'});
|
||||
expect(renderToHtml(parentTemplate, ctx, 2, 2, defs)).toEqual('<child>abchz</child>');
|
||||
|
||||
// To check the context easily:
|
||||
// console.log(JSON.stringify(ctx));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('component styles', () => {
|
||||
|
|
|
@ -1,120 +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 {ComponentTemplate, ɵɵdefineComponent, ɵɵdefineDirective, ɵɵproperty} from '../../src/render3/index';
|
||||
import {ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵprojection, ɵɵprojectionDef, ɵɵtext} from '../../src/render3/instructions/all';
|
||||
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
||||
import {NgIf} from './common_with_def';
|
||||
import {ComponentFixture, createComponent} from './render_util';
|
||||
|
||||
describe('lifecycles', () => {
|
||||
function getParentTemplate(name: string) {
|
||||
return (rf: RenderFlags, ctx: any) => {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵelement(0, name);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵproperty('val', ctx.val);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
describe('onInit', () => {
|
||||
let events: string[];
|
||||
|
||||
beforeEach(() => {
|
||||
events = [];
|
||||
});
|
||||
|
||||
let Comp = createOnInitComponent('comp', (rf: RenderFlags) => {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵprojectionDef();
|
||||
ɵɵelementStart(0, 'div');
|
||||
{ ɵɵprojection(1); }
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
}, 2);
|
||||
let Parent = createOnInitComponent('parent', getParentTemplate('comp'), 1, 1, [Comp]);
|
||||
let ProjectedComp = createOnInitComponent('projected', (rf: RenderFlags) => {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵtext(0, 'content');
|
||||
}
|
||||
}, 1);
|
||||
|
||||
function createOnInitComponent(
|
||||
name: string, template: ComponentTemplate<any>, decls: number, vars: number = 0,
|
||||
directives: any[] = []) {
|
||||
return class Component {
|
||||
val: string = '';
|
||||
ngOnInit() {
|
||||
if (!this.val) this.val = '';
|
||||
events.push(`${name}${this.val}`);
|
||||
}
|
||||
|
||||
static ɵfac = () => new Component();
|
||||
static ɵcmp = ɵɵdefineComponent({
|
||||
type: Component,
|
||||
selectors: [[name]],
|
||||
decls: decls,
|
||||
vars: vars,
|
||||
inputs: {val: 'val'},
|
||||
template,
|
||||
directives: directives
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
class Directive {
|
||||
ngOnInit() {
|
||||
events.push('dir');
|
||||
}
|
||||
|
||||
static ɵfac = () => new Directive();
|
||||
static ɵdir = ɵɵdefineDirective({type: Directive, selectors: [['', 'dir', '']]});
|
||||
}
|
||||
|
||||
const directives = [Comp, Parent, ProjectedComp, Directive, NgIf];
|
||||
|
||||
it('should call onInit every time a new view is created (if block)', () => {
|
||||
/**
|
||||
* % if (!skip) {
|
||||
* <comp></comp>
|
||||
* % }
|
||||
*/
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵcontainer(0);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(0);
|
||||
{
|
||||
if (!ctx.skip) {
|
||||
let rf1 = ɵɵembeddedViewStart(0, 1, 0);
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
ɵɵelement(0, 'comp');
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
}, 1, 0, directives);
|
||||
|
||||
const fixture = new ComponentFixture(App);
|
||||
expect(events).toEqual(['comp']);
|
||||
|
||||
fixture.component.skip = true;
|
||||
fixture.update();
|
||||
expect(events).toEqual(['comp']);
|
||||
|
||||
fixture.component.skip = false;
|
||||
fixture.update();
|
||||
expect(events).toEqual(['comp', 'comp']);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -8,14 +8,14 @@
|
|||
|
||||
import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util';
|
||||
|
||||
import {markDirty, ɵɵdefineComponent, ɵɵdefineDirective, ɵɵreference, ɵɵresolveBody, ɵɵresolveDocument, ɵɵselect, ɵɵtextInterpolate} from '../../src/render3/index';
|
||||
import {ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵgetCurrentView, ɵɵlistener, ɵɵtext} from '../../src/render3/instructions/all';
|
||||
import {ɵɵdefineComponent, ɵɵdefineDirective, ɵɵreference, ɵɵresolveBody, ɵɵresolveDocument} from '../../src/render3/index';
|
||||
import {ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵgetCurrentView, ɵɵlistener, ɵɵtext} from '../../src/render3/instructions/all';
|
||||
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
||||
import {GlobalTargetResolver} from '../../src/render3/interfaces/renderer';
|
||||
import {ɵɵrestoreView} from '../../src/render3/state';
|
||||
|
||||
import {getRendererFactory2} from './imported_renderer2';
|
||||
import {ComponentFixture, containerEl, createComponent, getDirectiveOnNode, renderToHtml, requestAnimationFrame, TemplateFixture} from './render_util';
|
||||
import {ComponentFixture, containerEl, createComponent, getDirectiveOnNode, renderToHtml, TemplateFixture} from './render_util';
|
||||
|
||||
|
||||
describe('event listeners', () => {
|
||||
|
@ -287,277 +287,6 @@ describe('event listeners', () => {
|
|||
expect(ctx.showing).toBe(false);
|
||||
});
|
||||
|
||||
it('should support listeners in views', () => {
|
||||
/**
|
||||
* % if (ctx.showing) {
|
||||
* <button (click)="onClick()"> Click me </button>
|
||||
* % }
|
||||
*/
|
||||
function Template(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵcontainer(0);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(0);
|
||||
{
|
||||
if (ctx.showing) {
|
||||
if (ɵɵembeddedViewStart(1, 2, 0)) {
|
||||
ɵɵelementStart(0, 'button');
|
||||
{
|
||||
ɵɵlistener('click', function() {
|
||||
return ctx.onClick();
|
||||
});
|
||||
ɵɵtext(1, 'Click me');
|
||||
}
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
}
|
||||
|
||||
let comp = new MyComp();
|
||||
renderToHtml(Template, comp, 1);
|
||||
const button = containerEl.querySelector('button')!;
|
||||
|
||||
button.click();
|
||||
expect(comp.counter).toEqual(1);
|
||||
|
||||
button.click();
|
||||
expect(comp.counter).toEqual(2);
|
||||
|
||||
// the listener should be removed when the view is removed
|
||||
comp.showing = false;
|
||||
renderToHtml(Template, comp, 1);
|
||||
button.click();
|
||||
expect(comp.counter).toEqual(2);
|
||||
});
|
||||
|
||||
it('should destroy listeners in views with renderer2', () => {
|
||||
/**
|
||||
* % if (ctx.showing) {
|
||||
* <button (click)="onClick()"> Click me </button>
|
||||
* % }
|
||||
*/
|
||||
class AppComp {
|
||||
counter = 0;
|
||||
showing = true;
|
||||
|
||||
onClick() {
|
||||
this.counter++;
|
||||
}
|
||||
|
||||
static ɵfac = () => new AppComp();
|
||||
static ɵcmp = ɵɵdefineComponent({
|
||||
type: AppComp,
|
||||
selectors: [['app-comp']],
|
||||
decls: 1,
|
||||
vars: 0,
|
||||
template:
|
||||
function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵcontainer(0);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(0);
|
||||
{
|
||||
if (ctx.showing) {
|
||||
if (ɵɵembeddedViewStart(0, 2, 0)) {
|
||||
ɵɵelementStart(0, 'button');
|
||||
{
|
||||
ɵɵlistener('click', function() {
|
||||
return ctx.onClick();
|
||||
});
|
||||
ɵɵtext(1, 'Click me');
|
||||
}
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const fixture = new ComponentFixture(AppComp, {rendererFactory: getRendererFactory2(document)});
|
||||
const comp = fixture.component;
|
||||
const button = fixture.hostElement.querySelector('button')!;
|
||||
|
||||
button.click();
|
||||
expect(comp.counter).toEqual(1);
|
||||
|
||||
button.click();
|
||||
expect(comp.counter).toEqual(2);
|
||||
|
||||
// the listener should be removed when the view is removed
|
||||
comp.showing = false;
|
||||
fixture.update();
|
||||
button.click();
|
||||
expect(comp.counter).toEqual(2);
|
||||
});
|
||||
|
||||
it('should destroy listeners in for loops', () => {
|
||||
/**
|
||||
* % for (let i = 0; i < ctx.buttons; i++) {
|
||||
* <button (click)="onClick(i)"> Click me </button>
|
||||
* % }
|
||||
*/
|
||||
class AppComp {
|
||||
buttons = 2;
|
||||
counters = [0, 0];
|
||||
|
||||
onClick(index: number) {
|
||||
this.counters[index]++;
|
||||
}
|
||||
|
||||
static ɵfac = () => new AppComp();
|
||||
static ɵcmp = ɵɵdefineComponent({
|
||||
type: AppComp,
|
||||
selectors: [['app-comp']],
|
||||
decls: 1,
|
||||
vars: 0,
|
||||
template:
|
||||
function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵcontainer(0);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(0);
|
||||
{
|
||||
for (let i = 0; i < ctx.buttons; i++) {
|
||||
if (ɵɵembeddedViewStart(0, 2, 0)) {
|
||||
ɵɵelementStart(0, 'button');
|
||||
{
|
||||
ɵɵlistener('click', function() {
|
||||
return ctx.onClick(i);
|
||||
});
|
||||
ɵɵtext(1, 'Click me');
|
||||
}
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const fixture = new ComponentFixture(AppComp);
|
||||
const comp = fixture.component;
|
||||
const buttons = fixture.hostElement.querySelectorAll('button')!;
|
||||
|
||||
buttons[0].click();
|
||||
expect(comp.counters).toEqual([1, 0]);
|
||||
|
||||
buttons[1].click();
|
||||
expect(comp.counters).toEqual([1, 1]);
|
||||
|
||||
// the listener should be removed when the view is removed
|
||||
comp.buttons = 0;
|
||||
fixture.update();
|
||||
|
||||
buttons[0].click();
|
||||
buttons[1].click();
|
||||
expect(comp.counters).toEqual([1, 1]);
|
||||
});
|
||||
|
||||
it('should destroy listeners in for loops with renderer2', () => {
|
||||
/**
|
||||
* % for (let i = 0; i < ctx.buttons; i++) {
|
||||
* <button (click)="onClick(i)"> Click me </button>
|
||||
* {{ counters[i] }}
|
||||
* % }
|
||||
*/
|
||||
class AppComp {
|
||||
buttons = 2;
|
||||
counters = [0, 0];
|
||||
|
||||
onClick(index: number) {
|
||||
this.counters[index]++;
|
||||
}
|
||||
|
||||
static ɵfac = () => new AppComp();
|
||||
static ɵcmp = ɵɵdefineComponent({
|
||||
type: AppComp,
|
||||
selectors: [['app-comp']],
|
||||
decls: 1,
|
||||
vars: 0,
|
||||
template:
|
||||
function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵcontainer(0);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(0);
|
||||
{
|
||||
for (let i = 0; i < ctx.buttons; i++) {
|
||||
const rf1 = ɵɵembeddedViewStart(1, 4, 1);
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
ɵɵelementStart(0, 'button');
|
||||
{
|
||||
ɵɵlistener('click', function() {
|
||||
return ctx.onClick(i);
|
||||
});
|
||||
ɵɵtext(1, 'Click me');
|
||||
}
|
||||
ɵɵelementEnd();
|
||||
ɵɵelementStart(2, 'div');
|
||||
{ ɵɵtext(3); }
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
if (rf1 & RenderFlags.Update) {
|
||||
ɵɵselect(3);
|
||||
ɵɵtextInterpolate(ctx.counters[i]);
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const fixture = new ComponentFixture(AppComp, {rendererFactory: getRendererFactory2(document)});
|
||||
const comp = fixture.component;
|
||||
const buttons = fixture.hostElement.querySelectorAll('button')!;
|
||||
const divs = fixture.hostElement.querySelectorAll('div');
|
||||
|
||||
buttons[0].click();
|
||||
expect(comp.counters).toEqual([1, 0]);
|
||||
expect(divs[0].textContent).toEqual('0');
|
||||
expect(divs[1].textContent).toEqual('0');
|
||||
|
||||
markDirty(comp);
|
||||
requestAnimationFrame.flush();
|
||||
expect(divs[0].textContent).toEqual('1');
|
||||
expect(divs[1].textContent).toEqual('0');
|
||||
|
||||
buttons[1].click();
|
||||
expect(comp.counters).toEqual([1, 1]);
|
||||
expect(divs[0].textContent).toEqual('1');
|
||||
expect(divs[1].textContent).toEqual('0');
|
||||
|
||||
markDirty(comp);
|
||||
requestAnimationFrame.flush();
|
||||
expect(divs[0].textContent).toEqual('1');
|
||||
expect(divs[1].textContent).toEqual('1');
|
||||
|
||||
// the listener should be removed when the view is removed
|
||||
comp.buttons = 0;
|
||||
fixture.update();
|
||||
|
||||
buttons[0].click();
|
||||
buttons[1].click();
|
||||
expect(comp.counters).toEqual([1, 1]);
|
||||
});
|
||||
|
||||
it('should support host listeners on components', () => {
|
||||
let events: string[] = [];
|
||||
class MyComp {
|
||||
|
@ -718,254 +447,6 @@ describe('event listeners', () => {
|
|||
expect(comp.counter).toEqual(6);
|
||||
});
|
||||
|
||||
it('should destroy listeners in nested views', () => {
|
||||
/**
|
||||
* % if (showing) {
|
||||
* Hello
|
||||
* % if (button) {
|
||||
* <button (click)="onClick()"> Click </button>
|
||||
* % }
|
||||
* % }
|
||||
*/
|
||||
function Template(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵcontainer(0);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(0);
|
||||
{
|
||||
if (ctx.showing) {
|
||||
let rf1 = ɵɵembeddedViewStart(0, 2, 0);
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
ɵɵtext(0, 'Hello');
|
||||
ɵɵcontainer(1);
|
||||
}
|
||||
if (rf1 & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(1);
|
||||
{
|
||||
if (ctx.button) {
|
||||
let rf1 = ɵɵembeddedViewStart(0, 2, 0);
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
ɵɵelementStart(0, 'button');
|
||||
{
|
||||
ɵɵlistener('click', function() {
|
||||
return ctx.onClick();
|
||||
});
|
||||
ɵɵtext(1, 'Click');
|
||||
}
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
}
|
||||
|
||||
const comp = {
|
||||
showing: true,
|
||||
counter: 0,
|
||||
button: true,
|
||||
onClick: function() {
|
||||
this.counter++;
|
||||
}
|
||||
};
|
||||
renderToHtml(Template, comp, 1);
|
||||
const button = containerEl.querySelector('button')!;
|
||||
|
||||
button.click();
|
||||
expect(comp.counter).toEqual(1);
|
||||
|
||||
// the child view listener should be removed when the parent view is removed
|
||||
comp.showing = false;
|
||||
renderToHtml(Template, comp, 1);
|
||||
button.click();
|
||||
expect(comp.counter).toEqual(1);
|
||||
});
|
||||
|
||||
it('should destroy listeners in component views', () => {
|
||||
/**
|
||||
* % if (showing) {
|
||||
* Hello
|
||||
* <comp></comp>
|
||||
* <comp></comp>
|
||||
* % }
|
||||
*
|
||||
* comp:
|
||||
* <button (click)="onClick()"> Click </button>
|
||||
*/
|
||||
function Template(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵcontainer(0);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(0);
|
||||
{
|
||||
if (ctx.showing) {
|
||||
let rf1 = ɵɵembeddedViewStart(0, 3, 0);
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
ɵɵtext(0, 'Hello');
|
||||
ɵɵelement(1, 'comp');
|
||||
ɵɵelement(2, 'comp');
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
}
|
||||
|
||||
const ctx = {showing: true};
|
||||
renderToHtml(Template, ctx, 1, 0, [MyComp]);
|
||||
const buttons = containerEl.querySelectorAll('button')!;
|
||||
|
||||
buttons[0].click();
|
||||
expect(comps[0]!.counter).toEqual(1);
|
||||
|
||||
buttons[1].click();
|
||||
expect(comps[1]!.counter).toEqual(1);
|
||||
|
||||
// the child view listener should be removed when the parent view is removed
|
||||
ctx.showing = false;
|
||||
renderToHtml(Template, ctx, 1, 0, [MyComp]);
|
||||
buttons[0].click();
|
||||
buttons[1].click();
|
||||
expect(comps[0]!.counter).toEqual(1);
|
||||
expect(comps[1]!.counter).toEqual(1);
|
||||
});
|
||||
|
||||
it('should destroy global listeners in component views', () => {
|
||||
const ctx = {showing: true};
|
||||
|
||||
const fixture = new TemplateFixture(
|
||||
() => {
|
||||
ɵɵcontainer(0);
|
||||
},
|
||||
() => {
|
||||
ɵɵcontainerRefreshStart(0);
|
||||
{
|
||||
if (ctx.showing) {
|
||||
let rf1 = ɵɵembeddedViewStart(0, 1, 0);
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
ɵɵelement(0, 'comp');
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
},
|
||||
1, 0, [MyCompWithGlobalListeners]);
|
||||
|
||||
const body = fixture.hostElement.ownerDocument!.body;
|
||||
|
||||
body.click();
|
||||
expect(events).toEqual(['component - body:click']);
|
||||
|
||||
// the child view listener should be removed when the parent view is removed
|
||||
ctx.showing = false;
|
||||
fixture.update();
|
||||
|
||||
body.click();
|
||||
// expecting no changes in events array
|
||||
expect(events).toEqual(['component - body:click']);
|
||||
});
|
||||
|
||||
it('should support listeners with sibling nested containers', () => {
|
||||
/**
|
||||
* % if (condition) {
|
||||
* Hello
|
||||
* % if (sub1) {
|
||||
* <button (click)="counter1++">there</button>
|
||||
* % }
|
||||
*
|
||||
* % if (sub2) {
|
||||
* <button (click)="counter2++">world</button>
|
||||
* % }
|
||||
* % }
|
||||
*/
|
||||
function Template(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵcontainer(0);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(0);
|
||||
{
|
||||
if (ctx.condition) {
|
||||
let rf1 = ɵɵembeddedViewStart(0, 3, 0);
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
ɵɵtext(0, 'Hello');
|
||||
ɵɵcontainer(1);
|
||||
ɵɵcontainer(2);
|
||||
}
|
||||
if (rf1 & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(1);
|
||||
{
|
||||
if (ctx.sub1) {
|
||||
let rf1 = ɵɵembeddedViewStart(0, 2, 0);
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
ɵɵelementStart(0, 'button');
|
||||
{
|
||||
ɵɵlistener('click', function() {
|
||||
return ctx.counter1++;
|
||||
});
|
||||
ɵɵtext(1, 'Click');
|
||||
}
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
ɵɵcontainerRefreshStart(2);
|
||||
{
|
||||
if (ctx.sub2) {
|
||||
let rf1 = ɵɵembeddedViewStart(0, 2, 0);
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
ɵɵelementStart(0, 'button');
|
||||
{
|
||||
ɵɵlistener('click', function() {
|
||||
return ctx.counter2++;
|
||||
});
|
||||
ɵɵtext(1, 'Click');
|
||||
}
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
}
|
||||
|
||||
const ctx = {condition: true, counter1: 0, counter2: 0, sub1: true, sub2: true};
|
||||
renderToHtml(Template, ctx, 1);
|
||||
const buttons = containerEl.querySelectorAll('button')!;
|
||||
|
||||
buttons[0].click();
|
||||
expect(ctx.counter1).toEqual(1);
|
||||
|
||||
buttons[1].click();
|
||||
expect(ctx.counter2).toEqual(1);
|
||||
|
||||
// the child view listeners should be removed when the parent view is removed
|
||||
ctx.condition = false;
|
||||
renderToHtml(Template, ctx, 1);
|
||||
buttons[0].click();
|
||||
buttons[1].click();
|
||||
expect(ctx.counter1).toEqual(1);
|
||||
expect(ctx.counter2).toEqual(1);
|
||||
});
|
||||
|
||||
it('should support local refs in listeners', () => {
|
||||
let compInstance: any;
|
||||
|
||||
|
@ -1019,4 +500,4 @@ describe('event listeners', () => {
|
|||
button.click();
|
||||
expect(fixture.component.comp).toEqual(compInstance);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,117 +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 {EventEmitter} from '@angular/core';
|
||||
|
||||
import {ɵɵdefineComponent, ɵɵdefineDirective} from '../../src/render3/index';
|
||||
import {ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵlistener, ɵɵtext} from '../../src/render3/instructions/all';
|
||||
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
||||
|
||||
import {renderToHtml} from './render_util';
|
||||
|
||||
describe('outputs', () => {
|
||||
let buttonToggle: ButtonToggle;
|
||||
|
||||
class ButtonToggle {
|
||||
change = new EventEmitter();
|
||||
resetStream = new EventEmitter();
|
||||
|
||||
static ɵfac = () => buttonToggle = new ButtonToggle();
|
||||
static ɵcmp = ɵɵdefineComponent({
|
||||
type: ButtonToggle,
|
||||
selectors: [['button-toggle']],
|
||||
template: function(rf: RenderFlags, ctx: any) {},
|
||||
decls: 0,
|
||||
vars: 0,
|
||||
outputs: {change: 'change', resetStream: 'reset'}
|
||||
});
|
||||
}
|
||||
|
||||
let otherDir: OtherDir;
|
||||
|
||||
class OtherDir {
|
||||
changeStream = new EventEmitter();
|
||||
|
||||
static ɵfac = () => otherDir = new OtherDir;
|
||||
static ɵdir = ɵɵdefineDirective(
|
||||
{type: OtherDir, selectors: [['', 'otherDir', '']], outputs: {changeStream: 'change'}});
|
||||
}
|
||||
|
||||
|
||||
const deps = [ButtonToggle, OtherDir];
|
||||
|
||||
it('should work with outputs at same index in if block', () => {
|
||||
/**
|
||||
* <button (click)="onClick()">Click me</button> // outputs: null
|
||||
* % if (condition) {
|
||||
* <button-toggle (change)="onChange()"></button-toggle> // outputs: {change: [0, 'change']}
|
||||
* % } else {
|
||||
* <div otherDir (change)="onChange()"></div> // outputs: {change: [0,
|
||||
* 'changeStream']}
|
||||
* % }
|
||||
*/
|
||||
function Template(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵelementStart(0, 'button');
|
||||
{
|
||||
ɵɵlistener('click', function() {
|
||||
return ctx.onClick();
|
||||
});
|
||||
ɵɵtext(1, 'Click me');
|
||||
}
|
||||
ɵɵelementEnd();
|
||||
ɵɵcontainer(2);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(2);
|
||||
{
|
||||
if (ctx.condition) {
|
||||
let rf1 = ɵɵembeddedViewStart(0, 1, 0);
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
ɵɵelementStart(0, 'button-toggle');
|
||||
{
|
||||
ɵɵlistener('change', function() {
|
||||
return ctx.onChange();
|
||||
});
|
||||
}
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
} else {
|
||||
if (ɵɵembeddedViewStart(1, 1, 0)) {
|
||||
ɵɵelementStart(0, 'div', 0);
|
||||
{
|
||||
ɵɵlistener('change', function() {
|
||||
return ctx.onChange();
|
||||
});
|
||||
}
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
}
|
||||
|
||||
let counter = 0;
|
||||
const ctx = {condition: true, onChange: () => counter++, onClick: () => {}};
|
||||
const attrs = [['otherDir', '']];
|
||||
renderToHtml(Template, ctx, 3, 0, deps, null, null, false, attrs);
|
||||
|
||||
buttonToggle!.change.next();
|
||||
expect(counter).toEqual(1);
|
||||
|
||||
ctx.condition = false;
|
||||
renderToHtml(Template, ctx, 3, 0, deps, null, null, false, attrs);
|
||||
expect(counter).toEqual(1);
|
||||
|
||||
otherDir!.changeStream.next();
|
||||
expect(counter).toEqual(2);
|
||||
});
|
||||
});
|
|
@ -9,8 +9,7 @@
|
|||
import {Component as _Component, ComponentFactoryResolver, ElementRef, Injectable as _Injectable, InjectFlags, InjectionToken, InjectorType, Provider, RendererFactory2, ViewContainerRef, ɵNgModuleDef as NgModuleDef, ɵɵdefineInjectable, ɵɵdefineInjector, ɵɵinject} from '../../src/core';
|
||||
import {forwardRef} from '../../src/di/forward_ref';
|
||||
import {createInjector} from '../../src/di/r3_injector';
|
||||
import {injectComponentFactoryResolver, ɵɵadvance, ɵɵdefineComponent, ɵɵdefineDirective, ɵɵdirectiveInject, ɵɵProvidersFeature, ɵɵtextInterpolate1} from '../../src/render3/index';
|
||||
import {ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵtext, ɵɵtextInterpolate} from '../../src/render3/instructions/all';
|
||||
import {injectComponentFactoryResolver, ɵɵdefineComponent, ɵɵdefineDirective, ɵɵdirectiveInject, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵProvidersFeature, ɵɵtext, ɵɵtextInterpolate1} from '../../src/render3/index';
|
||||
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
||||
import {NgModuleFactory} from '../../src/render3/ng_module_ref';
|
||||
import {getInjector} from '../../src/render3/util/discovery_utils';
|
||||
|
@ -983,192 +982,6 @@ describe('providers', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('- embedded views', () => {
|
||||
it('should have access to viewProviders of the host component', () => {
|
||||
@Component({
|
||||
template: '{{s}}{{n}}',
|
||||
})
|
||||
class Repeated {
|
||||
constructor(private s: String, private n: Number) {}
|
||||
|
||||
static ɵfac =
|
||||
() => {
|
||||
return new Repeated(ɵɵdirectiveInject(String), ɵɵdirectiveInject(Number));
|
||||
}
|
||||
|
||||
static ɵcmp = ɵɵdefineComponent({
|
||||
type: Repeated,
|
||||
selectors: [['repeated']],
|
||||
decls: 2,
|
||||
vars: 2,
|
||||
template:
|
||||
function(fs: RenderFlags, ctx: Repeated) {
|
||||
if (fs & RenderFlags.Create) {
|
||||
ɵɵtext(0);
|
||||
ɵɵtext(1);
|
||||
}
|
||||
if (fs & RenderFlags.Update) {
|
||||
ɵɵtextInterpolate(ctx.s);
|
||||
ɵɵadvance(1);
|
||||
ɵɵtextInterpolate(ctx.n);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `<div>
|
||||
% for (let i = 0; i < 3; i++) {
|
||||
<repeated></repeated>
|
||||
% }
|
||||
</div>`,
|
||||
providers: [{provide: Number, useValue: 1, multi: true}],
|
||||
viewProviders:
|
||||
[{provide: String, useValue: 'foo'}, {provide: Number, useValue: 2, multi: true}],
|
||||
})
|
||||
class ComponentWithProviders {
|
||||
static ɵfac = () => new ComponentWithProviders();
|
||||
static ɵcmp = ɵɵdefineComponent({
|
||||
type: ComponentWithProviders,
|
||||
selectors: [['component-with-providers']],
|
||||
decls: 2,
|
||||
vars: 0,
|
||||
template:
|
||||
function(fs: RenderFlags, ctx: ComponentWithProviders) {
|
||||
if (fs & RenderFlags.Create) {
|
||||
ɵɵelementStart(0, 'div');
|
||||
{ ɵɵcontainer(1); }
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
if (fs & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(1);
|
||||
{
|
||||
for (let i = 0; i < 3; i++) {
|
||||
let rf1 = ɵɵembeddedViewStart(1, 1, 0);
|
||||
{
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
ɵɵelement(0, 'repeated');
|
||||
}
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
},
|
||||
features:
|
||||
[
|
||||
ɵɵProvidersFeature(
|
||||
[{provide: Number, useValue: 1, multi: true}],
|
||||
[
|
||||
{provide: String, useValue: 'foo'},
|
||||
{provide: Number, useValue: 2, multi: true}
|
||||
]),
|
||||
],
|
||||
directives: [Repeated]
|
||||
});
|
||||
}
|
||||
|
||||
const fixture = new ComponentFixture(ComponentWithProviders);
|
||||
expect(fixture.html)
|
||||
.toEqual(
|
||||
'<div><repeated>foo1,2</repeated><repeated>foo1,2</repeated><repeated>foo1,2</repeated></div>');
|
||||
});
|
||||
|
||||
it('should have access to viewProviders of the repeated component', () => {
|
||||
@Component({
|
||||
template: '{{s}}{{n}}',
|
||||
providers: [{provide: Number, useValue: 1, multi: true}],
|
||||
viewProviders:
|
||||
[{provide: String, useValue: 'bar'}, {provide: Number, useValue: 2, multi: true}]
|
||||
})
|
||||
class Repeated {
|
||||
constructor(private s: String, private n: Number) {}
|
||||
|
||||
static ɵfac =
|
||||
() => {
|
||||
return new Repeated(ɵɵdirectiveInject(String), ɵɵdirectiveInject(Number));
|
||||
}
|
||||
|
||||
static ɵcmp = ɵɵdefineComponent({
|
||||
type: Repeated,
|
||||
selectors: [['repeated']],
|
||||
decls: 2,
|
||||
vars: 2,
|
||||
template:
|
||||
function(fs: RenderFlags, ctx: Repeated) {
|
||||
if (fs & RenderFlags.Create) {
|
||||
ɵɵtext(0);
|
||||
ɵɵtext(1);
|
||||
}
|
||||
if (fs & RenderFlags.Update) {
|
||||
ɵɵtextInterpolate(ctx.s);
|
||||
ɵɵadvance(1);
|
||||
ɵɵtextInterpolate(ctx.n);
|
||||
}
|
||||
},
|
||||
features:
|
||||
[
|
||||
ɵɵProvidersFeature(
|
||||
[{provide: Number, useValue: 1, multi: true}],
|
||||
[
|
||||
{provide: String, useValue: 'bar'},
|
||||
{provide: Number, useValue: 2, multi: true}
|
||||
]),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `<div>
|
||||
% for (let i = 0; i < 3; i++) {
|
||||
<repeated></repeated>
|
||||
% }
|
||||
</div>`,
|
||||
viewProviders: [{provide: toString, useValue: 'foo'}],
|
||||
})
|
||||
class ComponentWithProviders {
|
||||
static ɵfac = () => new ComponentWithProviders();
|
||||
static ɵcmp = ɵɵdefineComponent({
|
||||
type: ComponentWithProviders,
|
||||
selectors: [['component-with-providers']],
|
||||
decls: 2,
|
||||
vars: 0,
|
||||
template:
|
||||
function(fs: RenderFlags, ctx: ComponentWithProviders) {
|
||||
if (fs & RenderFlags.Create) {
|
||||
ɵɵelementStart(0, 'div');
|
||||
{ ɵɵcontainer(1); }
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
if (fs & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(1);
|
||||
{
|
||||
for (let i = 0; i < 3; i++) {
|
||||
let rf1 = ɵɵembeddedViewStart(1, 1, 0);
|
||||
{
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
ɵɵelement(0, 'repeated');
|
||||
}
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
},
|
||||
features: [ɵɵProvidersFeature([], [{provide: String, useValue: 'foo'}])],
|
||||
directives: [Repeated]
|
||||
});
|
||||
}
|
||||
|
||||
const fixture = new ComponentFixture(ComponentWithProviders);
|
||||
expect(fixture.html)
|
||||
.toEqual(
|
||||
'<div><repeated>bar1,2</repeated><repeated>bar1,2</repeated><repeated>bar1,2</repeated></div>');
|
||||
});
|
||||
});
|
||||
|
||||
describe('- dynamic components dependency resolution', () => {
|
||||
let hostComponent: HostComponent|null = null;
|
||||
|
||||
|
@ -1469,118 +1282,6 @@ describe('providers', () => {
|
|||
expect(injector.get(Some).location).toEqual('From app component');
|
||||
});
|
||||
});
|
||||
|
||||
describe('lifecycles', () => {
|
||||
it('should execute ngOnDestroy hooks on providers (and only this one)', () => {
|
||||
const logs: string[] = [];
|
||||
|
||||
@Injectable()
|
||||
class InjectableWithLifeCycleHooks {
|
||||
ngOnChanges() {
|
||||
logs.push('Injectable OnChanges');
|
||||
}
|
||||
ngOnInit() {
|
||||
logs.push('Injectable OnInit');
|
||||
}
|
||||
ngDoCheck() {
|
||||
logs.push('Injectable DoCheck');
|
||||
}
|
||||
ngAfterContentInit() {
|
||||
logs.push('Injectable AfterContentInit');
|
||||
}
|
||||
ngAfterContentChecked() {
|
||||
logs.push('Injectable AfterContentChecked');
|
||||
}
|
||||
ngAfterViewInit() {
|
||||
logs.push('Injectable AfterViewInit');
|
||||
}
|
||||
ngAfterViewChecked() {
|
||||
logs.push('Injectable gAfterViewChecked');
|
||||
}
|
||||
ngOnDestroy() {
|
||||
logs.push('Injectable OnDestroy');
|
||||
}
|
||||
}
|
||||
|
||||
@Component({template: `<span></span>`, providers: [InjectableWithLifeCycleHooks]})
|
||||
class MyComponent {
|
||||
constructor(foo: InjectableWithLifeCycleHooks) {}
|
||||
|
||||
static ɵfac =
|
||||
() => {
|
||||
return new MyComponent(ɵɵdirectiveInject(InjectableWithLifeCycleHooks));
|
||||
}
|
||||
|
||||
static ɵcmp = ɵɵdefineComponent({
|
||||
type: MyComponent,
|
||||
selectors: [['my-comp']],
|
||||
decls: 1,
|
||||
vars: 0,
|
||||
template:
|
||||
(rf: RenderFlags, ctx: MyComponent) => {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵelement(0, 'span');
|
||||
}
|
||||
},
|
||||
features: [ɵɵProvidersFeature([InjectableWithLifeCycleHooks])]
|
||||
});
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<div>
|
||||
% if (ctx.condition) {
|
||||
<my-comp></my-comp>
|
||||
% }
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
class App {
|
||||
public condition = true;
|
||||
|
||||
static ɵfac = () => new App();
|
||||
static ɵcmp = ɵɵdefineComponent({
|
||||
type: App,
|
||||
selectors: [['app-cmp']],
|
||||
decls: 2,
|
||||
vars: 0,
|
||||
template:
|
||||
(rf: RenderFlags, ctx: App) => {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵelementStart(0, 'div');
|
||||
{ ɵɵcontainer(1); }
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(1);
|
||||
{
|
||||
if (ctx.condition) {
|
||||
let rf1 = ɵɵembeddedViewStart(1, 2, 1);
|
||||
{
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
ɵɵelement(0, 'my-comp');
|
||||
}
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
},
|
||||
directives: [MyComponent]
|
||||
});
|
||||
}
|
||||
|
||||
const fixture = new ComponentFixture(App);
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<div><my-comp><span></span></my-comp></div>');
|
||||
|
||||
fixture.component.condition = false;
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<div></div>');
|
||||
expect(logs).toEqual(['Injectable OnDestroy']);
|
||||
});
|
||||
});
|
||||
});
|
||||
interface ComponentTest {
|
||||
providers?: Provider[];
|
||||
|
|
|
@ -1,89 +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 {ɵɵdefineComponent, ɵɵproperty, ɵɵselect} from '../../src/render3/index';
|
||||
import {ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart} from '../../src/render3/instructions/all';
|
||||
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
||||
import {ɵɵpureFunction2} from '../../src/render3/pure_function';
|
||||
import {getDirectiveOnNode, renderToHtml} from '../../test/render3/render_util';
|
||||
|
||||
|
||||
describe('object literals', () => {
|
||||
let objectComp: ObjectComp;
|
||||
|
||||
class ObjectComp {
|
||||
// TODO(issue/24571): remove '!'.
|
||||
config!: {[key: string]: any};
|
||||
|
||||
static ɵfac = function ObjectComp_Factory() {
|
||||
return objectComp = new ObjectComp();
|
||||
};
|
||||
static ɵcmp = ɵɵdefineComponent({
|
||||
type: ObjectComp,
|
||||
selectors: [['object-comp']],
|
||||
decls: 0,
|
||||
vars: 1,
|
||||
template: function ObjectComp_Template() {},
|
||||
inputs: {config: 'config'}
|
||||
});
|
||||
}
|
||||
|
||||
const defs = [ObjectComp];
|
||||
|
||||
// NOTE: This test cannot be ported to acceptance tests with TestBed because
|
||||
// the syntax is still unsupported.
|
||||
it('should support multiple view instances with multiple bindings', () => {
|
||||
let objectComps: ObjectComp[] = [];
|
||||
|
||||
/**
|
||||
* % for(let i = 0; i < 2; i++) {
|
||||
* <object-comp [config]="{opacity: configs[i].opacity, duration: configs[i].duration}">
|
||||
* </object-comp>
|
||||
* % }
|
||||
*/
|
||||
function Template(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵcontainer(0);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(0);
|
||||
{
|
||||
for (let i = 0; i < 2; i++) {
|
||||
let rf1 = ɵɵembeddedViewStart(0, 1, 4);
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
ɵɵelementStart(0, 'object-comp');
|
||||
objectComps.push(getDirectiveOnNode(0));
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
if (rf1 & RenderFlags.Update) {
|
||||
ɵɵselect(0);
|
||||
ɵɵproperty(
|
||||
'config',
|
||||
ɵɵpureFunction2(1, e0_ff, ctx.configs[i].opacity, ctx.configs[i].duration));
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
}
|
||||
|
||||
const e0_ff = (v1: any, v2: any) => {
|
||||
return {opacity: v1, duration: v2};
|
||||
};
|
||||
|
||||
const configs = [{opacity: 0, duration: 500}, {opacity: 1, duration: 600}];
|
||||
renderToHtml(Template, {configs}, 1, 0, defs);
|
||||
expect(objectComps[0].config).toEqual({opacity: 0, duration: 500});
|
||||
expect(objectComps[1].config).toEqual({opacity: 1, duration: 600});
|
||||
|
||||
configs[0].duration = 1000;
|
||||
renderToHtml(Template, {configs}, 1, 0, defs);
|
||||
expect(objectComps[0].config).toEqual({opacity: 0, duration: 1000});
|
||||
expect(objectComps[1].config).toEqual({opacity: 1, duration: 600});
|
||||
});
|
||||
});
|
|
@ -8,9 +8,8 @@
|
|||
|
||||
import {ElementRef, QueryList, TemplateRef, ViewContainerRef} from '@angular/core';
|
||||
|
||||
import {EventEmitter} from '../..';
|
||||
import {AttributeMarker, ɵɵdefineComponent, ɵɵdefineDirective, ɵɵProvidersFeature} from '../../src/render3/index';
|
||||
import {ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵdirectiveInject, ɵɵelement, ɵɵelementContainerEnd, ɵɵelementContainerStart, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵtemplate, ɵɵtext} from '../../src/render3/instructions/all';
|
||||
import {ɵɵdirectiveInject, ɵɵelement, ɵɵelementContainerEnd, ɵɵelementContainerStart, ɵɵelementEnd, ɵɵelementStart, ɵɵtemplate, ɵɵtext} from '../../src/render3/instructions/all';
|
||||
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
||||
import {ɵɵcontentQuery, ɵɵloadQuery, ɵɵqueryRefresh, ɵɵviewQuery} from '../../src/render3/query';
|
||||
import {getLView} from '../../src/render3/state';
|
||||
|
@ -1298,68 +1297,6 @@ describe('query', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('queryList', () => {
|
||||
it('should be destroyed when the containing view is destroyed', () => {
|
||||
let queryInstance: QueryList<any>;
|
||||
|
||||
const SimpleComponentWithQuery = createComponent(
|
||||
'some-component-with-query',
|
||||
function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵelement(0, 'div', null, 0);
|
||||
}
|
||||
},
|
||||
2, 0, [], [],
|
||||
function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵviewQuery(['foo'], false);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
let tmp: any;
|
||||
ɵɵqueryRefresh(tmp = ɵɵloadQuery<QueryList<any>>()) &&
|
||||
(ctx.query = queryInstance = tmp as QueryList<any>);
|
||||
}
|
||||
},
|
||||
[], [], undefined, [['foo', '']]);
|
||||
|
||||
function createTemplate() {
|
||||
ɵɵcontainer(0);
|
||||
}
|
||||
|
||||
function updateTemplate() {
|
||||
ɵɵcontainerRefreshStart(0);
|
||||
{
|
||||
if (condition) {
|
||||
let rf1 = ɵɵembeddedViewStart(1, 1, 0);
|
||||
{
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
ɵɵelement(0, 'some-component-with-query');
|
||||
}
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
|
||||
/**
|
||||
* % if (condition) {
|
||||
* <some-component-with-query></some-component-with-query>
|
||||
* %}
|
||||
*/
|
||||
let condition = true;
|
||||
const t =
|
||||
new TemplateFixture(createTemplate, updateTemplate, 1, 0, [SimpleComponentWithQuery]);
|
||||
expect(t.html).toEqual('<some-component-with-query><div></div></some-component-with-query>');
|
||||
expect((queryInstance!.changes as EventEmitter<any>).closed).toBeFalsy();
|
||||
|
||||
condition = false;
|
||||
t.update();
|
||||
expect(t.html).toEqual('');
|
||||
expect((queryInstance!.changes as EventEmitter<any>).closed).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('should restore queries if view changes', () => {
|
||||
class SomeDir {
|
||||
constructor(public vcr: ViewContainerRef, public temp: TemplateRef<any>) {
|
||||
|
@ -1481,55 +1418,6 @@ describe('query', () => {
|
|||
`Expected content query results to be available when ngAfterContentChecked was called.`);
|
||||
});
|
||||
|
||||
it('should support content queries for directives within repeated embedded views', () => {
|
||||
/**
|
||||
* % for (let i = 0; i < 3; i++) {
|
||||
* <div with-content>
|
||||
* <span #foo></span>
|
||||
* </div>
|
||||
* % }
|
||||
*/
|
||||
const AppComponent = createComponent(
|
||||
'app-component',
|
||||
function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵcontainer(0);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(0);
|
||||
{
|
||||
for (let i = 0; i < 3; i++) {
|
||||
let rf = ɵɵembeddedViewStart(1, 3, 0);
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵelementStart(0, 'div', 0);
|
||||
{ ɵɵelement(1, 'span', null, 1); }
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
},
|
||||
1, 0, [WithContentDirective], [], null, [], [], undefined,
|
||||
[[AttributeMarker.Bindings, 'with-content'], ['foo', '']]);
|
||||
|
||||
const fixture = new ComponentFixture(AppComponent);
|
||||
expect(withContentInstance!.foos.length)
|
||||
.toBe(1, `Expected content query to match <span #foo>.`);
|
||||
|
||||
expect(withContentInstance!.contentInitQuerySnapshot)
|
||||
.toBe(
|
||||
1,
|
||||
`Expected content query results to be available when ngAfterContentInit was called.`);
|
||||
|
||||
expect(withContentInstance!.contentCheckedQuerySnapshot)
|
||||
.toBe(
|
||||
1,
|
||||
`Expected content query results to be available when ngAfterContentChecked was called.`);
|
||||
});
|
||||
|
||||
it('should not match directive host with content queries', () => {
|
||||
/**
|
||||
* <div with-content #foo>
|
||||
|
|
|
@ -8,11 +8,11 @@
|
|||
|
||||
import {RendererType2, ViewEncapsulation} from '../../src/core';
|
||||
import {ɵɵdefineComponent} from '../../src/render3/index';
|
||||
import {ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵtext} from '../../src/render3/instructions/all';
|
||||
import {ɵɵelement, ɵɵtext} from '../../src/render3/instructions/all';
|
||||
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
||||
|
||||
import {getRendererFactory2} from './imported_renderer2';
|
||||
import {document, renderToHtml, TemplateFixture} from './render_util';
|
||||
import {document, renderToHtml} from './render_util';
|
||||
|
||||
describe('renderer factory lifecycle', () => {
|
||||
let logs: string[] = [];
|
||||
|
@ -109,101 +109,3 @@ describe('renderer factory lifecycle', () => {
|
|||
expect(logs).toEqual(['begin', 'function_with_component update', 'component update', 'end']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Renderer2 destruction hooks', () => {
|
||||
const rendererFactory = getRendererFactory2(document);
|
||||
|
||||
it('should call renderer.destroyNode for each node destroyed', () => {
|
||||
let condition = true;
|
||||
|
||||
function createTemplate() {
|
||||
ɵɵelementStart(0, 'div');
|
||||
{ ɵɵcontainer(1); }
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
|
||||
function updateTemplate() {
|
||||
ɵɵcontainerRefreshStart(1);
|
||||
{
|
||||
if (condition) {
|
||||
let rf1 = ɵɵembeddedViewStart(1, 3, 0);
|
||||
{
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
ɵɵelement(0, 'span');
|
||||
ɵɵelement(1, 'span');
|
||||
ɵɵelement(2, 'span');
|
||||
}
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
|
||||
const t = new TemplateFixture(
|
||||
createTemplate, updateTemplate, 2, 0, null, null, null, rendererFactory);
|
||||
|
||||
expect(t.html).toEqual('<div><span></span><span></span><span></span></div>');
|
||||
|
||||
condition = false;
|
||||
t.update();
|
||||
expect(t.html).toEqual('<div></div>');
|
||||
expect(ngDevMode).toHaveProperties({rendererDestroy: 0, rendererDestroyNode: 3});
|
||||
});
|
||||
|
||||
it('should call renderer.destroy for each component destroyed', () => {
|
||||
class SimpleComponent {
|
||||
static ɵfac = () => new SimpleComponent;
|
||||
static ɵcmp = ɵɵdefineComponent({
|
||||
type: SimpleComponent,
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
selectors: [['simple']],
|
||||
decls: 1,
|
||||
vars: 0,
|
||||
template:
|
||||
function(rf: RenderFlags, ctx: SimpleComponent) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵelement(0, 'span');
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
let condition = true;
|
||||
|
||||
function createTemplate() {
|
||||
ɵɵelementStart(0, 'div');
|
||||
{ ɵɵcontainer(1); }
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
|
||||
function updateTemplate() {
|
||||
ɵɵcontainerRefreshStart(1);
|
||||
{
|
||||
if (condition) {
|
||||
let rf1 = ɵɵembeddedViewStart(1, 3, 0);
|
||||
{
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
ɵɵelement(0, 'simple');
|
||||
ɵɵelement(1, 'span');
|
||||
ɵɵelement(2, 'simple');
|
||||
}
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
|
||||
const t = new TemplateFixture(
|
||||
createTemplate, updateTemplate, 2, 0, [SimpleComponent], null, null, rendererFactory);
|
||||
|
||||
expect(t.html).toEqual(
|
||||
'<div><simple><span></span></simple><span></span><simple><span></span></simple></div>');
|
||||
|
||||
condition = false;
|
||||
t.update();
|
||||
expect(t.html).toEqual('<div></div>');
|
||||
expect(ngDevMode).toHaveProperties({rendererDestroy: 2, rendererDestroyNode: 3});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,16 +6,16 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ChangeDetectorRef, Component as _Component, ComponentFactoryResolver, ComponentRef, ElementRef, QueryList, TemplateRef, ViewContainerRef, ViewRef,} from '../../src/core';
|
||||
import {ChangeDetectorRef, Component as _Component, ComponentFactoryResolver, ElementRef, QueryList, TemplateRef, ViewContainerRef, ViewRef} from '../../src/core';
|
||||
import {ViewEncapsulation} from '../../src/metadata';
|
||||
import {injectComponentFactoryResolver, ɵɵdefineComponent, ɵɵdefineDirective, ɵɵlistener, ɵɵloadQuery, ɵɵqueryRefresh, ɵɵviewQuery,} from '../../src/render3/index';
|
||||
|
||||
import {ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵdirectiveInject, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵtemplate, ɵɵtext,} from '../../src/render3/instructions/all';
|
||||
import {injectComponentFactoryResolver, ɵɵdefineComponent, ɵɵdefineDirective, ɵɵlistener, ɵɵloadQuery, ɵɵqueryRefresh, ɵɵviewQuery} from '../../src/render3/index';
|
||||
import {ɵɵdirectiveInject, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵtemplate, ɵɵtext} from '../../src/render3/instructions/all';
|
||||
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
||||
import {RElement} from '../../src/render3/interfaces/renderer';
|
||||
import {getLView} from '../../src/render3/state';
|
||||
import {getNativeByIndex} from '../../src/render3/util/view_utils';
|
||||
import {ComponentFixture, createComponent, TemplateFixture,} from './render_util';
|
||||
|
||||
import {ComponentFixture, createComponent, TemplateFixture} from './render_util';
|
||||
|
||||
const Component: typeof _Component = function(...args: any[]): any {
|
||||
// In test we use @Component for documentation only so it's safe to mock out the implementation.
|
||||
|
@ -133,110 +133,6 @@ describe('ViewContainerRef', () => {
|
|||
directiveInstances![0].insertTpl({});
|
||||
expect(fixture.html).toEqual('before|AB|after');
|
||||
});
|
||||
|
||||
|
||||
it('should add embedded views at the right position in the DOM tree (ng-template next to a JS block)',
|
||||
() => {
|
||||
let directiveInstance: TestDirective;
|
||||
|
||||
class TestDirective {
|
||||
static ɵfac = () => directiveInstance = new TestDirective(
|
||||
ɵɵdirectiveInject(ViewContainerRef as any), ɵɵdirectiveInject(TemplateRef as any))
|
||||
|
||||
static ɵdir =
|
||||
ɵɵdefineDirective({type: TestDirective, selectors: [['', 'testdir', '']]});
|
||||
|
||||
constructor(private _vcRef: ViewContainerRef, private _tplRef: TemplateRef<{}>) {}
|
||||
|
||||
insertTpl(ctx: {}) {
|
||||
this._vcRef.createEmbeddedView(this._tplRef, ctx);
|
||||
}
|
||||
|
||||
insertTpl2(ctx: {}) {
|
||||
const viewRef = this._tplRef.createEmbeddedView(ctx);
|
||||
this._vcRef.insert(viewRef);
|
||||
}
|
||||
|
||||
remove(index?: number) {
|
||||
this._vcRef.remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
function EmbeddedTemplateA(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵtext(0, 'A');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* before|
|
||||
* <ng-template testDir>A<ng-template>
|
||||
* % if (condition) {
|
||||
* B
|
||||
* % }
|
||||
* |after
|
||||
*/
|
||||
class TestComponent {
|
||||
condition = false;
|
||||
// TODO(issue/24571): remove '!'.
|
||||
testDir!: TestDirective;
|
||||
static ɵfac = () => new TestComponent();
|
||||
static ɵcmp = ɵɵdefineComponent({
|
||||
type: TestComponent,
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
selectors: [['test-cmp']],
|
||||
decls: 4,
|
||||
vars: 0,
|
||||
consts: [['testdir', '']],
|
||||
template:
|
||||
(rf: RenderFlags, cmp: TestComponent) => {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵtext(0, 'before|');
|
||||
ɵɵtemplate(1, EmbeddedTemplateA, 1, 0, 'ng-template', 0);
|
||||
ɵɵcontainer(2);
|
||||
ɵɵtext(3, '|after');
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(2);
|
||||
{
|
||||
if (cmp.condition) {
|
||||
let rf1 = ɵɵembeddedViewStart(0, 1, 0);
|
||||
{
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
ɵɵtext(0, 'B');
|
||||
}
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
},
|
||||
directives: [TestDirective]
|
||||
});
|
||||
}
|
||||
|
||||
const fixture = new ComponentFixture(TestComponent);
|
||||
expect(fixture.html).toEqual('before||after');
|
||||
|
||||
fixture.component.condition = true;
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('before|B|after');
|
||||
|
||||
directiveInstance!.insertTpl({});
|
||||
expect(fixture.html).toEqual('before|AB|after');
|
||||
|
||||
fixture.component.condition = false;
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('before|A|after');
|
||||
|
||||
directiveInstance!.insertTpl2({});
|
||||
expect(fixture.html).toEqual('before|AA|after');
|
||||
|
||||
fixture.component.condition = true;
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('before|AAB|after');
|
||||
});
|
||||
});
|
||||
|
||||
describe('createComponent', () => {
|
||||
|
|
Loading…
Reference in New Issue