-1

I have a class FileData from a custom library:

export class FileData {
  public name: string;
  public data: string;

  constructor(name: string, data: string) {
    this.name = name;
    this.data = data;
  }
}

I import the library in my app and wanted to add a clone method to it because I need a deep copy of a complex object (please note, the actual class is a bit more complex and this is not the only class I add a clone method to it). Following this answer to basically the same question but for typescript I added this code to my app in a separate file within my shared module, src/app/shared/models/extensions/file-data.ts:

import { FileData } from 'mat-fileupload';

declare module 'mat-fileupload' {
  interface FileData {
    clone(): FileData;
  }
}
if (!FileData.prototype.clone) {
  Object.defineProperty(FileData.prototype,
                        'clone',
                        { enumerable: false,
                          writable: false,
                          configurable: false,
                          value: function clone(this: FileData): FileData {
                            return new FileData(this.name, this.data);
                          } });
}

And in the class I use it:

import { FileData } from 'mat-fileupload';

export class Record extends DataEntry {
  ... // Other properties
  public img?: FileData;
  
  constructor(..., img?: FileData) {
    ... // Other initializations
    this.img = img;
  }

  public clone(): void {
    return new Record(..., img?.clone());
  }
}

The typescript compiler in VS Code seems to be happy about this and doesn't complain. However, if I run ng build or ng serve the angular compiler complains:

error TS2339: Property 'clone' does not exist on type 'FileData'.

Sure, technically all of this is my own code and I could just add the clone method to my library but I would like to keep responsibilities separate and the creation of a deep copy is an issue in my app, not in the library, which is only responsible for uploading a file.

So how can I add my clone method to the existing FileData class?

EDIT:

I've found this question asking on how to import a typescript module augmentation in an angular app. I've also modified the included stackblitz slightly, here. Notice that this works both in the stackblitz and, if I include this in my app, it also works there. So, if I can easily extend the Observable class from rxjs and add a method to it, it must also be possible to somehow add a method to my own FileData class.

Yet, if I do the same with the FileData:

observable.ts:

import { Observable } from 'rxjs';
import { Subscription } from 'rxjs';

declare module 'rxjs' {
  interface Observable<T> {
    subscribeAndLog<T>(this: Observable<T>,
                       next?: (value: T) => void,
                       error?: (error: any) => void,
                       complete?: () => void): Subscription;
  }
}
// This works!
Observable.prototype.subscribeAndLog = function <T>(this: Observable<T>,
                                                    next?: (value: T) => void,
                                                    error?: (error: any) => void,
                                                    complete?: () => void): Subscription {
  console.log('Subscribing to Observable');
  const sub = this.subscribe(next, error, complete);
  return sub;
};

file-data.ts:

import { FileData } from 'mat-fileupload';

declare module 'mat-fileupload' {
  interface FileData {
    clone(this: FileData): FileData;
  }
}
// 'FileData' only refers to a type, but is being used as a value here.
// Why is this different? What does that error message even mean?
FileData.prototype.clone = function(this: FileData): FileData {
  return new FileData(this.name, this.data);
};

main.ts:

import './app/shared/models/extensions/file-data';
import './app/shared/models/extensions/observable';

record.ts:

public clone(): Record {
  of(1).subscribeAndLog(); // This works!
  return new Record(..., this.img?.clone());
}

Note that still, the Typescript compiler is perfectly fine with this, only the angular compiler complains. How can I tell angular to use FileData the same as Observable?

2
  • This is a lot of code. Commented Mar 8, 2024 at 18:55
  • @possum Nah. Half of it is basically the same, just try and error with minor adjustments. Commented Mar 9, 2024 at 6:13

1 Answer 1

-1

I think the best solution for your case is extend the class with method(s) you need and use this class instead of one you have from the library:

export class Record extends FileData {
  constructor(...) {super(...);}
  clone(this: Record ): Record;
}

The issue you has appears in next cases:

  1. You haven't informed your libraby that it should use your class instead of default one.
  2. After clonning you return FileData instead of Record which won't allow you clone object after clonning.

Or you can simply create function which you will import anywhere you want to clone anything:

export function cloneAnything<T>(anyTypeValue: T): T {
    return JSON.parse(JSON.stringify(anyTypeValue)) // as T (if you have typescript errors)
}
Sign up to request clarification or add additional context in comments.

15 Comments

Hold up. The clone method of the FileData class returns a FileData object. The clone method of the Record returns a Record object. I do not return a FileData instead of a Record, so your 2. isn't correct.
I need a deep copy of my Record object, and this object has a FileData property, which means I need to copy that as well. Otherwise I get left with a reference to the same FileData object inside of my cloned Record. The Record's clone method does that internally inside of its own clone method so from the outside, all I have to call is the Record's clone method and get a deep copy because the Record calls the clone of its own object properties. The Record cannot and should not extend from the FileData.
1. is my actual issue, and that's basically the whole question. How do I tell angular to combine the FileData class definition of the library with the clone method I defined in my app?
The is no such an option (if the class from the library isn't static). That's why I wrote what I wrote. I didn't wrote how to make deep clonning beacuse I thought you already has the clone method and just don't know how to attach it (and there are many articles about deep clonning, so I don't think this topic worth to be discussed here). So in any case you need class which extends FileData (in the exaple named Record) and work with it everywhere where you need your clone function.
And simplest option for this is inform your library what class to you (personally I know only one library which provides such an option, and it is not free). The most common situation is to create wrapper for your componenet which will return you Record instear of FileData anytime you need it.
|

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.