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;