Returning $key in AngularFire 5 - angularfire

I have the following code that works with AngularFire 5:
export class NavigationComponent {
items: Observable<any[]>;
constructor(db: AngularFireDatabase) {
this.items = db.list('/pages', ref => {
let query = ref.limitToLast(100).orderByChild('sortOrder');
return query;
}).valueChanges();
}
}
But now need to return item.$key which apparently is no longer returned by default in AngularFire 5. I see mention in the migration guide of needing to "map" this, but can't seem to get the right syntax working on the above code.
Update: followed the advice below, and it seemed to have worked but there appears to be some difference still between the behavior between old and new.
<nav class="nav-standard">
<app-logo></app-logo>
<div class="nav-dropdown">
<ul *ngFor="let item of items | async | filter : 'parent' : '00000000-0000-0000-0000-000000000000'" class="nav-dropdown">
<li>
<a mat-button class="mat-button" href="{{item.path}}" data-id="{{item.key}}" target="{{item.target}}" title="{{item.tooltip}}" [attr.data-sort]="item.sortOrder" *ngIf="item.content; else elseBlock">{{item.menuText}}</a>
<ng-template #elseBlock>
<a mat-button class="mat-button" data-id="{{item.key}}" target="{{item.target}}" title="{{item.tooltip}}">{{item.menuText}}</a>
</ng-template>
<ul class="nav-dropdown-content">
<li *ngFor="let childItem of items | async | filter : 'parent' : item.key" class="">
<a class="mat-button" href="{{childItem.path}}" data-id="{{childItem.key}}" target="{{childItem.target}}" title="{{childItem.tooltip}}" [attr.data-sort]="childItem.sortOrder">{{childItem.menuText}}</a>
</li>
</ul>
</li>
</ul>
</div>
</nav>
The nested *ngFor never seems to fire, whereas before it did. It appears that items in the nested *ngFor is null ?? I found if I create another Observable in my component called childItems and assign duplicate logic to that, it works okay -- but to me that feels dirty and wrong. How can I get the data in the observable to persist long enough to use it in the nested *ngFor ?

item key information isn't 'free' anymore upon the new AngularFire API changes. Instead of use '.valueChanges()' to turn reference into Observable, you can use '.snapshotChanges()':
.snapshotChanges()
.map(changes => {
return changes.map(change => ({key: change.payload.key, ...change.payload.val()}));
})
from then on you can reference item key by using 'item.key' (notice that you cannot use '$key').

Related

vue v-model does not seem to be working in modal

I am pretty new to vue, and am trying to use it in a bootstrap modal. The relevant div in the modal is as follows.
<div class="form-group row">
<label for="priceQCField" class="col-sm-2 col-form-label">Price<span class="red"> *</span></label>
<input type="number" step="0.01" class="form-control col-sm-4" id="priceQCField" name="priceQCField" min="0" v-model="job.price">
</div>
I read some other questions about vue returning strings rather than numbers, so I have converted the job.price to a number inside my method to call the modal
showPriceJob: function (job) {
this.job = job;
this.job.price = parseFloat(this.job.price);
$('#mdlPriceJob').modal('show');
},
However, job.price refuses to appear in the input field either as a string or a number. I know it is available to the modal as I can see it using <span>{{job.price}}</span>.
Can anyone advise me please?
Additional - I think it is a display issue - if I change the input field, the entry in the <span> changes
2nd update - initial table
<tr class="light-grey" v-for="job in jobs" v-on:click="viewJob(job)">
<td>{{job.id}}</td>
<td>{{job.customerName}}</td>
<td>{{job.description}}</td>
<td v-bind:class="job.dueDate | dateColour">{{job.dueDate | dateOnly}}</td>
<td>£{{job.price}} {{job.isEstimate | priceEstimated}}</td>
<td>{{job.delivery}}</td>
</tr>
Upd.
According to your comments to my answer you are using v-for and you can't use this.job within your method. You should give us more code to see the whole picture.
Upd.2
You have showed more code but I didn't see any v-for so I am confused. You can try to use something like this if job is a property of appData.jobs:
showPriceJob: function (job) {
this.appData.jobs.job = Object.assign({}, job);
this.appData.jobs.job = parseFloat(this.appData.jobs.job.price);
$('#mdlPriceJob').modal('show');
},
But I'm not sure about this because I don't see where job is declared.
Upd.3
Oh! Wait! You have this code:
data: appData.jobs, but data should be in this format:
data: function(){
return {
appData: {
jobs: [],
},
}
},
Or show me what is your appData.jobs variable is.

v-for doesn't output anything even though data is returned from the GET

Creating a VUE 2 app using NUXT. My async method returns data fine. But, for some reason my v-for doesn't produce any html markup.
The test data, as returned by my node.js api, via postman, is...
{
    "status": true,
    "message": [
        {
            "rating": [],
            "_id": "5e113ce50d41592e38976e45",
            "title": "Book 2",
            "description": "This is amazon book2",
            "photo": "https://amazon-clone-v1-rg.s3.amazonaws.com/xxxxx",
            "stockQuantity": 14,
            "price": 33,
            "__v": 0
        },
        {
            "rating": [],
            "_id": "5e113cf00d41592e38976e46",
            "title": "Book 1",
            "description": "This is amazon book 1",
            "photo": "https://amazon-clone-v1-rg.s3.us-east-2.amazonaws.com/xxxxx",
            "stockQuantity": 14,
            "price": 25,
            "__v": 0
        }
    ]
}
Here's my code...
<template>
  <main>
    <div class="a-spacing-large">
      <div class="container-fluid browsing-history">
        <div class="row">
          <div class="col-sm-8 col-8">
            <h1 class="a-size-large a-spacing-none a-text-normal">All Products</h1>
            <div class="a-spacing-large"></div>
            <!-- Button -->
            <a href="#" class="a-button-buy-again">Add a new product</a>
            <a href="#" class="a-button-history margin-right-10">Add a new category</a>
            <a href="#" class="a-button-history margin-right-10">Add a new owner</a>
          </div>
          <!-- Listing page -->
        </div>
      </div>
    </div>
    <div class="a-spacing-large"></div>
    <div class="container-fluid browsing-history">
      <div class="row">
        <div
          v-for="(product, index) in products"
          :key="product._id"
          class="col-xl-2 col-lg-2 col-md-3 col-sm-6 col-6 br bb"
        >
          <div class="history-box">
            <!-- Product image -->
            <a href="#" class="a-link-normal">
              <img :src="product.photo" class="img-fluid" />
            </a>
            <!-- Product title -->
            <div class="a-spacing-top-base asin-title">
              <span class="a-text-normal">
                <div class="pl3n-sc-truncated">{{product.title}}</div>
              </span>
            </div>
            <!-- Product rating -->
            <div class="a-row">
              <a href="#">
                <i class="fas fa-star"></i>
                <i class="fas fa-star"></i>
                <i class="fas fa-star"></i>
                <i class="fas fa-star"></i>
                <i class="fas fa-star"></i>
              </a>
              <span class="a-letter-space"></span>
              <span class="a-color-tertiary a-size-small asin-reviews">(1732)</span>
            </div>
            <!-- Product price -->
            <div class="a-row">
              <span class="a-size-base a-color-price">
                <span class="pl3n-sc-price">${{product.price}}</span>
              </span>
            </div>
          </div>
          <!-- Product Buttons -->
          <div class="a-row">
            <a href="#" class="a-button-history margin-right-10">Update</a>
            <a href="#" class="a-button-history margin-right-10">Delete</a>
          </div>
        </div>
      </div>
    </div>
  </main>
</template>
<script>
export default {
  async asyncData({ $axios }) {
    try {
      let response = await $axios.$get("http://localhost:3000/api/products");
      console.log(response);
      return {
        products: response.products
      };
    } catch (err) {
      console.log("error in code: " + err);
    }
  }
};
</script>
I even tried a very simple v-for loop like this, but it also returned no data...
<ul>
<li v-for="(product, index) in products">{{product.title}}</li>
</ul>
Any suggestions would be appreciated. Thanks!
In your front-end you should return {products: response.message} because the field called message have yours "products" list.
or
In your API back-end should be use products instead of message to return your "product" list then you could be used as you mentioned.

How to keep track of an array change in Vue.js when the index is a dynamic value?

I am building an app using Node.js and Vue.
My DATA for the component is the following:
data() {
return {
campaign: {
buses: [],
weeks: [
{
oneWayBuses: [],
returnBuses: []
}
]
},
busesMap: {
// id is the bus ID. Value is the index in the campaign.buses array.
},
};
},
I fill the buses and weeks array in MOUNTED section in two separate methods after getting the data from the server:
responseForWeeks => {
responseForWeeks.forEach(
week => this.campaign.weeks.push(week);
)
}
responseForBuses => {
responseForBuses.forEach(
bus => this.campaign.buses.push(bus);
// Here I also fill the busesMap to link each week to its bus index in the array of buses
this.busesMap[bus.id] = this.campaign.buses.length - 1;
)
}
So the idea is that my busesMap looks like busesId keys and index values:
busesMap = {
'k3jbjdlkk': 0,
'xjkxh834b': 1,
'hkf37sndd': 2
}
However, when I try to iterate over weeks, v-if does not update so no bus info is shown:
<ul>
<li
v-for="(busId, index) in week.oneWayBuses"
:key="index"
:item="busId"
>
<span v-if="campaign.buses[busesMap.busId]">
<strong>{{ campaign.buses[busesMap.busId].busLabel }}</strong>
leaves on the
<span>{{ campaign.buses[busesMap.busId].oneWayDepartureDate.toDate() | formatDate }}</span>
</span>
</li>
</ul>
On the other side, if I shorten the v-if condition to campaign.buses, then I get into the condition but campaign.buses[busesMap.busId] is still undefined, so I get an ERROR trying to display busLabel and oneWayDepartureDate
I've read vue in depth documentation, but couldn't come up with a resolution.
Any gotchas you can find out?
Try this:
async mounted(){
await responseForWeeks
await responseForBuses
responseForWeeks => {
responseForWeeks.forEach(
week => this.campaign.weeks.push(week);
)
}
// this is partial since it is what you provided
responseForBuses => {
responseForBuses.forEach(
bus => this.campaign.buses.push(bus);
// Here I also fill the busesMap to link each week to its bus index in the array of buses
this.busesMap[bus.id] = this.campaign.buses.length - 1;
)
}
}
Basically you want to make sure that before your component loads your data is in place. You can also create computed properties which will force re rendering if dependencies are changed and they are in the dom.
Actually, the problem was indeed in the HTML.
When trying to access the object keys, better use [] intead of a dot .
Final HTML result would be as follows:
<ul>
<li
v-for="(busId, index) in week.oneWayBuses"
:key="index"
:item="busId"
>
<span v-if="campaign.buses[[busesMap[busId]]]">
<strong>{{ campaign.buses[busesMap[busId]].busLabel }}</strong>
leaves on the
<span>{{ campaign.buses[busesMap[busId]].oneWayDepartureDate.toDate() | formatDate }}</span>
</span>
</li>
</ul>
What was happening is that previously campaign.buses[busesMap.busId] did not exist, thus not rendering anything. Solved to campaign.buses[busesMap[busId]]. Also used claudators for the displayed moustache sintach.
Hope it helps someone else messing with Objects!

Angular: Getting the last 4 posts from Contentful

Hi I'm trying to get the last four blog posts from contentful api in angular 5. I've created a service and I'm capable of retrieving 1 with the following code.
getContent(contentId) {
const promise = this.client.getEntry(contentId);
return Observable.fromPromise(promise).map(entry => entry.fields);
};
I would like to return it as an observable, however if returned as a promise, I would like to know how to work with a promise in the component.ts and -.html.
getLastByCount(number) {
var promise = this.client.getEntries({
limit: number
})
...
}
If i do the same for getting multiple entries I get a 'PromiseObservable', which contains a 'promise: ZoneAwarePromise'. In which it has 'items: Array', where object are as when i log the single entry. How do I work which such objects?
Edited:
I've done as suggested by: Stephan
getLastByCount(number) {
var promise = this.client.getEntries({
limit: number
})
return Observable.fromPromise(promise).mergeMap((collection) => (
Observable.from(collection.items)
))
}
And in my component.ts in OnInit()
posts$: Observable<any>;
this.posts$ = this.contentfulService.getLastByCount(4).map(entry => entry.fields);
In my component.html, i do this when displaying the one entry
<div *ngIf="post$ | async as post">
<h1>{{ post.headline }}</h1>
<time>Published on {{ post.published | date: 'fullDate' }}</time>
<hr>-->
</div>
I try this when using the collection:
<div *ngIf="posts$ | async as posts">
<ul>
<li class="post" *ngFor="let post of posts$ | async">
<h1>{{ post.headline }}</h1>
<time>Published on {{ post.published | date: 'fullDate' }}</time>
<hr>
</li>
</ul>
</div>
I get this error: Error: Cannot find a differ supporting object '[object Object]' of type 'object'. NgFor only supports binding to Iterables such as Arrays.
What you probably want is an Observable that emits every single item of the collection that the Contentful client returns.
To do so, you can use the mergeMap functionality:
Observable
.fromPromise(promise)
.mergeMap((collection) => (
Rx.Observable.from(collection.items)
))
Now you can work with every item in the observable sequence as with the single entry above, e.g. you can
.map(entry => entry.fields)
EDIT:
Turns out Angular expects you to have an Observable<Entry[]>, instead of Observable<Entry>, so the proper way is:
Observable
.fromPromise(promise)
.map((collection) => collection.items)

Aurelia iterate over map where keys are strings

I'm having trouble getting Aurelia to iterate over a map where the keys are strings (UUIDs).
Here is an example of the data I'm getting from an API running somewhere else:
my_data = {
"5EI22GER7NE2XLDCPXPT5I2ABE": {
"my_property": "a value"
},
"XWBFODLN6FHGXN3TWF22RBDA7A": {
"my_property": "another value"
}
}
And I'm trying to use something like this:
<template>
<div class="my_class">
<ul class="list-group">
<li repeat.for="[key, value] of my_data" class="list-group-item">
<span>${key} - ${value.my_property}</span>
</li>
</ul>
</div>
</template>
But Aurelia is telling me that Value for 'my_data' is non-repeatable.
I've found various answer by googling, but they have not been clearly explained or incomplete. Either I'm googling wrong or a good SO question and answer is needed.
As another resource to the one supplied by ry8806, I also use a Value Converter:
export class KeysValueConverter {
toView(obj) {
if (obj !== null && typeof obj === 'object') {
return Reflect.ownKeys(obj).filter(x => x !== '__observers__');
} else {
return null;
}
}
}
It can easily be used to do what you're attempting, like this:
<template>
<div class="my_class">
<ul class="list-group">
<li repeat.for="key of my_data | keys" class="list-group-item">
<span>${key} - ${my_data[key]}</span>
</li>
</ul>
</div>
</template>
The easiest method would be to convert this into an array yourself (in the ViewModel code)
Or you could use a ValueConverter inside repeat.for as described in this article Iterating Objects
The code...
// A ValueConverter for iterating an Object's properties inside of a repeat.for in Aurelia
export class ObjectKeysValueConverter {
toView(obj) {
// Create a temporary array to populate with object keys
let temp = [];
// A basic for..in loop to get object properties
// https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Statements/for...in
for (let prop in obj) {
if (obj.hasOwnProperty(prop)) {
temp.push(obj[prop]);
}
}
return temp;
}
}
/**
* Usage
* Shows how to use the custom ValueConverter to iterate an objects properties
* aka its keys.
*
* <require from="ObjectKeys"></require>
* <li repeat.for="prop of myVmObject | objectKeys">${prop}</li>
*/
OR, you could use the Aurelia Repeat Strategies provided by an Aurelia Core Team member
You'd have to import the plugin into your app.
Then you'd use it using the pipe syntax in your repeat.for....like so....
<div repeat.for="[key, value] of data | iterable">
${key} ${value.my_property}
</div>