180

I have the following logging method:

private logData<T, S>(operation: string, responseData: T, requestData?: S) {
    this.logger.log(operation + ' ' + this.url);
    if (requestData) {
        this.logger.log('SENT');
        this.logger.log(requestData);
    }
    this.logger.log('RECEIVED');
    this.logger.log(responseData);
    return responseData;
}

The requestData is optional. I want to be able to call logData without having to specify the S type when I don't send the requestData to the method: instead of: this.logData<T, any>('GET', data), I want to call this.logData<T>('GET', data).

Is there a way to achieve this?

double-beep
  • 5,031
  • 17
  • 33
  • 41
Marius
  • 3,253
  • 5
  • 25
  • 29
  • 1
    Maybe overload the function instead? Or maybe try using a default parameter. – Luka Jacobowitz May 30 '16 at 12:00
  • 2
    I don't think it's possible to overload, if I do something like `private logData(operation: string, responseData: T)` and `private logData(operation: string, responseData: T, requestData: S)` I will get an duplicate function definition error – Marius May 30 '16 at 12:04

7 Answers7

260

As of TypeScript 2.3, you can use generic parameter defaults.

private logData<T, S = {}>(operation: string, responseData: T, requestData?: S) {
  // your implementation here
}
kimamula
  • 11,427
  • 8
  • 35
  • 29
129

TS Update 2020: Giving void will make the generic type optional.

type SomeType<T = void> = OtherType<T>;

The answer above where a default value as the object is given make it optional but still give value to it.


Example with default type value is {}:

type BaseFunctionType<T1, T2> = (a:T1, b:T2) => void;

type FunctionType<T = {}> = BaseFunctionType<{name: string}, T>

const someFunction:FunctionType = (a) => {

}

someFunction({ name: "Siraj" });
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
// Expected 2 arguments, but got 1.(2554)

Playground Link


Example with default generic type value is void

type BaseFunctionType<T1, T2> = (a:T1, b:T2) => void;

type FunctionType<T = void> = BaseFunctionType<{name: string}, T>

const someFunction:FunctionType = (a) => {

}

someFunction({ name: "Siraj" })

This is a good read on making generics optional.

Optional generic type in Typescript

Playground Link

Siraj Alam
  • 9,217
  • 9
  • 53
  • 65
26

As per TypeScript 2.2 (you can try it in the TS Playground), calling this.logData("GET", data) (with data of type T) gets inferred succesfully as this.logData<T, {}>("GET", data).

The overload suggested by David Bohunek can be applied if the inference fails with the TS version you use. Anyway, ensure that the second signature is before declared and then defined, otherwise it would not participate in the available overloads.

// Declarations
private logData<T>(operation: string, responseData: T);
private logData<T, S>(operation: string, responseData: T, requestData?: S);
// Definition
private logData<T, S>(operation: string, responseData: T, requestData?: S) {
    // Body
}
FstTesla
  • 844
  • 1
  • 7
  • 10
14

If you're looking for an optional generic type within a Type/Interface declaration, this might help.

(came looking for this, only found answers dealing with generic function declarations. Siraj's answer got me on the right track.)

type ResponseWithMessage = {
  message: string;
};

interface ResponseWithData<T> extends ResponseWithMessage {
  data: T;
}

export type ResponseObject<T = void> = T extends void
  ? ResponseWithMessage
  : ResponseWithData<T>;
panepeter
  • 3,224
  • 1
  • 29
  • 41
4

How about Partial?

Constructs a type with all properties of Type set to optional. This utility will return a type that represents all subsets of a given type.

4
  • void doesn't play well where some kind of object is expected. Edit: plays perfectly well, you just have to consider the fact that void will override anything you merge it with (void & string is basically void) - this is probably the one you want
  • unknown
  • {} basically the same as unknown
interface OffsetPagination {
    offset: number;
    limit: number;
}

interface PagePagination {
    page: number;
    size: number;
}

type HttpListParams<
    PaginationType extends OffsetPagination | PagePagination | void = void,
    Params = {
        filter?: string;
    },
> = PaginationType extends void ? Params : PaginationType & Params;

ts playground

cotneit
  • 304
  • 4
  • 3
3

You can write the overloading method like this:

private logData<T>(operation: string, responseData: T);
private logData<T, S>(operation: string, responseData: T, requestData?: S) {
    this.logger.log(operation + ' ' + this.url);
    if (requestData) {
        this.logger.log('SENT');
        this.logger.log(requestData);
    }
    this.logger.log('RECEIVED');
    this.logger.log(responseData);
    return responseData;
}

But I don't think you really need it, because you don't have to write this.logData<T, any>('GET', data) instead just write this.logData('GET', data). The T type will be infered

David Bohunek
  • 3,181
  • 1
  • 21
  • 26
  • 2
    I'd be careful with generic and the conditional statement of this code. If the generic `S` is of type boolean or number it will fail to go inside the `if(requireData)` because the boolean value false and the number value 0 will be false. It would be more resilient to use `if(requestData !== undefined)`. – Patrick Desjardins Mar 21 '18 at 17:55