Unusual Behavior of Observable.concat() in Angular 4 with RxJS 5

My Angular 4 / TypeScript 2.3 service has a method called build() that throws an error if a certain property is not initialized. I am attempting to create a safer alternative called safeBuild() that will return an Observable and wait for the property to be initialized before executing build()

export class BuildService {
  
  renderer:Renderer2; // must be set for build() below to work
  
  // emits the new Renderer2 when renderer is set
  private rendererSet$:BehaviorSubject<Renderer2> = new BehaviorSubject(null);

  /** Set renderer, and notify any listener */
  setRenderer(renderer:Renderer2){
    this.renderer = renderer;
    this.rendererSet$.next(renderer);
  }

  /** Returns a new DOM element. Requires renderer to be set */
  build(elemTag:string){
    // if renderer is not set, we can't proceed
    // why is this error thrown when safeBuild() is called?
    if (!this.renderer) 
      throw new Error('Renderer must be set before build() is run');

    return this.renderer.createElement(elemTag);
  }

  /**
   * A safe version of build(). Will wait until renderer is set
   * before attempting to call build (Asynchronous)
   */
  safeBuild(elemTag:string):Observable<any> {
    // inform user that renderer should be set
    // this warning is printed to the console as expected
    if (!this.renderer) 
      console.warn('The build will be delayed until setRenderer() is called');

    // Listen to rendererSet$, filter out the null output, and call build()
    // only once the renderer is set. Why does the error still get thrown?
    return Observable.concat(
      this.rendererSet$.filter(e=>!!e).take(1),
      Observable.of(this.build(elemTag))
    )
  }
}

I try to execute the build like this (from another service):

this.buildService.safeBuild(elemTag).subscribe(...)

In the console I see:

Warning: The build will be delayed until setRenderer() is called

Error: Renderer must be set before build() is run

I expected the warning, but then nothing to happen until another part of my app calls setRenderer(). At that point, the code in subscribe() would run.

But why am I still encountering the error?

Answer №1

One issue arises when this.build(elemTag) is triggered while creating the concat observable, not during the actual concatenation process.

To address this, you can resolve the problem with the use of defer:

import 'rxjs/add/observable/defer';

...
return Observable.concat(
  this.rendererSet$.filter(e => !!e).take(1),
  Observable.defer(() => Observable.of(this.build(elemTag)))
);

Alternatively, as mentioned in the comments, you can utilize map:

return this.rendererSet$
  .filter(e => !!e)
  .take(1)
  .map(() => this.build(elemTag));

Answer №2

The reason for this error is that you are creating an Observable based on the return value of the this.build method without having set the renderer first. To avoid this issue, make sure to call the setRenderer method before running the build function.

if (!this.renderer) 
    throw new Error('Renderer must be set before build() is run');

To resolve this, modify your code to return an Observable in the following manner:

return Observable.concat(
  this.rendererSet$.filter(e=>!!e).take(1),
  this.rendererSet$.asObservable().map(() => this.build(elemTag)) // this line will execute when there is a new value set to rendererSet
)

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

Unable to confirm the version of Angular

I am currently using node version 10.14.1 and npm version 6.4.1 with angular version 7.0.3 installed. However, when I try to check the angular version by running the ng --version command, I encounter an error message in the command prompt. C:\Users&b ...

The global variable in TypeScript is not specified; it is initialized within the declaration `declare global{}`

In my TypeScript code, I'm facing a challenge when trying to add a global scope variable. In JavaScript (NodeJS), this process is straightforward: // index.js globalThis.helloWorld = 'Hello World!'; require('./log.js') // log.js c ...

Is there a way to enhance a pre-existing component in Angular?

Trying to integrate an Angular component and wanting to implement a unique template. By referencing the documentation, it is possible to customize the menuComponent by utilizing the built-in TextInputAutocompleteMenuComponent component to apply a custom t ...

What is the best way to provide data types for Vuex mapState functions?

In my Vuex component using Typescript, I want to add types to the mapping functions in mapState. Previously, I had it set up like this: @Component({ computed: { ...mapState( MY_NAMESPACE, { fooIndex: ( state: MyModel ) => state.values.index ...

Having difficulties utilizing React Syntax Highlighter in Next.js 13 with Sanity version 3

Hello there, I've encountered a problem with my project while using Sanity v3 and React Syntax Highlighter. Initially, I had success displaying my code in the browser by utilizing the Refactor library after following a tutorial on the Code Input by Sa ...

Updating and Preserving Content in Angular

I've encountered an issue while working on a code that allows users to edit and save a paragraph on the screen. Currently, the editing functionality is working fine and the save() operation is successful. However, after saving, the edited paragraph do ...

Transform event binding using PrimeNG Elements and Angular

My challenge lies in dynamically binding change events within the PrimeNG TreeTable by defining functions on my columns. I've noticed that attempting to bind the change event dynamically with my columns doesn't seem to work properly inside the Tr ...

What is the process of creating a callback in Angular using TypeScript?

Despite finding numerous resources, I am still struggling to fully grasp the concept at hand. The issue revolves around two functions in particular: roulette_animation(){ do animation (may take 5 sec) } alertResult(){ alert('You win') } My obje ...

Steer clear of null validations when handling loaded .obj models in Three.js

In my Angular project, I am utilizing three.js to load a simple .obj file and animate it on the canvas. Everything is functioning properly, but I find myself needing to explicitly check for null in my animate() function to determine if the model has been ...

Issue: unable to establish a connection to server at localhost port 5000 while using Next.js getServerSideProps function

I am experiencing an issue with connecting to an API on localhost:5000. The API works perfectly when called from Postman or the browser, but it does not work when called inside Next.js getserverside props: mport { useEffect,useState } from "react"; i ...

How to make a div stand out when clicked in an Angular application

Within my code, there is a booking-list div that I am utilizing to showcase booking timings. When hovering over this div, the background-color changes, as depicted in the image below. https://i.stack.imgur.com/Iz1r6.png My current dilemma is that when se ...

Getting a "module not found" error in Next.js while trying to import a TypeScript

Check out this code snippet: // lib/customFunction.ts export function customFunction() { console.log("customFunction"); } // pages/homepage.tsx import { GetServerSideProps } from "next"; // works import { exampleFunction } from "../lib/exampleFile.js" ...

The styles are not being rendered on the Angular application

I have successfully implemented a Modal service with working HTML/CSS (Check it out on StackBlitz) div.cover { align-items: center; background-color: rgba(0, 0, 0, 0.4); bottom: 0; display: flex; justify-content: center; left: 0; ...

How to vertically align radio buttons with varying label lengths using Angular and Bootstrap 4?

Is there a way to properly align radio buttons with variable length labels? When each label has a different length, the radio buttons appear misaligned. How can this be fixed? <div class="row"> <div class="form-check form-check-inline" *ngFor="l ...

Resetting the initial values in Formik while utilizing Yup validation alongside it

Currently, I am working on a React application with Typescript and using Formik along with Yup validation. However, I have encountered an issue with setting values in a Select element. It seems that the value is not changing at all, or it may be resettin ...

Is it possible to define a multiplication margin using css?

I am trying to place a box in the center of a container, but I am unable to set a static height for the parent element as it may change. This is causing issues with aligning the box perfectly in the middle of the page. Any assistance would be greatly app ...

Testing the derived class resulted in failure with the error message: "Unable to resolve all parameters for : (?, ?) in Angular 5."

Encountering an issue when trying to run Karma on a structure consisting of an abstract class, a derived class, and a test. The error message that is being thrown is: Failed: Can't resolve all parameters for ActivationsComponent: (?, ?). The abstrac ...

The component is functioning properly, but it directs to a 404 page when the "**" route is set up

Description I am facing an issue in my Angular 14 application where one of the pages/components is not functioning correctly when I set the "**" route in the `app-routing.module`. Without this configuration, everything works fine, but as soon as I include ...

Having trouble with accessing properties like `d3.svg()`, `d3.scale()` and other features of d3js within an Angular 2 environment

Struggling to incorporate d3.js into angular2. Below is the command I used to install d3 in Angular2: npm install --save d3 install --save-dev @types/d3 This is how my package.json appears: { "name": "my-app", "version": "0.0.0", "license": "M ...

Issues arise when attempting to override attributes within the HTML of a parent component in Angular

Why does overriding an attribute in a child class that extends from another not work as expected? Here's a made-up scenario to simplify the issue: Parent class file: gridbase.component.ts import { Component, OnInit } from '@angular/core'; ...