angular - Mönster för att hantera kartläggning av API-objekt till UI-modellobjekt
Jag byter till att använda den nya HttpClient i Angular.
API:et jag anropar returnerar JSON-data i ett format. Jag vill ta denna data och omvandla den till en TypeScript-modellklass som är lämplig för att UI:t ska kunna arbeta med.
Sättet jag gjorde detta tidigare var genom att använda map-funktionen t.ex.
return this.httpClient.get(url, { headers: headers })
.map(mapFunction)
.catch(errorFunction);
Där map-funktionen står för det tunga arbetet med att omvandla API-svaret till ett modellobjekt, t.ex.
const mapFunction =
(response: Response) => {
const data = response.json();
const contacts: Contact[] = [];
let i = 0;
for (const result of data.resourceResults) {
i = i + 1;
const contact: Contact = new Contact();
contact.role = result.r
För mig verkar detta ganska omständligt och jag letar i princip efter ett sätt att mappa objekt från API-svarstypen till UI-modelltypen utan att behöva använda en anpassad map-funktion för varje förfrågan.
Svar #1
Det finns inget sätt att göra anpassad mappning utan att, ja, uttryckligen ange vad som behöver mappas. Antingen berättar du för serversidan att returnera ett användargränssnittsvänligt svar, eller så måste du göra mappningen själv på klientsidan.
Om du vill mappa svaret på klientsidan kan du utnyttja Typescript-klasser och använda dess constructor för att snabbt generera de objekt du vill ha:
export class Contact {
public name:string;
public roles:any;
constructor(name:string, roles: any) {
//specify your own constructor logic
this.name=name;
this.roles=roles
}
}
och nu kan du skriva i din mapFunction för att uttryckligen konvertera svaret till en lista med Contact-objekt. Dessutom kan du använda arrayens .map() för att iterera genom objekten, utan att skriva en for-loop :
public mapFunction = (response: Response) => {
const data = response.json();
const resource = data.resourceResults;
//map it
return resource.map(result => new Contact(result.n, result.r))
}
Otympligt eller inte, jag tycker det är subjektivt. Men du kan definitivt skriva din kod på ett mer elegant sätt.
Svar #2
För att lägga till fler exempel baserat på @CozyAzure:s svar, är detta vad jag gör.
Typescript-klass med gränssnitt (för bättre läsbarhet genom appen):
interface MessageConfig {
MessageId?: Guid;
SentAt?: Date;
Status?: string;
Text?: string;
}
export class Message {
MessageId: Guid;
SentAt: Date;
Status: string;
Text: string;
constructor(config: MessageConfig) {
this.MessageId = config.MessageId;
this.SentAt = config.SentAt || new Date();
this.Status = config.Status || "Unread";
this.Text = config.Text;
}
}
En hjälpfunktion för att mappa data till Message-klassen:
export function mapMessage(message: any) {
return new Message({
MessageId: message.messageId,
SentAt: new Date(message.sentAt),
Status: message.status,
Text: message.text,
});
}
Servicefunktion som kartlägger serverns svar till ett enda meddelande:
addMessage = (message: Message) => {
return this.http.post(API, message).pipe(
map((response: any) => {
if (response && response.success) {
const m = mapMessage(response.message);
return m;
}
throw new Error(response.errorMessage);
}),
catchError(error => {
return throwError(error);
}),
share()
);
};
Tjänstefunktion som mappar serverresponser till flera meddelanden:
getMessages = (): Observable<Array<Message>> => {
return this.http
.get(API)
.pipe(
map((response: any) => {
if (response && response.success) {
let messages: Array<Messages> = [];
if (response.count > 0) {
messages = response.messages.map(
message => mapMessage(message)
);
}
return messages;
}
throw new Error(response.errorMessage);
}),
catchError(error => {
return throwError(error);
}),
share()
);
};
Så, ja, det händer mycket, men det här är vad jag har kommit fram till efter många, många iterationer på idén. Det har varit det lättaste att replikera och underhålla, och det är tydligt och förståeligt, samt ganska DRY. Jag har vanligtvis flera API-anrop som kartläggs till samma klass, så 5 minuters uppsättning sparar mig så mycket tid för varje ytterligare API-anrop.
