我试图通过将服务中的旧方法转换为在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>
任何能启发我这里发生的事情的人吗?
答案 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}}
给克里斯加油