How can I implement a user notification service using rxjs within Angular?

As a newcomer to reactive programming, I am exploring ways to create an Angular service that can present notifications to the user. Check out what I have accomplished so far:

https://stackblitz.com/edit/angular-rxjs-notifications?file=app%2Fapp.component.html

The main challenge in my implementation lies in figuring out how to queue notifications in a reactive manner. My goal is for the notification div to display when the first notification is pushed, and disappear only when "Clear" is clicked unless more notifications have been added since. This way, clearing would reveal the next notification, continuing until all notifications are cleared. Subsequently, once a new notification is received, the div should reappear.

In my setup, I opted for a Subject instead of a ReplaySubject because I do not want users to see notifications sent while they were loading the next screen. However, I realized that if there is routing within my app.component.html, this behavior may still occur. Perhaps I need to clear the notification queue upon navigation?

Your insights are greatly appreciated!

Answer №1

Here is an example showcasing the structure of a service:

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import { merge } from 'rxjs/observable/merge';
import { map, scan } from 'rxjs/operators';

enum ActionType {
  insert = 'insert',
  remove = 'remove'
}

interface Action {
  type: ActionType;
}

interface InsertAction extends Action {
  payload: string;
}

interface RemoveAction extends Action { }

@Injectable()
export class NotificationService {
  messages$: Observable<string[]>;

  private insertSource = new Subject<string>();
  private removeSource = new Subject<void>();

  constructor() {
    const insert$ = this.insertSource.asObservable()
      .pipe(map((payload) => ({ type: ActionType.insert, payload })));

    const remove$ = this.removeSource.asObservable()
      .pipe(map((payload) => ({ type: ActionType.remove })));

    this.messages$ = merge(insert$, remove$)
      .pipe(
      scan((acc: any, { payload, type }) => {
        if (type === ActionType.remove) {
          acc = acc.slice(0, -1);
        }
        if (type === ActionType.insert) {
          acc = [...acc, payload]
        }
        return acc;
      }, [])
      );
  }

  insertMessage(msg: string) {
    this.insertSource.next(msg)
  }

  removeMessage() {
    this.removeSource.next()
  }
}

Check out the live demo here

We utilize two streams, remove$ and insert$, merging them together before reducing to a singular array through the scan operator.

For instance, a sequence like

insert(hello) -> insert(world) -> remove -> insert(kitty)
would be condensed down to [hello, kitty].

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

Informing the angular client of a controller error message

I have a controller method from which I want to send an error message back to my Angular client when the account object is null. The code snippet below currently handles this by throwing an exception, but I'm curious if there is a more efficient way t ...

Prevent the onclick function of a span element from being triggered when the user clicks on a dropdown menu contained within

Just delving into the world of web development and currently tackling a React project. Encountered an issue where I need to make a span element clickable. However, there's a dropdown within that span that doesn't have any onClick event attached t ...

Angular 8 does not show the default option in the select tag

When I use the following code snippet: <div style="text-align:center"> <form> <select type="checkbox" name="vehicle1" (change)="onchange()" > <option> 1 </option> <opti ...

Modifying styles/css in Angular2 using JavaScript results in a temporary change in styles before ultimately reverting back to the original styles

Recently, I encountered an issue while trying to change styles for some elements in Angular 2 using JavaScript. Interestingly, when the view is loaded, the height is initially set to maxHeight = 100, but after a few milliseconds, it reverts back to the o ...

Angular: Dynamically add or delete an interceptor based on conditions

Is it possible to dynamically include or exclude an interceptor based on user selection? For my application, I want to enable Azure AD based SSO using the @azure/msal-angular package https://www.npmjs.com/package/@azure/msal-angular that provides an inter ...

Angular 2: Issue with Table not Being Updated

https://i.stack.imgur.com/qLDUZ.png The UsersList component opens a new page upon clicking the table. https://i.stack.imgur.com/WwqIX.png After changing and saving user values, the updated value is not immediately reflected in the grid for the first tim ...

JavaScript - Trouble encountered while trying to use splice to insert one array into another array

I've been working on creating a Cache Hashtable using JavaScript. When I use the code cache.splice(0,0, ...dataPage);, it inserts my data starting from the first position up to the length of dataPage. Assuming that my dataPage size is always 10. Th ...

What is the best way to display the arrows on a sorted table based on the sorting order in Angular?

I need assistance with sorting a table either from largest to smallest or alphabetically. Here is the HTML code of the section I'm trying to sort: <tr> <th scope="col" [appSort]="dataList" data-order ...

What is the best way to change the parent route without losing the child routes?

Is there a simple and elegant solution to this routing issue in Angular 4? I have a master list with multiple child views underneath, such as: /plan/:id/overview /plan/:id/details ... and around 10 more different child views When navigating to a specifi ...

Using a Typescript typeguard to validate function parameters of type any[]

Is it logical to use this type of typeguard check in a function like the following: Foo(value: any[]) { if (value instanceof Array) { Console.log('having an array') } } Given that the parameter is defined as an array o ...

Angular version 9.1 includes a myriad of functioning routes, with the exception of just one

UPDATE: The issue was not related to the API, but rather with Angular. Please refer to the answer provided below for more details. I have been using this particular App for years without any route-related problems until recently. After deploying some upda ...

Troubleshooting a Missing Angular (8) Pipe Error in Your Ionic 4 Application

Despite seeing similar questions posted here, none have provided a solution to my issue. I believe I am implementing it correctly, but clearly something is not right. In the app I'm developing with Ionic 4, I need to add a key to a URL in a gallery. ...

Reestablishing communication with a SignalR socket in .NET CORE using Angular

For my current project, I am facing an issue with reconnecting to a SignalR Web Socket in case of a lost internet connection. I am working with Angular Ionic V4 and have installed the Network Information plugin. Whenever the "Connected" event on the plugin ...

Encountering an issue when using npm to add a forked repository as a required package

While attempting to install my fork of a repository, I encountered the error message "Can't install github:<repo>: Missing package name." The original repository can be accessed here, but the specific section I am modifying in my fork is located ...

Having trouble retrieving the "Text" from the "element" with the help of "Protractor"

Greetings! I am currently in the process of creating Protractor automation scripts for an "Angular 4" application. Below is the code from my development: <!--Byndd Title div--> <div class="ui-g-12 ui-md-8 ui-lg-10"> ...

The issue with launching a Node.js server in a production environment

I'm currently facing an issue when trying to start my TypeScript app after transpiling it to JavaScript. Here is my tsconfig: { "compilerOptions": { "module": "NodeNext", "moduleResolution": "NodeNext", "baseUrl": "src", "target": " ...

Arrange information in table format using Angular Material

I have successfully set up a component in Angular and Material. The data I need is accessible through the BitBucket status API: However, I am facing an issue with enabling column sorting for all 3 columns using default settings. Any help or guidance on th ...

Setting a timeout from the frontend in the AWS apigClient can be accomplished by adjusting the

I am currently integrating the Amazon API Client Gateway into my project and I have successfully set up all the necessary requests and responses. Now, I am trying to implement a timeout feature by adding the following code snippet: apigClient.me ...

Exploring TypeScript: Implementing a runtime data mapping in place of an interface

Take a look at this code snippet that defines two command handlers for a server: import { plainToClass } from "class-transformer"; enum Command { COMMAND_1, COMMAND_2, } class Command1Data { foo1!: string } class Command2Data { foo2!: ...

Learn how to set up browser targeting using differential loading in Angular 10, specifically for es2016 or newer versions

Seeking advice on JS target output for compiled Angular when utilizing differential loading. By default, Angular compiles TypeScript down to ES5 and ES2015, with browsers using either depending on their capabilities. In order to stay current, I've b ...