How to Dynamically Re-render Angular Component on Service Data Change

46 Views Asked by At

I'm developing an Angular application with a sidebar component that shows different menu items based on the user's data. The menu should only show an "Empresas" option if the user hasn't created any company. Once a company is created, the full menu should become visible. This functionality is controlled through a service that components use to communicate. Despite setting up my components and service to notify and react to changes, the sidebar doesn't re-render to reflect the updated state after adding a new company without manually reloading the page.

Once a company is created by the user, the complete menu should be displayed. I'm using conditional rendering in my sidebar's HTML to achieve this as follows:

<!-- Sidebar HTML snippet -->
<li routerLinkActive="active" class="nav-item" *ngIf="empresas.length === 0 && !visible">
  <a routerLink="/empresas" class="nav-link">
    <i class="material-icons">dashboard</i>
    <p>Empresas</p>
  </a>
</li>

<!-- Rest of the menu items -->
<ng-container *ngIf="visible">
  <li routerLinkActive="active" *ngFor="let menuitem of menuItems" class="nav-item">
    <!--If it's a single link-->
    <a [routerLink]="[menuitem.path]" *ngIf="menuitem.type === 'link'" class="nav-link">
      <i class="material-icons">{{menuitem.icontype}}</i>
      <p>{{menuitem.title}}</p>
    </a>
  </li>
</ng-container>

In my Sidebar component TypeScript file, I'm trying to re-render the component based on a service that notifies when the list of companies changes. This is particularly important after a new company is added. Here's a simplified version of my Sidebar component:

// Sidebar component TS snippet
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
/* Other imports */

export class SidebarComponent implements OnInit {
  nombre: string = '';
  nombre_empresa: string = '';
  empresas: any[] = [];
  public visible: boolean = true;

  constructor(private _empresas_service: EmpresasService, /* All services */) {
    this._empresas_service.reRender$.subscribe(() => {
      this.rerender(); 
    });
  }

  rerender() {
    this._cambios_empresas.detectChanges();
    this.visible = true;
  }

  async ngOnInit() {
     this.nombre = environment.usuario.nombre;
        this.empresas = environment.usuario.empresas;
        this.nombre_empresa = this.empresas.find(e => e.id == environment.empresa_id).nombre;
        if(this.empresas.length === 0) this.visible = false;
        await this._empresas_service.indexPanelAdmin();
        this.menuItems = ROUTES.filter(menuItem => menuItem);
        if (window.matchMedia(`(min-width: 960px)`).matches && !this.isMac()) {
            const elemSidebar = <HTMLElement>document.querySelector('.sidebar .sidebar-wrapper');
            this.ps = new PerfectScrollbar(elemSidebar);
        }
  }
}

When a company is created, the Empresa component calls a function in the service to notify about the change:

// Empresa.component.ts snippet
private async _store() {
  try {
    await this._spinner.show();
    const objeto = await this.empresas_service.store(this.form.value);
    this._toastr_service.success('Company successfully added!');
    this.empresas_service.emitFunction(); // Event to notify the change
    this._form_directive.resetForm();
    this._inicializaForm();
    this.close();
    await this._spinner.hide();
  } catch (error: any) {
    this._toastr_service.error(error.error);
    await this._spinner.hide();
  }
}

Finally, my service notifies the change as follows:

// Empresas.service.ts snippet
import { Subject } from 'rxjs';

export class EmpresasService {
  private reRender = new Subject<void>();
  reRender$ = this.reRender.asObservable();

  emitFunction() {
    this.reRender.next();
  }
}

Despite these implementations, the sidebar does not seem to re-render as expected after adding a new company. I'm looking for guidance on how to properly force the sidebar component to update and reflect the new state without having to reload the page manually.

Any suggestions or insights into what I might be missing or doing incorrectly would be greatly appreciated.

1

There are 1 best solutions below

0
alcfeoh On

When an Angular component re-renders, the constructor and ngOnInit aren't called again. Instead, you need menuItems (or any dynamic data) to come from your service and display that variable directly in your template.

I would move your ngOnInit code into a service and have your component display a property (variable) from that service. Whenever the property is updated, Angular will automatically render it. No need to use ChangeDetectorRef or call a render() method. Angular will take care of it.

If you want more information about Angular component lifecycle and services, here are some super short reads: