Transferring information between components, specifically when one of them is a routerOutlet within an Angular application

I need to transfer data from the category component to the product component within the dashboard component. However, I am facing an issue due to the presence of a router outlet inside the product component.

View Dashboard Screen

dashboard.component.html

<div class="columns">
    <div class="column is-2">
        <app-category></app-category>
    </div>
    <div class="column is-10">
        <router-outlet ></router-outlet>
    </div>
</div>

category.component.html

<div class="container">
    <input type="checkbox" id="menu-toggle">
    <label for="menu-toggle" class="hamburger-menu" (click)="showCategoryList()"><span></span></label>
    <div class="content">
        <p class="menu-title title is-4" style="font-weight: normal">Categories</p>
        <ul class="menu-list">
            <li class="subtitle category-list" *ngFor="let category of categories">
                <input type="checkbox" [id]="category.id" (change)="filterProductsByCategory($event)">
                <label [htmlFor]="category.id">{{category.name}}</label>
            </li>
        </ul>
        <div class="filter mb-3"><button (click)="getFilterData()">Filter</button></div>
    </div>
</div>

category.component.ts

export class CategoryComponent implements OnInit{

  categories: Category[] = [];
  activatedCategory?: Category;
  dataLoaded = false;
  currentCategories: Category[] = [];
  selectedCategory: Category[]  = [];

  constructor(private categoryService: CategoryService) { }

  hamburgerMenu: HTMLElement = document.querySelector(".hamburger-menu")!;
  ngOnInit(): void {
    this.getCategories();
   
  }

  getCategories() {
    this.categoryService.getCategories().subscribe(response => {
      this.categories = response.data;
      this.dataLoaded = true;
    })
  }
  
  getActivatedCategory(category: Category) {
    this.activatedCategory = category;
  }

  setCurrencyCategory(category: Category) {
    this.currentCategories.push(category);
  }

  showCategoryList() {
    var content = document.querySelector(".content")! as HTMLElement;
    if (content.style.display === "block") {
      content.style.transition = "0.5s ease";
      content.style.display = "none";
    }
    else {
      content.style.transition = "0.5s ease";
      content.style.display = "block";
    }
  }

  filterProductsByCategory(event: any){
    if(event.target.checked){
      console.log(event.target.id);
      this.selectedCategory.push(event.target.id);
    }else{
      const id = event.target.id;
      this.selectedCategory.forEach((category, index) => {
        if(category.id === id){
          const index = this.selectedCategory.indexOf(category);
          console.log(index);
          this.selectedCategory.splice(index, 1);
        }
      })
    }
  }

  getFilterData(){
    const pars = this.selectedCategory.map((category: any) => {
      return parseInt(category);
    })
    console.log(this.selectedCategory);
  }
}

products.component.html

<div id="products">   
    <div *ngIf="!dataLoaded" class="lds-ring"><div></div><div></div><div></div><div></div></div>
    <h1 class="title">Products</h1>
    <div id="search-box">
      <input class="input mb-4" type="text" placeholder="Filter by product name" [(ngModel)]="searchText">
      <button class="button is-dark" type="button">Search</button>
    </div>
    
    <table *ngIf="dataLoaded && products" class="table is-hoverable is-fullwidth  is-striped" >
      <thead>
        <tr>
            <th>ID</th>
            <th>Product Name</th>
            <th>Category ID</th>
            <th>Price</th>
            <th>Unit In Stock</th>
            <th>Unit On Order</th>
            <th>Reorder Level</th>
        </tr>
      </thead>
      <tbody>
        <tr *ngFor="let product of products | filterProducts: searchText">
          <td>{{ product.id }}</td>
          <td>{{ product.name }}</td>
          <td>{{ product.categoryID }}</td>
          <td>{{ product.unitPrice| currency: "TR" : '₺' : '2.2-2'  }}</td>
          <td>{{ product.unitsInStock }}</td>
          <td>{{ product.unitsOnOrder }}</td>
          <td>{{ product.reorderLevel }}</td>
        </tr>
    </table>
    <div *ngIf="!products">No Products!</div>
  </div>

products.component.ts

export class ProductsComponent implements OnInit{

  products: Product[] = [];
  searchText = '';

  dataLoaded = false;
  constructor(private productService: ProductService, private activatedRoute: ActivatedRoute) { }

  ngOnInit(): void {
    this.activatedRoute.params.subscribe(params => {
      if(params["id"]){
        this.getProductsByCategoryId(params["id"])
      }else{
        this.getProducts();

      }
    })
  }

  getProducts() {
    this.productService.getProducts().subscribe(response => {
        this.products = response.data;
        this.dataLoaded = true; 
    })
   }

   getProductsByCategoryId(categoryId: number) {
    this.productService.getProductsByCategoryId(categoryId).subscribe(response => {     
        this.products = response.data;
        this.dataLoaded = true;
    })
   }  
}

I am looking for a way to easily share data between the categories and products components

Answer №1

Create a service with providedIn: 'root' to allow the entire application access to the service and make sure to add it to the providers array.

Utilize this service to set and read data efficiently for passing between components!

To call methods from one component to another, utilize a Subject.

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class ShareService {
  dataToShare = null;
  subject: Subject<any> = new Subject<any>();
  subObs$: Observable<any>;

  constructor() {
      this.subObs$ = this.subject.toObservable();
  }

  emit(eventName: string, data = {}) {
      this.subject.next({eventName: eventName, data: data});
  }
}

Calling Methods Between Components

By using the following code, we can call methods between unrelated components by emitting from one location and subscribing in another. Using simple if statements, we filter out the methods - this concept is known as an event bus. Remember to unsubscribe from the subscription on component destroy to prevent memory leaks!

Component A

callBMethod() {
    this.shareService.emit('compBEvent', {});
}

Component B

ngOnInit() {
    this.shareService.subObs$.subscribe(({eventName, data}) => {
        if(eventName === 'compBEvent'){
             this.bMethod();
        }
    });
}

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

Tips for configuring the cookie expiration date using the ngx-cookie-service library

Can anyone offer assistance with setting the expire date for cookies in my Angular 5 app using ngx-cookie-service? Here is what I have tried: setCookies($event){ this.cookieService.set( 'retailAppCookies', "true", 30 ); this.cook ...

Angular 6 secure access control

To prevent unauthorized access, if a user is not logged in and attempts to access a secure URL, they should be redirected to the login page. Even if the URL is directly entered in the browser's address bar. I came across a solution on this website th ...

I am not currently working on developing an angular application

Seeking assistance for the issue described below, as I have been struggling with it for three days. Any help would be greatly appreciated. Despite multiple attempts, the situation only seems to worsen with each try. The problem arises when attempting to ...

Having difficulty accessing custom cells in Angular ng2-smart-table for search functionality

When rendering a "custom" field, one issue that arises is that the global search feature is no longer effective within it. This could be due to the fact that the cell initially appears as a JSON object and then only a single string is rendered from that ...

The argument type 'string' does not match the parameter type 'keyof Chainable' in Cypress JavaScript

Trying to incorporate a unique custom command in Cypress (commands.js file) as follows: Cypress.Commands.add("login", (email, password) => { cy.intercept('POST', '**/auth').as('login'); cy.visit(& ...

Experience the quirk of using Angular's mat-button-toggle-group with the multiple attribute, and uncover the

I'm experiencing a strange issue with the mat-button-toggle. Here is the code snippet: <mat-button-toggle-group formControlName="fcCWShowForms" multiple="true"> <mat-button-toggle value="HE" (change)="this.showHe=!this.showHe"> Button ...

Differences between Angular components and TypeScript classes in models

In my observation, I have noticed that in several instances, manual models are being created for components to specifically manage the data. Despite this, the component already contains a ts class along with the html and css data. Shouldn't the task ...

Turning an angular API into a synchronous one: A step-by-step guide

My goal is to perform consecutive http calls to a REST service, with each call requiring a unique valid token in the header. The challenge arises when multiple components initialize simultaneously and make "get" calls at the same time using the same token. ...

Angular FormData fails to append and upload files

I am attempting to use FormData in order to upload a file through an HTTP Request. Here is the HTML code: <ng-template #displayApp> <div class="display flex justify-content-center"> <div > <p-fileUploa ...

Encountering an error in Angular where the property does not exist in type

Struggling to create a collapsible menu within my header component in an Angular project, I've hit a snag with proper JSON formatting. The error message that keeps popping up reads: Error: src/app/components/header/header.component.html:48:49 - error ...

Typescript's interface for key-value pairing with generic types

Consider the example Object below: let obj1: Obj = { 'key1': { default: 'hello', fn: (val:string) => val }, 'key2': { default: 123, fn: (val:number) => val }, // this should throw an error, because the types of d ...

Error: The utilization of the life cycle interface mandates the implementation of type checking

Currently, I am in the process of translating my typescript project to Webpack 2. While one project transitioned smoothly, I encountered an error with the other project... Error: use-life-cycle-interface necessitates type checking After conducting a br ...

How can we include additional types for external npm packages in a React TypeScript project?

Recently, I encountered an issue while using the react-microsoft-login package from npm. I included a button in the children property and received a typescript error stating that "property 'children' does not exist on type 'intrinsicattribut ...

When attempting to pass an array of objects to a child component, TypeScript raises an error regarding types

Hey everyone, I'm currently facing an issue while trying to pass an array of objects as props. Here's the content of my data.json file: [ { "category": "Reaction", "score": 80, "icon": " ...

Error code TS1005: Compiling Typescript DefinitelyTyped assets encountered an issue

I'm confident in my setup, but I can't seem to get tsc to compile. Here's the branch of my repository: https://github.com/inosion/sample-atom-typescript-package/tree/add_react Although I have the latest versions of typescript, I'm uns ...

Adjust the control's value as you monitor any modifications

As I monitor the changes within a reactive form by subscribing to the value changes, I have encountered an issue where certain values unset in the form prevent the Angular Material Slide Toggle from toggling to false. This is crucial as it affects the "Act ...

Simulating a PubSub publish functionality

I have been trying to follow the instructions provided in this guide on mocking new Function() with Jest to mock PubSub, but unfortunately I am facing some issues. jest.mock('@google-cloud/pubsub', () => jest.fn()) ... const topic = jest.fn( ...

In an Angular component, attempt to retrieve the data type of a class property

Discover how to retrieve the type of properties from an object by following this Typescript tutorial link. However, it seems to be behaving like Javascript and returning the value of the property instead of the type: const x = { foo: 10, bar: 'hello! ...

Error: Platform core encountered a StaticInjectorError[t]: It is not possible to assign a value to the property '_injector', as it is undefined

This source code includes a rest API call and global variable reference. I have only utilized bootstrap CSS and omitted saving jQuery because bootstrap.js is not being used. After performing the (ng build -prod) command, I receive production build files ...

Sorting the material table based on the column IDs, which usually correspond to the column names, may not align with the properties of the data

.ts this.displayedColumns = [ { key: 'id', header: '#' }, { key: 'fullname', header: 'Full name' }, { key: 'email', header: 'email' }, { key: 'roleName', header: ...