0

I would like to run functions dynamically given an array function names, but TS has a complain on duration[unit] with the following message:

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Duration'.

No index signature with a parameter of type 'string' was found on type 'Duration'.ts(7053)
const duration: Duration = moment.duration(5000)
const unitsOfTime = ['years', 'months', 'weeks', 'days', 'hours', 'minutes', 'seconds']
const timeBreakdown = unitsOfTime.map((unit) => duration[unit]())
2
  • Does this answer your question? how to dynamically call instance methods in typescript? Commented May 30, 2021 at 16:58
  • Not really. My code would be duration[unit as keyof Duration]() if I follow the suggestion provided in the answer above which still does not work. Commented May 30, 2021 at 17:03

3 Answers 3

0

You have to add as const to unitsOfTime, because TS infers it as a stirng[] and duration is not indexed object.

Take a look on Duration interface:

  interface Duration {
    clone(): Duration;

    humanize(argWithSuffix?: boolean, argThresholds?: argThresholdOpts): string;
    
    humanize(argThresholds?: argThresholdOpts): string;

    abs(): Duration;

    as(units: unitOfTime.Base): number;
    get(units: unitOfTime.Base): number;

    milliseconds(): number;
    asMilliseconds(): number;

    seconds(): number;
    asSeconds(): number;

    minutes(): number;
    asMinutes(): number;

    hours(): number;
    asHours(): number;

    days(): number;
    asDays(): number;

    weeks(): number;
    asWeeks(): number;

    months(): number;
    asMonths(): number;

    years(): number;
    asYears(): number;

    add(inp?: DurationInputArg1, unit?: DurationInputArg2): Duration;
    subtract(inp?: DurationInputArg1, unit?: DurationInputArg2): Duration;

    locale(): string;
    locale(locale: LocaleSpecifier): Duration;
    localeData(): Locale;

    toISOString(): string;
    toJSON(): string;

    isValid(): boolean;

    /**
     * @deprecated since version 2.8.0
     */
    lang(locale: LocaleSpecifier): Moment;
    /**
     * @deprecated since version 2.8.0
     */
    lang(): Locale;
    /**
     * @deprecated
     */
    toIsoString(): string;
  }

You are allowed to use only existing props as indexes.

When you add as const, TS will allow you to use unitsOfTime values as a Durarion index

import * as moment from 'moment'

const duration = moment.duration(5000)
const unitsOfTime = ['years', 'months', 'weeks', 'days', 'hours', 'minutes', 'seconds'] as const
const timeBreakdown = unitsOfTime.map((unit) => duration[unit]())

`

Sign up to request clarification or add additional context in comments.

Comments

0

After consulting some friends and more thoughts, I realized that mapping through an array of string is simply not a very good idea as these are magic strings and it implies that the next person who look at this has a good understanding of what method the Duration object responds to.

A better way to do this is via an explicit configuration mapping

// First we define what should unit of time look like
interface unitOfTimeMap {
  unit: string,
  shortUnit: string,
  amount: (duration: Duration) => number
}

// Then we have an explicit configuration of what unit of time we and how they should be mapped to the duration object.

const unitsOfTimeMapping: unitOfTimeMap[] = [
  {
    unit: 'years',
    amount: (duration: Duration): number => duration.years()
  },
  {
    unit: 'months',
    amount: (duration: Duration): number => duration.months()
  },
  {
    unit: 'weeks',
    amount: (duration: Duration): number => duration.weeks()
  },
  {
    unit: 'days',
    amount: (duration: Duration): number => duration.days()
  },
  {
    unit: 'hours',
    amount: (duration: Duration): number => duration.hours()
  },
  {
    unit: 'minutes',
    amount: (duration: Duration): number => duration.minutes()
  },
  {
    unit: 'seconds',
    amount: (duration: Duration): number => duration.seconds()
  },
]

// Now TS knows exactly what we're working with and no longer complains
const duration: Duration = moment.duration(5000);
const timeBreakdown = unitsOfTimeMapping.map((unitOfTime) => unitOfTime.amount(duration))

Comments

-1

You can try below solution for moment. Use get method of moment duration and unit as unitOfTime.Base

import moment, { Duration , unitOfTime } from "moment-timezone";


const duration: Duration = moment.duration(5000);
const unitsOfTime: unitOfTime.Base[] = [
  "years",
  "months",
  "weeks",
  "days",
  "hours",
  "minutes",
  "seconds",
];
const timeBreakdown = unitsOfTime.map((unit:unitOfTime.Base) =>  duration.get(unit));

console.log(timeBreakdown);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.