Search code examples
angulartypescripteventsobservabletype-safety

Is it possible to get the type from a generic, given as parameter


I created a function that takes an object of subscribers, as seen below.

public subscribers = {
    count: <EventEmitter<number>>new EventEmitter<number>(),
    staticCount: <EventEmitter<number>>new EventEmitter<number>()
};

This function waits for both observables to complete, and returns an object with the keys of the object it received, as value the emitted value from the EventEmitter.

type AnyType<T> = {
    [P in keyof T]: any;
}

function ZipObservable<T extends { [name: string]: EventEmitter<any>}>
    (observables: T): Observable<AnyType<T>> {
    const m = CreateMapFromObject(observables);
    const o = new Subject<any>();

    Observable.zip(...Array.from(m.values())).subscribe((res) => {
        const keys = m.keys();
        const c = res.reduce((r, v) => {
            const k = <string>keys.next().value;
            r[k] = v;

            return r
        }, {});

        /**
         *
         */
        o.next(c);
        o.complete();
    });

    return o.asObservable();
}

My current problem is at the type of the value set in the returned object. The object its keys are typed (as you can see in the image).

I also want the type of the value to be set to number (in this case). I created a type; AnyType<T> that's able to set the returned object its keys. Is it possible to also get it to set its value's type?

The type i want it to become, comes from the parameter, which receives a generic; EventEmitter<number>.

Working type safety for the returned object.

screenshot


Solution

  • So, you want ZipObservable to return an object of type Observable<T>, where the input parameter has the same keys as T but the values for each key K are EventEmitter<T[K]> (where T[K] is the type of the property value of the K key of type T). That is definitely achievable, through mapped types and inference from mapped types:

    First let's define a mapped type which takes every property in T and converts it to an EventEmitter<> of the appropriate type:

    type EventEmitters<T> = {
      [K in keyof T]: EventEmitter<T[K]>
    }
    

    Now the ZipObservable function signature can be written directly (I will leave the implementation up to you):

    declare function ZipObservable<T>(eventEmitters: EventEmitters<T>): Observable<T>; 
    

    And it works because of inference from mapped types:

    const ret = ZipObservable(subscribers).subscribe(r => r.count * 3); // no error
    

    Hope that helps; good luck!!