Reputation: 671
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
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
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
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
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
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.
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