使用BehaviorSubject在同级组件之间共享数据

时间:2019-01-31 08:47:48

标签: angular

我仍在学习Angular,并尝试查看BehaviorSubject的工作原理,但被困在何处。这是我要尝试的地方:

创建一个名为HeroDetailComponent的搜索框组件,在其中输入一些文本,并根据此文本搜索英雄列表,并使用另一个组件HeroesComponent向用户显示它们。现在,该通信应在BehaviorSubject的帮助下使用服务组件完成。

这是我的主要组成部分:

app.component.html

<app-hero-detail></app-hero-detail>
<app-heroes></app-heroes>

这是我的 hero-detail.component.html ,我正在将其用作搜索框:

  <div>
    <label>name:
      <input [(ngModel)]="hero.name" placeholder="name"/>
    </label>
  </div>

和相应的 hero-detail.component.ts

import { Component, OnInit, Input } from '@angular/core';
import { Hero } from '../hero';

@Component({
  selector: 'app-hero-detail',
  templateUrl: './hero-detail.component.html',
  styleUrls: ['./hero-detail.component.css']
})
export class HeroDetailComponent implements OnInit {

  constructor() { }

  ngOnInit() {
  }

}

这是我的 heroes.component.html ,在这里我显示搜索结果:

<h2>My Heroes</h2>

<ul class="heroes">
  <li *ngFor="let hero of heroes"
    <span class="badge">{{hero.id}}</span> {{hero.name}}
  </li>
</ul>

及其 heroes.component.ts

import { Component, OnInit } from '@angular/core';
import { Hero } from '../hero';
import { HEROES } from '../mock-heroes';
import { HeroService } from '../hero.service';
@Component({
  selector: 'app-heroes',
  templateUrl: './heroes.component.html',
  styleUrls: ['./heroes.component.css']
})

export class HeroesComponent implements OnInit {

  heroes = HEROES;

  constructor(private heroService: HeroService) { }

    getHeroes(): void {
      // Logic to access heroes list matching search text and display to user
    }

  ngOnInit() {
    this.getHeroes();
  }
}

这是我的服务内容:

import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';

@Injectable({
  providedIn: 'root'
})
export class HeroService {

    private searchSource = new BehaviorSubject<string>("");
    searchText = this.searchSource.asObservable();

    constructor() { }

    searchByName(name: string) {
        this.searchSource.next(name);
    }

}

这是我的英雄榜模拟数据:

import { Hero } from './hero';

export const HEROES: Hero[] = [
  { id: 11, name: 'Mr. Nice' },
  { id: 12, name: 'Narco' },
  { id: 13, name: 'Bombasto' },
  { id: 14, name: 'Celeritas' },
  { id: 15, name: 'Magneta' },
  { id: 16, name: 'RubberMan' },
  { id: 17, name: 'Dynama' },
  { id: 18, name: 'Dr IQ' },
  { id: 19, name: 'Magma' },
  { id: 20, name: 'Tornado' }
];

您能帮我如何实现这一目标吗?

已更新:

我按照trichetriche答案中给出的步骤进行操作,但出现以下错误:

ERROR in src/app/hero-detail/hero-detail.component.ts(17,16): error TS2339: Property 'formValue$' does not exist on type 'HeroService'.
src/app/hero-detail/hero-detail.component.ts(17,61): error TS2304: Cannot find name 'startWith'.
src/app/heroes/heroes.component.ts(17,13): error TS2304: Cannot find name 'combineLatest'.
src/app/heroes/heroes.component.ts(19,22): error TS2339: Property 'formValue$' does not exist on type 'HeroService'.
src/app/heroes/heroes.component.ts(21,5): error TS2552: Cannot find name 'map'. Did you mean 'Map'?

如何解决这些错误?

更新: 以上错误已解决。现在我开始面对可观察的问题:

Observable.js:54 TypeError: You provided 'undefined' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.
    at subscribeTo (subscribeTo.js:41)
    at subscribeToResult (subscribeToResult.js:11)
    at CombineLatestSubscriber.push../node_modules/rxjs/_esm5/internal/observable/combineLatest.js.CombineLatestSubscriber._complete (combineLatest.js:62)
    at CombineLatestSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber.complete (Subscriber.js:66)
    at Observable._subscribe (subscribeToArray.js:8)
    at Observable.push../node_modules/rxjs/_esm5/internal/Observable.js.Observable._trySubscribe (Observable.js:43)
    at Observable.push../node_modules/rxjs/_esm5/internal/Observable.js.Observable.subscribe (Observable.js:29)
    at CombineLatestOperator.push../node_modules/rxjs/_esm5/internal/observable/combineLatest.js.CombineLatestOperator.call (combineLatest.js:32)
    at Observable.push../node_modules/rxjs/_esm5/internal/Observable.js.Observable.subscribe (Observable.js:24)
    at MapOperator.push../node_modules/rxjs/_esm5/internal/operators/map.js.MapOperator.call (map.js:18)

能帮我解决这个问题吗?

2 个答案:

答案 0 :(得分:1)

主题和行为主题是代理:它们既充当可观察者,又充当观察者。

您可以subject.next(value)时使代理的观察者做出反应:主题与行为主题之间的区别在于,主题的观察者将收到代理的当前值(而主题只会获取下一个next的值)。

所以,基本上,

BehaviorSubject === Subject.pipe(startWith(someValue))

现在,您实际上不需要行为主体即可完成您想要的事情。您可以使用一种更简洁的方法,使用反应式及其valueChanges属性。

在您的表单中,使用以下命令:

  <div>
    <label>name:
      <input [formControl]="heroName" placeholder="name"/>
    </label>
  </div>
export class HeroDetailComponent implements OnInit {
  heroName = new FormControl('');
  constructor(
    private service: HeroService
  ) {
    this.service.formValue$ = this.heroName.valueChanges.pipe(startWith(''));
  }
}

您将创建一个表单控件,然后将服务注入到组件中,最后,您将使用valueChanges表单控件的观察对象对同级英雄进行排序。

在服务中,只需添加formValue$: Observable<string>即可声明变量。 (xx$只是可观察对象的命名约定,可以快速发现它们。)

您需要startWith运算符来触发控件上的第一个值更改(否则,除非您键入内容,否则该值不会更改)

最后,在兄弟姐妹中:

export class HeroesComponent implements OnInit {

  private _heroesList$ = new BehaviorSubject(HEROES);

  heroes$;

  constructor(private heroService: HeroService) { }

    getHeroes(): void {
      this.heroes$ = combineLatest(
        this._heroesList$,
        this.heroService.formValue$
      ).pipe(
        map(([list, name]) => list.filter(hero => hero.name.includes(name)))
      );
    }

  ngOnInit() {
    this.getHeroes();
  }
}
<h2>My Heroes</h2>

<ul class="heroes">
  <li *ngFor="let hero of heroes$ | async"
    <span class="badge">{{hero.id}}</span> {{hero.name}}
  </li>
</ul>

通过donig,您可以创建一个新的流,该流由表单值和列表组成,并映射结果以返回筛选出的英雄列表。您可以使用async管道来按角度处理订阅,这是一个好习惯,可以防止您忘记取消订阅。

答案 1 :(得分:-1)

#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#include <string.h>

#pragma comment (lib, "Ws2_32.lib")

#define PORT 5555
#define HOST "192.168.1.30"
#define MAX_L 4096

int main(void) {
    char bfs[MAX_L], bfr[MAX_L];
    SOCKET sockfd;
    struct sockaddr_in target_addr;
    struct WSAData wsa;
    int err, num_recvd;
    size_t str_len;

    err = WSAStartup(MAKEWORD(2,2), &wsa);
    if (err != 0) {
        fprintf(stderr, "WSAStartup() failed, error %d\n", err);
        return -1;
    }

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == INVALID_SOCKET) {
        err = WSAGetLastError();
        fprintf(stderr, "socket() failed, error %d\n", err);
        WSACleanup();
        return -1;
    }

    target_addr.sin_port = htons(PORT);
    target_addr.sin_family = AF_INET;
    target_addr.sin_addr.s_addr = inet_addr(HOST);

    if (connect(sockfd, (struct sockaddr *)&target_addr, sizeof(target_addr)) == SOCKET_ERROR) {
        err = WSAGetLastError();
        fprintf(stderr, "connect() failed, error %d\n", err);
        closesocket(sockfd);
        WSACleanup();
        return -1;
    }

    while (fgets(bfs, MAX_L, stdin)) {
        str_len = strlen(bfs);
        if ((str_len > 0) && (bfs[str_len-1] == '\n')) {
            --str_len;
        }

        if (send(sockfd, bfs, str_len, 0) == SOCKET_ERROR) {
            err = WSAGetLastError();
            fprintf(stderr, "send() failed, error %d\n", err);
            closesocket(sockfd);
            WSACleanup();
            return -1;
        }

        num_recvd = recv(sockfd, bfr, MAX_L, 0);
        if (num_recvd == SOCKET_ERROR) {
            err = WSAGetLastError();
            fprintf(stderr, "recv() failed, error %d\n", err);
            closesocket(sockfd);
            WSACleanup();
            return -1;
        }

        if (num_recvd == 0) {
            break;
        }

        printf("%.*s\n", num_recvd, bfr);
    }

    closesocket(sockfd);
    WSACleanup();

    return 0;
}

然后像这样循环它:

getHeroes(): void {

  this.heroService.searchText
   .pipe(
     map((name) => HEROES.filter((hero) => hero.name.indexOf(name) !== -1))
   )
   .subscribe((results) => this.heroes = results);
}