在Angular 5中使用rxjs进行持久订阅

时间:2018-03-15 18:48:31

标签: angular rxjs

我对Angular 5中的rxjs仍然有点新意,并且有点难以表达我的问题。我仍然希望得到一些提示。

我经常以相同的设置结束:

  • 多个组件以显示相同的数据
  • 访问数据的单个服务

现在,我通过Observables接收数据时有两个选项:

a)订阅一个observable以获取一次数据,然后再次订阅以获取更新

b)订阅一个observable并在数据更改时始终获取更新

a)直截了当,但是b)我经常遇到麻烦,想知道这是否是使用Observables的正确方法。

一个问题是,取消订阅在某些情况下变得很重要,并且缺少取消订阅会导致每次更新observable时都会执行严重垃圾。

另一方面,使用选项a)当另一个组件更新基础数据时,我可能会错过一个组件中的一些更新。

有没有最佳做法可以避免所有这些陷阱?

3 个答案:

答案 0 :(得分:2)

听起来你想要弄清楚的是如何在使用Angular时规范RxJS的订阅管理。为此我想到了三个主要选项:

  1. 使用async管道自动创建和删除订阅。如果要根据可观察数​​据发出的数据进行UI更改,则async管道会在创建组件时轻松创建对给定observable的预订,并在销毁该组件时删除这些预订。这可以说是使用订阅的最简洁方式。
  2. 举个例子:

    @Component({
        selector: 'my-component',
        template: `
            <div *ngFor="let value of value$ | async">
                {{value}}
            </div>
        `
    })
    export class MyComponent {
        public value$: Observable<String> = this.myValueService
            .getValues()
            .map(value => `Value: $value`);
        constructor(myValueService: MyValueService) {}
    }
    
    1. 通过在Subscription方法中创建类级ngOnInit对象,然后在ngOnDestroy方法中取消订阅来管理组件中的订阅。当我需要访问组件代码中的订阅时,这是我倾向于的惯例。在使用订阅的每个组件中使用ngOnInitngOnDestroy方法会添加样板,但如果您需要在组件代码中订阅,则通常是必需的。
    2. 例如:

      @Component({
          selector: 'my-component',
          template: `
              <div #myDiv></div>
          `
      })
      export class MyComponent implements OnInit, OnDestroy {
          private mySub: Subscription;
          constructor(myValueService: MyValueService) {}
      
          public ngOnInit() {
              this.mySub = this.myValueService.getValue().subscribe((value) => {
                  console.log(value);
                  // Do something with value
              });
          }
          public ngOnDestroy() {
              this.mySub.unsubscribe();
          }
      }
      
      1. 使用限制操作限制订阅生命,例如first()。这是在您发起对HttpClient可观察量的订阅时默认执行的操作。这样做的好处是只需要很少的代码,但它也可能导致订阅未被清除的情况(例如,如果observable永远不会发出)。
      2. 如果我想用observable做的所有事情都可以在视图中完成,那么我几乎总是使用选项1 。这涵盖了我的经验中的大多数情况。如果需要,您始终可以使用中间可观察对象来生成可在视图中订阅的可观察对象。中间可观察量不会引入内存泄漏问题。

答案 1 :(得分:1)

另一个选择是使用observable来检索数据,然后让Angular的更改检测处理其余部分。使用Angular的更改检测,它将在数据更改时更新UI ...无需再次订阅以获取更新。

例如,我有这种类型的UI: enter image description here

我使用Http和一个observable检索数据。但随后我利用Angular的变化检测来处理任何更新。

这是我的一项服务:

@Injectable()
export class MovieService {
    private moviesUrl = 'api/movies';
    private movies: IMovie[];

    currentMovie: IMovie | null;

    constructor(private http: HttpClient) { }

    getMovies(): Observable<IMovie[]> {
        if (this.movies) {
            return of(this.movies);
        }
        return this.http.get<IMovie[]>(this.moviesUrl)
                        .pipe(
                            tap(data => console.log(JSON.stringify(data))),
                            tap(data => this.movies = data),
                            catchError(this.handleError)
                        );
    }

    // more stuff here
}

这是上面右侧显示的详细信息组件的完整代码(导入除外):

export class MovieDetailComponent implements OnInit {
    pageTitle: string = 'Movie Detail';
    errorMessage: string;

    get movie(): IMovie | null {
        return this.movieService.currentMovie;
    }

    constructor(private movieService: MovieService) {
    }

    ngOnInit(): void {
    }
}

您可以在此处查看完整示例(包含修改):https://github.com/DeborahK/MovieHunter-communication/tree/master/MH-Take5

答案 2 :(得分:0)

在组件之间传递数据时,我发现RxJS BehaviorSubject非常有用。

您还可以使用常规RxJS Subject通过服务共享数据,但这就是我更喜欢BehaviorSubject的原因。

  1. 它将始终返回订阅时的当前值 - 无需调用onnext()。
  2. 它有一个getValue()函数来提取最后一个值作为原始数据。
  3. 确保组件始终接收最新数据。
  4. 您可以使用asobservable()从行为主题中获得观察结果      关于行为主体的方法。
  5. Refer this for more
  6. 示例

    在服务中,我们将创建一个私有的BehaviorSubject,它将保存消息的当前值。我们定义一个currentMessage变量来处理这个数据流作为一个可供其他组件使用的observable。最后,我们创建了在BehaviorSubject上调用next的函数来更改其值。

    父,子和兄弟组件都接受相同的治疗。我们在组件中注入DataService,然后订阅currentMessage observable并将其值设置为等于消息变量。

    现在,如果我们在任何一个组件中创建一个更改消息值的函数。更新后的值会自动广播到所有其他组件。

    <强> shared.service.ts

    import { Injectable } from '@angular/core';
    import { BehaviorSubject } from 'rxjs/BehaviorSubject';
    
    @Injectable()
    export class SharedService {
    
      private messageSource = new BehaviorSubject<string>("default message");
      currentMessage = this.messageSource.asObservable();
    
      constructor() { }
    
      changeMessage(message: string) {
        this.messageSource.next(message)
      }
    
    }
    

    <强> parent.component.ts

    import { Component, OnInit } from '@angular/core';
    import { SharedService } from "../shared.service";
    
    @Component({
      selector: 'app-sibling',
      template: `
        {{message}}
        <button (click)="newMessage()">New Message</button>
      `,
      styleUrls: ['./sibling.component.css']
    })
    export class SiblingComponent implements OnInit {
    
      message: string;
    
      constructor(private service: sharedService) { }
    
      ngOnInit() {
        this.service.currentMessage.subscribe(message => this.message = message)
      }
    
      newMessage() {
        this.service.changeMessage("Hello from Sibling")
      }
    
    }
    

    <强> sibling.component.ts

    import { Component, OnInit } from '@angular/core';
    import { SharedService } from "../shared.service";
    
    @Component({
      selector: 'app-sibling',
      template: `
        {{message}}
        <button (click)="newMessage()">New Message</button>
      `,
      styleUrls: ['./sibling.component.css']
    })
    export class SiblingComponent implements OnInit {
    
      message: string;
    
      constructor(private service: SharedService) { }
    
      ngOnInit() {
        this.service.currentMessage.subscribe(message => this.message = message)
      }
    
      newMessage() {
        this.service.changeMessage("Hello from Sibling");
      }
    
    }