Angular 6 передача данных между двумя несвязанными компонентами

У меня есть компонент с подробностями курса, который содержит данные (названный курс) из моего внутреннего приложения, и я хочу передать эти данные другому компоненту (курс-игра), не связанному с этим компонентом. Я хочу отображать те же данные, которые я получил от моего бэкэнда, в этих двух компонентах. Это соответствующие файлы:

модуль маршрутизации приложения:

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

курсы / курс (интерфейс)

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

курсы / курс. сервис:

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


}

курсы / детали курса / детали курса.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']);
   }

}

курсы / 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']);
     }

}

Не могли бы вы сделать рабочий фрагмент на stackblitz.com. Будет легче обнаружить ошибку и помочь

edkeveked 19.07.2018 15:29

используйте наблюдаемый и подписывайтесь на него в каждом компоненте через службу.

Pavankumar Shukla 19.07.2018 15:35

Я пытаюсь, но это не позволяет мне сохранить изменения

Ofir Sasson 19.07.2018 15:38

Паван Шукла как? Я получаю курс от функции getCourse, и это наблюдаемое, которое я хочу передать компоненту курсовой игры.

Ofir Sasson 19.07.2018 15:38

вы можете определить getcourses в методе в course-play.ts и вызвать ngOninit, если вы не хотите вызывать метод из других источников

Pavankumar Shukla 19.07.2018 15:43

извините, это course-play.ts, вы уже используете общий сервис. дайте мне знать, работает ли это

Pavankumar Shukla 19.07.2018 15:47

Если я понимаю, что вы написали, я это сделал. Я подробно описываю метод getCourse, вызываю его в ngOninit и подписываюсь на полученные данные. конечно, содержит данные, и я хочу, чтобы эта же переменная передавалась в игру, но я не знаю, как

Ofir Sasson 19.07.2018 15:48
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Angular и React для вашего проекта веб-разработки?
Angular и React для вашего проекта веб-разработки?
Когда дело доходит до веб-разработки, выбор правильного front-end фреймворка имеет решающее значение. Angular и React - два самых популярных...
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Мы провели Twitter Space, обсудив несколько проблем, связанных с последними дополнениями в Angular. Также прошла Angular Tiny Conf с 25 докладами.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
Мое недавнее углубление в Angular
Мое недавнее углубление в Angular
Недавно я провел некоторое время, изучая фреймворк Angular, и я хотел поделиться своим опытом со всеми вами. Как человек, который любит глубоко...
Освоение Observables и Subjects в Rxjs:
Освоение Observables и Subjects в Rxjs:
Давайте начнем с основ и постепенно перейдем к более продвинутым концепциям в RxJS в Angular
4
7
10 190
5

Ответы 5

Без использования каких-либо других библиотек, Angular определяет несколько способов взаимодействия компонентов друг с другом. Документация

Поскольку ваши компоненты не являются родительскими / дочерними, а являются братьями и сестрами, варианты еще более ограничены.

  1. Попросите общий родительский компонент передать данные обоим дочерним элементам
  2. Храните данные в общем сервисе

Основываясь на показанном вами коде, я считаю, что №2 - ваш лучший вариант. Итак, вы можете добавить свойство к вашему CourseService:

public selectedCourse: ICourse;

Затем вы можете получить к нему доступ в обоих компонентах:

this.courseService.selectedCourse;

Проблемы с этим подходом заключаются в том, что затем вам нужно управлять псевдоглобальным состоянием и убедиться, что служба вводится / предоставляется только один раз (в противном случае каждый компонент получит свой собственный экземпляр службы, и вы не сможете обмениваться данными).


Как отмечено в комментарии к вопросу Павана, вам, вероятно, следует использовать Observable и подписаться на него. При использовании упомянутого выше подхода компоненты не будут получать уведомления при изменении значения, и им необходимо будет заранее проверять наличие изменений при загрузке.

как я это сделал? Я использую Observable <ICourse>, когда вызываю getCourse и получаю курс: ICourse в компоненте detail-course. Я хочу, чтобы та же переменная передавалась компоненту курсовой игры

Ofir Sasson 19.07.2018 15:41

Вы должны добавить новое свойство к CourseService, а затем, когда вы вызываете getCourse, вместо передачи результата любому компоненту, вы должны установить значение на CourseService, поскольку каждый из компонентов использует значение из CourseService, они оба получат значение. Если вы используете Observable, вы должны добавить новый Observable к CourseService и записать на нем .next с результатом getCourse. Каждый из компонентов должен затем .subscribe реагировать на изменения.

Vlad274 19.07.2018 15:46

вы имеете в виду с темой? как здесь ?: stackoverflow.com/questions/46487255/…

Ofir Sasson 19.07.2018 15:50

@OfirSasson Ага! Это действительно хороший пример того, как организовать такой тип общения.

Vlad274 19.07.2018 15:51

хорошо, я попробую, и если у меня будут ошибки, я отредактирую сообщение и комментарий. Спасибо!

Ofir Sasson 19.07.2018 15:52

@OfirSasson Если у вас возникнут проблемы с новым шаблоном, вам, вероятно, следует опубликовать новый вопрос, а не редактировать этот. Это поможет лучше увидеть ответ

Vlad274 19.07.2018 15:54

Открыл новый вопрос с некоторыми изменениями: stackoverflow.com/questions/51425917/…

Ofir Sasson 19.07.2018 17:09

Если у вас есть идентификатор на вашем пути, попробуйте это

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

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

Когда дело доходит до несвязанных компонентов, мы можем воспользоваться Шаблон посредника.

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.

Я видел, как термин «несвязанные компоненты» используется для описания компонентов с общим родительским элементом или без него.


Если есть общий родитель (братья и сестры), то родительский элемент может использоваться как посредник. Допустим, у нас есть следующая иерархия компонентов

-Component A
--Component B
--Component C

и мы хотим передать данные от Компонента B к Компоненту C. Способ, которым это будет работать, - это передать данные от Дочернего к Родителю (Компонент B к Компоненту A), а затем передать данные от Родителя к Дочернему (Компонент A к Компоненту B).

Итак, в компоненте B, использующем привязку вывода, с декоратором @Output мы генерируем событие (EventEmitter), а компонент A получает полезную нагрузку этого события. Затем компонент A вызывает обработчик событий, а компонент C получает данные с помощью декоратора @Input.

Иначе (если есть нет общего родителя), мы можем использовать сервис. В этом случае просто внедрите службу в компоненты и подпишитесь на ее события.

Вы можете использовать статические атрибуты (или методы) для обмена данными между несколькими компонентами. Ниже приведен пример:

компонент заголовка:

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

компонент нижнего колонтитула:

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

Хотя использование этого метода вместо создания службы значительно снижает накладные расходы, более сложная логика не рекомендуется. Если логика станет более сложной, вы можете столкнуться с предупреждениями о циклических зависимостях и проблемами связи. Примером этого является то, что для заголовка нужны данные из нижнего колонтитула и наоборот. В случае сложной логики используйте сервис с BehaviorSubject rxjs. Службы должны быть самодостаточными и не должны сильно зависеть от других служб или компонентов. Это исключит возможность появления всех предупреждений о циклической зависимости.

Другие вопросы по теме