From da8ee29e7299940b4275c7a6388ca5395c65e58c Mon Sep 17 00:00:00 2001 From: crisbeto Date: Fri, 11 Jan 2019 21:14:42 +0100 Subject: [PATCH] fix(ivy): throw meaningful error for uninitialized output (#28085) Throws a similar error to ViewEngine when encountering an `@Output` that hasn't been initialized to an `Observable`. These changes resolve FW-680. PR Close #28085 --- packages/core/src/render3/instructions.ts | 10 ++++++- packages/core/test/linker/integration_spec.ts | 26 +++++++++---------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 6e7363345f..fbad5ce5f3 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -14,6 +14,7 @@ import {validateAttribute, validateProperty} from '../sanitization/sanitization' import {Sanitizer} from '../sanitization/security'; import {StyleSanitizeFn} from '../sanitization/style_sanitizer'; import {assertDataInRange, assertDefined, assertEqual, assertLessThan, assertNotEqual} from '../util/assert'; +import {isObservable} from '../util/lang'; import {normalizeDebugBindingName, normalizeDebugBindingValue} from '../util/ng_reflect'; import {assertHasParent, assertPreviousIsParent} from './assert'; @@ -936,7 +937,14 @@ export function listener( const declaredName = props[i++] as string; ngDevMode && assertDataInRange(lView, directiveIndex as number); const directive = unwrapOnChangesDirectiveWrapper(lView[directiveIndex]); - const subscription = directive[minifiedName].subscribe(listenerFn); + const output = directive[minifiedName]; + + if (ngDevMode && !isObservable(output)) { + throw new Error( + `@Output ${minifiedName} not initialized in '${directive.constructor.name}'.`); + } + + const subscription = output.subscribe(listenerFn); const idx = lCleanup.length; lCleanup.push(listenerFn, subscription); tCleanup && tCleanup.push(eventName, tNode.index, idx, -(idx + 1)); diff --git a/packages/core/test/linker/integration_spec.ts b/packages/core/test/linker/integration_spec.ts index 1e6badf7a2..067d8a6bfe 100644 --- a/packages/core/test/linker/integration_spec.ts +++ b/packages/core/test/linker/integration_spec.ts @@ -347,22 +347,20 @@ function declareTests(config?: {useJit: boolean}) { expect(tc.injector.get(EventDir)).not.toBeNull(); }); - fixmeIvy('FW-680: Throw meaningful error for uninitialized @Output') - .it('should display correct error message for uninitialized @Output', () => { - @Component({selector: 'my-uninitialized-output', template: '

It works!

'}) - class UninitializedOutputComp { - @Output() customEvent !: EventEmitter; - } + it('should display correct error message for uninitialized @Output', () => { + @Component({selector: 'my-uninitialized-output', template: '

It works!

'}) + class UninitializedOutputComp { + @Output() customEvent !: EventEmitter; + } - const template = - ''; - TestBed.overrideComponent(MyComp, {set: {template}}); + const template = + ''; + TestBed.overrideComponent(MyComp, {set: {template}}); - TestBed.configureTestingModule({declarations: [MyComp, UninitializedOutputComp]}); - expect(() => TestBed.createComponent(MyComp)) - .toThrowError( - '@Output customEvent not initialized in \'UninitializedOutputComp\'.'); - }); + TestBed.configureTestingModule({declarations: [MyComp, UninitializedOutputComp]}); + expect(() => TestBed.createComponent(MyComp)) + .toThrowError('@Output customEvent not initialized in \'UninitializedOutputComp\'.'); + }); it('should read directives metadata from their binding token', () => { TestBed.configureTestingModule({declarations: [MyComp, PrivateImpl, NeedsPublicApi]});