Connect multiple observables together into a single subscription

Within my model class 'Item', I needed to load two separate images from the backend - a sprite-texture and a shadow-texture. To achieve this, I implemented parallel loading and joining of the textures in my Item so that they could be easily accessed elsewhere.

Initially, I used Base64 to transfer the files to my Angular2 Application, which worked well. However, I now want to switch to using plain blobs. Although Angular2 does not directly support this feature, it is possible to access the private _body property of the response. My goal is for my model class to retrieve the images as HTMLImageElements with the data parsed as a Base64 data URL. In order to generate this data URL from my blobs, I need to utilize FileReader.readAsDataURL(), which operates based on a callback function. I believe I have come up with a way to encapsulate this callback into an Observable, but I am open to corrections if needed.

Currently, I am struggling with chaining my calls correctly to subscribe to a resulting Observable that will produce the desired Item. For example, ItemService.getItem(1).subscribe(item => ...)

The current setup is throwing an error stating that the subscribe method is not defined on the resulting Observable. As someone who is new to RxJS, I would greatly appreciate guidance on how to properly set this up :)

/* implementation in ItemService */

getItem(id:number):Observable<Item> {
    return Observable.forkJoin(
        this.http.get('/api/items/' + id + '/sprite'),  
        this.http.get('/api/items/' + id + '/shadow'), 
        (spriteResponse, shadowResponse) => {
            const spriteBlob = new Blob([spriteResponse._body], {type: 'image/png'})
            const shadowBlob = new Blob([shadowResponse._body], {type: 'image/png'})
            return [spriteBlob, shadowBlob]
        })
        .flatMap(result => Observable.forkJoin(
            ItemService.generateImage(result[0]), 
            ItemService.generateImage(result[1]), 
            (spriteImage, shadowImage) => {
                const item = new Item()
                item.setSprite(spriteImage)
                item.setShadow(shadowImage)
                return item
            })
        )
}

static generateImage(data:Blob):Observable<HTMLImageElement> {
    const img = new Image() 
    const reader = new FileReader() 
    reader.readAsDataURL(data)  

    return Observable.bindCallback(reader.onloadend, () => {
        img.src = reader.result
        return img
    })
}

Answer №1

There are two main issues at play here. The first concern is that bindCallback returns a function which, when invoked, returns an Observable, rather than immediately returning one. The intended purpose of binding a callback is to convert a function that typically reports its asynchronous result through a callback into an Observable.

In this scenario, since you are waiting for an event (loadend) to be triggered, it would be more appropriate to use the fromEvent method instead.

static generateImage(data:Blob):Observable<HTMLImageElement> {
    const reader = new FileReader(); // initialize file reader
    reader.readAsDataURL(data);  // transform blob to base64 data URL

    // create observable from callback (is this correct?)
    return Observable.fromEvent(reader, 'load', (e) => {
       var img = new Image();
       img.src = reader.result;
       return img;
    }).first();
}

Although not completely foolproof, this approach should suffice for your needs. For a more comprehensive example, you can refer to the fromReader method that was developed for RxJS4, which could potentially be repurposed for your requirements (note that a version for RxJS5 has yet to be implemented).

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

I encountered an error when attempting to retrieve a JSON from a URL using a previously provided solution. What might be causing this issue?

Currently, I am facing an issue while trying to retrieve a JSON file for my memory card game. Even after following the solution provided in this question: How to get json file from HttpClient?, I encounter an error message that is quite confusing for me: h ...

Tips for preventing a wrapped union type from collapsing

It seems like there is an issue with Typescript collapsing a wrapped union type when it knows the initial value, which is not what I want. I'm uncertain if this is a bug or intended behavior, so I'm curious if there's a way to work around it ...

Possible revision: "Dynamic property naming in TypeScript interface based on specified type"

The concept might seem complex, but here's the gist of it. I have a User interface that may or may not contain certain properties depending on where it is fetched from. For example, there are optional properties like role and client_details. export i ...

How to Send Data with NodeJS by Utilizing the Finish Event

Is there a way to retrieve the JSON data sent during the nodejs finish event? This is how I send the JSON data: oResponse.json({ version: "1.0.0", author: "Someone", contributors: "also Someone" }); I would like ...

The function Observable.timer in Angular rxjs is throwing an error when imported

Encountering an error in my Angular application that reads: ERROR TypeError: rxjs_Observable__WEBPACK_IMPORTED_MODULE_4__.Observable.timer is not a function at SwitchMapSubscriber.project (hybrid.effect.ts:20) at SwitchMapSubscriber.push ...

How can we define and put into action a function in TypeScript that incorporates certain properties?

I have a vision of creating a message feature that can be invoked directly with overloading capabilities. I also wish to incorporate other function calls within this message feature, such as message.confirm(). Achieving this in TypeScript seems challenging ...

Encountering build issues in my next.js application post updating to version 12.#.# and implementing Typescript

In my next.js application, I recently upgraded to version 10 and added TypeScript to the mix. Despite ironing out all issues during development, I encountered errors when running yarn next build due to my use of the keyword interface. ./src/components/ ...

Adding dynamic values to nested form groups in Angular Form Array

After following a tutorial on creating a reactive form in an Angular application, I managed to implement it successfully. However, I encountered an issue when trying to add an additional control called "setNumber" to the form array. I want this control to ...

Indicate the type of content returned by a Controller

I'm dealing with a metrics.controller.ts file that looks like this: import { Controller, Get } from '@nestjs/common'; import { ApiOperation, ApiResponse, ApiUseTags, ApiModelProperty } from '@nestjs/swagger'; import { PrometheusSe ...

Can a TypeScript function be structured to return never (or throw) if a generic type extends a subtype without requiring casting?

(This code snippet is purely for demonstration purposes, as no real use-case exists here) I am attempting to create a function that throws an error if the input string is equal to "fish". I have achieved this using the as keyword, but I am curious if ther ...

Issue with Angular 8: click event is not triggering when using ngFor directive to iterate through arrays of objects

Update: The original post has been modified to omit implementation details and complexity. I am facing an issue with an ngFor loop that invokes a method on a service. The method returns an array which is then iterated over by the for loop. The click even ...

Unleash the power of Ionic 3 Calendar by capturing the date change event during selection

When a user selects a date, I need to use that date to perform a search in a database. However, I also need to capture the event when another date is selected in the calendar. Here is my HTML code: <ion-calendar #calendar [events]="currentEvents" ...

Updating a property value within a JSON object: Adjusting attributes in a JSON data format

How can I modify a specific key's value in a JSON array like the following example: input = [{"201708":10,"201709": 12, "metric":"attritionManaged"},{"201708":10,"201709": 12, "metric":"attritionUnManaged"},{"201708":10,"201709": 12, "metric":"EHC"}] ...

How can I handle the different data type returned by ReactDom.render?

My main focus is on rendering Markdown. Additionally, I also need to parse HTML which is passed as a string. In this scenario, children represents the HTML passed as a string, while isParseRequired indicates if parsing is needed. import cx from 'clas ...

Tips for guaranteeing a Typescript string enum with identical key and value pairs

I am looking for a way to create a generic type that verifies certain criteria on an enum: all fields must be strings all values must be equal to their respective keys For example, the following enums would meet the requirements: enum correct1 { bar ...

Unable to properly display date formatting in AG-Grid using the Angular date pipe

Currently, I am utilizing ag-grid in conjunction with Angular 8. Within my table, there is a column where my intention is to exhibit dates in a concise format. In order to achieve this, I opted to utilize the Angular date pipe. However, it appears that the ...

Utilizing 'nestjs/jwt' for generating tokens with a unique secret tailored to each individual user

I'm currently in the process of generating a user token based on the user's secret during login. However, instead of utilizing a secret from the environment variables, my goal is to use a secret that is associated with a user object stored within ...

Guide to limiting interceptor scope to a specific module in Angular

Below is the angular interceptor used to replace the auth token: @Injectable() export class TokenInterceptor implements HttpInterceptor { constructor( ) { } intercept( request: HttpRequest<any>, next: HttpHandler ): Observable<H ...

What is the best approach to implementing React Hooks in Typescript when using Context?

I have implemented the following code snippet in my new React Native project to enable Dark Mode using TailwindCSS: import React, { createContext, useState, useContext } from 'react'; import { Appearance } from 'react-native'; import { ...

In Angular 15, CSS override configurations do not function as intended

Exploring the world of Angular is exciting, and I am a newcomer to it. Currently, I am tackling an innovative Angular 15 project that makes use of the Material library. My current predicament lies in the fact that none of the CSS overrides appear to be tak ...