feat(ivy): separate attributes for directive matching purposes (#23991)
In ngIvy directives matching (determining which directives are active based on a CSS seletor) happens at runtime. This means that runtime needs to have enough context to match directives. This PR takes care of cases where a directive's selector should match bindings (ex. [foo]="exp") and event handlers (ex. (out)="do()"). In the mentioned cases we need to have binding / output "attributes" for directive's CSS selector matching purposes. At the same time those are not regular attributes and as such should not be reflected in the DOM. Closes #23706 PR Close #23991
This commit is contained in:

committed by
Victor Berchet

parent
b87d650da2
commit
90bf5d8961
@ -12,9 +12,9 @@ import {RenderFlags} from '@angular/core/src/render3/interfaces/definition';
|
||||
import {defineComponent} from '../../src/render3/definition';
|
||||
import {bloomAdd, bloomFindPossibleInjector, getOrCreateNodeInjector, injectAttribute} from '../../src/render3/di';
|
||||
import {NgOnChangesFeature, PublicFeature, defineDirective, directiveInject, injectChangeDetectorRef, injectElementRef, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index';
|
||||
import {bind, container, containerRefreshEnd, containerRefreshStart, createLNode, createLView, createTView, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, enterView, interpolation2, leaveView, load, projection, projectionDef, text, textBinding} from '../../src/render3/instructions';
|
||||
import {bind, container, containerRefreshEnd, containerRefreshStart, createLNode, createLView, createTView, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, enterView, interpolation2, leaveView, load, projection, projectionDef, text, textBinding} from '../../src/render3/instructions';
|
||||
import {LInjector} from '../../src/render3/interfaces/injector';
|
||||
import {TNodeType} from '../../src/render3/interfaces/node';
|
||||
import {AttributeMarker, TNodeType} from '../../src/render3/interfaces/node';
|
||||
import {LViewFlags} from '../../src/render3/interfaces/view';
|
||||
import {ViewRef} from '../../src/render3/view_ref';
|
||||
|
||||
@ -1199,21 +1199,61 @@ describe('di', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should injectAttribute', () => {
|
||||
let exist: string|undefined = 'wrong';
|
||||
let nonExist: string|undefined = 'wrong';
|
||||
describe('@Attribute', () => {
|
||||
|
||||
const MyApp = createComponent('my-app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
elementStart(0, 'div', ['exist', 'existValue', 'other', 'ignore']);
|
||||
exist = injectAttribute('exist');
|
||||
nonExist = injectAttribute('nonExist');
|
||||
}
|
||||
it('should inject attribute', () => {
|
||||
let exist: string|undefined = 'wrong';
|
||||
let nonExist: string|undefined = 'wrong';
|
||||
|
||||
const MyApp = createComponent('my-app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
elementStart(0, 'div', ['exist', 'existValue', 'other', 'ignore']);
|
||||
exist = injectAttribute('exist');
|
||||
nonExist = injectAttribute('nonExist');
|
||||
}
|
||||
});
|
||||
|
||||
const fixture = new ComponentFixture(MyApp);
|
||||
expect(exist).toEqual('existValue');
|
||||
expect(nonExist).toEqual(undefined);
|
||||
});
|
||||
|
||||
const app = renderComponent(MyApp);
|
||||
expect(exist).toEqual('existValue');
|
||||
expect(nonExist).toEqual(undefined);
|
||||
// https://stackblitz.com/edit/angular-8ytqkp?file=src%2Fapp%2Fapp.component.ts
|
||||
it('should not inject attributes representing bindings and outputs', () => {
|
||||
let exist: string|undefined = 'wrong';
|
||||
let nonExist: string|undefined = 'wrong';
|
||||
|
||||
const MyApp = createComponent('my-app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
elementStart(0, 'div', ['exist', 'existValue', AttributeMarker.SELECT_ONLY, 'nonExist']);
|
||||
exist = injectAttribute('exist');
|
||||
nonExist = injectAttribute('nonExist');
|
||||
}
|
||||
});
|
||||
|
||||
const fixture = new ComponentFixture(MyApp);
|
||||
expect(exist).toEqual('existValue');
|
||||
expect(nonExist).toEqual(undefined);
|
||||
});
|
||||
|
||||
it('should not accidentally inject attributes representing bindings and outputs', () => {
|
||||
let exist: string|undefined = 'wrong';
|
||||
let nonExist: string|undefined = 'wrong';
|
||||
|
||||
const MyApp = createComponent('my-app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
elementStart(0, 'div', [
|
||||
'exist', 'existValue', AttributeMarker.SELECT_ONLY, 'binding1', 'nonExist', 'binding2'
|
||||
]);
|
||||
exist = injectAttribute('exist');
|
||||
nonExist = injectAttribute('nonExist');
|
||||
}
|
||||
});
|
||||
|
||||
const fixture = new ComponentFixture(MyApp);
|
||||
expect(exist).toEqual('existValue');
|
||||
expect(nonExist).toEqual(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('inject', () => {
|
||||
|
Reference in New Issue
Block a user