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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds an event listener to the current node.
|
||||
*
|
||||
|
@ -1215,29 +1214,35 @@ export function hostElement(
|
|||
*/
|
||||
export function listener(
|
||||
eventName: string, listenerFn: (e?: any) => any, useCapture = false): void {
|
||||
ngDevMode && assertPreviousIsParent();
|
||||
ngDevMode && assertNodeOfPossibleTypes(previousOrParentNode, TNodeType.Element);
|
||||
ngDevMode &&
|
||||
assertNodeOfPossibleTypes(
|
||||
previousOrParentNode, TNodeType.Element, TNodeType.Container, TNodeType.ElementContainer);
|
||||
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
|
||||
// events (including outputs).
|
||||
if (isProceduralRenderer(renderer)) {
|
||||
const wrappedListener = wrapListenerWithDirtyLogic(viewData, listenerFn);
|
||||
const cleanupFn = renderer.listen(native, eventName, wrappedListener);
|
||||
storeCleanupFn(viewData, cleanupFn);
|
||||
} else {
|
||||
const wrappedListener = wrapListenerWithDirtyAndDefault(viewData, listenerFn);
|
||||
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);
|
||||
// add native event listener - applicable to elements only
|
||||
if (previousOrParentNode.tNode.type === TNodeType.Element) {
|
||||
const native = node.native as RElement;
|
||||
ngDevMode && ngDevMode.rendererAddEventListener++;
|
||||
|
||||
// In order to match current behavior, native DOM event listeners must be added for all
|
||||
// events (including outputs).
|
||||
if (isProceduralRenderer(renderer)) {
|
||||
const wrappedListener = wrapListenerWithDirtyLogic(viewData, listenerFn);
|
||||
const cleanupFn = renderer.listen(native, eventName, wrappedListener);
|
||||
storeCleanupFn(viewData, cleanupFn);
|
||||
} else {
|
||||
const wrappedListener = wrapListenerWithDirtyAndDefault(viewData, listenerFn);
|
||||
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;
|
||||
if (tNode.outputs === undefined) {
|
||||
// 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 {AttributeMarker, defineDirective} from '../../src/render3/index';
|
||||
import {bind, element, elementEnd, elementProperty, elementStart, listener, loadDirective} from '../../src/render3/instructions';
|
||||
import {AttributeMarker, RenderFlags, defineDirective} from '../../src/render3/index';
|
||||
|
||||
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', () => {
|
||||
|
||||
|
@ -149,6 +150,67 @@ describe('directive', () => {
|
|||
expect(fixture.html).toEqual('<span></span>');
|
||||
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>');
|
||||
});
|
||||
|
||||
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', () => {
|
||||
|
|
Loading…
Reference in New Issue