fix(ivy): register to directive outputs on ng-template / ng-container (#25698)
Runtime part of #25697 PR Close #25698
This commit is contained in:
parent
3809e0fcae
commit
371df35624
|
@ -1202,7 +1202,6 @@ export function hostElement(
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds an event listener to the current node.
|
* Adds an event listener to the current node.
|
||||||
*
|
*
|
||||||
|
@ -1215,29 +1214,35 @@ export function hostElement(
|
||||||
*/
|
*/
|
||||||
export function listener(
|
export function listener(
|
||||||
eventName: string, listenerFn: (e?: any) => any, useCapture = false): void {
|
eventName: string, listenerFn: (e?: any) => any, useCapture = false): void {
|
||||||
ngDevMode && assertPreviousIsParent();
|
ngDevMode &&
|
||||||
ngDevMode && assertNodeOfPossibleTypes(previousOrParentNode, TNodeType.Element);
|
assertNodeOfPossibleTypes(
|
||||||
|
previousOrParentNode, TNodeType.Element, TNodeType.Container, TNodeType.ElementContainer);
|
||||||
const node = previousOrParentNode;
|
const node = previousOrParentNode;
|
||||||
const native = node.native as RElement;
|
|
||||||
ngDevMode && ngDevMode.rendererAddEventListener++;
|
|
||||||
|
|
||||||
// In order to match current behavior, native DOM event listeners must be added for all
|
// add native event listener - applicable to elements only
|
||||||
// events (including outputs).
|
if (previousOrParentNode.tNode.type === TNodeType.Element) {
|
||||||
if (isProceduralRenderer(renderer)) {
|
const native = node.native as RElement;
|
||||||
const wrappedListener = wrapListenerWithDirtyLogic(viewData, listenerFn);
|
ngDevMode && ngDevMode.rendererAddEventListener++;
|
||||||
const cleanupFn = renderer.listen(native, eventName, wrappedListener);
|
|
||||||
storeCleanupFn(viewData, cleanupFn);
|
// In order to match current behavior, native DOM event listeners must be added for all
|
||||||
} else {
|
// events (including outputs).
|
||||||
const wrappedListener = wrapListenerWithDirtyAndDefault(viewData, listenerFn);
|
if (isProceduralRenderer(renderer)) {
|
||||||
native.addEventListener(eventName, wrappedListener, useCapture);
|
const wrappedListener = wrapListenerWithDirtyLogic(viewData, listenerFn);
|
||||||
const cleanupInstances = getCleanup(viewData);
|
const cleanupFn = renderer.listen(native, eventName, wrappedListener);
|
||||||
cleanupInstances.push(wrappedListener);
|
storeCleanupFn(viewData, cleanupFn);
|
||||||
if (firstTemplatePass) {
|
} else {
|
||||||
getTViewCleanup(viewData).push(
|
const wrappedListener = wrapListenerWithDirtyAndDefault(viewData, listenerFn);
|
||||||
eventName, node.tNode.index, cleanupInstances !.length - 1, useCapture);
|
native.addEventListener(eventName, wrappedListener, useCapture);
|
||||||
|
const cleanupInstances = getCleanup(viewData);
|
||||||
|
cleanupInstances.push(wrappedListener);
|
||||||
|
if (firstTemplatePass) {
|
||||||
|
getTViewCleanup(viewData).push(
|
||||||
|
eventName, node.tNode.index, cleanupInstances !.length - 1, useCapture);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// subscribe to directive outputs
|
||||||
let tNode: TNode|null = node.tNode;
|
let tNode: TNode|null = node.tNode;
|
||||||
if (tNode.outputs === undefined) {
|
if (tNode.outputs === undefined) {
|
||||||
// if we create TNode here, inputs must be undefined so we know they still need to be
|
// if we create TNode here, inputs must be undefined so we know they still need to be
|
||||||
|
|
|
@ -8,10 +8,11 @@
|
||||||
|
|
||||||
import {EventEmitter} from '@angular/core';
|
import {EventEmitter} from '@angular/core';
|
||||||
|
|
||||||
import {AttributeMarker, defineDirective} from '../../src/render3/index';
|
import {AttributeMarker, RenderFlags, defineDirective} from '../../src/render3/index';
|
||||||
import {bind, element, elementEnd, elementProperty, elementStart, listener, loadDirective} from '../../src/render3/instructions';
|
|
||||||
|
|
||||||
import {TemplateFixture} from './render_util';
|
import {bind, element, elementEnd, elementProperty, elementStart, listener, template, elementContainerStart, elementContainerEnd} from '../../src/render3/instructions';
|
||||||
|
|
||||||
|
import {ComponentFixture, TemplateFixture, createComponent} from './render_util';
|
||||||
|
|
||||||
describe('directive', () => {
|
describe('directive', () => {
|
||||||
|
|
||||||
|
@ -149,6 +150,67 @@ describe('directive', () => {
|
||||||
expect(fixture.html).toEqual('<span></span>');
|
expect(fixture.html).toEqual('<span></span>');
|
||||||
expect(directiveInstance !).not.toBeUndefined();
|
expect(directiveInstance !).not.toBeUndefined();
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('outputs', () => {
|
||||||
|
|
||||||
|
let directiveInstance: Directive;
|
||||||
|
|
||||||
|
class Directive {
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
type: Directive,
|
||||||
|
selectors: [['', 'out', '']],
|
||||||
|
factory: () => directiveInstance = new Directive,
|
||||||
|
outputs: {out: 'out'}
|
||||||
|
});
|
||||||
|
|
||||||
|
out = new EventEmitter();
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should allow outputs of directive on ng-template', () => {
|
||||||
|
/**
|
||||||
|
* <ng-template (out)="value = true"></ng-template>
|
||||||
|
*/
|
||||||
|
const Cmpt = createComponent('Cmpt', function(rf: RenderFlags, ctx: {value: any}) {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
template(0, null, 0, 0, null, [AttributeMarker.SelectOnly, 'out']);
|
||||||
|
listener('out', () => { ctx.value = true; });
|
||||||
|
}
|
||||||
|
}, 1, 0, [Directive]);
|
||||||
|
|
||||||
|
const fixture = new ComponentFixture(Cmpt);
|
||||||
|
|
||||||
|
expect(directiveInstance !).not.toBeUndefined();
|
||||||
|
expect(fixture.component.value).toBeFalsy();
|
||||||
|
|
||||||
|
directiveInstance !.out.emit();
|
||||||
|
fixture.update();
|
||||||
|
expect(fixture.component.value).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow outputs of directive on ng-container', () => {
|
||||||
|
/**
|
||||||
|
* <ng-container (out)="value = true"></ng-container>
|
||||||
|
*/
|
||||||
|
const Cmpt = createComponent('Cmpt', function(rf: RenderFlags, ctx: {value: any}) {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
elementContainerStart(0, [AttributeMarker.SelectOnly, 'out']);
|
||||||
|
{
|
||||||
|
listener('out', () => { ctx.value = true; });
|
||||||
|
}
|
||||||
|
elementContainerEnd();
|
||||||
|
}
|
||||||
|
}, 1, 0, [Directive]);
|
||||||
|
|
||||||
|
const fixture = new ComponentFixture(Cmpt);
|
||||||
|
|
||||||
|
expect(directiveInstance !).not.toBeUndefined();
|
||||||
|
expect(fixture.component.value).toBeFalsy();
|
||||||
|
|
||||||
|
directiveInstance !.out.emit();
|
||||||
|
fixture.update();
|
||||||
|
expect(fixture.component.value).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -862,25 +862,6 @@ describe('render3 integration test', () => {
|
||||||
expect(fixture.html).toEqual('<div></div>');
|
expect(fixture.html).toEqual('<div></div>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw when trying to add event listener', () => {
|
|
||||||
/**
|
|
||||||
* <div><ng-container (click)="..."></ng-container></div>
|
|
||||||
*/
|
|
||||||
function Template() {
|
|
||||||
elementStart(0, 'div');
|
|
||||||
{
|
|
||||||
elementContainerStart(1);
|
|
||||||
{
|
|
||||||
listener('click', function() {});
|
|
||||||
}
|
|
||||||
elementContainerEnd();
|
|
||||||
}
|
|
||||||
elementEnd();
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(() => { new TemplateFixture(Template, () => {}, 2); }).toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('tree', () => {
|
describe('tree', () => {
|
||||||
|
|
Loading…
Reference in New Issue