Building an observable TypeScript class with RXJS: A step-by-step guide

I can't seem to find a solution for making an entire model observable. Despite reading this post, it doesn't quite meet my requirements. I'm not looking to make individual properties observable, but rather the entire class itself. Here's what I have managed to come up with so far:

export class ObservableModel extends Observable<any> {
    __internalSource = new BehaviorSubject(this);
    source = this.__internalSource.asObservable();
}

/**
 * Base entity class that all models should inherit from
 */
export class EntityModel extends ObservableModel {
   protected entityData: AnyEntity;
   protected entityState: {
      loading: boolean;
      loaded: boolean;
      reloading: boolean;
      error: boolean;
   } = {
      loading: false,
      loaded: false,
      reloading: false,
      error: false,
   };

   constructor(public type: string, public id?: string) {}

   from(value: AnyEntity) {
      const { type, id } = value;
      this.entityState = {
         loaded: true,
         loading: false,
         reloading: false,
         error: false,
      };

      this.entityData = value;
      this.__internalSource.next(this);
   }

   load() { ... }
   reload() { ... }
   private requestData() { ... }
}

export class CollectionModel extends EntityModel {
   @attr name: string;
}

export class ProductModel extends EntityModel {
   @attr name: string;
   @attr description: string;
   @hasOne('collection') collection: CollectionModel;
}

While this setup somewhat works, it eventually encounters issues. For example, when using the following in Angular:

// component
export AppComponent {
   product = new ProductModel('product', '1');
}

// The following works...
<div *ngIf="product | async as prod">
   {{prod.loading}} works
   {{prod.reloading}} works
   {{prod.loaded}} works
   {{prod.error}} works
   {{prod.name}} works
   {{prod.id}} works
   {{prod.type}} works
</div>

// This doesn't work...
<div *ngIf="product | async as prod">
   <div *ngIf="prod.collection | async as collection">
      {{collection.loaded}} // TS throws an error that "Object is of type unknown"
   </div>
   <div *ngIf="prod.collection.loaded"> This works?
      {{ prod.collection.name }} works
   </div>
</div>

The reason behind this behavior eludes me.

Answer №1

While I can't speak to its usability, here's the solution I was able to devise. The main hurdle I encountered was related to the types being used. With Observable<any>, TypeScript hit a roadblock in handling the data.

export class ObservableModel<T> extends Observable<T> {
    __internalSource = new BehaviorSubject(this);
    source = this.__internalSource.asObservable();
}

As a result, adjustments needed to be made to reflect that change within the entity:

/**
 * Base entity class which all models should extend
 */
export class EntityModel<T> extends ObservableModel<T & EntityModel<T>> {
   protected entityData: any;
   protected entityState: {
      loading: boolean;
      loaded: boolean;
      reloading: boolean;
      error: boolean;
   } = {
      loading: false,
      loaded: false,
      reloading: false,
      error: false,
   };

   constructor(public type: string, public id?: string) {
     super();
   }

   from(value: any) {
      const { type, id } = value;
      this.entityState = {
         loaded: true,
         loading: false,
         reloading: false,
         error: false,
      };

      this.entityData = value;
      this.__internalSource.next(this);
   }
   
   getState() {
     return this.entityState;
   }
}

I also included a getter function for accessing the state as it seemed like the previous approach in the template needed adjustment (or perhaps an alternative to using this).

With these changes, let's see how the altered template appears now:

// The following works...
<div *ngIf="product | async as prod">
   {{prod.getState().loading}} works
   {{prod.getState().reloading}} works
   {{prod.getState().loaded}} works
   {{prod.getState().error}} works
   {{prod.name}} works
   {{prod.id}} works
   {{prod.type}} works
</div>

<div *ngIf="product | async as prod">
   <div *ngIf="prod.collection | async as collection">
      {{collection.getState().loaded}}
   </div>
   <div *ngIf="prod.collection | async as collection">
      <div *ngIf="collection.getState().loaded">
        {{ collection.name }} works
      </div>
   </div>
</div>

The functionality remains mostly unchanged, except for these key points:

  • Use of getState() is necessary to access the internal state
  • If we want to present a more stream-like flow of data, the .loaded condition needs to be nested within the async pipe

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

Revised: "Mastering the Art of using useLoaderData Properly with Remix V2

It seems that the correct way to type useLoaderData has changed since V2. export const loader = async () => { return json({ messages: [...] }) } // In component... const { messages } = useLoaderData<typeof loader> Prior examples show it type ...

Before computing the width, Next.js Swiper slide occupies the entire width

Check out the code snippet below: 'use client'; import { Swiper, SwiperSlide } from 'swiper/react'; import 'swiper/css'; import 'swiper/css/pagination'; export default function Component() { const cards = [{ id: 0 ...

Having trouble handling responses in nodejs when using inversifyjs

Currently, I am utilizing nodejs alongside typescript, typeorm, and inversify for managing dependency injection. I am also using inversify express utils to handle controllers. However, I am facing an issue where sending a response inside the then or catch ...

Resolving Angular Issue: Error code (5, 12) TS2314 - Solving the requirement for 1 type argument in the 'Array<T>' generic type

Encountered an issue in the JSON data within my typescript file. I'm working on creating an Angular API that performs a git-search operation. Initially, I had the JSON data set up correctly but then I modified all data values to their respective data ...

Confirm that a new class has been assigned to an element

I'm having trouble creating a unit test for a Vue.js component where I need to check if a specific CSS class is added to the template. Below is the template code: <template> <div id="item-list" class="item-list"> <table id="item ...

Navigating an array and organizing items based on matching properties

When I receive an array that looks like this: errors = [ { "row": 1, "key": "volume", "errorType": "Data type", "expectedType": "number", &quo ...

The "unsubscribe" property of undefined cannot be read (rxJS - Subscription)

Exploring the world of subscriptions and observables in rxJS is my current focus. My goal is to modify the interval for an Observable by unsubscribing and then resubscribing with the updated interval. While this seems like a straightforward task, I could ...

What is the best way to transfer information from a child to its parent component?

In my child component, I have a variable containing the array result of a Barcode Scanner that I need to pass to the parent page. This array needs to be sent to an API. childComponent.ts this.consultList; parentComponent.ts export class ParentComponent i ...

Getting information from the firebase database

I'm currently working on a web application that utilizes Firebase database. I'm encountering difficulties when trying to access the elements that are labeled as encircled. Can anyone offer any guidance or suggestions? Here is the code snippet I& ...

Running into issues with TypeScript in conjunction with Redux-Form and React-Redux connect

My excitement for TypeScript grew until I encountered some frustrating incompatibilities between Redux-Form and React-Redux. I am aiming to wrap a component decorated with reduxForm using the connect decorator from react-redux—this method has always bee ...

The return type from the RxJS Observable reduce method may differ from the type initially provided

Dealing with Angular4, TypeScript, and RxJS 5 I am encountering an issue where I have a complex type being passed into a reduce method, but I want the return type to be a simple boolean value. Currently, the following code is giving me a return type erro ...

Calculating the minimum value of a number in Angular 8

I attempted to convert a decimal number to a whole number and encountered an issue. When using the angular pipe method {{myNumber | number: '1.0-0.'}}, it provides a rounded off value instead of the floor value. For example, with number = 3.8, ...

Encountering a Circular JSON stringify error on Nest.js without a useful stack trace

My application is being plagued by this critical error in production: /usr/src/app/node_modules/@nestjs/common/services/console-logger.service.js:137 ? `${this.colorize('Object:', logLevel)}\n${JSON.stringify(message, (key, value ...

Is it possible to rotate an image with a random angle when hovering in Angular?

I'm currently working on a photo gallery project and my goal is to have the images rotate when hovered over. However, I am experiencing difficulties in passing values from TypeScript into the CSS. HTML <div class="back"> <div cl ...

The Battle: TypeScript versus JSX

After dabbling in TypeScript, I recently stumbled upon JSX and found them to be quite similar. However, when I tried to compare the two on this site, they only referenced Dart and other technologies, not TypeScript. Although both TypeScript and JSX compil ...

I am seeking guidance on how to manually provide data to an rxjs observable. Can anyone help me with this basic question?

I am interested in using an observable to communicate "exceptional states" to different parts of my Angular application, but I am struggling to grasp their functionality. In the code snippet below, I have created an observer object and turned it into an o ...

Creating a test scenario for a combineLatest Observable using marbles

I am working with an observable that combines two Observable<boolean> using the "or" operation with combineLatest. interface LoadingEventEmitter { isLoading$: Observable<boolean> } export class LoadingService { isLoading$: Observable<bo ...

Creating a hyperlink to a subdomain in TypeScript Execution File

I am seeking to add a link directing to a subdomain in the navigation bar of a react theme. Although I feel a bit hesitant about asking for help on something seemingly simple, I have struggled to find any relevant examples online to assist me. Below is the ...

Error in typescript Autocomplete component from @mui/material

Currently, I am working on implementing an Autocomplete field using react-hook-form. Everything seems to be functioning correctly, but I have encountered a TypeScript error: The provided object does not match the expected type 'AutocompleteProps<{ ...

When moving to the next page in server-side pagination, the checkbox remains unchecked

I have encountered an issue with my checkbox. When I navigate to the next page in server-side pagination and then return, the checkbox becomes unchecked. I need to extend the object to include the checked field. How can I accomplish this? Currently, the ...