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?