feat(ivy): adding support for ngNonBindable attribute
This commit is contained in:
parent
eeebe28c0f
commit
b286abeabe
@ -122,4 +122,83 @@ describe('compiler compliance: bindings', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('non bindable behaviour', () => {
|
||||||
|
const getAppFiles = (template: string = ''): MockDirectory => ({
|
||||||
|
app: {
|
||||||
|
'example.ts': `
|
||||||
|
import {Component, NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'my-app',
|
||||||
|
template: \`${template}\`
|
||||||
|
})
|
||||||
|
export class MyComponent {
|
||||||
|
name = 'John Doe';
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({declarations: [MyComponent]})
|
||||||
|
export class MyModule {}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep local ref for host element', () => {
|
||||||
|
const files: MockDirectory = getAppFiles(`
|
||||||
|
<b ngNonBindable #myRef id="my-id">
|
||||||
|
<i>Hello {{ name }}!</i>
|
||||||
|
</b>
|
||||||
|
{{ myRef.id }}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const template = `
|
||||||
|
const $_c0$ = ["id", "my-id"];
|
||||||
|
const $_c1$ = ["myRef", ""];
|
||||||
|
…
|
||||||
|
template:function MyComponent_Template(rf, $ctx$){
|
||||||
|
if (rf & 1) {
|
||||||
|
$i0$.ɵelementStart(0, "b", $_c0$, $_c1$);
|
||||||
|
$i0$.ɵsetBindingsDisabled();
|
||||||
|
$i0$.ɵelementStart(2, "i");
|
||||||
|
$i0$.ɵtext(3, "Hello {{ name }}!");
|
||||||
|
$i0$.ɵelementEnd();
|
||||||
|
$i0$.ɵsetBindingsEnabled();
|
||||||
|
$i0$.ɵelementEnd();
|
||||||
|
$i0$.ɵtext(4);
|
||||||
|
}
|
||||||
|
if (rf & 2) {
|
||||||
|
const $_r0$ = $i0$.ɵreference(1);
|
||||||
|
$i0$.ɵtextBinding(4, $i0$.ɵinterpolation1(" ", $_r0$.id, " "));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
const result = compile(files, angularFiles);
|
||||||
|
expectEmit(result.source, template,
|
||||||
|
'Incorrect handling of local refs for host element');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not have local refs for nested elements', () => {
|
||||||
|
const files: MockDirectory = getAppFiles(`
|
||||||
|
<div ngNonBindable>
|
||||||
|
<input value="one" #myInput> {{ myInput.value }}
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
|
||||||
|
const template = `
|
||||||
|
const $_c0$ = ["value", "one", "#myInput", ""];
|
||||||
|
…
|
||||||
|
template:function MyComponent_Template(rf, $ctx$){
|
||||||
|
if (rf & 1) {
|
||||||
|
$i0$.ɵelementStart(0, "div");
|
||||||
|
$i0$.ɵsetBindingsDisabled();
|
||||||
|
$i0$.ɵelement(1, "input", $_c0$);
|
||||||
|
$i0$.ɵtext(2, " {{ myInput.value }} ");
|
||||||
|
$i0$.ɵsetBindingsEnabled();
|
||||||
|
$i0$.ɵelementEnd();
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
const result = compile(files, angularFiles);
|
||||||
|
expectEmit(result.source, template,
|
||||||
|
'Incorrect handling of local refs for nested elements');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -60,6 +60,10 @@ export class Identifiers {
|
|||||||
|
|
||||||
static bind: o.ExternalReference = {name: 'ɵbind', moduleName: CORE};
|
static bind: o.ExternalReference = {name: 'ɵbind', moduleName: CORE};
|
||||||
|
|
||||||
|
static setBindingsEnabled: o.ExternalReference = {name: 'ɵsetBindingsEnabled', moduleName: CORE};
|
||||||
|
|
||||||
|
static setBindingsDisabled: o.ExternalReference = {name: 'ɵsetBindingsDisabled', moduleName: CORE};
|
||||||
|
|
||||||
static getCurrentView: o.ExternalReference = {name: 'ɵgetCurrentView', moduleName: CORE};
|
static getCurrentView: o.ExternalReference = {name: 'ɵgetCurrentView', moduleName: CORE};
|
||||||
|
|
||||||
static restoreView: o.ExternalReference = {name: 'ɵrestoreView', moduleName: CORE};
|
static restoreView: o.ExternalReference = {name: 'ɵrestoreView', moduleName: CORE};
|
||||||
|
@ -30,7 +30,7 @@ import {htmlAstToRender3Ast} from '../r3_template_transform';
|
|||||||
|
|
||||||
import {R3QueryMetadata} from './api';
|
import {R3QueryMetadata} from './api';
|
||||||
import {parseStyle} from './styling';
|
import {parseStyle} from './styling';
|
||||||
import {CONTEXT_NAME, I18N_ATTR, I18N_ATTR_PREFIX, ID_SEPARATOR, IMPLICIT_REFERENCE, MEANING_SEPARATOR, REFERENCE_PREFIX, RENDER_FLAGS, asLiteral, invalid, isI18NAttribute, mapToExpression, trimTrailingNulls, unsupported} from './util';
|
import {CONTEXT_NAME, NON_BINDABLE_ATTR, I18N_ATTR, I18N_ATTR_PREFIX, ID_SEPARATOR, IMPLICIT_REFERENCE, MEANING_SEPARATOR, REFERENCE_PREFIX, RENDER_FLAGS, asLiteral, invalid, isI18NAttribute, mapToExpression, trimTrailingNulls, unsupported} from './util';
|
||||||
|
|
||||||
function mapBindingToInstruction(type: BindingType): o.ExternalReference|undefined {
|
function mapBindingToInstruction(type: BindingType): o.ExternalReference|undefined {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
@ -304,11 +304,15 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
|||||||
this._phToNodeIdxes[this._i18nSectionIndex][phName].push(elementIndex);
|
this._phToNodeIdxes[this._i18nSectionIndex][phName].push(elementIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let isNonBindableMode: boolean = false;
|
||||||
|
|
||||||
// Handle i18n attributes
|
// Handle i18n attributes
|
||||||
for (const attr of element.attributes) {
|
for (const attr of element.attributes) {
|
||||||
const name = attr.name;
|
const name = attr.name;
|
||||||
const value = attr.value;
|
const value = attr.value;
|
||||||
if (name === I18N_ATTR) {
|
if (name === NON_BINDABLE_ATTR) {
|
||||||
|
isNonBindableMode = true;
|
||||||
|
} else if (name === I18N_ATTR) {
|
||||||
if (this._inI18nSection) {
|
if (this._inI18nSection) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Could not mark an element as translatable inside of a translatable section`);
|
`Could not mark an element as translatable inside of a translatable section`);
|
||||||
@ -487,6 +491,10 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
|||||||
element.sourceSpan, isNgContainer ? R3.elementContainerStart : R3.elementStart,
|
element.sourceSpan, isNgContainer ? R3.elementContainerStart : R3.elementStart,
|
||||||
trimTrailingNulls(parameters));
|
trimTrailingNulls(parameters));
|
||||||
|
|
||||||
|
if (isNonBindableMode) {
|
||||||
|
this.creationInstruction(element.sourceSpan, R3.setBindingsDisabled);
|
||||||
|
}
|
||||||
|
|
||||||
// initial styling for static style="..." attributes
|
// initial styling for static style="..." attributes
|
||||||
if (hasStylingInstructions) {
|
if (hasStylingInstructions) {
|
||||||
const paramsList: (o.Expression)[] = [];
|
const paramsList: (o.Expression)[] = [];
|
||||||
@ -652,6 +660,11 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
|||||||
|
|
||||||
if (!createSelfClosingInstruction) {
|
if (!createSelfClosingInstruction) {
|
||||||
// Finish element construction mode.
|
// Finish element construction mode.
|
||||||
|
if (isNonBindableMode) {
|
||||||
|
this.creationInstruction(
|
||||||
|
element.endSourceSpan || element.sourceSpan,
|
||||||
|
R3.setBindingsEnabled);
|
||||||
|
}
|
||||||
this.creationInstruction(
|
this.creationInstruction(
|
||||||
element.endSourceSpan || element.sourceSpan,
|
element.endSourceSpan || element.sourceSpan,
|
||||||
isNgContainer ? R3.elementContainerEnd : R3.elementEnd);
|
isNgContainer ? R3.elementContainerEnd : R3.elementEnd);
|
||||||
|
@ -35,6 +35,9 @@ export const I18N_ATTR_PREFIX = 'i18n-';
|
|||||||
export const MEANING_SEPARATOR = '|';
|
export const MEANING_SEPARATOR = '|';
|
||||||
export const ID_SEPARATOR = '@@';
|
export const ID_SEPARATOR = '@@';
|
||||||
|
|
||||||
|
/** Non bindable attribute name **/
|
||||||
|
export const NON_BINDABLE_ATTR = 'ngNonBindable';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an allocator for a temporary variable.
|
* Creates an allocator for a temporary variable.
|
||||||
*
|
*
|
||||||
|
@ -84,6 +84,8 @@ export {
|
|||||||
elementProperty as ɵelementProperty,
|
elementProperty as ɵelementProperty,
|
||||||
projectionDef as ɵprojectionDef,
|
projectionDef as ɵprojectionDef,
|
||||||
reference as ɵreference,
|
reference as ɵreference,
|
||||||
|
setBindingsEnabled as ɵsetBindingsEnabled,
|
||||||
|
setBindingsDisabled as ɵsetBindingsDisabled,
|
||||||
elementAttribute as ɵelementAttribute,
|
elementAttribute as ɵelementAttribute,
|
||||||
elementStyling as ɵelementStyling,
|
elementStyling as ɵelementStyling,
|
||||||
elementStylingMap as ɵelementStylingMap,
|
elementStylingMap as ɵelementStylingMap,
|
||||||
|
@ -67,6 +67,9 @@ export {
|
|||||||
namespaceMathML,
|
namespaceMathML,
|
||||||
namespaceSVG,
|
namespaceSVG,
|
||||||
|
|
||||||
|
setBindingsEnabled,
|
||||||
|
setBindingsDisabled,
|
||||||
|
|
||||||
projection,
|
projection,
|
||||||
projectionDef,
|
projectionDef,
|
||||||
|
|
||||||
|
@ -99,6 +99,8 @@ export function getCurrentSanitizer(): Sanitizer|null {
|
|||||||
*/
|
*/
|
||||||
let elementDepthCount !: number;
|
let elementDepthCount !: number;
|
||||||
|
|
||||||
|
let bindingsEnabled !: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the current OpaqueViewState instance.
|
* Returns the current OpaqueViewState instance.
|
||||||
*
|
*
|
||||||
@ -538,6 +540,7 @@ export function resetComponentState() {
|
|||||||
isParent = false;
|
isParent = false;
|
||||||
previousOrParentTNode = null !;
|
previousOrParentTNode = null !;
|
||||||
elementDepthCount = 0;
|
elementDepthCount = 0;
|
||||||
|
bindingsEnabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -862,6 +865,7 @@ function nativeNodeLocalRefExtractor(tNode: TNode, currentView: LViewData): RNod
|
|||||||
function createDirectivesAndLocals(
|
function createDirectivesAndLocals(
|
||||||
localRefs: string[] | null | undefined,
|
localRefs: string[] | null | undefined,
|
||||||
localRefExtractor: LocalRefExtractor = nativeNodeLocalRefExtractor) {
|
localRefExtractor: LocalRefExtractor = nativeNodeLocalRefExtractor) {
|
||||||
|
if (!bindingsEnabled) return;
|
||||||
if (firstTemplatePass) {
|
if (firstTemplatePass) {
|
||||||
ngDevMode && ngDevMode.firstTemplatePass++;
|
ngDevMode && ngDevMode.firstTemplatePass++;
|
||||||
cacheMatchingDirectivesForNode(previousOrParentTNode, tView, localRefs || null);
|
cacheMatchingDirectivesForNode(previousOrParentTNode, tView, localRefs || null);
|
||||||
@ -1394,6 +1398,22 @@ export function elementProperty<T>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables bindings processing for further instructions
|
||||||
|
* (used while processing "ngNonBindable" element's attribute).
|
||||||
|
*/
|
||||||
|
export function setBindingsEnabled(): void {
|
||||||
|
bindingsEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disables bindings processing for further instructions
|
||||||
|
* (used while processing "ngNonBindable" element's attribute).
|
||||||
|
*/
|
||||||
|
export function setBindingsDisabled(): void {
|
||||||
|
bindingsEnabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a TNode object from the arguments.
|
* Constructs a TNode object from the arguments.
|
||||||
*
|
*
|
||||||
|
@ -46,6 +46,8 @@ export const angularCoreEnv: {[name: string]: Function} = {
|
|||||||
'ɵnamespaceHTML': r3.namespaceHTML,
|
'ɵnamespaceHTML': r3.namespaceHTML,
|
||||||
'ɵnamespaceMathML': r3.namespaceMathML,
|
'ɵnamespaceMathML': r3.namespaceMathML,
|
||||||
'ɵnamespaceSVG': r3.namespaceSVG,
|
'ɵnamespaceSVG': r3.namespaceSVG,
|
||||||
|
'ɵsetBindingsEnabled': r3.setBindingsEnabled,
|
||||||
|
'ɵsetBindingsDisabled': r3.setBindingsDisabled,
|
||||||
'ɵelementStart': r3.elementStart,
|
'ɵelementStart': r3.elementStart,
|
||||||
'ɵelementEnd': r3.elementEnd,
|
'ɵelementEnd': r3.elementEnd,
|
||||||
'ɵelement': r3.element,
|
'ɵelement': r3.element,
|
||||||
|
@ -7,12 +7,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {ElementRef, TemplateRef, ViewContainerRef} from '@angular/core';
|
import {ElementRef, TemplateRef, ViewContainerRef} from '@angular/core';
|
||||||
import {RenderFlags} from '@angular/core/src/render3';
|
import {reference, RenderFlags} from '@angular/core/src/render3';
|
||||||
|
|
||||||
import {RendererStyleFlags2, RendererType2} from '../../src/render/api';
|
import {RendererStyleFlags2, RendererType2} from '../../src/render/api';
|
||||||
import {AttributeMarker, defineComponent, defineDirective} from '../../src/render3/index';
|
import {AttributeMarker, defineComponent, defineDirective} from '../../src/render3/index';
|
||||||
|
|
||||||
import {NO_CHANGE, bind, container, containerRefreshEnd, containerRefreshStart, element, elementAttribute, elementClassProp, elementContainerEnd, elementContainerStart, elementEnd, elementProperty, elementStart, elementStyleProp, elementStyling, elementStylingApply, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV, listener, load, loadDirective, projection, projectionDef, text, textBinding, template} from '../../src/render3/instructions';
|
import {NO_CHANGE, bind, container, containerRefreshEnd, containerRefreshStart, element, elementAttribute, elementClassProp, elementContainerEnd, elementContainerStart, elementEnd, elementProperty, elementStart, elementStyleProp, elementStyling, elementStylingApply, embeddedViewEnd, embeddedViewStart, setBindingsEnabled, setBindingsDisabled, interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV, listener, load, loadDirective, projection, projectionDef, text, textBinding, template} from '../../src/render3/instructions';
|
||||||
import {InitialStylingFlags} from '../../src/render3/interfaces/definition';
|
import {InitialStylingFlags} from '../../src/render3/interfaces/definition';
|
||||||
import {RElement, Renderer3, RendererFactory3, domRendererFactory3, RText, RComment, RNode, RendererStyleFlags3, ProceduralRenderer3} from '../../src/render3/interfaces/renderer';
|
import {RElement, Renderer3, RendererFactory3, domRendererFactory3, RText, RComment, RNode, RendererStyleFlags3, ProceduralRenderer3} from '../../src/render3/interfaces/renderer';
|
||||||
import {HEADER_OFFSET, CONTEXT, DIRECTIVES} from '../../src/render3/interfaces/view';
|
import {HEADER_OFFSET, CONTEXT, DIRECTIVES} from '../../src/render3/interfaces/view';
|
||||||
@ -132,6 +132,132 @@ describe('render3 integration test', () => {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('ngNonBindable handling', () => {
|
||||||
|
it('should keep local ref for host element', () => {
|
||||||
|
/**
|
||||||
|
* <b ngNonBindable #myRef id="my-id">
|
||||||
|
* <i>Hello {{ name }}!</i>
|
||||||
|
* </b>
|
||||||
|
* {{ myRef.id }}
|
||||||
|
*/
|
||||||
|
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
elementStart(0, 'b', ['id', 'my-id'], ['myRef', '']);
|
||||||
|
setBindingsDisabled();
|
||||||
|
elementStart(2, 'i');
|
||||||
|
text(3, 'Hello {{ name }}!');
|
||||||
|
elementEnd();
|
||||||
|
setBindingsEnabled();
|
||||||
|
elementEnd();
|
||||||
|
text(4);
|
||||||
|
}
|
||||||
|
if (rf & RenderFlags.Update) {
|
||||||
|
const ref = reference(1) as any;
|
||||||
|
textBinding(4, interpolation1(" ", ref.id, " "));
|
||||||
|
}
|
||||||
|
}, 5, 1);
|
||||||
|
|
||||||
|
const fixture = new ComponentFixture(App);
|
||||||
|
expect(fixture.html).toEqual('<b id="my-id"><i>Hello {{ name }}!</i></b> my-id ');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not have local refs for nested elements', () => {
|
||||||
|
/**
|
||||||
|
* <div ngNonBindable>
|
||||||
|
* <input value="one" #myInput> {{ myInput.value }}
|
||||||
|
* </div>
|
||||||
|
*/
|
||||||
|
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
elementStart(0, 'div');
|
||||||
|
setBindingsDisabled();
|
||||||
|
element(1, 'input', ['value', 'one']);
|
||||||
|
text(2, '{{ myInput.value }}');
|
||||||
|
setBindingsEnabled();
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
}, 3, 0);
|
||||||
|
|
||||||
|
const fixture = new ComponentFixture(App);
|
||||||
|
expect(fixture.html).toEqual('<div><input value="one">{{ myInput.value }}</div>');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should invoke directives for host element', () => {
|
||||||
|
let directiveInvoked: boolean = false;
|
||||||
|
|
||||||
|
class TestDirective {
|
||||||
|
ngOnInit() {
|
||||||
|
directiveInvoked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
type: TestDirective,
|
||||||
|
selectors: [['', 'directive', '']],
|
||||||
|
factory: () => new TestDirective()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <b ngNonBindable directive>
|
||||||
|
* <i>Hello {{ name }}!</i>
|
||||||
|
* </b>
|
||||||
|
*/
|
||||||
|
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
elementStart(0, 'b', ['directive', '']);
|
||||||
|
setBindingsDisabled();
|
||||||
|
elementStart(1, 'i');
|
||||||
|
text(2, 'Hello {{ name }}!');
|
||||||
|
elementEnd();
|
||||||
|
setBindingsEnabled();
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
}, 3, 0, [TestDirective]);
|
||||||
|
|
||||||
|
const fixture = new ComponentFixture(App);
|
||||||
|
expect(fixture.html).toEqual('<b directive=""><i>Hello {{ name }}!</i></b>');
|
||||||
|
expect(directiveInvoked).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not invoke directives for nested elements', () => {
|
||||||
|
let directiveInvoked: boolean = false;
|
||||||
|
|
||||||
|
class TestDirective {
|
||||||
|
ngOnInit() {
|
||||||
|
directiveInvoked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
type: TestDirective,
|
||||||
|
selectors: [['', 'directive', '']],
|
||||||
|
factory: () => new TestDirective()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <b ngNonBindable>
|
||||||
|
* <i directive>Hello {{ name }}!</i>
|
||||||
|
* </b>
|
||||||
|
*/
|
||||||
|
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
elementStart(0, 'b');
|
||||||
|
setBindingsDisabled();
|
||||||
|
elementStart(1, 'i', ['directive', '']);
|
||||||
|
text(2, 'Hello {{ name }}!');
|
||||||
|
elementEnd();
|
||||||
|
setBindingsEnabled();
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
}, 3, 0, [TestDirective]);
|
||||||
|
|
||||||
|
const fixture = new ComponentFixture(App);
|
||||||
|
expect(fixture.html).toEqual('<b><i directive="">Hello {{ name }}!</i></b>');
|
||||||
|
expect(directiveInvoked).toEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('Siblings update', () => {
|
describe('Siblings update', () => {
|
||||||
it('should handle a flat list of static/bound text nodes', () => {
|
it('should handle a flat list of static/bound text nodes', () => {
|
||||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user