Pass in params to function by object spreading a params object not working

Viewed 50

I want to pass in all the params to this function by spreading a params object like this SO answer:

public showToast(
    content: string,
    buttonLabel = 'Ok',
    hideDelay?: number,
    buttonAction?: Function,
    uniqueKey?: string,
    canBeCleared?: boolean,
    templateCtrl?: Object
): Toast {
    const toast = new Toast(
        content,
        this,
        buttonLabel,
        hideDelay,
        buttonAction,
        uniqueKey,
        canBeCleared,
        templateCtrl
    );
    toast.id = !this.lastToast ? 1 : this.lastToast.id + 1;
    this.toasts.push(toast);
    toast.top = toast.fromTop;

    // hiding is handled in the ToastComponent
    return toast;
}

My attempt:

const toastArgs = {
    content: 'test toast',
    buttonLabel: 'test button',
    hideDelay: 1000,
    buttonAction: () => 5,
    uniqueKey: 'uk',
    canBeCleared: true,
    templateCtrl: { strl: 3 }
};
service.showToast(...Object.values(toastArgs));

The compile time error:

Expected 1-7 arguments, but got 0 or more.ts(2556) toast.service.ts(18, 9): An argument for 'content' was not provided.

Why do I get this error? I have followed the SO answer.

2 Answers

You're getting this since Object.values(toastArgs) gets inferred as:

(true | "test toast" | "test button" | 1000 | (() => number) | "uk" | {
    readonly strl: 3;
})[]

Typescript does not really have the concept of an array's 'length'. As far as typescript is concerned, toastArgs could be { x: true }, and it would satisfy the type of Object.values(toastArgs). You can test this out like so:

const toastArgsValid = {
  content: 'test toast',
  buttonLabel: 'test button',
  hideDelay: 1000,
  buttonAction: () => 5,
  uniqueKey: 'uk',
  canBeCleared: true,
  templateCtrl: {strl: 3},
};

const toastArgsValuesValid = Object.values(toastArgsValid);

type ToastParamsValid = typeof toastArgsValuesValid;

const toastArgsInvalid = {
  x: true,
};

const toastArgsValuesInvalid: ToastParamsValid = Object.values(
  toastArgsInvalid,
); // No error here since the invalid type is a subtype of the valid type

One way to achieve what you're looking for is to refactor your showToast method to instead take a single object, instead of positional args:

public showToast({
  content,
  buttonLabel,
  hideDelay,
  buttonAction,
  uniqueKey,
  canBeCleared,
  templateCtrl,
}: {
  content: string;
  buttonLabel: string;
  hideDelay?: number;
  buttonAction?: Function;
  uniqueKey?: string;
  canBeCleared?: boolean;
  templateCtrl?: Object;
}): Toast {
 // function body
}

You can then call:

service.showToast(toastArgs)

Alternatively you can also use a type assertion, without refactoring your code like so:

service.showToast(...(Object.values(toastArgs) as Parameters<typeof service.showToast>));

Object.values() is supported from es2017, ECMAScript_2017. And the default target version of TypeScript is ES3. The error can be fixed by just adding the compiler option --target es2017.

Related