【问题标题】:Angular 6 passing data between two unrelated componentsAngular 6 在两个不相关的组件之间传递数据
【发布时间】:2018-12-27 15:55:35
【问题描述】:

我的课程详细信息组件包含来自我的后端应用程序的数据(命名课程),我想将该数据传递给与该组件无关的另一个组件(课程播放)。我想在这两个组件中显示从后端获得的相同数据。这是相关文件:

应用路由模块:

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

}

课程/课程播放/课程播放.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 上创建一个有效的sn-p。发现错误并提供帮助会更容易
  • 使用 observable 并通过 service 在各个组件中订阅它。
  • 我正在尝试,但它不会让我保存更改
  • Pavan Shukla 怎么样?我从 getCourse 函数获取课程,这是我想传递给课程播放组件的可观察对象
  • 你可以在 course-play.ts 的方法中定义 getcourses,如果你不想从其他来源调用该方法,可以在 ngOninit 上调用

标签: angular typescript routing observable


【解决方案1】:

在不引入任何其他库的情况下,Angular 指定了几种组件相互通信的方式。 Documentation

由于您的组件不是父/子而是兄弟,因此选项更加有限。

  1. 让一个共享的父组件将数据传递给两个子组件
  2. 将数据存储在共享服务中

根据您展示的代码,我相信 #2 是您的最佳选择。因此,您可以将属性添加到您的 CourseService

public selectedCourse: ICourse;

然后你可以在两个组件中访问它:

this.courseService.selectedCourse;

这种方法的问题是您必须管理一个伪全局状态并确保服务只注入/提供一次(否则每个组件将获得自己的服务实例并且您无法共享数据)。


正如 Pavan 对问题的评论中所述,您可能应该使用 Observable 并订阅它。使用上面提到的方法,当值发生变化时,组件将不会收到通知,并且需要主动检查负载变化。

【讨论】:

  • 我该怎么做?当我调用 getCourse 并在 detail-course 组件中获取 course: ICourse 时,我使用 Observable 。我希望将相同的变量传递给 course-play 组件
  • 您将向CourseService 添加一个新属性,然后当您调用getCourse 而不是将结果传递给任一组件时,您将在CourseService 上设置值,因为每个组件使用来自CourseService 的值,它们都将获得该值。如果您使用的是Observable,您将添加一个新的ObservableCourseService,并在其上调用.next,结果为getCourse。然后每个组件都应该.subscribe 来响应更改
  • 你的意思是主题?喜欢这里吗?:stackoverflow.com/questions/46487255/…
  • @OfirSasson 是的!这是一个很好的例子来说明如何组织这种类型的交流
  • 好的,我会试试看,如果我有错误,我会编辑帖子和评论。谢谢!
【解决方案2】:

如果你的路径中有 id,那么试试这个

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

}

【讨论】:

    【解决方案3】:
        //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>
    

    【讨论】:

    • 你的意思是 this.localService.loadData(data) 而不是 nextCount?请提供正确答案
    【解决方案4】:

    当涉及到不相关的组件时,我们可以利用Mediator Pattern

    使用中介者模式,对象之间的通信是 封装在中介对象中。对象不再通信 直接相互交流,而是通过 调解员。

    我见过术语不相关的组件被用来描述有或没有共同父级的组件。


    如果有一个共同的父母(兄弟姐妹),那么父母可以用作调解人。假设我们有以下组件的层次结构

    -Component A
    --Component B
    --Component C
    

    并且我们想将数据从组件 B 传递到组件 C。这样做的方式是将数据从子组件传递到父组件(组件 B 到组件 A),然后将数据从父组件传递到子组件(组件 A 到组件 B )。

    因此,在使用输出绑定的组件 B 中,使用 @Output 装饰器,我们发出一个事件 (EventEmitter),组件 A 接收该事件的有效负载。然后,组件 A 调用事件处理程序,组件 C 使用 @Input 装饰器接收数据。

    否则(如果有没有共同的父母),我们可以使用服务。在这种情况下,只需将服务注入组件并订阅其事件即可。

    【讨论】:

      【解决方案5】:

      您可以使用静态属性(或方法)在多个组件之间共享数据。下面是一个例子:

      标题组件:

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

      虽然使用这种方法而不是创建服务可以大大减少开销,但不建议使用更复杂的逻辑。如果逻辑变得更复杂,您可能会遇到循环依赖警告和耦合问题。例如,如果页眉需要来自页脚的数据,反之亦然。在复杂逻辑的情况下,使用带有 rxjs 的 BehaviorSubject 的服务。服务旨在自力更生,不应严重依赖其他服务或组件。这将消除所有循环依赖警告的可能性。

      【讨论】:

        猜你喜欢
        • 2017-11-08
        • 2020-02-12
        • 1970-01-01
        • 2018-12-31
        • 2020-10-10
        • 2018-02-22
        • 1970-01-01
        • 1970-01-01
        • 2021-12-29
        相关资源
        最近更新 更多