Eine alte Frage mit meist korrekten, aber nicht sehr effizienten Antworten. Dies ist mein Vorschlag:
Erstellen Sie eine Basisklasse, die die init()-Methode und statische Cast-Methoden enthält (für ein einzelnes Objekt und ein Array). Die statischen Methoden können überall sein; die Version mit der Basisklasse und init() ermöglicht einfache Erweiterungen danach.
export class ContentItem {
// Parameter: doc - plain JS-Objekt, proto - Klasse, zu der wir casten wollen (Unterklasse von ContentItem)
static castAs(doc: T, proto: typeof ContentItem): T {
// Wenn wir bereits die richtige Klasse haben, überspringen wir den Cast
if (doc instanceof proto) { return doc; }
// Ein neues Objekt erstellen (create) und alle Eigenschaften kopieren (assign)
const d: T = Object.create(proto.prototype);
Object.assign(d, doc);
// Grund für die Erweiterung der Basisklasse - wir möchten init() nach dem Cast aufrufen können
d.init();
return d;
}
// Eine andere Methode castet ein Array
static castAllAs(docs: T[], proto: typeof ContentItem): T[] {
return docs.map(d => ContentItem.castAs(d, proto));
}
init() { }
}
Ähnliche Mechanismen (mit assign()) wurden im Beitrag von @Adam111p erwähnt. Nur ein weiterer (vollständigerer) Weg, es zu tun. @Timothy Perez kritisiert assign(), aber meiner Meinung nach ist es hier vollkommen angemessen.
Implementieren Sie eine abgeleitete (die echte) Klasse:
import { ContentItem } from './content-item';
export class SubjectArea extends ContentItem {
id: number;
title: string;
areas: SubjectArea[]; // enthält eingebettete Objekte
depth: number;
// Die Methode wird nicht verfügbar sein, es sei denn, wir verwenden cast
lead(): string {
return '. '.repeat(this.depth);
}
// Falls wir eingebettete Objekte haben, rufen wir hier cast auf
init() {
if (this.areas) {
this.areas = ContentItem.castAllAs(this.areas, SubjectArea);
}
}
}
Jetzt können wir ein Objekt, das vom Service abgerufen wurde, casten:
const area = ContentItem.castAs(docFromREST, SubjectArea);
Die gesamte Hierarchie von SubjectArea-Objekten wird die richtige Klasse haben.
Ein Anwendungsfall/Beispiel; erstellen Sie einen Angular-Service (wieder abstrakte Basisklasse):
export abstract class BaseService {
BASE_URL = 'http://host:port/';
protected abstract http: Http;
abstract path: string;
abstract subClass: typeof ContentItem;
cast(source: T): T {
return ContentItem.castAs(source, this.subClass);
}
castAll(source: T[]): T[] {
return ContentItem.castAllAs(source, this.subClass);
}
constructor() { }
get(): Promise {
const value = this.http.get(`${this.BASE_URL}${this.path}`)
.toPromise()
.then(response => {
const items: T[] = this.castAll(response.json());
return items;
});
return value;
}
}
Die Verwendung wird sehr einfach; erstellen Sie einen Area-Service:
@Injectable()
export class SubjectAreaService extends BaseService {
path = 'area';
subClass = SubjectArea;
constructor(protected http: Http) { super(); }
}
Die get()-Methode des Dienstes gibt ein Promise eines Arrays zurück, das bereits als SubjectArea-Objekte (ganze Hierarchie) gecastet ist.
Nehmen wir jetzt an, wir haben eine andere Klasse:
export class OtherItem extends ContentItem {...}
Die Erstellung eines Dienstes, der Daten abruft und in die richtige Klasse castet, ist so einfach wie:
@Injectable()
export class OtherItemService extends BaseService {
path = 'other';
subClass = OtherItem;
constructor(protected http: Http) { super(); }
}