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:
Pawel Kozlowski
2018-05-04 15:58:42 +02:00
committed by Victor Berchet
parent b87d650da2
commit 90bf5d8961
9 changed files with 349 additions and 74 deletions

View File

@ -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', () => {