fix(ivy): queries should match container node itself before matching its views (#28473)
PR Close #28473
This commit is contained in:
parent
6656328538
commit
1b6d8a78b0
|
@ -31,11 +31,11 @@ import {CssSelectorList, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection'
|
||||||
import {LQueries} from './interfaces/query';
|
import {LQueries} from './interfaces/query';
|
||||||
import {GlobalTargetResolver, ProceduralRenderer3, RComment, RElement, RText, Renderer3, RendererFactory3, isProceduralRenderer} from './interfaces/renderer';
|
import {GlobalTargetResolver, ProceduralRenderer3, RComment, RElement, RText, Renderer3, RendererFactory3, isProceduralRenderer} from './interfaces/renderer';
|
||||||
import {SanitizerFn} from './interfaces/sanitization';
|
import {SanitizerFn} from './interfaces/sanitization';
|
||||||
import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTENT_QUERIES, CONTEXT, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, HOST_NODE, INJECTOR, InitPhaseState, LView, LViewFlags, NEXT, OpaqueViewState, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TAIL, TData, TVIEW, TView} from './interfaces/view';
|
import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTEXT, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, HOST_NODE, INJECTOR, InitPhaseState, LView, LViewFlags, NEXT, OpaqueViewState, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TAIL, TData, TVIEW, TView} from './interfaces/view';
|
||||||
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
|
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
|
||||||
import {appendChild, appendProjectedNode, createTextNode, getLViewChild, insertView, removeView} from './node_manipulation';
|
import {appendChild, appendProjectedNode, createTextNode, getLViewChild, insertView, removeView} from './node_manipulation';
|
||||||
import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher';
|
import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher';
|
||||||
import {decreaseElementDepthCount, enterView, getBindingsEnabled, getCheckNoChangesMode, getContextLView, getCurrentDirectiveDef, getCurrentQueryIndex, getElementDepthCount, getIsParent, getLView, getPreviousOrParentTNode, increaseElementDepthCount, isCreationMode, leaveView, nextContextImpl, resetComponentState, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setCurrentQueryIndex, setIsParent, setPreviousOrParentTNode} from './state';
|
import {decreaseElementDepthCount, enterView, getBindingsEnabled, getCheckNoChangesMode, getContextLView, getCurrentDirectiveDef, getElementDepthCount, getIsParent, getLView, getPreviousOrParentTNode, increaseElementDepthCount, isCreationMode, leaveView, nextContextImpl, resetComponentState, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setCurrentQueryIndex, setIsParent, setPreviousOrParentTNode} from './state';
|
||||||
import {getInitialClassNameValue, initializeStaticContext as initializeStaticStylingContext, patchContextWithStaticAttrs, renderInitialStylesAndClasses, renderStyling, updateClassProp as updateElementClassProp, updateContextWithBindings, updateStyleProp as updateElementStyleProp, updateStylingMap} from './styling/class_and_style_bindings';
|
import {getInitialClassNameValue, initializeStaticContext as initializeStaticStylingContext, patchContextWithStaticAttrs, renderInitialStylesAndClasses, renderStyling, updateClassProp as updateElementClassProp, updateContextWithBindings, updateStyleProp as updateElementStyleProp, updateStylingMap} from './styling/class_and_style_bindings';
|
||||||
import {BoundPlayerFactory} from './styling/player_factory';
|
import {BoundPlayerFactory} from './styling/player_factory';
|
||||||
import {createEmptyStylingContext, getStylingContext, hasClassInput, hasStyling, isAnimationProp} from './styling/util';
|
import {createEmptyStylingContext, getStylingContext, hasClassInput, hasStyling, isAnimationProp} from './styling/util';
|
||||||
|
@ -324,7 +324,7 @@ export function renderTemplate<T>(
|
||||||
* Such lViewNode will then be renderer with renderEmbeddedTemplate() (see below).
|
* Such lViewNode will then be renderer with renderEmbeddedTemplate() (see below).
|
||||||
*/
|
*/
|
||||||
export function createEmbeddedViewAndNode<T>(
|
export function createEmbeddedViewAndNode<T>(
|
||||||
tView: TView, context: T, declarationView: LView, renderer: Renderer3, queries: LQueries | null,
|
tView: TView, context: T, declarationView: LView, queries: LQueries | null,
|
||||||
injectorIndex: number): LView {
|
injectorIndex: number): LView {
|
||||||
const _isParent = getIsParent();
|
const _isParent = getIsParent();
|
||||||
const _previousOrParentTNode = getPreviousOrParentTNode();
|
const _previousOrParentTNode = getPreviousOrParentTNode();
|
||||||
|
@ -2100,12 +2100,7 @@ export function template(
|
||||||
}
|
}
|
||||||
|
|
||||||
createDirectivesAndLocals(tView, lView, localRefs, localRefExtractor);
|
createDirectivesAndLocals(tView, lView, localRefs, localRefExtractor);
|
||||||
|
addTContainerToQueries(lView, tContainerNode);
|
||||||
const currentQueries = lView[QUERIES];
|
|
||||||
if (currentQueries) {
|
|
||||||
lView[QUERIES] = currentQueries.addNode(tContainerNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
attachPatchData(getNativeByTNode(tContainerNode, lView), lView);
|
attachPatchData(getNativeByTNode(tContainerNode, lView), lView);
|
||||||
registerPostOrderHooks(tView, tContainerNode);
|
registerPostOrderHooks(tView, tContainerNode);
|
||||||
setIsParent(false);
|
setIsParent(false);
|
||||||
|
@ -2126,6 +2121,7 @@ export function container(index: number): void {
|
||||||
if (lView[TVIEW].firstTemplatePass) {
|
if (lView[TVIEW].firstTemplatePass) {
|
||||||
tNode.tViews = [];
|
tNode.tViews = [];
|
||||||
}
|
}
|
||||||
|
addTContainerToQueries(lView, tNode);
|
||||||
setIsParent(false);
|
setIsParent(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2148,16 +2144,28 @@ function containerInternal(
|
||||||
// because views can be removed and re-inserted.
|
// because views can be removed and re-inserted.
|
||||||
addToViewTree(lView, index + HEADER_OFFSET, lContainer);
|
addToViewTree(lView, index + HEADER_OFFSET, lContainer);
|
||||||
|
|
||||||
const currentQueries = lView[QUERIES];
|
|
||||||
if (currentQueries) {
|
|
||||||
// prepare place for matching nodes from views inserted into a given container
|
|
||||||
lContainer[QUERIES] = currentQueries.container();
|
|
||||||
}
|
|
||||||
|
|
||||||
ngDevMode && assertNodeType(getPreviousOrParentTNode(), TNodeType.Container);
|
ngDevMode && assertNodeType(getPreviousOrParentTNode(), TNodeType.Container);
|
||||||
return tNode;
|
return tNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reporting a TContainer node queries is a 2-step process as we need to:
|
||||||
|
* - check if the container node itself is matching (query might match a <ng-template> node);
|
||||||
|
* - prepare room for nodes from views that might be created based on the TemplateRef linked to this
|
||||||
|
* container.
|
||||||
|
*
|
||||||
|
* Those 2 operations need to happen in the specific order (match the container node itself, then
|
||||||
|
* prepare space for nodes from views).
|
||||||
|
*/
|
||||||
|
function addTContainerToQueries(lView: LView, tContainerNode: TContainerNode): void {
|
||||||
|
const queries = lView[QUERIES];
|
||||||
|
if (queries) {
|
||||||
|
lView[QUERIES] = queries.addNode(tContainerNode);
|
||||||
|
const lContainer = lView[tContainerNode.index];
|
||||||
|
lContainer[QUERIES] = queries.container();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets a container up to receive views.
|
* Sets a container up to receive views.
|
||||||
*
|
*
|
||||||
|
|
|
@ -35,7 +35,7 @@ export const enum TNodeFlags {
|
||||||
/** This bit is set if the node has been projected */
|
/** This bit is set if the node has been projected */
|
||||||
isProjected = 0b0010,
|
isProjected = 0b0010,
|
||||||
|
|
||||||
/** This bit is set if the node has any content queries */
|
/** This bit is set if any directive on this node has content queries */
|
||||||
hasContentQuery = 0b0100,
|
hasContentQuery = 0b0100,
|
||||||
|
|
||||||
/** This bit is set if the node has any directives that contain [class properties */
|
/** This bit is set if the node has any directives that contain [class properties */
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ChangeDetectorRef as ViewEngine_ChangeDetectorRef} from '../change_detection/change_detector_ref';
|
import {ChangeDetectorRef as ViewEngine_ChangeDetectorRef} from '../change_detection/change_detector_ref';
|
||||||
import {Injector, NullInjector} from '../di/injector';
|
import {Injector} from '../di/injector';
|
||||||
import {ComponentFactory as viewEngine_ComponentFactory, ComponentRef as viewEngine_ComponentRef} from '../linker/component_factory';
|
import {ComponentFactory as viewEngine_ComponentFactory, ComponentRef as viewEngine_ComponentRef} from '../linker/component_factory';
|
||||||
import {ElementRef as ViewEngine_ElementRef} from '../linker/element_ref';
|
import {ElementRef as ViewEngine_ElementRef} from '../linker/element_ref';
|
||||||
import {NgModuleRef as viewEngine_NgModuleRef} from '../linker/ng_module_factory';
|
import {NgModuleRef as viewEngine_NgModuleRef} from '../linker/ng_module_factory';
|
||||||
|
@ -19,9 +19,7 @@ import {assertDefined, assertGreaterThan, assertLessThan} from '../util/assert';
|
||||||
import {NodeInjector, getParentInjectorLocation} from './di';
|
import {NodeInjector, getParentInjectorLocation} from './di';
|
||||||
import {addToViewTree, createEmbeddedViewAndNode, createLContainer, renderEmbeddedTemplate} from './instructions';
|
import {addToViewTree, createEmbeddedViewAndNode, createLContainer, renderEmbeddedTemplate} from './instructions';
|
||||||
import {ACTIVE_INDEX, LContainer, NATIVE, VIEWS} from './interfaces/container';
|
import {ACTIVE_INDEX, LContainer, NATIVE, VIEWS} from './interfaces/container';
|
||||||
import {RenderFlags} from './interfaces/definition';
|
|
||||||
import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeType, TViewNode} from './interfaces/node';
|
import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeType, TViewNode} from './interfaces/node';
|
||||||
import {LQueries} from './interfaces/query';
|
|
||||||
import {RComment, RElement, Renderer3, isProceduralRenderer} from './interfaces/renderer';
|
import {RComment, RElement, Renderer3, isProceduralRenderer} from './interfaces/renderer';
|
||||||
import {CONTAINER_INDEX, CONTEXT, HOST_NODE, LView, QUERIES, RENDERER, TView} from './interfaces/view';
|
import {CONTAINER_INDEX, CONTEXT, HOST_NODE, LView, QUERIES, RENDERER, TView} from './interfaces/view';
|
||||||
import {assertNodeOfPossibleTypes} from './node_assert';
|
import {assertNodeOfPossibleTypes} from './node_assert';
|
||||||
|
@ -64,7 +62,7 @@ export function createElementRef(
|
||||||
let R3TemplateRef: {
|
let R3TemplateRef: {
|
||||||
new (
|
new (
|
||||||
_declarationParentView: LView, elementRef: ViewEngine_ElementRef, _tView: TView,
|
_declarationParentView: LView, elementRef: ViewEngine_ElementRef, _tView: TView,
|
||||||
_renderer: Renderer3, _queries: LQueries | null, _injectorIndex: number):
|
_renderer: Renderer3, _hostLContainer: LContainer, _injectorIndex: number):
|
||||||
ViewEngine_TemplateRef<any>
|
ViewEngine_TemplateRef<any>
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -97,7 +95,7 @@ export function createTemplateRef<T>(
|
||||||
R3TemplateRef = class TemplateRef_<T> extends TemplateRefToken<T> {
|
R3TemplateRef = class TemplateRef_<T> extends TemplateRefToken<T> {
|
||||||
constructor(
|
constructor(
|
||||||
private _declarationParentView: LView, readonly elementRef: ViewEngine_ElementRef,
|
private _declarationParentView: LView, readonly elementRef: ViewEngine_ElementRef,
|
||||||
private _tView: TView, private _renderer: Renderer3, private _queries: LQueries|null,
|
private _tView: TView, private _renderer: Renderer3, private _hostLContainer: LContainer,
|
||||||
private _injectorIndex: number) {
|
private _injectorIndex: number) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
@ -107,7 +105,7 @@ export function createTemplateRef<T>(
|
||||||
hostTNode?: TElementNode|TContainerNode|TElementContainerNode, hostView?: LView,
|
hostTNode?: TElementNode|TContainerNode|TElementContainerNode, hostView?: LView,
|
||||||
index?: number): viewEngine_EmbeddedViewRef<T> {
|
index?: number): viewEngine_EmbeddedViewRef<T> {
|
||||||
const lView = createEmbeddedViewAndNode(
|
const lView = createEmbeddedViewAndNode(
|
||||||
this._tView, context, this._declarationParentView, this._renderer, this._queries,
|
this._tView, context, this._declarationParentView, this._hostLContainer[QUERIES],
|
||||||
this._injectorIndex);
|
this._injectorIndex);
|
||||||
if (container) {
|
if (container) {
|
||||||
insertView(lView, container, hostView !, index !, hostTNode !.index);
|
insertView(lView, container, hostView !, index !, hostTNode !.index);
|
||||||
|
@ -125,7 +123,7 @@ export function createTemplateRef<T>(
|
||||||
ngDevMode && assertDefined(hostTNode.tViews, 'TView must be allocated');
|
ngDevMode && assertDefined(hostTNode.tViews, 'TView must be allocated');
|
||||||
return new R3TemplateRef(
|
return new R3TemplateRef(
|
||||||
hostView, createElementRef(ElementRefToken, hostTNode, hostView), hostTNode.tViews as TView,
|
hostView, createElementRef(ElementRefToken, hostTNode, hostView), hostTNode.tViews as TView,
|
||||||
getLView()[RENDERER], hostContainer[QUERIES], hostTNode.injectorIndex);
|
getLView()[RENDERER], hostContainer, hostTNode.injectorIndex);
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -362,6 +362,9 @@
|
||||||
{
|
{
|
||||||
"name": "addRemoveViewFromContainer"
|
"name": "addRemoveViewFromContainer"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "addTContainerToQueries"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "addToViewTree"
|
"name": "addToViewTree"
|
||||||
},
|
},
|
||||||
|
|
|
@ -94,21 +94,19 @@ describe('Query API', () => {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
fixmeIvy(
|
it('should contain the first content child when target is on <ng-template> with embedded view (issue #16568)',
|
||||||
'FW-982: For queries, ng-template own directives should be registered before the ones from inside the template')
|
() => {
|
||||||
.it('should contain the first content child when target is on <ng-template> with embedded view (issue #16568)',
|
const template =
|
||||||
() => {
|
'<div directive-needs-content-child><ng-template text="foo" [ngIf]="true"><div text="bar"></div></ng-template></div>' +
|
||||||
const template =
|
'<needs-content-child #q><ng-template text="foo" [ngIf]="true"><div text="bar"></div></ng-template></needs-content-child>';
|
||||||
'<div directive-needs-content-child><ng-template text="foo" [ngIf]="true"><div text="bar"></div></ng-template></div>' +
|
const view = createTestCmp(MyComp0, template);
|
||||||
'<needs-content-child #q><ng-template text="foo" [ngIf]="true"><div text="bar"></div></ng-template></needs-content-child>';
|
view.detectChanges();
|
||||||
const view = createTestCmp(MyComp0, template);
|
const q: NeedsContentChild = view.debugElement.children[1].references !['q'];
|
||||||
view.detectChanges();
|
expect(q.child.text).toEqual('foo');
|
||||||
const q: NeedsContentChild = view.debugElement.children[1].references !['q'];
|
const directive: DirectiveNeedsContentChild =
|
||||||
expect(q.child.text).toEqual('foo');
|
view.debugElement.children[0].injector.get(DirectiveNeedsContentChild);
|
||||||
const directive: DirectiveNeedsContentChild =
|
expect(directive.child.text).toEqual('foo');
|
||||||
view.debugElement.children[0].injector.get(DirectiveNeedsContentChild);
|
});
|
||||||
expect(directive.child.text).toEqual('foo');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should contain the first view child', () => {
|
it('should contain the first view child', () => {
|
||||||
const template = '<needs-view-child #q></needs-view-child>';
|
const template = '<needs-view-child #q></needs-view-child>';
|
||||||
|
|
Loading…
Reference in New Issue