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();
}
})
}
Related
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.
While learning Angular and NGRX, ran into what I thought would be simple example of common problem but having trouble understanding best way to accomplish the control logic with in the Redux store\effects patterns.
General Process:
User types in credit card info, hit pay button > Dispatch a "GetTokenAction" from component > Make an Http request to external 3rd party API to tokenize > Submit that information if successful to Payment API
Here is my latest attempt:
#Effect() getToken$ = this.actions$
.ofType(TokenizerActions.GET_TOKEN)
.pipe(switchMap((action: TokenizerActions.GetTokenAction) => {
return this.TokenizerApiService.getToken(action.payload)
.pipe(
map(tokenResponse => {
console.log(tokenResponse);
// service will return 200 with "declined" status. In this case I want to treat it like an Error.
if (tokenResponse.results.Error != null) {
return new TokenizerActions.GetTokenFailedAction(tokenResponse.results.Error.messages);
}
// What is best practice here? Call Payment Service? Dispatch Actions? Where should this mapping logic live?
const paymentRequest: PaymentRequest = new PaymentRequest();
paymentRequest.token = tokenResponse.results.token;
paymentRequest.amount = action.payload.amount;
paymentRequest.customerNumber = action.payload.customerNumber;
paymentRequest.correlationId = tokenResponse.results.correlation_id;
// this does not work, "dispatched an invalid action: undefined" error.
mergeMap((payReq: PaymentRequest) => [new paymentActions.MakePaymentAction(paymentRequest),
new TokenizerActions.GetTokenSuccessAction(tokenResponse.results.token)]);
}),
catchError(error => of(new TokenizerActions.GetTokenFailedAction(error)))
);
}));
constructor(
private actions$: Actions,
private TokenizerApiService: TokenizerApiService,
private paymentApiService: PaymentApiService
) { }
Question/Considerations:
Is the effect the appropriate place to handle this? The first working version had the component controlling the flow and dispatching multiple actions, could also be handled within the services, not sure which is best practice.
What is the preferred method for error notification within the Effects pattern? Online and in sample application there are a lot of simple examples, but I am having trouble translating that to a slightly more complex example (inspect response and then throw errors and stop processing as needed). Currently the application is doing something like this:
<span class="error" *ngFor="let error of tokenErrors$ | async">{{error.description}}</span>
<span class="error" *ngFor="let error of paymentErrors$ | async">{{error.description}}</span>
<div class="success" *ngIf="(payment$ | async)?.transactionStatus === 'approved'">Success</div>
this.paymentErrors$ = this.store.select(state => state.payment.paymentErrorMessages);
this.tokenErrors$ = this.store.select(state => state.token.tokenErrorMessages);
this.payment$ = this.store.select(state => state.payment.paymentStatus);
Can it be simpler? Should errors be combined into one PayError array? Is there a catch in the component level if subscribed or is it all to be handled in the effects level?
First, don't do the arrow function directly and create selectors instead.
Secondly, select a ViewModel (transactionInfo) that's needed for this component with one selector instead of having 3 different ones. You can combine multiple selectors to achieve that.
The result could be pushed higher in the template with *ngIf, for example
<ng-container *ngIf="transactionInfo$ | async as transactionInfo">
<span class="error" *ngFor="let error of tokenErrors">{{error.description}}</span>
<span class="error" *ngFor="let error of transactionInfo.paymentErrors">{{error.description}}</span>
<div class="success" *ngIf="transactionInfo.transactionStatus === 'approved'">Success</div>
</ng-container>
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 :)
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
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' }}