Reputation: 99
I have 2 components - product-overview
and product-overview-item
. The product-overview-item
is an individual item that gets rendered inside of product-overview
component every time the built-in render()
method loads the HTML template on the screen.
I have an object (say, agreement
) with the HATEOAS links array as shown below.
{
"id": "1"
"type": "INVEST"
"group": "INVEST"
"role": "HOLD"
"accountId": "1",
"commercialId": {
"value": "DEFAULT",
"type": "3000",
},
"productName": "Self Invest",
"displayAlias": "Superman",
"visible": true,
"status": "ACTIVE"
"balance": {
"currency": "EUR",
"value": "34.00",
},
"_links": [
{
"rel": "details",
"href": "/nl/agree/192712812/investbalance",
},
{
"rel": "portfolio",
"href": "/dv/agreesecurities/981261892y9aksjakcnm",
},
{
"rel": "edit",
"href": "/mb/port/921nclaskofknsscsdwd",
}
]
}
I need to make an API call to the href value inside the HATEOAS links array that starts with /nl/agreement and ends with /investmentbalance. The response from the investment balance is very similar to the above response. From that, I need to fetch the balance object and pass it down to the child component (product-overview-item).
The balance object is similar to the one shown above. Here is what I have tried so far.
1. product-overview.js (Parent component)
_getUrl(agreement) {
const { _links } = agreement;
const nlAgreements = _links?.filter(link => link?.rel && link?.rel === 'details' && link?.href && link?.href.startsWith('/nl/agreements'));
if (nlAgreements === undefined || nlAgreements.length === 0) return;
return nlAgreements[0]?.href;
}
async _fetchMigratedInvestmentBalance(agreement) {
await ajax.get(this._getUrl(agreement))
.then(response => {
if (!response) throw new Error('Failed to fetch balance');
else return response;
})
.then(response => this.investmentBalance = response?.data?.balance)
.catch(error => {
throw new Error('Failed to fetch balance due to: ', error);
});
}
_getProducts(status, role, tab) {
...
...
if (this.agreementsList && this.agreementsList.length > 0) {
let groupedAgreements = [];
const filteredAgreements = this._filterAgreements(this.agreementsList, status, role);
groupedAgreements = this._getGroupedAgreements(filteredAgreements);
tab.count = filteredAgreements.length;
if (groupedAgreements && groupedAgreements.length > 0) {
const noBalanceAlert = this.isAPA ? html`
<div class="alert"><uic-alert type="error">
${localize.msg('product-overview:NO_BALANCE')}
</uic-alert></div>
` : '';
return html`
${noBalanceAlert}
${groupedAgreements.map(item => html`
<uic-expandable-item>
...
<article slot="details" class="item-details productDetails">
<ul class="list-unstyled">
${item.agreements.map((agreement, index) => html`
<li class="productDetailsItem">
${when(BeProductOverviewServices._isClickable((agreement.type).toUpperCase()), () => html` <a @click='${e => this._clickedToProductDetailsPage(e, item.agreements)}'
data-group="${item.agreementType}" data-index="${index}"
data-url="product-details/be/${item.agreementType}/${agreement.commercialId.value}">
<product-overview-item .agreement="${agreement}" @connected="${this._fetchMigratedInvestmentBalance(agreement)}" .balance="${this.investmentBalance}" .role="${role}">
</product-overview-item></a>`, () => html`<product-overview-item
.agreement="${agreement}" @connected="${this._fetchMigratedInvestmentBalance(agreement)}" .balance="${this.investmentBalance}" .role="${role}"
.disabled="${this.disabled}"></product-overview-item>
`)}
<li>
`)}
</ul>
</article>
</uic-expandable-item>`)}`;
`)}`;
}
}
return html``;
}
The _getProducts() function gets called in the render() function.
2. product-overview-item.js (child component)
In the child component, I am dispatching an event each time the item loads in connectedCallback()
function.
class beProductOverviewItem extends LitElement {
static get properties() {
return {
agreement: { type: Object },
balance: { type: Object, attribute: 'investment-balance', reflect: true },
role: { type: String },
disabled: { type: Boolean },
};
}
static get styles() {
return beProductOverviewStyle;
}
connectedCallback() {
super.connectedCallback();
this.dispatchEvent(new CustomEvent('connected'));
}
render() {
return html`<uic-item ?disabled="${this.disabled}">
<uic-item-header>
<span class="bold">${this.agreement?.productName}</span>
<span class="bold" slot="header-addon">
${(this.agreement?.status === 'CLOSED') ? html`
${localize.msg('product-overview:CLOSED')}` : (this.agreement?.type !== 'INVESTMENT' && this.agreement?.balance) ? html`
${formatAmountHtml(this.agreement?.balance?.value, {
locale: localize.locale,
style: 'currency',
currencyDisplay: 'symbol',
currency: this.agreement?.balance?.currency,
})}
` : (this.agreement?.type === 'INVESTMENT' && this.balance) ? html`
${formatAmountHtml(this.balance?.value, {
locale: localize.locale,
style: 'currency',
currencyDisplay: 'symbol',
currency: this.balance?.currency,
})}
` : ''}
</uic-item>`;
}
}
The promise resolves without undefined/pending but this time it is going in to an infinite loop loading the balance continuously. Is there something we are missing out?
NOTE: Now, I'll be the first to state I don't have enough experience to understand what is going on with these async calls. How do I incorporate async/await logic into my component?
Upvotes: 1
Views: 5145
Reputation: 99
I am answering my own question because the solution to this is a bit different than what has been suggested above.
I idea of calling the API in the child component and keeping the balance object ready in connectedCallback()
did the trick!
class ProductOverviewItem extends LitElement {
static get properties() {
return {
balance: { state: true },
loading: { type: Boolean, reflect: true },
}
}
_getUrl() {
const { _links } = this.agreement;
const nlAgree = _links?.filter(link => link?.rel && link?.rel === 'details' && link?.href && link?.href.startsWith('/nl/agree'));
if (nlAgree === undefined && nlAgree.length === 0) return;
return nlAgree[0].href;
}
async _fetchInvestBalance() {
const url = this._getUrl();
const investmentBalance = await ajax.get(this._getUrl());
this.balance = investmentBalance.data.balance;
}
connectedCallback() {
super.connectedCallback();
until(this._fetchInvestBalance(), 'Loading...');
}
render() {
// displaying the balance from the property value set here.
}
}
Upvotes: 0
Reputation: 5836
try something like this:
class ProductOverviewItem extends LitElement {
static get properties() {
return {
balance: { state: true },
loading: { type: Boolean, reflect: true },
}
}
_getUrl() {
const { _links } = this.agreement;
const nlAgree = _links?.filter(link => link?.rel && link?.rel === 'details' && link?.href && link?.href.startsWith('/nl/agree'));
if (nlAgree === undefined && nlAgree.length === 0) return;
return nlAgree[0].href;
}
async _fetchInvestBalance() {
await fetch(this._getUrl())
.then(x => {
if (!x.ok)
throw new Error(x.status)
else
return x;
})
.then(x => x.json())
.then(x => this.balance = x.data?.data?.data?.balance)
.catch(error => {
this.error = error;
});
}
render() {
return html`
<product-overview-item
?hidden="${this.loading}"
@connected="${this._fetchInvestBalance()}"
.agree="${agree}"
.balance="${this.balance}"></product-overview-item>
`;
}
}
class ProduceOverviewItem extends LitElement {
// ...
connectedCallback() {
super.connectedCallback();
this.dispatchEvent(new CustomEvent('connected'));
}
}
Where you fetch the data using the in-build fetch API, and set it as state on the parent component, to pass it down to the child.
Upvotes: 1