angular - Endre returtypen til en Observable med map
Jeg har forsket på hvordan man endrer returtypen til en Observable.
Jeg bruker Angular 5.
Her er et eksempel:
public getButterfly(): Observable<Butterfly[]> {
return http.get<Larva[]>('url').pipe(
map( larva => new Butterfly(larva.dna))
);
}
Denne koden forårsaker en FEIL fordi kompilatoren forventer at larve-objektet skal være en Sommerfugl på grunn av returverdien og kaster en feil:
"feil TS2339: Egenskapen 'id' finnes ikke på typen 'Butterfly[]'."
Det ser ut til at TypeScript ikke tillater typeendringer inne i observablen, men hvis du kjenner en måte, er jeg all ears.
Takk for at du viste interesse for spørsmålet mitt.
Svar #1
Jeg tror det aksepterte svaret har maskert et større problem, og kanskje en forvirring i din forståelse av hva map faktisk gjør i rxjs.
I 'normal' Javascript fungerer map()-funksjonen direkte på en array og kjører en funksjon for hvert element i arrayen. For eksempel [1,2,3].map(x => x + 1). Du får umiddelbart tilbake en fullstendig array med samme lengde som originalen - med de transformerede elementene.
I rxjs er det en helt annen funksjon som er en del av rxjs og bare har samme navn som Arrays map (gå og finn kildekoden hvis du tør!).
Den faktiske funksjonen du sender til rxjs map() tar fortsatt en enkeltverdi og returnerer en enkeltverdi (så den ser veldig lik ut som den rene javascript map), men i stedet for å operere på en array opererer den på en enkeltverdi i en strøm.
Ok, så du vet allerede at RXJS handler om strømmer! Og du har en strøm av larver, ikke sant! (Jeg vet ikke om dette er skremmende eller søtt!)
Vel, det gjør du faktisk ikke. Med http.get har du en strøm, men den inneholder bare ÉN verdi, og den ÉN verdien er hele responsen fra HTTP-kallet ditt uansett hva det måtte være.
Ser man på det, returnerer du et array av Larva-objekter fra get-kallet, men så langt som RXJS er bekymret, er dette en strøm med KUN ÉN gjenstand.
Dette er din originale kode (kommentert):
public getButterfly(): Observable<Butterfly[]> {
return http.get<Larva[]>('url').pipe(
map( larva => {
// We are now inside the RXJS 'map' function, NOT a Javascript map
// here larva is an ARRAY (assuming your HTTP call actually returns an array)
// So if you execute larva.dna you'll get 'undefined'
// (because dna is not a property on a javascript array!)
// So you will return a butterfly OBJECT, but initialized with 'undefined' DNA. Scary!
return new Butterfly(larva.dna);
})
);
}
Så det jeg tror du faktisk vil ha, er dette:
public getButterflies() {
return http.get<Larva[]>('url').pipe(
map( larvae => larvae.map(larva => new Butterfly(larva.dna)))
);
}
Hvis det ikke umiddelbart gir mening, her er hva som skjer:
- Du får tilbake et array fra HTTP-kallet (larvae - som du sier til TypeScript er typet som Larva[])
- Vi kjører deretter den standard JavaScript
map-funksjonen på det arrayet for HVER enkelt element og lager en sommerfugl for hver. Du returnerer deretter det nye sommerfugl-arrayet som et erstatningselement i strømmen. - Husk at du har en strøm. Men det er en strøm av ett element. Og hvert element er et array.
- Merk: Utdatatype trenger ikke å spesifiseres og er automatisk
Butterfly[]
Ved å sette inn <Larva, Butterfly> forteller du bare kompilatoren at det er det det er, slik at du ikke får kompileringstidsfeil. Du endrer aldri noe som helst.
PS. Noen ganger liker jeg å spesifisere utdata-type, når det er enkelt - for å vise feil inne i 'pipe'. Jeg vil gjøre dette hvis jeg har flere kodebaner, som hver må returnere det samme. Ved å begrense utdata-typen avslører det flere feil.
Tips: Bruk tap(x => console.log('Melding', x) i pipe for å skrive til konsollen hva du har på hvert trinn slik:
public getButterflies() {
return http.get<Larva[]>('url').pipe(
tap( x => console.log('Before map', x) ),
map( larvae => larvae.map(larva => new Butterfly(larva.dna)),
tap( x => console.log('After map', x) )
));
}
Hvorfor kalles det kran? Fordi rør har kraner! Og en (vanlig vann)kran lar deg se hva som er inni et (vanlig vann)rør :-)
Svar #2
Typedefinisjonene av map-operatoren er som følger:
export declare function map<T, R>(
project: (value: T, index: number
) => R, thisArg?: any): OperatorFunction<T, R>;
Som du kan se, kan du sette generiske typer i map-operatørkallet. I ditt eksempel er T-verdien Sommerfugl, og R-verdien er Larve.
public getButterfly(): Observable<Butterfly[]> {
return http.get<Larva[]>('url').pipe(
map<Larva, Butterfly>(larva => new Butterfly(larva.dna))
);
}
