How to share data between two different components in Angular 6

I have a component called course-detail that fetches data (referred to as course) from my backend application. I want to pass this data to another component named course-play, which is not directly related to the course-detail component. The goal is to display the same data retrieved from the backend in both components. Below are the relevant files:

app-routing-module:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { CourseListComponent } from './courses/course-list/course-list.component';
import { CourseDetailComponent } from './courses/course-detail/course-detail.component';
import { CoursePlayComponent } from './courses/course-play/course-play.component';
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';

const appRoutes: Routes = [
  { path: '', redirectTo: '/courses', pathMatch: 'full' },
  { path: 'courses', component: CourseListComponent,  pathMatch: 'full' },
  { path: 'courses/:id', component: CourseDetailComponent, pathMatch: 'full'},
  { path: 'courses/:id/:id', component: CoursePlayComponent, pathMatch: 'full' },
  { path: '**', component: PageNotFoundComponent, pathMatch: 'full' }];

@NgModule({
  imports: [RouterModule.forRoot(appRoutes)],
  exports: [RouterModule]
})

export class AppRoutingModule {  }

courses/course (interface)

export interface ICourse {
  course_id: number;
  title: string;
  author: string;
  segments: ISegment[];
}

export interface ISegment {
  segment_id: number;
  unit_id: number;
  unit_title: string;
  name: string;
  type: string;
  data: string;
}

courses/course.service:

import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';

import { Observable, throwError } from 'rxjs';
import { catchError, groupBy } from 'rxjs/operators';

import { ICourse } from './course';

@Injectable()
export class CourseService{
  constructor(private http: HttpClient) {  }

  private url = 'http://localhost:3000/courses';
  private courseUrl = 'http://localhost:3000/courses.json';

  private handleError(error: HttpErrorResponse) {

    if (error.error instanceof ErrorEvent) {
      console.error('An error occurred:', error.error.message);
    }
    else {
      console.error(
        'Backend returned code ${error.status}, ' +
        'body was ${error.error}');
    }

    return throwError(
      'Something went wrong; please try again later.');
  }

  getCourses(): Observable<ICourse[]> {
  const coursesUrl = `${this.url}` + '.json';

  return this.http.get<ICourse[]>(coursesUrl)
      .pipe(catchError(this.handleError));
  }

  getCourse(id: number): Observable<ICourse> {
    const detailUrl = `${this.url}/${id}` + '.json';

    return this.http.get<ICourse>(detailUrl)
        .pipe(catchError(this.handleError));
  }


}

courses/course-detail/course-detail.ts:

import { Component, OnInit, Pipe, PipeTransform } from '@angular/core';
import { ActivatedRoute, Router, Routes } from '@angular/router';

import { ICourse } from '../course';
import { CourseService } from '../course.service';

@Component({
  selector: 'lg-course-detail',
  templateUrl: './course-detail.component.html',
  styleUrls: ['./course-detail.component.sass']
})

export class CourseDetailComponent implements OnInit {
  course: ICourse;
  errorMessage: string;

  constructor(private courseService: CourseService,
        private route: ActivatedRoute,
        private router: Router) {
  }

  ngOnInit() {
    const id = +this.route.snapshot.paramMap.get('id');
    this.getCourse(id);
    }

   getCourse(id: number) {
     this.courseService.getCourse(id).subscribe(
       course => this.course = course,
       error  => this.errorMessage = <any>error);
   }

   onBack(): void {
     this.router.navigate(['/courses']);
   }

}

courses/course-play/course-play.ts:

import { Component, OnInit} from '@angular/core';
import { ActivatedRoute, Router, Routes, NavigationEnd } from '@angular/router';
import { MatSidenavModule } from '@angular/material/sidenav';

import { ICourse } from '../course';
import { CourseService } from '../course.service';

@Component({
  selector: 'lg-course-play-course-play',
  templateUrl: './course-play.component.html',
  styleUrls: ['./course-play.component.sass']
})

export class CoursePlayComponent implements OnInit {
  courseId: number;
  errorMessage: string;
  private sub: any;

  constructor(private courseService: CourseService,
      private route: ActivatedRoute,
      private router: Router) {

    }

    ngOnInit() {


      }


     onBack(): void {
       this.router.navigate(['/courses/:id']);
     }

}

Answer №1

Angular provides various ways for components to communicate with each other without relying on external libraries. Check out the official documentation

When dealing with sibling components rather than parent-child relationships, your options are limited.

  1. Have a shared parent component pass data to both children.
  2. Store the data in a shared service.

Based on the code you've shared, using option #2 seems like the most suitable solution. You can create a property in your CourseService:

public selectedCourse: ICourse;

Then, you can access this property in both components:

this.courseService.selectedCourse;

The drawback of this approach is that you'll need to manage a pseudo-global state and ensure that the service is only injected/provided once to prevent each component from having its own instance and hindering data sharing.


As suggested by Pavan in a comment on the question, consider using an Observable and subscribing to it. This way, the components will be notified of any changes in the value instead of having to manually check for updates upon loading.

Answer №2

If your URL contains an ID, you can use the following code snippet:

import { Component, OnInit} from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { MatSidenavModule } from '@angular/material/sidenav';

import { ICourse } from '../course';
import { CourseService } from '../course.service';

@Component({
    selector: 'lg-course-play-course-play',
    templateUrl: './course-play.component.html',
    styleUrls: ['./course-play.component.sass']
})

export class CoursePlayComponent implements OnInit {
    courseId: number;
    errorMessage: string;
    private sub: any;

    constructor(private courseService: CourseService,
        private route: ActivatedRoute,
        private router: Router) {

    }

    ngOnInit() {
        const id = +this.route.snapshot.paramMap.get('id');
        this.getCourse(id);
    }

    // Retrieve course details by ID
    getCourse(id: number) {
        this.courseService.getCourse(id).subscribe(
        course => this.course = course,
        error  => this.errorMessage = <any>error);
    }

    onBack(): void {
        this.router.navigate(['/courses/:id']);
    }

}

Answer №3

If you want to share data across multiple components, one approach is to use static attributes or methods. Here's an example:

header component:

@Component({
  selector: 'app-header',
  templateUrl: './header.component.html',
  styleUrls: ['./header.component.scss'],
})
export class HeaderComponent {
   public static headerTitle: string = "hello world";
}

footer component:

import { HeaderComponent } from '../header/header.component';

@Component({
  selector: 'app-footer',
  templateUrl: './footer.component.html',
  styleUrls: ['./footer.component.scss'],
})
export class FooterComponent {
   public footerText: string = HeaderComponent.headerTitle;
}

While using static attributes/methods can reduce overhead compared to creating a service, it may not be suitable for complex logic. Complex scenarios could lead to circular dependency warnings and coupling issues, especially when components need data from each other. In such cases, using a service with rxjs' BehaviorSubject is recommended. Services should be self-reliant and avoid heavy reliance on other services or components to prevent circular dependency warnings.

Answer №4

    //Service Implementation
    export class DataService {
      data: BehaviorSubject<any>;
      constructor() {
        this.data = new BehaviorSubject(this.data);
      }
      loadData(selectedData: any): void {
        this.data.next(selectedData);
      }
    }

    //Component1 Implementation
    const info = {
          id: 7444,
          company: 'TechCo'
    };
    this.dataService.loadData(info);

    //Component2 - Subscribe to data
    this.dataService.data.subscribe(newData => {
         this.selectedData = newData;
    });

   // Component2 HTML Template
    <div class="pl-20 color-black font-18 ml-auto">
                    {{selectedData?.company}}  
         <span><b>{{selectedData?.id}}</b></span>
    </div>

Answer №5

One effective way to handle communication between unrelated components is by utilizing the Mediator Pattern.

The mediator pattern allows for encapsulation of communication within a mediator object, eliminating direct communication between objects.

I have come across the term "unrelated components" which refers to components that may or may not share a common parent.


If there is a common parent (siblings), then the parent component can act as a mediator. For instance, if we have the following hierarchy of components:

-Component A
--Component B
--Component C

and there is a need to pass data from Component B to Component C, the process involves passing data from Child to Parent (Component B to Component A) and then from Parent to Child (Component A to Component B).

In Component B, we can use Output binding with the @Output decorator to emit an event (EventEmitter), allowing Component A to receive the event payload. Component A can then invoke the event handler, enabling Component C to receive the data using the @Input decorator.

If there is no common parent, another approach would be to utilize a service. In this scenario, simply inject the service into the components and subscribe to its events.

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

The subscription function in observables may result in values that are undefined

I integrated a new angular 2 library into my application called "angular2-grid". This library is located within the node_modules folder. Furthermore, I created a service as shown below: import { Injectable } from '@angular/core'; import { Htt ...

Issue with Angular: event.key doesn't register as shft+tab when pressing Shift+Tab key

Our website features a dropdown menu that can be opened and closed by clicking, revealing a list of li items. However, we are currently experiencing an issue with keyboard focus navigation. When the Tab key is pressed, the focus properly moves from one li ...

Count of items in filtered data displayed using Angular 5 *ngFor

Looking for help with my ngFor loop: *ngFor="let item of items | filterBy: ['SN', 'Name']: userFilter | orderBy: order | paginate: {itemsPerPage: 10, currentPage: 1}; let i = index;" Trying to retrieve the count of filtered items. Whe ...

What is the best way to retrieve a value from an array of objects containing both objects and strings in TypeScript?

Consider this scenario with an array: const testData = [ { properties: { number: 1, name: 'haha' } , second: 'this type'}, ['one', 'two', 'three'], ]; The goal is to access the value of 'second&ap ...

Unable to utilize combined data types in React properties

In my theme.interface.ts file, I defined the following 2 types: type ThemeSize = | 'px' | '1' | '1/2' | 'full' | 'fit' type ThemeWidthSpecific = 'svw' | 'lvw' | 'dvw&apos ...

Using ReactJS with Material UI and applying styles with withStyles including themes in TypeScript

I've been working on converting the Material UI Dashboard into TypeScript. You can find the original code here: Material UI Dashboard One issue I'm encountering is that I am unable to define CSS styles within the withStyles function while export ...

Converting a string to the Date class type in Angular 4: A comprehensive guide

Within my .ts file, I have a string that looks like this: const date = "5/03/2018"; I am looking to convert it into the default date format returned by Angular's Date class: Tue Apr 03 2018 20:20:12 GMT+0530 (India Standard Time) I attempted to do ...

Tips for moving between multiple router outlets on a page?

My search has lasted for half a day, but I am still unable to find a solution that works. Here are the routes I currently have: app-routing.module.ts const routes: Routes = [ { path: '', redirectTo: 'feature2', pathMatch ...

Dynamically adjust row height in AG Grid within an Angular application based on the visibility of other columns

My current AG-Grid version is 21.2.1, and I am unable to upgrade it for certain reasons. I have successfully implemented wrapping cell content to ensure all content is visible. Additionally, I have buttons in my application to toggle the visibility of col ...

Replicate the process of transferring table rows to the clipboard, but exclusively copying the contents

Currently, I am attempting to copy a paginated table to my clipboard by referring to this guide: Select a complete table with Javascript (to be copied to clipboard). However, the issue lies in the fact that it only copies the data from the first page. In ...

Unable to assign to 'disabled' as it is not recognized as a valid attribute for 'app-button'

How to link the disabled property with my button component? I attempted to add 'disabled' to the HTML file where it should be recognized as an input in the button component (similar to how color and font color are recognized as inputs) ... but ...

What is the process for obtaining a literal type from a class property when the class is passed as an argument, in order to use it as a computed property

Just a moment ago I posted this question on Stack Overflow, and now I have a follow-up query :) Take a look at this code snippet: import { ClassConstructor } from "class-transformer"; import { useQuery as useApolloQuery } from "@apollo/clie ...

Tips for addressing style issues within innerText

I am trying to use PrismJS to highlight HTML code, but the inner text function doesn't recognize line breaks (\n). <pre class="language-markup background-code"><code [innerText]="getHtmlCode()""></code> I have been working wi ...

Guide on navigating to a specific page with ngx-bootstrap pagination

Is there a way to navigate to a specific page using ngx-bootstrap pagination by entering the page number into an input field? Check out this code snippet: ***Template:*** <div class="row"> <div class="col-xs-12 col-12"> ...

Identifying favorited items by a user within the result list obtained from a GET request

Through a GET request, I am seeking to identify which of the outcomes have already been favorited or liked by the current user using the unique id associated with each object. There is a service in place that can determine if the user has favored an object ...

Search for an element deep within a tree structure, and once found, retrieve the object along with the specific path leading to

I created a recursive function to search for a specific object and its path within a tree structure. However, when I changed the target ID (from 7 to 10) in the function, I encountered an error: "message": "Uncaught TypeError: Cannot read ...

Testing Angular components with Karma Server: An uncaught promise rejection occurred stating, "The code should be executed within the fakeAsync zone."

For my unit test development, I am utilizing karma. In some of my test cases, I have used fakeSync - tick() and promises. Although my tests are running successfully, there seems to be an issue with the karma server: my test.spec.ts : import { async, Co ...

Access SCSS variable values in Angular HTML or TypeScript files

So, I've been looking into whether it's feasible to utilize the SCSS variable value within HTML or TS in Angular. For instance: Let's say I have a variable called $mdBreakpoint: 992px; stored inside the _variable.scss file. In my HTML cod ...

Anticipate receiving a 'Type' returned by external library functions

I've recently started learning TypeScript and have encountered a situation where I need to assign a type to a variable that is returned from a third-party library function with its own type definition. For instance: import {omit} from 'lodash&ap ...

What is the process for including a resource parameter in the acquireTokenSilent method of an MSAL instance within an Angular application?

const requestToken = { permissions: ['view_only'], }; newToken = await this.authInstance.acquireTokenSilent(requestToken); I'm trying to include the client ID of my Web API as a resource parameter when requesting the access token. Strugg ...