Subscribing to a push notification service and incorporating pipes afterward

Scenario:
I have come across a scenario where I need to incorporate additional commands in a Subscription using the rxjs Observable system after it has been initiated.

In this case, the application I am working on must passively monitor a push notification system. Various messages can be disseminated through this system, which my application needs to act upon. However, there is a foreseeable situation where a dynamically-loaded view that will be added in the future might require adding a listener to the push notification system.

Query:
Considering that my app already has an existing Subscription, can I append another pipe after .subscribe(() => {}) has been called?

// this.something is an Observable<any>, for discussion purposes.
const subscription = this.something.subscribe(() => { // commands });

this.something.pipe(
  map((something) => {
    // ...Commands to add to the subscription...
  })
);

...And if I attempt that, what would be the outcome, if any?

Solution:
Both answers provided by @user2216584 and @SerejaBogolubov shed light on the answer to this question.

My overarching push notification listener service required two key components:

  1. To retain the subscription, and
  2. To be able to access a repository of listeners.

The challenge lies in each listener needing to monitor a different message. To put it simply, if a message arrives on foo_DEV, the application should react differently than when a message is pushed on bar_DEV.

Here's what I devised:

export interface PushNotificationListener {
  name: string,
  onMessageReceived: (msg: PushNotificationMessage) => any,
  messageSubject$: Subject<PushNotificationMessage>
}

export class PushNotificationListenerService {
  private connection$: Observable<PushNotificationConnection>;
  private subscription$: Subscription;

  private listeners: PushNotificationListener[] = [];

  constructor(
    private connectionManager: PushNotificationConnectionManager
  ) {
  }

  connect() {
    // Step 1 - Establish the socket connection!
    this.connection$ = this.connectionManager.connect(
      // Details of setting up the websocket are irrelevant here.
      // The implementation specifics are not pertinent either.
    );
  } 

  setListener(
    name: string,
    onMessageReceived: (msg: PushNotificationMessage) => any
  ) {
    // Step 3...or maybe 2...(shrug)...
    // Assign listeners that the subscription to the primary connection
    // will utilize.
    const newListener: PushNotificationListener = {
      name: name,
      onMessageReceived: onMessageReceived,
      messageSubject$: null
    };

    this.listeners.push(newListener);
  }

  listen() {
    // Step 2 - Monitor changes to the primary connection observable.
    this.subscription$ = this.connection$
      .subscribe((connection: PushNotificationConnection) => {
        console.info('Push notification connection established');

        for (let listener of this.listeners) {
         listener.messageSubject$ = connection.subscribe(listener.name);
         listener.messageSubject$.subscribe((message: PushNotificationMessage) => {
           listener.onMessageReceived(message);
         }
        }
      },
      (error: any) => {
        console.warn('Push notification connection error', error);
      }
  }
}

Upon examining the inner workings of my core push notification system code, I realized we already possessed a high-order Observable. The websocket code generates an observable (connectionManager.connect()) that must be cached in the service and subscribed to. Since this code pertains specifically to my workplace, further details cannot be divulged.

However, storing the listeners is also crucial! The subscribe action in .listen() iterates through all attached listeners each time the connection state changes, enabling me to dynamically include listeners via .addListener(). Leveraging how rxjs' Observable system inherently functions, coupled with having an in-scope list of listeners, allows me to dynamically configure listeners even if .connect() is invoked before any listeners are set up.

This code could likely benefit from redesigning/refactoring, but having something functional marks a significant initial advancement in coding. Thank you all!

Answer №1

[I have decided to revise my response due to changes made by the author in their initial code submission; As highlighted in the comments, adjustments have been implemented] -

I am skeptical about how effective the following code snippet will be with regards to subscription impact -

this.something.pipe(
  map((something) => {
    // ...Additional commands to incorporate into the subscription...
  })
);

You might want to consider utilizing a higher order function during the initial setup of your observable. However, it is uncertain whether this approach will yield desired results due to the following factors -

  1. Upon setting up an Observable, it retains a reference to the initially passed function, which gets invoked upon subscription [https://medium.com/@benlesh/learning-observable-by-building-observable-d5da57405d87]. If you attempt to reassign the higher order function, the Observable still refers to the original function. Essentially, changing the higher order function does not alter the original function reference established during the initial setup.

  2. In a scenario where higher order reassignment seems plausible, there is a possibility that prior to the execution of the older higher-order function, a reassignment might occur. This can transpire if the source Observable initiates an asynchronous call to the backend, leading to potential interference by the JavaScript event loop. The following example elucidates this notion-

let higherOrderFunc = map(x => x * 2);

this.something
    .pipe(
          mergeMap(_ => //call to backend; async call),
          higherOrderFunc,
         ).subscribe();
higherOrderFunc = map(x => x * 3); // this will execute before async call completes

Answer №2

If you're looking to achieve some runtime-deferred functionality with a map operation, one approach could be defining a private field like myMapper and then calling map(this.myMapper). By modifying this private field, you can customize the mapping behavior. For instance, using map(x => x) would result in no mapping at all.

However, it appears that relying heavily on rxjs might not be the most appropriate solution. Consider exploring the concept of higher order observables, which involve observing observables (a "stream of streams"). This approach aligns better with the principles of rxjs and offers a cleaner solution overall. Take some time to reconsider your strategy.

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

Instance of "this" is undefined in Typescript class

After stumbling upon this code online, I decided to try implementing it in TypeScript. However, when running the code, I encountered an error: Uncaught TypeError: Cannot set property 'toggle' of null @Injectable() export class HomeUtils { p ...

A step-by-step guide to activating multi-selection in the Primary SideBar of Visual Studio Code using your custom extension

Currently, I'm in the process of developing an extension for Visual Studio Code where I've added a new activityBar containing treeViews that showcase information pulled from a JSON file. My goal is to enable users to multi-select certain displaye ...

Identical names found in typescript within a react project

Having an issue with my react project where I am seeing a lot of duplication errors in almost all files located in this directory: C:\Users[user]\AppData\Local\Microsoft\TypeScript\4.3\node_modules@types\ I suspect ...

What is the capability of dynamically generating an index in Typescript?

Can you explain why the Typescript compiler successfully compiles this code snippet? type O = { name: string city: string } function returnString(s: string) { return s } let o1: O = { name: "Marc", city: "Paris", [returnString("random")]: ...

Steer clear of duplicating template literal type entries when dealing with optional routes

My code includes a type called ExtractParams that extracts parameters from a URL string: type ExtractParams<Path extends string> = Path extends `${infer Start}(${infer Rest})` ? ExtractParams<Start> & Partial<ExtractParams<Rest>& ...

Checking that an object's keys are all present in an array in TypeScript should be a top priority

I am working with a const object that is used to simulate enums. My goal is to extract the keys from this object and store them in an array. I want TypeScript to generate an error if any of these keys are missing from the array. // Enum definition. export ...

Handling errors in nested asynchronous functions in an express.js environment

I am currently developing a microservice that sends messages to users for phone number verification. My focus is on the part of the microservice where sending a message with the correct verification code will trigger the addition of the user's phone n ...

Declaring TypeScript functions with variable numbers of parameters

Is it possible to define a custom type called OnClick that can accept multiple types as arguments? How can I implement this feature so that I can use parameters of different data types? type OnClick<..> = (..) => void; // example usage: const o ...

Creating a new endpoint within the Angular2 framework using typescript

I am brand new to Angular2 and I would like to streamline my API endpoints by creating a single class that can be injected into all of my services. What is the most optimal approach for achieving this in Angular2? Should I define an @Injectable class sim ...

Animating progress bars using React Native

I've been working on implementing a progress bar for my react-native project that can be used in multiple instances. Here's the code I currently have: The progress bar tsx: import React, { useEffect } from 'react' import { Animated, St ...

Sometimes, it feels like TypeScript's async await does not actually wait for the task to complete before moving on

Recently, I have been transitioning to using the async await pattern more frequently instead of the traditional Promise syntax because it can help in keeping the code structure cleaner. After some experimentation, I felt like I had a good grasp on how to u ...

Having difficulties incorporating a separate library into an Angular project

My typescript library contains the following code, inspired by this singleton example code export class CodeLib { private static _instance: CodeLib; constructor() { } static get instance(): CodeLib { if(!this._instance){ ...

What steps can I take to stop a browser from triggering a ContextMenu event when a user performs a prolonged touch

While touch events are supported by browsers and may trigger mouse events, in certain industrial settings, it is preferred to handle all touch events as click events. Is there a way to globally disable context menu events generated by the browser? Alternat ...

Creating an array object in TypeScript is a straightforward process

Working on an Angular 4 project, I am attempting to declare an attribute in a component class that is an object containing multiple arrays, structured like this: history: { Movies: Array<Media>, Images: Array<Media>, Music: Array<Medi ...

Handling HTTP Errors in Angular Components with NGRX

I have successfully integrated the store into my angular project. I am able to handle and process the successSelector, but I am facing difficulty in capturing any data with the errorSelector when an HTTP error occurs from the backend. The error is being c ...

Tips for extracting individual keys from an object and transferring them into a separate object

I have an array of objects containing all the form data. However, I need to format this data differently before sending it to the backend. Each object needs to be fetched separately and then pushed into another object as shown below. The current data looks ...

Limitation for class instance, not an object

Is it possible to implement type constraints for class instances only in TypeScript, without allowing objects? Here is an example of what I am trying to achieve: class EmptyClass {} class ClassWithConstructorParams { constructor (public name: string) ...

Prompt the user to take an action by opening a modal or dialogue box from a dropdown menu

I am looking to implement a functionality where a modal or dialogue will be opened when a user selects an option from a dropdown menu. As the dropdown menu will have multiple options, different dialogues/modals should appear based on the selected option. ...

Leverage the power of npm to utilize various javascript libraries for

I seem to be a bit confused here. Currently, I have the following code snippets: import * as angular from 'angular'; import 'ts-angular-jsonapi'; Interestingly, no errors are being returned with this setup. But as soon as I try this: ...

Exploring Angular 10's advanced forms: delving into three levels of nested form groups combined with

Project Link: Click here to view the project In my testForm, I have 3 levels of formGroup, with the last formGroup being an array of formGroups. I am trying to enable the price field when a checkbox is clicked. I am unsure how to access the price contro ...