响应式开发| RxJS异步管道不起作用

时间:2019-09-27 04:05:20

标签: angular rxjs rxjs-pipeable-operators

我试图通过将服务中的旧方法转换为在Observable中完全不订阅来学习Angular中的响应式开发。到目前为止,我已经转换的最简单的方法是getAll方法。我现在想要的是,当我在卡列表中选择一张卡时,我将被重定向到另一个页面以查看其详细信息。考虑下面我正在工作的代码。

服务

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

  private selectedCardIdSubject = new Subject<number>();
  selectedCardIdAction$ = this.selectedCardIdSubject.asObservable();

 /**
   * Retrieve single card based on card id.
   */
  card$ = this.selectedCardIdAction$
    .pipe(
      tap(id => console.log(`card$: ${this._cardsApi}/${id}`)),
      concatMap(id =>
        this._http.get<Card>(`${this._cardsApi}/${id}`)
          .pipe(
            map(card => ({
              ...card,
              imageUrl: `${this._cardImageApi}/${card.idName}.png`
            }))
          )
      ),
      catchError(this.handleError)
    );

  onSelectedCardId(id: number) {
    this.selectedCardIdSubject.next(id);
    console.log(`onSelectedCardId: ${id}`)
  }    
}

组件

@Component({})
export class CardDetailsComponent {
    constructor(private _cardService: CardService,
        private route: ActivatedRoute) { 

        this.selectedCardId = this.route.snapshot.params['id'];
        this._cardService.onSelectedCardId(this.selectedCardId);
    }

    card$ = this._cardService.card$;
}

HTML

<div class="container mb-5" *ngIf="card$ | async as card">
    <p>{{ card.name }}</>
</div>

在上面的代码中,重定向后,它不会呈现我的简单HTML来显示所选卡的名称。我正在使用[routerLink]来重定向卡片详细信息页面。

卡列表组件

<div *ngIf="cards$ | async as cards">
    <div class="col-md-1 col-4 hover-effect" *ngFor='let card of cards'>
      <a class="d-block mb-1">
        <figure>
          <img class="img-fluid img-thumbnail" [src]='card.imageUrl' [title]='card.name' [alt]='card.name'
            [routerLink]="['/cards', card.id]" />
        </figure>
      </a>
    </div>
</div>

任何能启发我这里发生的事情的人吗?

2 个答案:

答案 0 :(得分:3)

不起作用的原因是在构造函数中已经发射了发射之后,您的$ card被订阅,并且此时视图尚未呈现,这意味着aysnc管道不存在。

这种情况非常类似于以下代码顺序,因此您没有收到更新

// constructor
const a=new Subject()
a.next('some value')
// rendering
a.subscribe(console.log) // never triggered

有两种方法可以解决此问题:

  • 第一个使用shareReplay(1)缓存最新值,因此在订阅时它总是发出最后一个值

    selectedCardIdAction$ = this.selectedCardIdSubject.asObservable().pipe(shareReplay(1));
    

或使用BehaviorSubject

private selectedCardIdSubject = new BehaviorSubject<number | null>(null);
  • 第二次将.next()移动到ngAfterViewInit-在创建视图并订阅异步管道后触发

我个人将采用第一种方法,如果您的代码没有太多缺陷,结果将更加一致

答案 1 :(得分:1)

我认为还有第二个陷阱:

在您的组件代码中,您可以通过快照访问路由参数:this.route.snapshot.params['id']

由于构造函数仅被调用一次,即使参数发生更改,我也怀疑更改所选卡时您的解决方案是否有效。

最佳做法是通过RxJ订阅路由参数。为了避免不必要的订阅,我们可以在路线参数每次更改时使用高阶可观察运算符mergeMap加载单个卡条目

@Component({
  selector: "card",
  template: `
    CARD_DETAIL
    <div *ngIf="card$ | async; let card">Test {{ card.name }}</div>
  `
})
export class CardComponent {
  card$;

  constructor(private cardService: CardService, private route: ActivatedRoute) {
    this.card$ = this.route.params.pipe(
      map(getIdParameter),
      mergeMap(this.cardService.loadSingleCard)
    );
  }
}

const getParameter = param => params => params[param];
const getIdParameter = getParameter("id");

这还将清理您的服务并将其减少到一项BehaviorSubject

@Injectable()
export class CardService {
  card$ = new BehaviorSubject<Card>(null);

  constructor(private httpClient: HttpClient) {}

  loadSingleCard = (id: number) => {
    const url = `https://api.punkapi.com/v2/beers/${id}`;

    this.httpClient
      .get<Card>(url)
      .pipe(map(cards => cards[0]))
      .subscribe(card => this.card$.next(card));

    return this.card$;
  };
}

编辑:

以下是所谓的“箭头函数表达式”,它使用一种功能方法,称为“部分应用程序”(afaik)。在我们的例子中,它是一个接受特定参数的函数,并返回另一个接受整个params对象的函数。当调用返回的函数时,我们最终可以从params对象返回指定的param。

const getParameter = param => params => params[param];

与以下相同:

const getParameter = function(param) {
    return function(params) {
       params[param];
    };
}

const getIdParameter = getParameter("id");

// Could be called like that aswell:
const params = { id: 1, name: 'chris'};
const idParam = getParameter("id")(params); // hence the name partial application, i guess ;)

我同意它看起来确实不错,但是现在我们可以将getIdParameter函数引用传递到map(getParameterdId)中,该引用基本上是较短的语法,例如:map(params => getParameterId(params)或{{ 1}}

My Playground

给克里斯加油