diff --git a/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts b/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts
index daa73949aa..f0bdbbd077 100644
--- a/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts
+++ b/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts
@@ -1169,7 +1169,6 @@ describe('compiler compliance', () => {
const $c3$ = ["id","first"];
const $c4$ = ["id","second"];
const $c1$ = [[["span", "title", "tofirst"]], [["span", "title", "tosecond"]]];
- const $c2$ = ["span[title=toFirst]", "span[title=toSecond]"];
…
ComplexComponent.ngComponentDef = $r3$.ΔdefineComponent({
type: ComplexComponent,
@@ -1180,7 +1179,7 @@ describe('compiler compliance', () => {
vars: 0,
template: function ComplexComponent_Template(rf, ctx) {
if (rf & 1) {
- $r3$.ΔprojectionDef($c1$, $c2$);
+ $r3$.ΔprojectionDef($c1$);
$r3$.ΔelementStart(0, "div", $c3$);
$r3$.Δprojection(1, 1);
$r3$.ΔelementEnd();
@@ -1252,11 +1251,10 @@ describe('compiler compliance', () => {
}
}
const $_c4$ = [[["span", "title", "tofirst"]]];
- const $_c5$ = ["span[title=toFirst]"];
…
template: function Cmp_Template(rf, ctx) {
if (rf & 1) {
- $r3$.ΔprojectionDef($_c4$, $_c5$);
+ $r3$.ΔprojectionDef($_c4$);
$r3$.Δtemplate(0, Cmp_div_0_Template, 2, 0, "div", $_c0$);
$r3$.Δtemplate(1, Cmp_div_1_Template, 2, 0, "div", $_c1$);
$r3$.Δtemplate(2, Cmp_ng_template_2_Template, 2, 0, "ng-template");
@@ -1326,7 +1324,7 @@ describe('compiler compliance', () => {
…
template: function Cmp_Template(rf, ctx) {
if (rf & 1) {
- $r3$.ΔprojectionDef($_c2$, $_c3$);
+ $r3$.ΔprojectionDef($_c2$);
$r3$.Δprojection(0, 1);
$r3$.Δtemplate(1, Cmp_ng_template_1_Template, 2, 0, "ng-template");
$r3$.Δtemplate(2, Cmp_ng_template_2_Template, 2, 0, "ng-template");
@@ -1338,6 +1336,117 @@ describe('compiler compliance', () => {
const {source} = compile(files, angularFiles);
expectEmit(source, output, 'Invalid content projection instructions generated');
});
+
+ it('should parse the selector that is passed into ngProjectAs', () => {
+ const files = {
+ app: {
+ 'spec.ts': `
+ import {Component, NgModule} from '@angular/core';
+
+ @Component({
+ selector: 'simple',
+ template: '
'
+ })
+ export class SimpleComponent {}
+
+ @NgModule({declarations: [SimpleComponent]})
+ export class MyModule {}
+
+ @Component({
+ selector: 'my-app',
+ template: ''
+ })
+ export class MyApp {}
+ `
+ }
+ };
+
+ // Note that the c0 and c1 constants aren't being used in this particular test,
+ // but they are used in some of the logic that is folded under the ellipsis.
+ const SimpleComponentDefinition = `
+ const $_c0$ = [[["", "title", ""]]];
+ const $_c1$ = ["[title]"];
+ const $_c2$ = [5, ["", "title", ""]];
+ …
+ MyApp.ngComponentDef = $r3$.ΔdefineComponent({
+ type: MyApp,
+ selectors: [["my-app"]],
+ factory: function MyApp_Factory(t) {
+ return new(t || MyApp)();
+ },
+ consts: 2,
+ vars: 0,
+ template: function MyApp_Template(rf, ctx) {
+ if (rf & 1) {
+ $r3$.ΔelementStart(0, "simple");
+ $r3$.Δelement(1, "h1", $_c2$);
+ $r3$.ΔelementEnd();
+ }
+ },
+ encapsulation: 2
+ })`;
+
+ const result = compile(files, angularFiles);
+
+ expectEmit(
+ result.source, SimpleComponentDefinition, 'Incorrect SimpleComponent definition');
+ });
+
+ it('should take the first selector if multiple values are passed into ngProjectAs', () => {
+ const files = {
+ app: {
+ 'spec.ts': `
+ import {Component, NgModule} from '@angular/core';
+
+ @Component({
+ selector: 'simple',
+ template: '
'
+ })
+ export class SimpleComponent {}
+
+ @NgModule({declarations: [SimpleComponent]})
+ export class MyModule {}
+
+ @Component({
+ selector: 'my-app',
+ template: ''
+ })
+ export class MyApp {}
+ `
+ }
+ };
+
+ // Note that the c0 and c1 constants aren't being used in this particular test,
+ // but they are used in some of the logic that is folded under the ellipsis.
+ const SimpleComponentDefinition = `
+ const $_c0$ = [[["", "title", ""]]];
+ const $_c1$ = ["[title]"];
+ const $_c2$ = [5, ["", "title", ""]];
+ …
+ MyApp.ngComponentDef = $r3$.ΔdefineComponent({
+ type: MyApp,
+ selectors: [["my-app"]],
+ factory: function MyApp_Factory(t) {
+ return new(t || MyApp)();
+ },
+ consts: 2,
+ vars: 0,
+ template: function MyApp_Template(rf, ctx) {
+ if (rf & 1) {
+ $r3$.ΔelementStart(0, "simple");
+ $r3$.Δelement(1, "h1", $_c2$);
+ $r3$.ΔelementEnd();
+ }
+ },
+ encapsulation: 2
+ })`;
+
+ const result = compile(files, angularFiles);
+
+ expectEmit(
+ result.source, SimpleComponentDefinition, 'Incorrect SimpleComponent definition');
+ });
+
});
describe('queries', () => {
diff --git a/packages/compiler/src/core.ts b/packages/compiler/src/core.ts
index ae78dd3cf6..b3424ddfbf 100644
--- a/packages/compiler/src/core.ts
+++ b/packages/compiler/src/core.ts
@@ -475,4 +475,21 @@ export const enum AttributeMarker {
* ```
*/
Template = 4,
+
+ /**
+ * Signals that the following attribute is `ngProjectAs` and its value is a parsed `CssSelector`.
+ *
+ * For example, given the following HTML:
+ *
+ * ```
+ *
+ * ```
+ *
+ * the generated code for the `element()` instruction would include:
+ *
+ * ```
+ * ['attr', 'value', AttributeMarker.ProjectAs, ['', 'title', '']]
+ * ```
+ */
+ ProjectAs = 5
}
diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts
index e0089bd5aa..01ea23e9ea 100644
--- a/packages/compiler/src/render3/view/template.ts
+++ b/packages/compiler/src/render3/view/template.ts
@@ -45,6 +45,9 @@ const DEFAULT_NG_CONTENT_SELECTOR = '*';
// Selector attribute name of ``
const NG_CONTENT_SELECT_ATTR = 'select';
+// Attribute name of `ngProjectAs`.
+const NG_PROJECT_AS_ATTR_NAME = 'ngProjectAs';
+
// List of supported global targets for event listeners
const GLOBAL_TARGET_RESOLVERS = new Map(
[['window', R3.resolveWindow], ['document', R3.resolveDocument], ['body', R3.resolveBody]]);
@@ -264,11 +267,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver
// Only selectors with a non-default value are generated
if (this._ngContentSelectors.length) {
const r3Selectors = this._ngContentSelectors.map(s => core.parseSelectorToR3Selector(s));
- // `projectionDef` needs both the parsed and raw value of the selectors
- const parsed = this.constantPool.getConstLiteral(asLiteral(r3Selectors), true);
- const unParsed =
- this.constantPool.getConstLiteral(asLiteral(this._ngContentSelectors), true);
- parameters.push(parsed, unParsed);
+ parameters.push(this.constantPool.getConstLiteral(asLiteral(r3Selectors), true));
}
// Since we accumulate ngContent selectors while processing template elements,
@@ -475,18 +474,19 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver
0 :
this._ngContentSelectors.push(ngContent.selector) + this._ngContentSelectorsOffset;
const parameters: o.Expression[] = [o.literal(slot)];
-
- const attributeAsList: string[] = [];
+ const attributes: o.Expression[] = [];
ngContent.attributes.forEach((attribute) => {
const {name, value} = attribute;
- if (name.toLowerCase() !== NG_CONTENT_SELECT_ATTR) {
- attributeAsList.push(name, value);
+ if (name === NG_PROJECT_AS_ATTR_NAME) {
+ attributes.push(...getNgProjectAsLiteral(attribute));
+ } else if (name.toLowerCase() !== NG_CONTENT_SELECT_ATTR) {
+ attributes.push(o.literal(name), o.literal(value));
}
});
- if (attributeAsList.length > 0) {
- parameters.push(o.literal(selectorIndex), asLiteral(attributeAsList));
+ if (attributes.length > 0) {
+ parameters.push(o.literal(selectorIndex), o.literalArr(attributes));
} else if (selectorIndex !== 0) {
parameters.push(o.literal(selectorIndex));
}
@@ -574,7 +574,11 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver
});
outputAttrs.forEach(attr => {
- attributes.push(...getAttributeNameLiterals(attr.name), o.literal(attr.value));
+ if (attr.name === NG_PROJECT_AS_ATTR_NAME) {
+ attributes.push(...getNgProjectAsLiteral(attr));
+ } else {
+ attributes.push(...getAttributeNameLiterals(attr.name), o.literal(attr.value));
+ }
});
// add attributes for directive and projection matching purposes
@@ -1571,6 +1575,17 @@ function createCssSelector(tag: string, attributes: {[name: string]: string}): C
return cssSelector;
}
+/**
+ * Creates an array of expressions out of an `ngProjectAs` attributes
+ * which can be added to the instruction parameters.
+ */
+function getNgProjectAsLiteral(attribute: t.TextAttribute): o.Expression[] {
+ // Parse the attribute value into a CssSelectorList. Note that we only take the
+ // first selector, because we don't support multiple selectors in ngProjectAs.
+ const parsedR3Selector = core.parseSelectorToR3Selector(attribute.value)[0];
+ return [o.literal(core.AttributeMarker.ProjectAs), asLiteral(parsedR3Selector)];
+}
+
function interpolate(args: o.Expression[]): o.Expression {
args = args.slice(1); // Ignore the length prefix added for render2
switch (args.length) {
diff --git a/packages/core/src/render3/instructions/projection.ts b/packages/core/src/render3/instructions/projection.ts
index 38ef6d79dd..18c1a9be6e 100644
--- a/packages/core/src/render3/instructions/projection.ts
+++ b/packages/core/src/render3/instructions/projection.ts
@@ -5,15 +5,17 @@
* 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 {TElementNode, TNode, TNodeType} from '../interfaces/node';
+import {TAttributes, TElementNode, TNode, TNodeType} from '../interfaces/node';
import {CssSelectorList} from '../interfaces/projection';
import {T_HOST} from '../interfaces/view';
import {appendProjectedNodes} from '../node_manipulation';
import {matchingProjectionSelectorIndex} from '../node_selector_matcher';
import {getLView, setIsParent} from '../state';
import {findComponentView} from '../util/view_traversal_utils';
+
import {createNodeAtIndex} from './shared';
+
/**
* Instruction to distribute projectable nodes among occurrences in a given template.
* It takes all the selectors from the entire component's template and decides where
@@ -37,7 +39,7 @@ import {createNodeAtIndex} from './shared';
*
* @publicApi
*/
-export function ΔprojectionDef(selectors?: CssSelectorList[], textSelectors?: string[]): void {
+export function ΔprojectionDef(selectors?: CssSelectorList[]): void {
const componentNode = findComponentView(getLView())[T_HOST] as TElementNode;
if (!componentNode.projection) {
@@ -49,9 +51,8 @@ export function ΔprojectionDef(selectors?: CssSelectorList[], textSelectors?: s
let componentChild: TNode|null = componentNode.child;
while (componentChild !== null) {
- const bucketIndex = selectors ?
- matchingProjectionSelectorIndex(componentChild, selectors, textSelectors !) :
- 0;
+ const bucketIndex =
+ selectors ? matchingProjectionSelectorIndex(componentChild, selectors) : 0;
if (tails[bucketIndex]) {
tails[bucketIndex] !.projectionNext = componentChild;
@@ -77,7 +78,8 @@ export function ΔprojectionDef(selectors?: CssSelectorList[], textSelectors?: s
*
* @publicApi
*/
-export function Δprojection(nodeIndex: number, selectorIndex: number = 0, attrs?: string[]): void {
+export function Δprojection(
+ nodeIndex: number, selectorIndex: number = 0, attrs?: TAttributes): void {
const lView = getLView();
const tProjectionNode =
createNodeAtIndex(nodeIndex, TNodeType.Projection, null, null, attrs || null);
diff --git a/packages/core/src/render3/instructions/shared.ts b/packages/core/src/render3/instructions/shared.ts
index f22ba1a9ad..ad81489b94 100644
--- a/packages/core/src/render3/instructions/shared.ts
+++ b/packages/core/src/render3/instructions/shared.ts
@@ -1360,18 +1360,22 @@ function generateInitialInputs(
// We do not allow inputs on namespaced attributes.
i += 4;
continue;
+ } else if (attrName === AttributeMarker.ProjectAs) {
+ // Skip over the `ngProjectAs` value.
+ i += 2;
+ continue;
}
// If we hit any other attribute markers, we're done anyway. None of those are valid inputs.
if (typeof attrName === 'number') break;
- const minifiedInputName = inputs[attrName];
+ const minifiedInputName = inputs[attrName as string];
const attrValue = attrs[i + 1];
if (minifiedInputName !== undefined) {
const inputsToStore: InitialInputs =
initialInputData[directiveIndex] || (initialInputData[directiveIndex] = []);
- inputsToStore.push(attrName, minifiedInputName, attrValue as string);
+ inputsToStore.push(attrName as string, minifiedInputName, attrValue as string);
}
i += 2;
diff --git a/packages/core/src/render3/interfaces/node.ts b/packages/core/src/render3/interfaces/node.ts
index 0874abd259..c4f476ce7c 100644
--- a/packages/core/src/render3/interfaces/node.ts
+++ b/packages/core/src/render3/interfaces/node.ts
@@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
+import {CssSelector} from './projection';
import {RNode} from './renderer';
import {StylingContext} from './styling';
import {LView, TView} from './view';
@@ -163,14 +164,32 @@ export const enum AttributeMarker {
* ```
*/
Template = 4,
+
+ /**
+ * Signals that the following attribute is `ngProjectAs` and its value is a parsed `CssSelector`.
+ *
+ * For example, given the following HTML:
+ *
+ * ```
+ *
+ * ```
+ *
+ * the generated code for the `element()` instruction would include:
+ *
+ * ```
+ * ['attr', 'value', AttributeMarker.ProjectAs, ['', 'title', '']]
+ * ```
+ */
+ ProjectAs = 5
}
/**
* A combination of:
- * - attribute names and values
- * - special markers acting as flags to alter attributes processing.
+ * - Attribute names and values.
+ * - Special markers acting as flags to alter attributes processing.
+ * - Parsed ngProjectAs selectors.
*/
-export type TAttributes = (string | AttributeMarker)[];
+export type TAttributes = (string | AttributeMarker | CssSelector)[];
/**
* Binding data (flyweight) for a particular node that is shared between all templates
diff --git a/packages/core/src/render3/interfaces/projection.ts b/packages/core/src/render3/interfaces/projection.ts
index beffaa06db..891dd28135 100644
--- a/packages/core/src/render3/interfaces/projection.ts
+++ b/packages/core/src/render3/interfaces/projection.ts
@@ -65,8 +65,6 @@ export const enum SelectorFlags {
CLASS = 0b1000,
}
-export const NG_PROJECT_AS_ATTR_NAME = 'ngProjectAs';
-
// Note: This hack is necessary so we don't erroneously get a circular dependency
// failure based on types.
export const unusedValueExportToPlacateAjd = 1;
diff --git a/packages/core/src/render3/node_selector_matcher.ts b/packages/core/src/render3/node_selector_matcher.ts
index 3806ce0418..107e2b1917 100644
--- a/packages/core/src/render3/node_selector_matcher.ts
+++ b/packages/core/src/render3/node_selector_matcher.ts
@@ -11,7 +11,7 @@ import '../util/ng_dev_mode';
import {assertDefined, assertNotEqual} from '../util/assert';
import {AttributeMarker, TAttributes, TNode, TNodeType, unusedValueExportToPlacateAjd as unused1} from './interfaces/node';
-import {CssSelector, CssSelectorList, NG_PROJECT_AS_ATTR_NAME, SelectorFlags, unusedValueExportToPlacateAjd as unused2} from './interfaces/projection';
+import {CssSelector, CssSelectorList, SelectorFlags, unusedValueExportToPlacateAjd as unused2} from './interfaces/projection';
import {getInitialClassNameValue} from './styling/class_and_style_bindings';
import {isNameOnlyAttributeMarker} from './util/attrs_utils';
@@ -234,14 +234,14 @@ export function isNodeMatchingSelectorList(
return false;
}
-export function getProjectAsAttrValue(tNode: TNode): string|null {
+export function getProjectAsAttrValue(tNode: TNode): CssSelector|null {
const nodeAttrs = tNode.attrs;
if (nodeAttrs != null) {
- const ngProjectAsAttrIdx = nodeAttrs.indexOf(NG_PROJECT_AS_ATTR_NAME);
+ const ngProjectAsAttrIdx = nodeAttrs.indexOf(AttributeMarker.ProjectAs);
// only check for ngProjectAs in attribute names, don't accidentally match attribute's value
// (attribute names are stored at even indexes)
if ((ngProjectAsAttrIdx & 1) === 0) {
- return nodeAttrs[ngProjectAsAttrIdx + 1] as string;
+ return nodeAttrs[ngProjectAsAttrIdx + 1] as CssSelector;
}
}
return null;
@@ -251,18 +251,19 @@ export function getProjectAsAttrValue(tNode: TNode): string|null {
* Checks a given node against matching projection selectors and returns
* selector index (or 0 if none matched).
*
- * This function takes into account the ngProjectAs attribute: if present its value will be
- * compared to the raw (un-parsed) CSS selector instead of using standard selector matching logic.
+ * This function takes into account the parsed ngProjectAs selector from the node's attributes.
+ * If present, it will check whether the ngProjectAs selector matches any of the projection
+ * selectors.
*/
export function matchingProjectionSelectorIndex(
- tNode: TNode, selectors: CssSelectorList[], textSelectors: string[]): number {
+ tNode: TNode, selectors: CssSelectorList[]): number {
const ngProjectAsAttrVal = getProjectAsAttrValue(tNode);
for (let i = 0; i < selectors.length; i++) {
- // if a node has the ngProjectAs attribute match it against unparsed selector
- // match a node against a parsed selector only if ngProjectAs attribute is not present
- if (ngProjectAsAttrVal === textSelectors[i] ||
- ngProjectAsAttrVal === null &&
- isNodeMatchingSelectorList(tNode, selectors[i], /* isProjectionMode */ true)) {
+ // If we ran into an `ngProjectAs` attribute, we should match its parsed selector
+ // to the list of selectors, otherwise we fall back to matching against the node.
+ if (ngProjectAsAttrVal === null ?
+ isNodeMatchingSelectorList(tNode, selectors[i], /* isProjectionMode */ true) :
+ isSelectorInSelectorList(ngProjectAsAttrVal, selectors[i])) {
return i + 1; // first matching selector "captures" a given node
}
}
@@ -290,3 +291,24 @@ function matchTemplateAttribute(attrs: TAttributes, name: string): number {
}
return -1;
}
+
+/**
+ * Checks whether a selector is inside a CssSelectorList
+ * @param selector Selector to be checked.
+ * @param list List in which to look for the selector.
+ */
+function isSelectorInSelectorList(selector: CssSelector, list: CssSelectorList): boolean {
+ selectorListLoop: for (let i = 0; i < list.length; i++) {
+ const currentSelectorInList = list[i];
+ if (selector.length !== currentSelectorInList.length) {
+ continue;
+ }
+ for (let j = 0; j < selector.length; j++) {
+ if (selector[j] !== currentSelectorInList[j]) {
+ continue selectorListLoop;
+ }
+ }
+ return true;
+ }
+ return false;
+}
diff --git a/packages/core/src/render3/styling/class_and_style_bindings.ts b/packages/core/src/render3/styling/class_and_style_bindings.ts
index 81146f79ee..0237d071c9 100644
--- a/packages/core/src/render3/styling/class_and_style_bindings.ts
+++ b/packages/core/src/render3/styling/class_and_style_bindings.ts
@@ -75,10 +75,10 @@ export function patchContextWithStaticAttrs(
mode = attr;
} else if (mode == AttributeMarker.Classes) {
initialClasses = initialClasses || context[StylingIndex.InitialClassValuesPosition];
- patchInitialStylingValue(initialClasses, attr, true, directiveIndex);
+ patchInitialStylingValue(initialClasses, attr as string, true, directiveIndex);
} else if (mode == AttributeMarker.Styles) {
initialStyles = initialStyles || context[StylingIndex.InitialStyleValuesPosition];
- patchInitialStylingValue(initialStyles, attr, attrs[++i], directiveIndex);
+ patchInitialStylingValue(initialStyles, attr as string, attrs[++i], directiveIndex);
}
}
}
diff --git a/packages/core/src/render3/util/attrs_utils.ts b/packages/core/src/render3/util/attrs_utils.ts
index ec580a25f5..ff9bdbf383 100644
--- a/packages/core/src/render3/util/attrs_utils.ts
+++ b/packages/core/src/render3/util/attrs_utils.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {AttributeMarker, TAttributes} from '../interfaces/node';
-import {NG_PROJECT_AS_ATTR_NAME} from '../interfaces/projection';
+import {CssSelector} from '../interfaces/projection';
import {ProceduralRenderer3, RElement, isProceduralRenderer} from '../interfaces/renderer';
import {RENDERER} from '../interfaces/view';
import {getLView} from '../state';
@@ -70,19 +70,17 @@ export function setUpAttributes(native: RElement, attrs: TAttributes): number {
/// attrName is string;
const attrName = value as string;
const attrVal = attrs[++i];
- if (attrName !== NG_PROJECT_AS_ATTR_NAME) {
- // Standard attributes
- ngDevMode && ngDevMode.rendererSetAttribute++;
- if (isAnimationProp(attrName)) {
- if (isProc) {
- (renderer as ProceduralRenderer3).setProperty(native, attrName, attrVal);
- }
- } else {
- isProc ?
- (renderer as ProceduralRenderer3)
- .setAttribute(native, attrName as string, attrVal as string) :
- native.setAttribute(attrName as string, attrVal as string);
+ // Standard attributes
+ ngDevMode && ngDevMode.rendererSetAttribute++;
+ if (isAnimationProp(attrName)) {
+ if (isProc) {
+ (renderer as ProceduralRenderer3).setProperty(native, attrName, attrVal);
}
+ } else {
+ isProc ?
+ (renderer as ProceduralRenderer3)
+ .setAttribute(native, attrName as string, attrVal as string) :
+ native.setAttribute(attrName as string, attrVal as string);
}
i++;
}
@@ -113,6 +111,6 @@ export function attrsStylingIndexOf(attrs: TAttributes, startIndex: number): num
* @param marker The attribute marker to test.
* @returns true if the marker is a "name-only" marker (e.g. `Bindings` or `Template`).
*/
-export function isNameOnlyAttributeMarker(marker: string | AttributeMarker) {
+export function isNameOnlyAttributeMarker(marker: string | AttributeMarker | CssSelector) {
return marker === AttributeMarker.Bindings || marker === AttributeMarker.Template;
}
diff --git a/packages/core/test/acceptance/content_spec.ts b/packages/core/test/acceptance/content_spec.ts
index e73279837f..dff0702576 100644
--- a/packages/core/test/acceptance/content_spec.ts
+++ b/packages/core/test/acceptance/content_spec.ts
@@ -81,6 +81,76 @@ describe('projection', () => {
expect(fixture.nativeElement).toHaveText('hello');
});
+ it('should support ngProjectAs on elements (including )', () => {
+ @Component({
+ selector: 'card',
+ template: `
+
+ ---
+
+ `
+ })
+ class Card {
+ }
+
+ @Component({
+ selector: 'card-with-title',
+ template: `
+
+ Title
+
+
+ `
+ })
+ class CardWithTitle {
+ }
+
+ @Component({
+ selector: 'app',
+ template: `
+ content
+ `
+ })
+ class App {
+ }
+
+ TestBed.configureTestingModule({declarations: [Card, CardWithTitle, App]});
+ const fixture = TestBed.createComponent(App);
+ fixture.detectChanges();
+
+ // Compare the text output, because Ivy and ViewEngine produce slightly different HTML.
+ expect(fixture.nativeElement.textContent).toContain('Title --- content');
+ });
+
+ it('should not match multiple selectors in ngProjectAs', () => {
+ @Component({
+ selector: 'card',
+ template: `
+
+ content
+ `
+ })
+ class Card {
+ }
+
+ @Component({
+ template: `
+
+ Title
+
+ `
+ })
+ class App {
+ }
+
+ TestBed.configureTestingModule({declarations: [Card, App]});
+ const fixture = TestBed.createComponent(App);
+ fixture.detectChanges();
+
+ // Compare the text output, because Ivy and ViewEngine produce slightly different HTML.
+ expect(fixture.nativeElement.textContent).not.toContain('Title content');
+ });
+
describe('on inline templates (e.g. *ngIf)', () => {
it('should work when matching the element name', () => {
let divDirectives = 0;
diff --git a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json
index 9fbf4ccb44..3a280df1a9 100644
--- a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json
+++ b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json
@@ -86,9 +86,6 @@
{
"name": "NG_PIPE_DEF"
},
- {
- "name": "NG_PROJECT_AS_ATTR_NAME"
- },
{
"name": "NG_TEMPLATE_SELECTOR"
},
diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json
index 62c164155b..992ae96090 100644
--- a/packages/core/test/bundling/todo/bundle.golden_symbols.json
+++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json
@@ -131,9 +131,6 @@
{
"name": "NG_PIPE_DEF"
},
- {
- "name": "NG_PROJECT_AS_ATTR_NAME"
- },
{
"name": "NG_TEMPLATE_SELECTOR"
},
diff --git a/packages/core/test/render3/content_spec.ts b/packages/core/test/render3/content_spec.ts
index e5948f4fbe..bc1a16d478 100644
--- a/packages/core/test/render3/content_spec.ts
+++ b/packages/core/test/render3/content_spec.ts
@@ -941,7 +941,7 @@ describe('content projection', () => {
*/
const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
- ΔprojectionDef([[['div']]], ['div']);
+ ΔprojectionDef([[['div']]]);
Δprojection(0);
Δtext(1, 'Before-');
Δtemplate(2, IfTemplate, 1, 0, 'ng-template', [AttributeMarker.Bindings, 'ngIf']);
@@ -1531,9 +1531,7 @@ describe('content projection', () => {
*/
const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
- ΔprojectionDef(
- [[['span', 'title', 'toFirst']], [['span', 'title', 'toSecond']]],
- ['span[title=toFirst]', 'span[title=toSecond]']);
+ ΔprojectionDef([[['span', 'title', 'toFirst']], [['span', 'title', 'toSecond']]]);
ΔelementStart(0, 'div', ['id', 'first']);
{ Δprojection(1, 1); }
ΔelementEnd();
@@ -1577,7 +1575,7 @@ describe('content projection', () => {
*/
const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
- ΔprojectionDef([[['', 'title', '']]], ['[title]']);
+ ΔprojectionDef([[['', 'title', '']]]);
{ Δprojection(0, 1); }
}
}, 1);
@@ -1614,12 +1612,10 @@ describe('content projection', () => {
*/
const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
- ΔprojectionDef(
- [
- [['span', SelectorFlags.CLASS, 'toFirst']],
- [['span', SelectorFlags.CLASS, 'toSecond']]
- ],
- ['span.toFirst', 'span.toSecond']);
+ ΔprojectionDef([
+ [['span', SelectorFlags.CLASS, 'toFirst']],
+ [['span', SelectorFlags.CLASS, 'toSecond']]
+ ]);
ΔelementStart(0, 'div', ['id', 'first']);
{ Δprojection(1, 1); }
ΔelementEnd();
@@ -1663,12 +1659,10 @@ describe('content projection', () => {
*/
const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
- ΔprojectionDef(
- [
- [['span', SelectorFlags.CLASS, 'toFirst']],
- [['span', SelectorFlags.CLASS, 'toSecond']]
- ],
- ['span.toFirst', 'span.toSecond']);
+ ΔprojectionDef([
+ [['span', SelectorFlags.CLASS, 'toFirst']],
+ [['span', SelectorFlags.CLASS, 'toSecond']]
+ ]);
ΔelementStart(0, 'div', ['id', 'first']);
{ Δprojection(1, 1); }
ΔelementEnd();
@@ -1712,8 +1706,7 @@ describe('content projection', () => {
*/
const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
- ΔprojectionDef(
- [[['span']], [['span', SelectorFlags.CLASS, 'toSecond']]], ['span', 'span.toSecond']);
+ ΔprojectionDef([[['span']], [['span', SelectorFlags.CLASS, 'toSecond']]]);
ΔelementStart(0, 'div', ['id', 'first']);
{ Δprojection(1, 1); }
ΔelementEnd();
@@ -1757,7 +1750,7 @@ describe('content projection', () => {
*/
const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
- ΔprojectionDef([[['span', SelectorFlags.CLASS, 'toFirst']]], ['span.toFirst']);
+ ΔprojectionDef([[['span', SelectorFlags.CLASS, 'toFirst']]]);
ΔelementStart(0, 'div', ['id', 'first']);
{ Δprojection(1, 1); }
ΔelementEnd();
@@ -1802,7 +1795,7 @@ describe('content projection', () => {
*/
const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
- ΔprojectionDef([[['span', SelectorFlags.CLASS, 'toSecond']]], ['span.toSecond']);
+ ΔprojectionDef([[['span', SelectorFlags.CLASS, 'toSecond']]]);
ΔelementStart(0, 'div', ['id', 'first']);
{ Δprojection(1); }
ΔelementEnd();
@@ -1854,7 +1847,7 @@ describe('content projection', () => {
*/
const GrandChild = createComponent('grand-child', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
- ΔprojectionDef([[['span']]], ['span']);
+ ΔprojectionDef([[['span']]]);
Δprojection(0, 1);
Δelement(1, 'hr');
Δprojection(2);
@@ -1915,9 +1908,7 @@ describe('content projection', () => {
*/
const Card = createComponent('card', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
- ΔprojectionDef(
- [[['', 'card-title', '']], [['', 'card-content', '']]],
- ['[card-title]', '[card-content]']);
+ ΔprojectionDef([[['', 'card-title', '']], [['', 'card-content', '']]]);
Δprojection(0, 1);
Δelement(1, 'hr');
Δprojection(2, 2);
@@ -1963,64 +1954,6 @@ describe('content projection', () => {
'Title
content');
});
-
- it('should support ngProjectAs on elements (including )', () => {
-
- /**
- *
- *
- *
- */
- const Card = createComponent('card', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ΔprojectionDef(
- [[['', 'card-title', '']], [['', 'card-content', '']]],
- ['[card-title]', '[card-content]']);
- Δprojection(0, 1);
- Δelement(1, 'hr');
- Δprojection(2, 2);
- }
- }, 3);
-
- /**
- *
- *
- *
- */
- const CardWithTitle = createComponent('card-with-title', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ΔprojectionDef();
- ΔelementStart(0, 'card');
- {
- ΔelementStart(1, 'h1', ['ngProjectAs', '[card-title]']);
- { Δtext(2, 'Title'); }
- ΔelementEnd();
- Δprojection(3, 0, ['ngProjectAs', '[card-content]']);
- }
- ΔelementEnd();
- }
- }, 4, 0, [Card]);
-
- /**
- *
- * content
- *
- */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ΔelementStart(0, 'card-with-title');
- { Δtext(1, 'content'); }
- ΔelementEnd();
- }
- }, 2, 0, [CardWithTitle]);
-
- const app = renderComponent(App);
- expect(toHtml(app))
- .toEqual('Title
content');
-
- });
-
it('should not match selectors against node having ngProjectAs attribute', function() {
/**
@@ -2028,7 +1961,7 @@ describe('content projection', () => {
*/
const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
- ΔprojectionDef([[['div']]], ['div']);
+ ΔprojectionDef([[['div']]]);
Δprojection(0, 1);
}
}, 1);
@@ -2043,7 +1976,7 @@ describe('content projection', () => {
if (rf & RenderFlags.Create) {
ΔelementStart(0, 'child');
{
- ΔelementStart(1, 'div', ['ngProjectAs', 'span']);
+ ΔelementStart(1, 'div', [AttributeMarker.ProjectAs, ['span']]);
{ Δtext(2, 'should not project'); }
ΔelementEnd();
ΔelementStart(3, 'div');
@@ -2067,7 +2000,7 @@ describe('content projection', () => {
*/
const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
- ΔprojectionDef([[['div']]], ['div']);
+ ΔprojectionDef([[['div']]]);
ΔelementStart(0, 'span');
{ Δprojection(1, 1); }
ΔelementEnd();
diff --git a/packages/core/test/render3/i18n_spec.ts b/packages/core/test/render3/i18n_spec.ts
index f3bd89833a..79d6ac7c5a 100644
--- a/packages/core/test/render3/i18n_spec.ts
+++ b/packages/core/test/render3/i18n_spec.ts
@@ -2058,7 +2058,7 @@ describe('Runtime i18n', () => {
vars: 0,
template: (rf: RenderFlags, cmp: Child) => {
if (rf & RenderFlags.Create) {
- ΔprojectionDef([[['span']]], ['span']);
+ ΔprojectionDef([[['span']]]);
Δprojection(0, 1);
}
}
diff --git a/packages/core/test/render3/node_selector_matcher_spec.ts b/packages/core/test/render3/node_selector_matcher_spec.ts
index eda60addb7..294f43df17 100644
--- a/packages/core/test/render3/node_selector_matcher_spec.ts
+++ b/packages/core/test/render3/node_selector_matcher_spec.ts
@@ -6,13 +6,13 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {AttributeMarker, TAttributes, TNode, TNodeType} from '../../src/render3/interfaces/node';
-
-import {CssSelector, CssSelectorList, NG_PROJECT_AS_ATTR_NAME, SelectorFlags,} from '../../src/render3/interfaces/projection';
-import {getProjectAsAttrValue, isNodeMatchingSelectorList, isNodeMatchingSelector} from '../../src/render3/node_selector_matcher';
-import {initializeStaticContext} from '../../src/render3/styling/class_and_style_bindings';
import {createTNode} from '@angular/core/src/render3/instructions/shared';
+import {AttributeMarker, TAttributes, TNode, TNodeType} from '../../src/render3/interfaces/node';
+import {CssSelector, CssSelectorList, SelectorFlags} from '../../src/render3/interfaces/projection';
+import {getProjectAsAttrValue, isNodeMatchingSelector, isNodeMatchingSelectorList} from '../../src/render3/node_selector_matcher';
+import {initializeStaticContext} from '../../src/render3/styling/class_and_style_bindings';
+
function testLStaticData(tagName: string, attrs: TAttributes | null): TNode {
return createTNode(null, TNodeType.Element, 0, tagName, attrs);
}
@@ -466,11 +466,11 @@ describe('css selector matching', () => {
describe('reading the ngProjectAs attribute value', function() {
- function testTNode(attrs: string[] | null) { return testLStaticData('tag', attrs); }
+ function testTNode(attrs: TAttributes | null) { return testLStaticData('tag', attrs); }
it('should get ngProjectAs value if present', function() {
- expect(getProjectAsAttrValue(testTNode([NG_PROJECT_AS_ATTR_NAME, 'tag[foo=bar]'])))
- .toBe('tag[foo=bar]');
+ expect(getProjectAsAttrValue(testTNode([AttributeMarker.ProjectAs, ['tag', 'foo', 'bar']])))
+ .toEqual(['tag', 'foo', 'bar']);
});
it('should return null if there are no attributes',
@@ -481,7 +481,7 @@ describe('css selector matching', () => {
});
it('should not accidentally identify ngProjectAs in attribute values', function() {
- expect(getProjectAsAttrValue(testTNode(['foo', NG_PROJECT_AS_ATTR_NAME]))).toBe(null);
+ expect(getProjectAsAttrValue(testTNode(['foo', AttributeMarker.ProjectAs]))).toBe(null);
});
});
diff --git a/packages/core/test/render3/view_container_ref_spec.ts b/packages/core/test/render3/view_container_ref_spec.ts
index 4819ef35e0..d0c1462cd7 100644
--- a/packages/core/test/render3/view_container_ref_spec.ts
+++ b/packages/core/test/render3/view_container_ref_spec.ts
@@ -1475,7 +1475,7 @@ describe('ViewContainerRef', () => {
vars: 0,
template: (rf: RenderFlags, cmp: ChildWithSelector) => {
if (rf & RenderFlags.Create) {
- ΔprojectionDef([[['header']]], ['header']);
+ ΔprojectionDef([[['header']]]);
ΔelementStart(0, 'first');
{ Δprojection(1, 1); }
ΔelementEnd();
diff --git a/tools/public_api_guard/core/core.d.ts b/tools/public_api_guard/core/core.d.ts
index 9142125b0b..75e2915127 100644
--- a/tools/public_api_guard/core/core.d.ts
+++ b/tools/public_api_guard/core/core.d.ts
@@ -1313,9 +1313,9 @@ export declare function ΔpipeBindV(index: number, slotOffset: number, values: a
export declare type ΔPipeDefWithMeta = PipeDef;
-export declare function Δprojection(nodeIndex: number, selectorIndex?: number, attrs?: string[]): void;
+export declare function Δprojection(nodeIndex: number, selectorIndex?: number, attrs?: TAttributes): void;
-export declare function ΔprojectionDef(selectors?: CssSelectorList[], textSelectors?: string[]): void;
+export declare function ΔprojectionDef(selectors?: CssSelectorList[]): void;
export declare function ΔProvidersFeature(providers: Provider[], viewProviders?: Provider[]): (definition: DirectiveDef) => void;