From 2a262d2d5f9e6dce760e8f927515e8ace6afab07 Mon Sep 17 00:00:00 2001 From: Klaus Meinhardt Date: Sat, 16 Jan 2021 12:42:04 +0100 Subject: [PATCH] fix(io): invoke markForCheck when output handler is called Also use local injector in dynamic directives directive. fixes #430 --- .../dynamic-directives.directive.ts | 1 + .../dynamic-io/dynamic-io.directive.spec.ts | 38 ++++++++++++++++++- .../src/lib/io/io-factory.service.spec.ts | 6 ++- .../src/lib/io/io-factory.service.ts | 6 ++- .../src/lib/io/io.service.spec.ts | 3 +- .../src/lib/io/io.service.ts | 7 +++- 6 files changed, 54 insertions(+), 7 deletions(-) diff --git a/projects/ng-dynamic-component/src/lib/dynamic-directives/dynamic-directives.directive.ts b/projects/ng-dynamic-component/src/lib/dynamic-directives/dynamic-directives.directive.ts index c5cc31517..5b730bdbc 100644 --- a/projects/ng-dynamic-component/src/lib/dynamic-directives/dynamic-directives.directive.ts +++ b/projects/ng-dynamic-component/src/lib/dynamic-directives/dynamic-directives.directive.ts @@ -54,6 +54,7 @@ export interface DirectiveRef { @Directive({ selector: '[ndcDynamicDirectives],[ngComponentOutletNdcDynamicDirectives]', + providers: [IoFactoryService], }) export class DynamicDirectivesDirective implements OnDestroy, DoCheck { @Input() diff --git a/projects/ng-dynamic-component/src/lib/dynamic-io/dynamic-io.directive.spec.ts b/projects/ng-dynamic-component/src/lib/dynamic-io/dynamic-io.directive.spec.ts index a7b7c3317..084f5364a 100644 --- a/projects/ng-dynamic-component/src/lib/dynamic-io/dynamic-io.directive.spec.ts +++ b/projects/ng-dynamic-component/src/lib/dynamic-io/dynamic-io.directive.spec.ts @@ -1,5 +1,10 @@ // tslint:disable: no-string-literal -import { ChangeDetectorRef, SimpleChange, SimpleChanges } from '@angular/core'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + SimpleChange, + SimpleChanges, +} from '@angular/core'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { Observable, Subject } from 'rxjs'; @@ -593,4 +598,35 @@ describe('Directive: DynamicIo', () => { expect(outputHandler).toHaveBeenCalledWith('data'); }); }); + + describe('outputs with OnPush', () => { + function testOnPush(withArgs: boolean) { + const template = ``; + TestBed.overrideComponent(TestComponent, { + set: { template, changeDetection: ChangeDetectionStrategy.OnPush }, + }); + const fixture = TestBed.createComponent(TestComponent); + const injectorComp = getComponentInjectorFrom(fixture).component; + const injectedComp = injectorComp.component; + + const eventHandler = (v: string) => + ((fixture.componentInstance as any).value = v); + fixture.componentInstance['outputs'] = { + onEvent: withArgs ? { handler: eventHandler } : eventHandler, + }; + fixture.detectChanges(); + expect(fixture.nativeElement.querySelector('.foo')).toBeFalsy(); + injectedComp.onEvent.emit('foo'); + fixture.detectChanges(); + expect(fixture.nativeElement.querySelector('.foo')).toBeTruthy(); + } + + it('should mark host component for check when calling event handler', () => { + testOnPush(false); + }); + + it('should mark host component for check when calling output with args', () => { + testOnPush(true); + }); + }); }); diff --git a/projects/ng-dynamic-component/src/lib/io/io-factory.service.spec.ts b/projects/ng-dynamic-component/src/lib/io/io-factory.service.spec.ts index ebcc169e3..a46c8ebf8 100644 --- a/projects/ng-dynamic-component/src/lib/io/io-factory.service.spec.ts +++ b/projects/ng-dynamic-component/src/lib/io/io-factory.service.spec.ts @@ -1,3 +1,4 @@ +import { ChangeDetectorRef } from '@angular/core'; import { inject, TestBed } from '@angular/core/testing'; import { IoFactoryService } from './io-factory.service'; @@ -5,7 +6,10 @@ import { IoFactoryService } from './io-factory.service'; describe('Service: IoFactory', () => { beforeEach(() => { TestBed.configureTestingModule({ - providers: [IoFactoryService], + providers: [ + IoFactoryService, + { provide: ChangeDetectorRef, useValue: {} }, + ], }); }); diff --git a/projects/ng-dynamic-component/src/lib/io/io-factory.service.ts b/projects/ng-dynamic-component/src/lib/io/io-factory.service.ts index a275d869a..aaf889965 100644 --- a/projects/ng-dynamic-component/src/lib/io/io-factory.service.ts +++ b/projects/ng-dynamic-component/src/lib/io/io-factory.service.ts @@ -1,4 +1,5 @@ import { + ChangeDetectorRef, ComponentFactoryResolver, Inject, Injectable, @@ -8,16 +9,17 @@ import { import { EventArgumentToken } from './event-argument'; import { IoService } from './io.service'; -@Injectable({ providedIn: 'root' }) +@Injectable() export class IoFactoryService { constructor( private differs: KeyValueDiffers, private cfr: ComponentFactoryResolver, @Inject(EventArgumentToken) private eventArgument: string, + private cdr: ChangeDetectorRef, ) {} create() { - return new IoService(this.differs, this.cfr, this.eventArgument); + return new IoService(this.differs, this.cfr, this.eventArgument, this.cdr); } } diff --git a/projects/ng-dynamic-component/src/lib/io/io.service.spec.ts b/projects/ng-dynamic-component/src/lib/io/io.service.spec.ts index 9d8534a57..4d7843c7a 100644 --- a/projects/ng-dynamic-component/src/lib/io/io.service.spec.ts +++ b/projects/ng-dynamic-component/src/lib/io/io.service.spec.ts @@ -1,3 +1,4 @@ +import { ChangeDetectorRef } from '@angular/core'; import { TestBed } from '@angular/core/testing'; import { IoService } from './io.service'; @@ -7,7 +8,7 @@ describe('Service: Io', () => { beforeEach(() => { TestBed.configureTestingModule({ - providers: [IoService], + providers: [IoService, { provide: ChangeDetectorRef, useValue: {} }], }); service = TestBed.inject(IoService); }); diff --git a/projects/ng-dynamic-component/src/lib/io/io.service.ts b/projects/ng-dynamic-component/src/lib/io/io.service.ts index 7495a1205..9fd8d5c08 100644 --- a/projects/ng-dynamic-component/src/lib/io/io.service.ts +++ b/projects/ng-dynamic-component/src/lib/io/io.service.ts @@ -74,6 +74,7 @@ export class IoService implements OnDestroy { private cfr: ComponentFactoryResolver, @Inject(EventArgumentToken) private eventArgument: string, + private cdr: ChangeDetectorRef, ) {} ngOnDestroy(): void { @@ -166,7 +167,6 @@ export class IoService implements OnDestroy { inputs = this._resolveInputs(inputs); Object.keys(inputs).forEach(p => (compInst[p] = inputs[p])); - // Mark component for check to re-render with new inputs if (this.compCdr) { this.compCdr.markForCheck(); @@ -192,7 +192,10 @@ export class IoService implements OnDestroy { .forEach(p => compInst[p] .pipe(takeUntil(this.outputsShouldDisconnect$)) - .subscribe((event: any) => (outputs[p] as EventHandler)(event)), + .subscribe((event: any) => { + this.cdr.markForCheck(); + return (outputs[p] as EventHandler)(event); + }), ); }