Ofir Sasson
Ofir Sasson

Reputation: 671

Angular 6 passing data between two unrelated components

I have course-detail component that contain data (named course) from my backend app and I want to pass that data to another component (course-play) that's not related to the component. I want to display the same data I got from my backend in this two components. This 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;
  autor: 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';

// Inject Data from Rails app to Angular app
@Injectable()
export class CourseService{
  constructor(private http: HttpClient) {  }

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

  // Handle Any Kind of Errors
  private handleError(error: HttpErrorResponse) {

    // A client-side or network error occured. Handle it accordingly.
    if (error.error instanceof ErrorEvent) {
      console.error('An error occured:', error.error.message);
    }

    // The backend returned an unsuccessful response code.
    // The response body may contain clues as to what went wrong.
    else {
      console.error(
        'Backend returned code ${error.status}, ' +
        'body was ${error.error}');
    }

    // return an Observable with a user-facing error error message
    return throwError(
      'Something bad happend; please try again later.');
  }

  // Get All Courses from Rails API App
  getCourses(): Observable<ICourse[]> {
  const coursesUrl = `${this.url}` + '.json';

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

  // Get Single Course by id. will 404 if id not found
  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);
    }

   // Get course detail 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']);
   }

}

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']);
     }

}

Upvotes: 5

Views: 12405

Answers (5)

otboss
otboss

Reputation: 741

You can use static attributes (or methods) to share data across multiple components. Below is 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 compoennt:

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;
}

Although there is drastically reduced overhead using this method instead of creating a service, it is not recommended more complex logic. If the logic becomes more complex you may run into circular dependency warnings and coupling issues. An example of this is if the header needs data from the footer and vice versa. In the case of complex logic use a service with rxjs' BehaviorSubject. Services are meant to be self-reliant and should not rely heavily on other services or components. This will eliminate the possibility of all circular dependency warnings.

Upvotes: 1

Tiago Peres
Tiago Peres

Reputation: 15451

When it comes to unrelated components, we can take advantage of the Mediator Pattern.

With the mediator pattern, communication between objects is encapsulated within a mediator object. Objects no longer communicate directly with each other, but instead communicate through the mediator.

I've seen the term unrelated components being used to describe components with or without common parent.


If there's a common parent (siblings), then the parent can be used as mediator. Let's say we have the following components' hierarchy

-Component A
--Component B
--Component C

and we want to pass data from Component B to Component C. The way this would work is to pass data from Child to Parent (Component B to Component A) and then pass data from Parent to Child (Component A to Component B).

So, in Component B using Output binding, with the @Output decorator, we emit an event (EventEmitter) and Component A receives the payload of that event. Then, Component A invokes the event handler and Component C receives the data with the @Input decorator.

Else (if there's no common parent), we can use a service. In this case, simply inject the service in the components and subscribe to its events.

Upvotes: 0

goutam reddy
goutam reddy

Reputation: 57

    //inside service
    export class LocalService {
      obj: BehaviorSubject<any>;
      constructor() {
        this.obj = new BehaviorSubject(this.obj);
      }
      loadData(selectedObj: any): void {
        this.obj.next(selectedObj);
      }
    }

    //inside component1
    const data = {
          object_id: 7444,
          name: 'Relaince'
    };
    this.localService.nextCount(data);

    //inside component2 - subscribe to obj
    this.localService.obj.subscribe(data => {
         this.selectedObj = data;
    });

   // inside component2 html
    <div class="pl-20 color-black font-18 ml-auto">
                    {{selectedObj?.name}}  
         <span><b>{{selectedObj?.object_id}}</b></span>
    </div>

Upvotes: 0

Pavankumar Shukla
Pavankumar Shukla

Reputation: 423

If you have id in your path then try this

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() {
        const id = +this.route.snapshot.paramMap.get('id');
        this.getCourse(id);
    }

    // Get course detail 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']);
    }

}

Upvotes: 1

Vlad274
Vlad274

Reputation: 6844

Without pulling in any other libraries, Angular specifies a few ways for components to talk to each other. Documentation

Since your components are not parent/child but siblings, the options are even more limited.

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

Based on the code you showed, I believe #2 is your best option. So, you can add a property to your CourseService:

public selectedCourse: ICourse;

Then you can access it in both components:

this.courseService.selectedCourse;

The issues with this approach are that you then have to manage a psuedo-global state and make sure the the service is only injected/provided once (otherwise each component will get its own instance of the service and you can't share data).


As noted in a comment on the question by Pavan, you should probably use an Observable and subscribe to it. With the approach mentioned above, the components will not receive notifications when the value changes and will need to proactively check for changes on load.

Upvotes: 5

Related Questions