Why can I not call toArray() on FirebaseListObservable? - firebase

In an Ionic Project, I have:
import { AngularFireDatabase, FirebaseListObservable} from 'angularfire2/database';
and a class with the field:
songs: FirebaseListObservable<any>;
therefore, the line of code:
this.songs = db.list('/songs');
works and allows me to put the line:
<button ion-button ouline *ngFor="let song of songs | async">
in my html without problem.
Now, FirebaseListObservable extends the RxJS Observable (source).
Furthermore, Observable has a method toArray(). But, when I run my project, I see:
Typescript Error
Property 'toArray' does not exist on type 'FirebaseListObservable<any>'.
Why is this? Is there another way I can get an array from what songs is observing?

I don't really sure why the toArray() not working , but i can suggest you a way to get the array you want from the DB.( I usually do that when i want just the array without the ability to listen to any changes of the DB - like Observable does) :
this.db.list('/songs')
.first().toPromise()
.then(response => {
//code to handle the response - in this case its a list
This.items = response;
})
.catch(error => { //error code here });
dont forget to import the rxjs first and toPromise
I really hope it fits your wish and helps you :)

Related

Next.js getInitialProps not rendering on the index.js page

I really can't figure out what is wrong with this code on Next.js.
index.js :
import { getUsers } from "../utils/users";
import React from "react";
Home.getInitialProps = async (ctx) => {
let elements = [];
getUsers().then((res) => {
res.map((el) => {
elements.push(el.name);
});
console.log(elements);
});
return { elements: elements };
};
function Home({ elements }) {
return (
<div>
{elements.map((el, i) => {
<p key={i}>{el}</p>;
})}
</div>
);
}
export default Home;
This doesn't render anything on my main page but still console logs the right data on server side (inside the vscode console). I really can't figure out what's going on, I followed precisely the article on the next.js site.
The getUsers function is an async function that returns an array of objects (with name,surname props), in this case in the .then I'm grabbing the names and pushing them into an array that correctly logs out to the console.
How can I make this data that I get render on the page?? Surely something to do with SSR.
The problem is using async function. Try as following.
...
elements = await getUsers();
...
In your code, component is rendered before response is finished. So the data is not rendered. Suggest using "async...await...". Infact "async" and "await" are like a couple of one.

Sinon restore cannot read property 'restore' of undefined

I have the following code which I am using Sinon for
import React from 'react';
import { mount } from 'enzyme';
import sinon from 'sinon';
import { IncrementalSearch } from './IncrementalSearch';
describe('<IncrementalSearch />', () => {
afterEach(() => {
this.constructorSpy.restore();
});
it('calls constructor', () => {
this.constructorSpy = sinon.spy(IncrementalSearch, 'constructor');
const wrapper = mount(<IncrementalSearch />);
expect(IncrementalSearch.prototype.constructor.calledOnce).toEqual(true);
// expect(1).toEqual(1);
});
});
Now when I run this I get the following error
TypeError: Cannot read property 'restore' of undefined
What am I doing wrong here?
You have two problems here.
The immediate problem of this referring to something else than what you believe it to.
You misunderstand what the constructor property refers to.
The this pointer in afterEach refers to another context (the outer describe context) than the this in the test. Generally, to make life simpler, avoid using this when not necessary and simply use normal variables.
To fix the first problem, let's just rework your example a bit:
import React from 'react';
import { mount } from 'enzyme';
import sinon from 'sinon';
import { IncrementalSearch } from './IncrementalSearch';
describe('<IncrementalSearch />', () => {
let constructorSpy;
afterEach(() => {
constructorSpy.restore();
});
it('calls constructor', () => {
constructorSpy = sinon.spy(IncrementalSearch, 'constructor');
const wrapper = mount(<IncrementalSearch />);
expect(IncrementalSearch.prototype.constructor.calledOnce).toEqual(true);
});
});
Your test will still fail, but for another reason
You should read this thread on the Sinon issue tracker, especially the discussion between me and machineghost. The quick summary in your regard is that you need to read up on how Javascript protypes actually work, as the constructor property does not have anything to do with the constructor keyword used in ES6 "classes." It refers to the function that created the object. Changing it after the fact, will not have any effect.
The actual constructor is the function ("class") you export from ./IncrementalSearch. Just see how babel transpiles it. So, in essence, if you really want this test to pass, which has nothing to do with your code, but is just a check if React is actually calling your code (which you should really just trust it does...), it should suffice to wrap the function. I haven't tested this though, as this might fail due to the spy not calling the wrapped function with new.

FirebaseListObservable and ion-slides not working

I have a FirebaseListObservable and want to iterate over the resulting elements to create ion-slides:
<ion-slides [initialSlide]="currentDay - 1">
<ion-slide *ngFor="let secret of secrets | async let i = index;">
<big-secret-card [secret]="secret"></big-secret-card>
</ion-slide>
</ion-slides>
However when I do this, initialSlide doesn't work. I think this might be a bug of ion-slides.
What is the best way to handle this? Skip the nice async pipe and subscribe to the FirebaseListObservable instead, and include an *ngIf="secrets.length>0" to ion-slides?
In this case do I have to use unsibscribe() when leaving the page?
Or is there any better solution?
I am now using this workaround, converting the FirebaseListObservable into a regular Observable and pre-loading with an array of 7 empty Objects. It works for my case:
getWeek(week): Observable<any> {
// get all secrets of one week into an array
let emptyWeek = [{},{},{},{},{},{},{}];
return Observable.create(observer => {
observer.next(emptyWeek);
let week$ = this.af.database.list('/dhsweek/en/week)).subscribe(result => observer.next(result));
return () => {
// unsubscribe function called automatically by async pipe when leaving page
week$.unsubscribe();
}
})
}

How can I change the subscriptions query parameters in react-komposer (meteor) from a child component?

I'm building an app with Meteor using the react-komposer package. It is very simple: There's a top-level component (App) containing a search form and a list of results. The list gets its entries through the props, provided by the komposer container (AppContainer). It works perfectly well, until I try to implement the search, to narrow down the results displayed in the list.
This is the code I've started with (AppContainer.jsx):
import { Meteor } from 'meteor/meteor';
import { composeWithTracker } from 'react-komposer';
import React, { Component } from 'react';
import Entries from '../api/entries.js';
import App from '../ui/App.jsx';
function composer(props, onData) {
if (Meteor.subscribe('entries').ready()) {
const entries = Entries.find({}).fetch();
onData(null, {entries});
};
};
export default composeWithTracker(composer)(App);
App simply renders out the whole list of entries.
What I'd like to achieve, is to pass query parameters to Entries.find({}).fetch(); with data coming from the App component (captured via a text input e.g.).
In other words: How can I feed a parameter into the AppContainer from the App (child) component, in order to search for specific entries and ultimately re-render the corresponding results?
To further clarify, here is the code for App.jsx:
import React, { Component } from 'react';
export default class App extends Component {
render() {
return (
<div>
<form>
<input type="text" placeholder="Search" />
</form>
<ul>
{this.props.entries.map((entry) => (
<li key={entry._id}>{entry.name}</li>
))}
</ul>
</div>
);
}
}
Thanks in advance!
I was going to write a comment for this to clarify on nupac's answer, but the amount of characters was too restrictive.
The sample code you're looking for is in the search tutorial link provided by nupac. Here is the composer function with the corresponding changes:
function composer(props, onData) {
if (Meteor.subscribe('entries', Session.get("searchValues")).ready()) {
const entries = Entries.find({}).fetch();
onData(null, {entries});
};
};
The solution is the session package. You may need to add it to your packages file and it should be available without having to import it. Otherwise try with import { Session } from 'meteor/session';
You just need to set the session when submitting the search form. Like this for instance:
Session.set("searchValues", {
key: value
});
The subscription will fetch the data automatically every time the specific session value changes.
Finally, you'll be able to access the values in the publish method on the server side:
Meteor.publish('entries', (query) => {
if (query) {
return Entries.find(query);
} else {
return Entries.find();
}
});
Hope this helps. If that's not the case, just let me know.
There are 2 approaches that you can take.
The Subscription way,
The Meteor.call way,
The Subscription way
It involves you setting a property that you fetch from the url. So you setup your routes to send a query property to you Component.Your component uses that property as a param to send to your publication and only subscribe to stuff that fits the search criteria. Then you put your query in your fetch statement and render the result.
The Meteor.call way
Forget subscription and do it the old way. Send your query to an endpoint, in this case a Meteor method, and render the results. I prefer this method for one reason, $text. Minimongo does not support $text so you cannot use $text to search for stuff on the client. Instead you can set up your server's mongo with text indexes and meteor method to handle the search and render the results.
See what suits your priorities. The meteor.call way requires you to do a bit more work to make a "Search result" shareable through url but you get richer search results. The subscription way is easier to implement.
Here is a link to a search tutorial for meteor and read about $text if you are interested

Angular 2 - Displaying async Object data from promise

Edit: It looks like my main problem now is that I can't seem to display async data from an object. I have a promise containing the data object, and when I use
{{ data | async }}
it will display
[object Object]
The issue is, I want to be able to display all the different attributes; i.e, Name, Symbol, etc. In Angular 1, I would just use
{{ data.Name | async }}
but that doesn't work here, since the async pipe tries to resolve the data.Name promise, which doesn't exist. I want to resolve the data promise and then display the Name key from it. At the moment, I'm working on creating my own pipe to display a key from an async object, but I'm wondering if there's a built-in Angular 2 pipe or function to handle this!
I've created a StockService class that returns a Promise containing an object to my StockInfo class, which contains the HTML to be displayed. I want to display the name of this object in my HTML, but I can't seem to get it to display.
In my StockInfo constructor:
this.stock.getStockData(this.ticker, http).then(function(val) {
this.data = val;
this.name = new Promise<string>(function(resolve) {
resolve(this.data.Name);
});
});
where this.stock is the StockService object.
In my HTML:
<h2>{{name | async}}</h2>
I've tried a number of different arrangements before settling on this one. I want the StockService class to handle the data fetching and the StockInfo class to handle the display. In Angular 1, I would create a factory for fetching data and handle the data processing in the controller, but I'm not quite sure how to go about this in Angular 2.
Is there a way to get it to display, or are there better ways to design my code that I should look into? Thanks!
You do not need any special pipe. Angular 2 suppport optional field. You just need to add ? in your object
{{ (data | async)?.name }}
or
{{(name | async)?}}
There's nothing wrong with the accepted answer above. But it becomes a hassle to append | async? when we need to display many properties of the object. The more convenient solution is as follows:
<div *ngIf="data | async as localData">
<div> {{ localData.name }} </div>
<div> {{ localData.property1 }} </div>
<div> {{ localData.property2 }} </div>
</div>
You can also use pluck from rxjs/observable:
{{ user.pluck("name") | async }}
Pluck
Returns an Observable containing the value of a specified nested property from all elements in the Observable sequence. If a property can't be resolved, it will return undefined for that value.
If you work with Observable you can display data like this way:
<div *ngIf="data | async; let _data">
<h3>{{_data.name}}</h3>
</div>
or
<h3>{{(data | async).name}}</h3>
I think you are making this too complex, and just need to do something like this.
this.name =
this.stock.getStockData(this.ticker, http)
.then( val => val.Name )
and
<h2>{{name.Name | async}}</h2>
So I ended up writing my own asynchronous key pipe. Huge thanks to Simon for helping guide me here.
import {Pipe} from 'angular2/core';
#Pipe({
name: 'key',
pure: false
})
export class KeyPipe {
private fetchedPromise: Promise<Object>;
private result: string;
transform(value: Promise<Object>, args: string[]) {
if(!this.fetchedPromise) {
this.fetchedPromise = value
.then((obj) => this.result = obj[args[0]] );
}
return this.result;
}
}
Usage:
<h2>{{ data | key: 'Name' }}</h2>
Someone please comment if Angular has its own functions for resolving a key from an asynchronous object.
The OP asked for promises but in case people are using Observables, adapting #user2884505's answer, since pluck isn't directly available on observables as a method in recent versions of RxJS, you may have something like this :
import { Pipe, PipeTransform } from '#angular/core';
import { Observable } from 'rxjs';
import { pluck } from 'rxjs/operators';
#Pipe({
name: 'asyncKey',
pure: false
})
export class AsyncKeyPipe implements PipeTransform {
private observable: Observable<Object>;
private result: Object;
transform(value: any, ...args: any[]): any {
if (!this.observable) {
this.observable = value.pipe(pluck(...args));
this.observable.subscribe(r => this.result = r);
}
return this.result;
}
}
And then, you can use it, even for nested keys :
{{ user$ | asyncKey: 'address' : 'street' }}

Resources