ReplaySubject in Angular is failing to update the array when a new object is added

I am encountering an issue where, upon attempting to create a new page Object, it successfully sends the data to the backend but does not update the array. I have to refresh the page in order to view the entire array.

Within the frontend, I am utilizing Observable in conjunction with async. I have tried to log the ngOnInit of the page.component.ts, but upon adding a new page and navigating to the pages, the ngOnInit is not being called.

This issue occurs when creating a new page. Upon creating a new Page, it redirects me to the pages route where the list of pages is displayed. However, when creating a new Page, it returns an error message stating:

ERROR Error: Error trying to diff 'Here is the name of the object'. Only arrays and iterables are allowed
.

Update: As Marco mentioned, this problem arises from incorrectly treating page as an Object instead of iterating through an array. I am having difficulty resolving this and seek your assistance. In the page.service.ts, within the pageModel, when I add a new Object, it only returns the added Object instead of the entire array, which I believe is the root of the issue. However, I am unsure how to rectify it. Upon refreshing the page, I can see the complete array.

The following is my updated code.

This is my code.

  export class PagesService {
  public baseUrl = environment.backend;
  private data = new ReplaySubject<any>();
  public userID = this.authService.userID;
  public editDataDetails: any = [];
  public subject = new Subject<any>();
  private messageSource = new BehaviorSubject(this.editDataDetails);
  getPageID = this.messageSource.asObservable();

  constructor(private http: HttpClient, private authService: AuthService) { }

  public getPages() {
    return this.http.get<any>(`${this.baseUrl}/pages/${this.userID}`).subscribe(res => this.data.next(res));
  }
  public pageModel(): Observable<Page[]> {
    return this.data.asObservable(); // Here it throws error
  }
  public getPage(id): Observable<any> {
    return this.http.get(`${this.baseUrl}/page/${id}`);
  }

  public setPage(page: Page, id: string) {
    const api = `${this.baseUrl}/page`;
    const user_id = id;
    this.http.post<any>(api, page, {
      headers: { user_id }
    }).subscribe(res => this.data.next(res));
  }

  changeMessage(message: string) {
    this.messageSource.next(message)
  }

  public updateDate(id: string, page: Page) {
    const api = `${this.baseUrl}/page/${id}`;
    return this.http.put<any>(api, page).subscribe(res => this.data.next(res.data));
  }

Updated Code from Answer.

  public updateDate(id: string, page: Page) {
    const api = `${this.baseUrl}/page/${id}`;
    return this.http.put<any>(api, page).subscribe(res => {
      this.lastSetOfData = res;
      this.data.next(this.lastSetOfData);
    });
  }    
}




export class Page {
  _id = "";
  name = "";
  slogan = "";
  description = "";
  url = "";
  telephone: number;
  pageUrl: string;
  website: string;
  founded: number;
  organization: number;
  email: string;
  coverImage: string;
  profileImage: string;
  specialty?: Specialty[];
  branches: Branches[];
  locations?: Location[];
  phone?:Phone;
  userRole?: string;
  roles?: Roles[];
}
export class Roles {
  role= "";
  userID = "";
}

This is the HTML of page.component .

  <div class="main" *ngIf="!showWeb">
    <div *ngFor="let page of pages$ | async" class="card width-900">
      <app-pages-list class="d-flex width-900" [page]="page" [details]="'details'"></app-pages-list>
    </div>
    <div>
    </div>
  </div>

This is the TS file.

public pages$: Observable<Page[]>;
ngOnInit(): void {    
this.pageService.getPages();
this.pages$ = this.pageService.pageModel();
}

And this is the code when I create a new Page.

  export class CreatePageComponent implements OnInit {
  public page = new Page();
  search;
  public branch = [];

  constructor(public router: Router,
    public branchesService: BranchesService,
    public authService: AuthService,
      public pageService: PagesService,
      public shareData: SenderService) { }

  ngOnInit(): void {
  }
  createPage() {
    this.page.url = this.page.name;
    this.page.branches = this.branch;
    this.page.locations = [];
    this.page.specialty = [];
    this.page.roles = [];
    this.page.phone = this.page.phone;
    this.page.pageUrl = `${this.page.name.replace(/\s/g, "")}${"-Page"}${Math.floor(Math.random() * 1000000000)}`;
    this.pageService.setPage(this.page, this.authService.userID);
  }
  addBranch(event) {
      this.branch.push(event);
      this.search = "";
  }
  removeBranch(index) {
      this.branch.splice(index, 1);
  }

}

Answer №1

Based on my analysis of the code provided, it seems that the error is occurring due to the fact that the variable data is being assigned different types of objects.

Within the PagesServices class:

  • When calling getPages, the data variable is assigned a list of Page objects.
  • However, in both setPage and updatePage functions, the data variable is assigned a single instance of a Page object.
private data = new ReplaySubject<any>();

The issue arises when attempting to iterate over the data variable which may hold a single page, rather than an array of pages.

<div *ngFor="let page of pages$ | async"

This issue arises because it is not possible to iterate over a single Page object. To prevent such errors, it is recommended to avoid using the any type to allow for compilation-time error checking. It is also advised to store an array of pages, add new items to the array after a post operation, and then update the entire array.

Refactored Code:

public updatePage(id: string, page: Page) {
    const api = `${this.baseUrl}/page/${id}`;
    return this.http.put<any>(api, page).subscribe((res) => {   
        const index: number = lastSetOfData.findIndex((_page: Page) => _page._id === res._id);
        lastSetOfData[index] = res;
        lastSetOfData = [...lastSetOfData];
        this.data.next(lastSetOfData);
    });
}

Additionally, the function updateDate should be renamed to updatePage for clarity.

Answer №2

The issue mentioned aligns with @Marco's response, and I will now expand on that further.

To resolve this issue, there are multiple solutions available. One of the quickest methods is to introduce a new instance variable called lastSetOfData within the PagesService class to store the latest version of the array. Initially, you can initialize this variable in the getPages method. Then, in the setPage method, you can update the lastSetOfData by appending the returned Page from the service at the end of the existing data and notify it using the ReplaySubject.

Below is an example implementation:

export class PagesService {
  public baseUrl = environment.backend;
  private data = new ReplaySubject<Array<Page>>();
  private lastSetOfData: Array<Page> = [];
  ....
  public getPages() {
    return this.http.get<any>(`${this.baseUrl}/page/${this.userID}`).subscribe(res => {
     lastSetOfData = res;
     this.data.next(lastSetOfData)
    });
  }
  ....

  public setPage(page: Page, id: string) {
    const api = `${this.baseUrl}/page`;
    const user_id = id;
    this.http.post<any>(api, page, {
      headers: { user_id }
    }).subscribe(res => {
         lastSetOfData = [...lastSetOfData, resp];
         this.data.next(lastSetOfData)
    });
  }

  public updateDate(id: string, page: Page) {
    ....
  }
  ....
  ....
}

This approach may provide a quick fix for the problem at hand. It is recommended to test this solution first and then consider refining the code for a more rx-idiomatic approach. However, initial testing should help determine if this solves the issue.

Answer №3

Issue arises when inserting an object into a replaysubject when an array is expected in other instances.

next(myarray)
next(myobject)

This action does not automatically add an object to the array.

To achieve this, you would need to use something like the following:

data.pipe(take(1)).subscribe(list => {
    list.push(newvalue);
    data.next(list);
});

Essentially, you take the last value, add the new item, and update the list.

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

steps for executing a Google script

In my program, the structure is as follows: // JavaScript function using Google Script 1. function F1() { ...... return (v1); } // HTML code for Google 1. <script> 2. function F2() { 3. alert ( 1 ); 4. function F2(); 5. alert ( 2 ); 6 ...

Refresh the current page in Next.js when a tab is clicked

I am currently working on a Next.js page located at /product While on the /product page, I want to be able to refresh the same page when I click on the product link in the top banner (navbar) that takes me back to /product. Is there a way to achieve this ...

What could be causing the Error 400 message to appear when trying to upload a JSON file via an HTTP request?

This Code Snippet is Essential function makeRequest() { var dataToSend = { "username": "234zu", "subject": "qwertz", "content": "qw", "created_at": "2018-12-15 22:18:54", "updated_at": "2018-12-15 22:18:54" ...

When a row is clicked, retrieve the data specific to that row

I have implemented a data-grid using react-table where I pass necessary props to a separate component for rendering the grid. My issue is that when I click on a particular row, I am unable to retrieve the information related to that row using getTrProps. ...

How can I retrieve both the keys and values of $scope in AngularJS?

Here is the code I have written to retrieve all key values using $scope. $scope.Signup={}; $scope.Message=''; $scope.SubmitPhysicianSignup = function() { var url = site_url + 'webservices/physician_signup'; //console.lo ...

The error message received states: "materialize-css Uncaught TypeError: Vel is not defined as

My current setup involves webpack as the bundler/loader, and I've successfully loaded materialize css (js/css). However, when attempting to use the toast feature, an error is thrown: Uncaught TypeError: Vel is not a function The library is being inc ...

Trouble with fetching data in Backbone

I'm facing an issue where the Backbone/Marionette Controller and Collection are not fetching properly. define(["jquery", "backbone","models/Poi"], function($, Backbone, Poi) { // Creating a new instance of Backbone Poi class object ...

Encountering an unhandled runtime error while importing the Client component into the server component: The JSON format is invalid, with the error message stating "undefined"

I've been attempting to create a basic counter component that increments the count of a state variable when a button is clicked. To achieve this, I created a separate file named Counter.tsx and placed it in the components folder at the root of my next ...

Error: Failed to cast value to ObjectId in the author field of the Blog model withRouter ID "6359f421fd4678e2eba3ffee"

One of the models in my blog application is the blog model: let blogSchema = new mongoose.Schema({ author: { type: mongoose.Schema.Types.ObjectId, ref: "User" }, title: { type: String, required: true, unique: true }, description: { type: String, ...

`The dilemma of z-index in a dropdown menu nested within an animated card`

Having an issue that I have attempted to simplify in this StackBlitz example (the actual project is created with Angular components and Bootstrap, etc.): https://stackblitz.com/edit/angular-bootstrap-4-starter-njixcw When incorporating a dropdown within ...

Creating a custom function in a Node.js application and rendering its output in the front-end interface

I've been trying to scrape data from a website and display it using innerHTML, but I'm running into some issues. The code snippet I'm using is: document.getElementById('results').innerHTML = searchJobs(''); However, I ke ...

Unable to locate the name 'X' despite importing the name directly above

I'm a little confused about the situation at hand. The name is clearly mentioned right above. https://i.sstatic.net/D0CEV.png Displayed below is the content of storage-backend.interface.ts: export declare interface StorageBackend extends Storage { ...

Limiting the display to only a portion of the document in Monaco Editor

Is there a way to display only a specific portion of a document, or in the case of Monaco, a model, while still maintaining intellisense for the entire document? I am looking to enable users to edit only certain sections of a document, yet still have acce ...

Package videojs along with the videojs-ima extension

For a few days now, I have been struggling to create a single JavaScript file that contains all the necessary components to play videos with Google IMA ads. However, I keep encountering errors, particularly player.ads is not function, which seem to be rela ...

Stop the sudden jump when following a hashed link using jQuery

Below is the code snippet I am working with: $( document ).ready(function() { $( '.prevent-default' ).click(function( event ) { event.preventDefault(); }); }); To prevent the window from jumping when a hashed anchor ...

What could be the reason why PhantomJS is not able to catch the <script> tags in the opened HTML using webpage.onConsoleMessage function?

In this particular query, I have defined the const contents to be the content of my HTML file for the sake of convenience. var webPage = require('webpage'); var page = webPage.create(); const contents = ` <!DOCTYPE html> <html lang=&quo ...

Setting up "connect-redis" in a TypeScript environment is a straightforward process

Currently, I am diving into the Fullstack React GraphQL TypeScript Tutorial I encountered an issue while trying to connect Redis with express-session... import connectRedis from "connect-redis"; import session from "express-session"; ...

Encountered an unhandled runtime error: TypeError - the function destroy is not recognized

While working with Next.js and attempting to create a component, I encountered an Unhandled Runtime Error stating "TypeError: destroy is not a function" when using useEffect. "use client" import { useEffect, useState} from "react"; exp ...

Bar graph width in Chart.js is displayed horizontally

Check out the bar chart image here: https://i.sstatic.net/RLeZp.png This particular graph was created using the code snippet provided below: var myChart = new Chart(ctx, { type: 'horizontalBar', data: { labels: labels, ...

What is the best way to modify an array's property in order to achieve the desired outcome when using json_encode?

Expected Result ['#ff0000','#4caf50','#4caf50','#4caf50','#00bcd4','#00bcd4','#4caf50','#4caf50'] The output I am receiving is as follows: ["'#ff0000','#4caf5 ...