angular - Getter-metoder på deserialiserade objekt i Typescript


Vi har en mängd Http-anrop till våra tjänster som returnerar en samling objekt. I vår Angular-applikation tolkar Typescript dessa till rätt typ om det anges, till exempel Person:

export class Person{
  public firstName: string;
  public lastName: string;
  public salutation: string;
}

Låt oss säga att jag vill kunna enkelt bygga en hälsningssträng för denna användare, men jag vill vara en bra programmerare och göra det en gång istället för på flera ställen. Så jag skapar en getter som kommer att hämta den:

export class Person{
  public firstName: string;
  public lastName: string;
  public salutation: string;
  public get greeting(): string {
      return salutation + " " + firstName + " " + lastName;
  }
}

Men när TypeScript deserialiserade det saknas prototypen för egenskapen greeting (den är satt till undefined). Jag förstår att TypeScript kartlägger JSON-objektet till den klass som den har, och eventuella saknade fält/egenskaper finns inte på det kartlagda objektet (returnerar undefined om det anropas). Vad är det bästa sättet att fixa detta?

Som ett exempel på hur vi anropar tjänsten:

this.authHttp.post(this.url, user, params).then(
  (res: Person) => {
      console.log(res);
      console.log(res.greeting);
  }
);

Svar #1

Det du hade fungerat av en slump eftersom de egenskapsnamn du åtkomst behövde också tillhandahölls av svaret från .post. I verkligheten används inte TypeScript längre efter tsc-kommandot som transpilerar det till JavaScript-kod. På grund av detta förlorar du all typkontroll vid körning.

Om du skulle hårdkoda denna uppgift, på följande sätt:

let me: Person = { firstName: 'Ian', lastName: 'MacDonald', salutation: 'Hello.' };

du kommer att få ett TypeScript-fel när det försöker skapa JavaScript.

Property 'greeting' is missing in type
    '{ firstName: string; lastName: string; salutation: string; }'
    but required in type 'Person'.ts(2741)

Samma fel kommer att visas för alla funktioner som du har definierat i din klass också. Detta beror på att { ... } inte är din typ; den innehåller bara det som uttryckligen är definierat. Vad som kommer ner som ditt server-svar kommer att omfattas av samma tilldelningsregler, men problemet kommer inte att vara närvarande (eftersom datan inte är närvarande) förrän vid körning.

Jag rekommenderar att du använder ett rör för att konstruera en instans av din klass innan något försöker använda serverns svar, så att eventuella fel kan fångas vid första kontakt istället för tio sekunder senare när du slutligen försöker få åtkomst till .save()-åtgärden som inte finns.

.post(...).pipe(map((incoming: any) => {
  let person: Person = new Person();
  person.firstName = incoming.firstName;
  person.lastName = incoming.lastName;
  person.salutation = incoming.salutation;
  return person;
});

Svar #2

Du måste skapa det som kallas en kopieringskonstruktör som tar in alla objektets fält:

constructor(firstName: string, lastName: string) {
    this.firstName = firstName;
    this.lastName = lastName;
}

Nu kan du göra

http.post(...)
    .pipe(map(res => new Person(res.firstName, res.lastName)))
    .subscribe(...);

Det finns många varianter av detta, förstås. Du kan bara göra

constructor(public firstName: string, public lastName: string) {}

för att undvika att manuellt tilldela värdena igen, till exempel. Eller tänk på något annat sätt du vill skriva detta på. Allt handlar om samma sak, vilket är att du faktiskt behöver skapa en instans av din klass snarare än att använda klassen endast som en typ.

Relaterade artiklar