fix(common): correct and simplify typing of AsyncPipe (#37447)

`AsyncPipe.transform` will never return `undefined`, even when passed
`undefined` in input, in contrast with what was declared in the
overloads.

Additionally the "actual" method signature can be updated to match the
most generic case, since the implementation does not rely on wrappers
anymore.

BREAKING CHANGE:
The async pipe no longer claims to return `undefined` for an input that
was typed as `undefined`. Note that the code actually returned `null` on
`undefined` inputs. In the unlikely case you were relying on this,
please fix the typing of the consumers of the pipe output.

PR Close #37447
This commit is contained in:
Andrea Canciani 2020-03-27 11:27:54 +01:00 committed by Alex Rickabaugh
parent c7d5555dfb
commit 5f815c0565
4 changed files with 18 additions and 13 deletions

View File

@ -3,10 +3,9 @@ export declare const APP_BASE_HREF: InjectionToken<string>;
export declare class AsyncPipe implements OnDestroy, PipeTransform { export declare class AsyncPipe implements OnDestroy, PipeTransform {
constructor(_ref: ChangeDetectorRef); constructor(_ref: ChangeDetectorRef);
ngOnDestroy(): void; ngOnDestroy(): void;
transform<T>(obj: null): null; transform<T>(obj: Observable<T> | Promise<T>): T | null;
transform<T>(obj: undefined): undefined; transform<T>(obj: null | undefined): null;
transform<T>(obj: Observable<T> | null | undefined): T | null; transform<T>(obj: Observable<T> | Promise<T> | null | undefined): T | null;
transform<T>(obj: Promise<T> | null | undefined): T | null;
} }
export declare class CommonModule { export declare class CommonModule {

View File

@ -94,11 +94,10 @@ export class AsyncPipe implements OnDestroy, PipeTransform {
} }
} }
transform<T>(obj: null): null; transform<T>(obj: Observable<T>|Promise<T>): T|null;
transform<T>(obj: undefined): undefined; transform<T>(obj: null|undefined): null;
transform<T>(obj: Observable<T>|null|undefined): T|null; transform<T>(obj: Observable<T>|Promise<T>|null|undefined): T|null;
transform<T>(obj: Promise<T>|null|undefined): T|null; transform<T>(obj: Observable<T>|Promise<T>|null|undefined): T|null {
transform(obj: Observable<any>|Promise<any>|null|undefined): any {
if (!this._obj) { if (!this._obj) {
if (obj) { if (obj) {
this._subscribe(obj); this._subscribe(obj);
@ -108,7 +107,7 @@ export class AsyncPipe implements OnDestroy, PipeTransform {
if (obj !== this._obj) { if (obj !== this._obj) {
this._dispose(); this._dispose();
return this.transform(obj as any); return this.transform(obj);
} }
return this._latestValue; return this._latestValue;

View File

@ -129,7 +129,7 @@ import {SpyChangeDetectorRef} from '../spies';
reject = rej; reject = rej;
}); });
ref = new SpyChangeDetectorRef(); ref = new SpyChangeDetectorRef();
pipe = new AsyncPipe(<any>ref); pipe = new AsyncPipe(ref as any);
}); });
describe('transform', () => { describe('transform', () => {
@ -218,10 +218,17 @@ import {SpyChangeDetectorRef} from '../spies';
}); });
}); });
describe('undefined', () => {
it('should return null when given undefined', () => {
const pipe = new AsyncPipe(null as any);
expect(pipe.transform(undefined)).toEqual(null);
});
});
describe('other types', () => { describe('other types', () => {
it('should throw when given an invalid object', () => { it('should throw when given an invalid object', () => {
const pipe = new AsyncPipe(null as any); const pipe = new AsyncPipe(null as any);
expect(() => pipe.transform(<any>'some bogus object')).toThrowError(); expect(() => pipe.transform('some bogus object' as any)).toThrowError();
}); });
}); });
}); });

View File

@ -251,7 +251,7 @@ describe('definitions', () => {
expect(textSpan).toEqual(marker); expect(textSpan).toEqual(marker);
expect(definitions).toBeDefined(); expect(definitions).toBeDefined();
expect(definitions!.length).toBe(4); expect(definitions!.length).toBe(3);
const refFileName = '/node_modules/@angular/common/common.d.ts'; const refFileName = '/node_modules/@angular/common/common.d.ts';
for (const def of definitions!) { for (const def of definitions!) {