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

Array of dynamically typed objects in Typescript

Hello, I am a newbie to Typescript and I recently encountered an issue that has left me stumped. The problem I am facing involves feeding data to a Dygraph chart which requires data in the format [Date, number, number,...]. However, the API I am using prov ...

What is the method for transmitting a URL API from an ASP.NET Core server to my Angular 2 single application?

Is there a way to securely share the url of the web api, which is hosted on a different server with a different domain, from my asp net core server to my client angular2? Currently, I am storing my settings in a typescript config file within my angular2 ap ...

The reason for my inability to include a fresh method in String.prototype using typescript

I attempted to extend the String.prototype with a new method, but I encountered an issue. interface String { newMethod(): void } String.prototype.newMethod = function() {} Although there were no errors in the typescriptlang.org playground, I received ...

Troubleshooting: Inability to Use Type Assertions while Retrieving Data from

Struggling to retrieve and analyze complex data from Firebase for computation and Cloud Function execution. The format of the data is not aligning with my needs, as shown in this example: interface CourseEvent { coucourseGroupType: string; date: Fireb ...

Guide on creating rsa key pair on the client side with angular2

Is there a way to generate an 'rsa' key-pair on the client-side using angular2? I am looking to create a private/public key pair and store the private key in a database while using the public key on the client side. How can this be achieved? I c ...

What are the steps to troubleshoot a Node Package Manager library in Visual Studio Code?

I have created a Typescript library that I plan to use in various NodeJS projects. The source code is included in the NPM package, so when I install it in my projects, the source also gets added to the node_modules folder. Now, during debugging, I want to ...

Where should global['window'] be defined for Angular 9 SSR with the use of domino?

Since the upgrade to Angular 9, an error message ReferenceError: window is not defined pops up when executing yarn serve:ssr. Our Angular apps utilize a trick involving Domino to simulate window for SSR (as shown in https://github.com/Angular-RU/angular-u ...

What is the recommended way to handle data upon retrieval from a Trino database?

My goal is to retrieve data from a Trino database. Upon sending my initial query to the database, I receive a NextURI. Subsequently, in a while loop, I check the NextURI to obtain portions of the data until the Trino connection completes sending the entire ...

Akita and Angular Error Exploration: Analyzing the StaticInjector and NullInjector in the Context of Store and

I encountered an issue with the Akita state management implementation while working on an Angular project. Here is a brief solution to help others who may face the same problem. The documentation and examples provided by Akita do not offer a clear explana ...

The Angular application is encountering difficulty accessing the Django Rest Framework API due to a CORS problem

Encountering a CORS problem while trying to access a Django Rest Framework REST API from an Angular 6 application. The API is hosted at http://localhost:55098/admin. It functions properly when accessed with Insomnia. The Angular app is running on http://l ...

Exploring techniques to iterate through this specific JSON data within an Angular frontend

I am looking for assistance in looping the data below into a 'Select dropdown' using Angular 10. The data consists of all the States in India along with their districts. I have retrieved this information from the internet, but I am unsure how to ...

I am experiencing difficulties with TypeORM connecting to my Postgres database

Currently, I am utilizing Express, Postgres, and TypeORM for a small-scale website development project. However, I am encountering challenges when it comes to establishing a connection between TypeORM and my Postgres database. index.ts ( async ()=>{ ...

Navigating away from the IdentityServer page within the Angular SPA in an ASP.NET Core application triggers a refresh of the entire

My ASP.NET Core Angular SPA app is integrated with IdentityServer 4. Whenever I navigate to the Identity pages (such as Manage Account) and then return to my app (by clicking the Home link or Back button), the website reloads. Is this normal behavior? Can ...

Incorporate an Array of Objects into the UseState hook with an initial value

I have encountered an issue with the following error message: "Error: Objects are not valid as a React child (found: object with keys {fzfvhv76576, user }). If you meant to render a collection of children, use an array instead." I have been attem ...

Ways to invoke a component function from another component

Hello all, I am currently learning Angular and I have encountered an issue where I need to call a function from one component in another component. Most of the solutions I found online involve scenarios where the components are either child or sibling comp ...

Capable of retrieving information from an API, yet unable to display it accurately within the table structure

I'm currently working with Angular version 13.2.6 and a .NET Core API. I have two components, PaymentdetailsView (parent) and PaymentDetailsForm (child). Within the PaymentDetailsForm component, there is a form that, when submitted, makes a call to ...

Unlocking the potential of nested conditional types in TypeScript

The source of the autogenerated type below stems from a GraphQL query: export type OfferQuery = { __typename?: 'Query' } & { offer: Types.Maybe< { __typename?: 'Offer' } & Pick<Types.Offer, 'id' | 'nam ...

Ongoing state configuration in a React hook

My custom hook: export function useToken2() { const { data: session, status } = useSession(); const [token, setToken] = useState<string | null>(null); useEffect(() => { if (status === 'authenticated' && session?.accessToken) { ...

Display Material UI icons as markers within Highcharts

Does anyone know how to use Material UI icons as markers in rendering? I have been searching for documentation but can't seem to find a clear explanation. Any guidance would be greatly appreciated! ...

Dealing with Errors in Node.js Using Typescript and Express

As I embark on my journey with Node and Typescript, a question has cropped up in my mind. There are two key files in my project: server.ts import express = require('express'); import IConfiguration from "../config/config"; export default clas ...