0

Since I have a C# backend, I thought of applying a C# principle to the Angular frontend, so I came up with this:

declare interface Date {
    addDays(days: number): Date;
    addYears(years: number): Date;
    isToday(): boolean;
    isSameDate(date: Date): boolean;
    customFormat(): string;
}

declare interface String {
    ToDate(): Date;
}

declare interface Array<T> {
    ToDate(): string[];
}

Array.prototype.ToDate = function (): string[] {
    return this.valueOf().map(timestamp => timestamp.ToDate());
};

String.prototype.ToDate = function (): Date {
    return new Date(this.valueOf());
};

Date.prototype.addDays = function (days: number): Date {
    if (!days) return this;
    let date = new Date(this.valueOf());
    date.setDate(date.getDate() + days);
    return date;
};

Date.prototype.addYears = function (years: number): Date {
    if (!years) return this;
    let date = new Date(this.valueOf());
    date.setFullYear(date.getFullYear() + years);
    return date;
};

Date.prototype.isToday = function (): boolean {
    let today = new Date();
    return this.isSameDate(today);
};

Date.prototype.isSameDate = function (date: Date): boolean {
    return date && this.getFullYear() === date.getFullYear() && this.getMonth() === date.getMonth() && this.getDate() === date.getDate();
};

Date.prototype.customFormat = function (): string {
    let date = new Date(this.valueOf());
    let yyyy = date.getFullYear();
    let mm = date.getMonth() + 1;
    let dd = date.getDate();
    return dd + "/" + mm + "/" + yyyy;
};

The idea being that I can do stuff like

let foo = new Date();
foo.addYears(10);

Which is basically how extensions work in C#. The problem is that these prototype extensions work during development, but it looks like they disappear once in production.

At some point I tried declaring everything as global like this:

export {};

declare global {
    interface Date {
        addDays(days: number): Date;
        addYears(years: number): Date;
        isToday(): boolean;
        isSameDate(date: Date): boolean;
        customFormat(): string;
    }
}

declare global {
    interface String {
        ToDate(): Date;
    }
}

declare global {
    interface Array<T> {
        ToDate(): string[];
    }
}

// etc

to no avail.

I also tried importing the file date.extensions.ts in index.html in a script tag, but it still wouldn't work.

I also looked at this, but I don't really know where I should use the import statement in the last step of the answer.

How do I make the extensions work as expected?

2 Answers2

1

Not sure it's really going to count as an answer in particular to the question 'how can I make this work', but it's my experience with it...

I did this originally. I had it set up so that the constructors had additional overrides and that my types had extension-like functionality.

E.g. I could new up a Date with my custom whatever new Date('...'); and then do something with it with my extensions dt.AddDays(1).

However, this was a pain to set up initially, because you need to do the whole global thing to make it recognise it throughout the app, and also it's not really good to do because you're encroaching on the base workings of the classes and might end up clashing with existing or future functions they (may) have on there.

After using it for a while, it stopped working with some update to either Angular or TypeScript itself, I never figured out which. Also stopped working for several projects, but continued to work for a couple of others; I never got to the bottom of why that was and never made the broken ones work again.

Instead I saved myself the effort and just made static helper classes I could use instead. Not as nice, but it still wraps up your relevant functions so that you can use them. It's just... not as nice. (Ignoring any discussion around using static methods vs an injectable service...)

export class DateHelper {
    public static addDays(dt: Date, days: number): Date {
        //...
    }
}
public doSomething(): void {
    let dt = ...;
    dt = DateHelper.addDays(dt, 1);
    // ...
}

It's good enough. It gives us a place to wrap up all of our date functions, for example, so that we never directly use Date objects. Very useful for when moment went dark and was replaced by luxon - one place to change the Date functions, zero changes to the rest of the app.

Added tip: We also thought the same due to C#, just like you - as such, our DateHelper wrapper includes a toString method, which alters the given C# type ToString formats into the relevant moment (originally) and then luxon string formats because they're all slightly different. However, with that in place, we can just use C# date ToString formats in the UI so that they're consistent between front and back end.

Krenom
  • 1,894
  • 1
  • 13
  • 20
  • 1
    Thanks a lot for your answer, but it looks like I found what I needed here: https://stackoverflow.com/questions/45437974/typescript-extend-string-interface-runtime-error – nonnodacciaio Aug 29 '23 at 15:00
1

The answer was here. Creating a global.d.ts file was the solution.

I should've checked better in the first place. Sorry in case the question wasn't clear

Thank you Krenom for your answer