66 * found in the LICENSE file at https://angular.dev/license
77 */
88
9- import { ComponentRef } from '@angular/core' ;
9+ import { ComponentRef , inject } from '@angular/core' ;
1010import { ComponentFactoryResolver } from '@angular/core/src/render3/component_ref' ;
1111import { Renderer } from '@angular/core/src/render3/interfaces/renderer' ;
1212import { RElement } from '@angular/core/src/render3/interfaces/renderer_dom' ;
@@ -15,10 +15,13 @@ import {TestBed} from '@angular/core/testing';
1515import {
1616 ChangeDetectionStrategy ,
1717 Component ,
18+ Directive ,
19+ EventEmitter ,
1820 Injector ,
1921 Input ,
2022 NgModuleRef ,
2123 OnChanges ,
24+ output ,
2225 Output ,
2326 RendererType2 ,
2427 SimpleChanges ,
@@ -467,4 +470,105 @@ describe('ComponentFactory', () => {
467470 expect ( fixture . nativeElement . innerText ) . toBe ( '2' ) ;
468471 } ) ;
469472 } ) ;
473+
474+ describe ( 'listen' , ( ) => {
475+ it ( 'should allow listening to on the ComponentRef' , ( ) => {
476+ const logs : string [ ] = [ ] ;
477+
478+ @Directive ( { standalone : true } )
479+ class MyDirective {
480+ @Output ( ) onDirChange = new EventEmitter < void > ( ) ;
481+ @Output ( ) onChange = new EventEmitter < void > ( ) ;
482+
483+ emit ( ) {
484+ this . onChange . emit ( ) ;
485+ this . onDirChange . emit ( ) ;
486+ }
487+ }
488+
489+ @Component ( {
490+ template : `{{in}}` ,
491+ hostDirectives : [ { directive : MyDirective , outputs : [ 'onDirChange' , 'onChange' ] } ] ,
492+ standalone : true ,
493+ } )
494+ class MyComponent {
495+ @Output ( ) onChange = new EventEmitter < void > ( ) ;
496+ @Output ( 'onSomeOtherChange' ) onOtherChange = new EventEmitter < void > ( ) ;
497+
498+ private readonly directive : MyDirective = inject ( MyDirective , { self : true } ) ;
499+
500+ emit ( ) {
501+ this . onChange . emit ( ) ;
502+ this . onOtherChange . emit ( ) ;
503+ this . directive . emit ( ) ;
504+ }
505+ }
506+
507+ const fixture = TestBed . createComponent ( MyComponent ) ;
508+ fixture . detectChanges ( ) ;
509+
510+ const cleanUp1 = fixture . componentRef . unsafeListenToOutput ( 'onChange' , ( ) => {
511+ logs . push ( 'onChange' ) ;
512+ } ) ;
513+ const cleanUp2 = fixture . componentRef . unsafeListenToOutput ( 'onSomeOtherChange' , ( ) => {
514+ logs . push ( 'onSomeOtherChange' ) ;
515+ } ) ;
516+ const cleanUp3 = fixture . componentRef . unsafeListenToOutput ( 'onDirChange' , ( ) => {
517+ logs . push ( 'onDirChange' ) ;
518+ } ) ;
519+
520+ expect ( logs . length ) . toBe ( 0 ) ;
521+
522+ fixture . componentInstance . emit ( ) ;
523+ expect ( logs . length ) . toBe ( 4 ) ;
524+ // Emited by the component
525+ expect ( logs . at ( - 4 ) ) . toBe ( 'onChange' ) ;
526+ expect ( logs . at ( - 3 ) ) . toBe ( 'onSomeOtherChange' ) ;
527+
528+ // Emited by the HostDirective
529+ expect ( logs . at ( - 2 ) ) . toBe ( 'onChange' ) ;
530+ expect ( logs . at ( - 1 ) ) . toBe ( 'onDirChange' ) ;
531+
532+ cleanUp1 ( ) ;
533+ cleanUp2 ( ) ;
534+ cleanUp3 ( ) ;
535+ fixture . componentInstance . emit ( ) ;
536+ expect ( logs . length ) . toBe ( 4 ) ;
537+ } ) ;
538+
539+ it ( 'should ensure type safety for listenToOutput' , ( ) => {
540+ @Component ( { template : `` , standalone : true } )
541+ class MyComponent {
542+ @Output ( ) eventEmitterString = new EventEmitter < { foo : string } > ( ) ;
543+ }
544+
545+ const fixture = TestBed . createComponent ( MyComponent ) ;
546+
547+ let foo : { foo : string } ;
548+ fixture . componentRef . listenToOutput ( 'eventEmitterString' , ( event ) => {
549+ foo = event ;
550+ } ) ;
551+
552+ // Testing an non matching type throws
553+ let bar : { bar : string } ;
554+ fixture . componentRef . listenToOutput ( 'eventEmitterString' , ( event ) => {
555+ /* @ts -expect-error */
556+ bar = event ;
557+ } ) ;
558+ } ) ;
559+
560+ it ( 'should throw when listening a non-existing output' , ( ) => {
561+ const logs : string [ ] = [ ] ;
562+
563+ @Component ( { template : `{{in}}` } )
564+ class DynamicCmp {
565+ notAnInput : any ;
566+ }
567+
568+ const fixture = TestBed . createComponent ( DynamicCmp ) ;
569+ fixture . detectChanges ( ) ;
570+
571+ expect ( ( ) => fixture . componentRef . unsafeListenToOutput ( 'notAnOutput' , ( ) => { } ) ) . toThrow ( ) ;
572+ } ) ;
573+ } ) ;
470574} ) ;
0 commit comments