Descending orderByChild() on firebase and Ionic 2 - sorting

I need get items sorted by date, but obviously I need descending sorted to show the posts in correct order...
import {
AngularFireDatabase
} from 'angularfire2/database';
import 'rxjs/add/operator/map';
/*
Generated class for the FirebaseProvider provider.
See https://angular.io/docs/ts/latest/guide/dependency-injection.html
for more info on providers and Angular 2 DI.
*/
#Injectable()
export class FirebaseProvider {
constructor(public afd: AngularFireDatabase) {}
getPostsItems() {
return this.afd.list('/Posts/', {
query: {
orderByChild: "date",
}
});
}
This query returns a ascendent order and I need a descendent order that it's not explained in Firebase web.
Which are queries I need?

One approach could be reversing the order in your component's template. First, you get a list of posts directly in your component:
posts.component.ts
export class PostsComponent {
posts: FirebaseListObservable<any>;
constructor(db: AngularFireDatabase) {
this.posts = db.list('/posts', {
query: {
orderByChild: 'date'
}
});
}
}
Then, you can use the reverse method to reverse your posts' order in your template:
posts.component.html
<div *ngFor="let post of (posts | async)?.slice().reverse()">
<h1>{{ post.title }}</h1>
</div>

Leaving this for Ionic 3 + AngularFire 4
posts.component.ts
import { AngularFireDatabase } from 'angularfire2/database';
export class PostsComponent {
posts;
constructor(db: AngularFireDatabase) {
this.posts = db.list('/posts', ref => ref.orderByChild('date')).valueChanges();
}
}
posts.component.html
<div *ngFor="let post of (posts | async)?.slice().reverse()">
<h1>{{ post.title }}</h1>
</div>

You should also use limitTolast() instead of limitToFirst() to ensure that your array will start with the last value that match with your filter in your DB (by default the filter is increasing order) and then use .reverse on your array to reverse it order:
1 - let array = db.list('/posts')ref => ref.orderByChild('date').limitToLast(5);
array = array.reverse();

Working without async in the ngFor
posts.component.ts
export class PostsComponent {
posts: FirebaseListObservable<any>;
constructor(db: AngularFireDatabase) {
this.posts = db.list('/posts', {
query: {
orderByChild: 'date'
}
});
}
}
Then, you can use the reverse method to reverse your posts' order in your template:
posts.component.html
<div *ngFor="let post of posts?.slice().reverse()">
<h1>{{ post.title }}</h1>
</div>

Related

Use the first query result as a condition for the second query

I have 2 queries like this
const featuredArticles = gql `
query featureArticles() {
articles(where: {limit: 4, feature: true, sort: "published_at:desc") {
id
}
}
`;
const NO_FEATURE_ARTICLES_QUERY = gql`
query noFeatureArticles($slug: String!) {
articles(where: { id_nin: ??? },limit: 4, sort: "published_at:desc") {
${SINGLE_ARTICLE_MODEL}
}
}
`;
The first query will get the ID of 4 latest featured articles, the second one will get all articles available in the database. Now, I'm trying to change the second query to get all articles EXCEPT 4 articles from the first query but I just don't know how to use the result as a condition. Can you guy give me some hint? Thanks in advance!
My index.jsx looks like this:
const Home = () => (
<div style={{
overflowX: 'hidden',
}}
>
let arr=[]
<Query query={FEATURE_ARTICLES_QUERY}>
{({ data: { articles } }) => {
if (!articles.length) {
return null;
}
arr=[articles[0].id,.......,articles[10].id];
return <SectionFeature articles={articles} />;
}}
</Query>
<Query query={ARTICLES_QUERY} id={arr}>
{({ data: { quotes } }) => {
if (!quotes.length) {
return null;
}
return <SectionQuote quotes={quotes} />;
}}
</Query>
</div>
);
export default Home;
For example, I want to pass 1st query's result to arr[], then pass it to 2nd query. But the array will be gone right after the 1st query ends.
UPDATE: I'd done it. Thank you so much for the hint

Get state from select form in child component with Gatsby

I code a Gatsby app with a Main page and two components. The value from a select form will be used to query a Postgresql database through a graphql query.
What I can already do: in the form component, I get the value from the select menu and pass it from this child component to the parent (the main page). In the data component, I can query the database with graphql and get the results with hardcoded values.
What I can't do yet: get the value from the select component to the data component and use it in my graphql query.
I tried different ways to get the value without success using this.props.value1 or this.state.value1. I also tested a simple component to make sure I could get the value from the parent to a child component and it worked seamlessly. So it's the way I try to import the value in a querying component that is the problem.
**//Data component**
let val = 88 //for hardcoded test. That works.
const DataPage = () => {
const data = useStaticQuery(query)
return (
<div>
<p>From Postgres: {data.postgres.allEstivalsList[val].nbr}</p>
</div>
)
}
const query = graphql`
{
postgres {
allAveragesList {
avg
}
allEstivalsList {
year
nbr
}
}
}
`;
export default DataPage;
**//Main page**
export default class App extends React.Component {
constructor(props) {
super(props);
}
state = {
value1: null,
// other values
}
render() {
return (
<div>
<p>Get state in main page: {this.state.value1}</p>
<DataPage val = {this.state.value1} />
<SelectForm clickHandler={y => { this.setState({ value1: y }); }} />
</div>
)
}
}
**//Form component**
export default class IndexPage extends React.Component {
state = {
value1: null,
}
handleClick = () => {
this.props.clickHandler(this.state.value1);
this.setState(prevState => {
return { value1: prevState.value1 };
});
};
render() {
return (
<Formlayout>
<p>Test -- Value for the selected year: {this.state.value1}</p>
<select onChange = {(e) => this.setState({ value1: e.target.value })}>
<option value="">-- Year --</option>
<option value="1">1901</option>
<option value="2">1902</option>
<option value="3">1903</option>
</select>
<button onClick={this.handleClick}>Go!</button>
</Formlayout>
)
}
}
I'd appreciate to get some directions to get the select value in the data component. As my test variable val is effectively working when used in the query, what I'd like to achieve is to apply to that variable the state from the component. And that's where I'm stuck right now.
The GraphQL query is executed at the build time, not at runtime. Have a look at the docs, it's possible to query data at build and at runtime.
https://www.gatsbyjs.org/docs/client-data-fetching/
// edit
Normally you would pre generate all (static) content pages - e.g. if you have data for 50 years, you let gatsby build those 50 pages and then you could navigate by path to the page.
If you want to pass a variable to the query, check the docs: https://www.gatsbyjs.org/docs/graphql-reference/#query-variables
query GetBlogPosts(
$limit: Int, $filter: MarkdownRemarkFilterInput, $sort: MarkdownRemarkSortInput
) {
allMarkdownRemark(
limit: $limit,
filter: $filter,
sort: $sort
) {
edges {
node {
frontmatter {
title
date(formatString: "dddd DD MMMM YYYY")
}
}
}
}
}

How to get 'Last Update Date' of a blog post in GATSBY.js

Hello I'm not a dev so may the question will be easy for you guys. I used the advance starter from gatsby site. The blog is working perfect but I need to provide the LAST UPDATED time under my title. Searched for some solutions but none of them worked. Could you Provide some help?
gatsby-node.js
exports.onCreateNode = ({ node, getNode, actions }) => {
const { createNodeField } = actions;
if (node.internal.type !== 'MarkdownRemark') {
return;
}
const fileNode = getNode(node.parent);
createNodeField({
node,
name: 'modifiedTime',
value: fileNode.mtime
});
};
`````````````````````````
PostListing.jsx
class PostListing extends React.Component {
getPostList() {
const postList = [];
this.props.postEdges.forEach(postEdge => {
postList.push({
path: postEdge.node.fields.slug,
tags: postEdge.node.frontmatter.tags,
cover: postEdge.node.frontmatter.cover,
title: postEdge.node.frontmatter.title,
date: postEdge.node.fields.date,
excerpt: postEdge.node.excerpt,
timeToRead: postEdge.node.timeToRead,
modifiedTime:postEdge.node.modifiedTime
});
});
return postList;
}
render() {
const postList = this.getPostList();
return (
<div className='posts'>
{/* Your post list here. */
postList.map(post => (
<Fragment>
<div className='singlePost__date'>
<h4 style={{color:'white'}}> {post.modifiedTime}</h4>
</div>
<div className='singlePost__Title'>
<Link classname='singlePost' to={post.path} key={post.title}>
<h1 className='singlePost__title'>{post.title}</h1>
</Link>
</div>
</Fragment>
))}
</div>
);
}
}
export default PostListing;
I expect something like
TITLE
last updated : 3/2/2019
You can use information stored in Git to get the latest time when a file was modified.
1st approach
Track it manually, but this can be error-prone if you forget to edit the modified time. So I would recommend that as the last option if you can't get others to work.
2nd approach
You can edit your gatsby-node.js to pull information from Git like so:
const { execSync } = require("child_process")
exports.onCreateNode = ({ node, actions }) => {
// ...
if (node.internal.type === "MarkdownRemark") {
const gitAuthorTime = execSync(
`git log -1 --pretty=format:%aI ${node.fileAbsolutePath}`
).toString()
actions.createNodeField({
node,
name: "gitAuthorTime",
value: gitAuthorTime,
})
}
// ...
}
Then, in your template, you can fetch it:
query($slug: String!) {
markdownRemark(fields: { slug: { eq: $slug } }) {
# ...
fields {
gitAuthorTime
}
# ...
}
}
And, finally, use it in JSX like so:
import React from "react"
const BlogPost = (props) => {
const { gitAuthorTime } = props.data.markdownRemark.fields
render(<p>Updated at: ${gitAuthorTime}</p>)
}
export default BlogPost
3rd approach
Similar to the previous one but it uses a plugin gatsby-transformer-info. It does a similar thing as in the 2nd approach, but you need to access the modified time differently this time. Like so:
query($slug: String!) {
markdownRemark(fields: { slug: { eq: $slug } }) {
# ...
parent {
... on File {
fields {
gitLogLatestDate
}
}
}
# ...
}
}
I wrote more about this in my blog post "Add Updated At To Your Gatsby Blog" if you want to check it out.
Edit: The answer below is actually wrong, since File. modifiedTime is the modifiedTime of the markdown file itself & not the modifiedTime for your content. For example, if you deploy your blog on say, Netlify, then the modifiedTime of your files there will be different than in your local environment.
I think the right answer is to track it separately. If you're using a CMS like NetlifyCMS, you can create a field that automatically update the date/time on every edit.
Wherever you're querying for your markdown files, you can use the below field:
query {
allMarkdownRemark {
edges {
node {
frontmatter { /* other stuff */ }
parent {
... on File {
modifiedTime(formatString: "MM/DD/YYYY")
}
}
}
}
}
}
And access it in your via postEdge.node.parent.modifiedTime

Cannot get Relay to make GraphQL Call over the network

I am new to Relay, and I am having problems making it work with a GraphQL server.
I have adapted the Tea sample from the relay homepage to the SWAPI relay service. I cloned swapi-graphql, and added cors to the express server. I tested the link with this code:
var query = `
{
allFilms {
edges {
node {
id
}
}
}
}
`
fetch("http://localhost:50515/graphiql?query=" + query)
.then(response=>response.json())
.then(json=>console.log(json))
I got a response from the server, I saw some network action, it worked! I can communicate with the graphiql service.
Next, I created a query that was structured similar to the TeaStoreQuery. I tested it, and it returned the expected results.
query AllFilmQuery {
allFilms {
...filmListFragment
}
}
fragment filmListFragment on FilmsConnection {
edges {
node {
...filmFragment
}
}
}
fragment filmFragment on Film {
id
title
releaseDate
}
HOW DO YOU MAKE THIS WORK WITH RELAY??
I cannot figure out how to use Relay to query the server. Here is the code that I adapted from the Tea sample.
import { render } from 'react-dom'
import {
RootContainer,
createContainer,
Route,
injectNetworkLayer
} from 'react-relay'
// React component for each star wars film
const Film = ({ id, title, releaseDate }) =>
<li key={id}>
{title} (<em>{releaseDate}</em>)
</li>
// Relay container for each film
const FilmContainer = createContainer(Film, {
fragments: {
film: () => Relay.QL`
fragment on Film {
id,
title,
releaseDate
}
`
}
})
// React component for listing films
const FilmList = ({ films=[] }) =>
<ul>
{films.map(
film => <Film {...film} />
)}
</ul>
// Relay container for Listing all Films
const FilmListContainer = createContainer(FilmList, {
fragments: {
films: () => Relay.QL`
fragment on FilmsConnection {
edges {
node {
${ Film.getFragment('film') }
}
}
}
`
}
})
// The Home Route
class FilmHomeRoute extends Route {
static routeName = 'Home'
static queries = {
allFilms: (Component) => Relay.QL`
query AllFilmQuery {
allFilms {
${Component.getFragment('allFilms')}
}
}
`
}
}
// Is this how you setup a network layer
// I am using CORS, and I testing the graphql service with fetch
// The graphql service works but Relay never seems to try to connect
Relay.injectNetworkLayer(
new Relay.DefaultNetworkLayer('http://localhost:50515/graphiql')
)
render(
<RootContainer
Component={FilmListContainer}
route={new FilmHomeRoute()}
/>,
document.getElementById('react-container')
)
When I run this sample (source | output) I do not see any attempts at making network requests. I do see an error "Cannot render map of null". It seems like it cannot map the allfilms data.
What am I doing wrong?
According to section 5.1 of this document, the "Relay Object Identification Specification":
Relay‐compliant servers may expose root fields that are not plural identifying root fields; the Relay client will just be unable to use those fields as root fields in its queries.
Based on this specification, Relay can not query plural fields at the root of a query unless the field takes a list of arguments that exactly maps to the results. That means the allFilms field cannot be used with Relay. You can read more about the limitations in this other StackOverflow answer: How to get RelayJS to understand that a response from GraphQL is an array of items, not just a single item
If you would like to have a GraphQL schema with root fields that return arrays, you might want to use a different GraphQL client, since Relay is particularly restrictive in what kinds of schemas it works with. graphql.org has a list: http://graphql.org/code/#graphql-clients

Pipe filter based on two or more attributes value in Angular2

If I have an array of objects like
[
{name: 'aaa', type: 'A'},
{name: 'aaa', type: 'B'},
....
]
How can I create a filter using pipe inside ngFor expression, something like
*ngFor='let obj of array | filter:name[nameValue]:type[typeValue]
Here, name[nameValue] name is a property, nameValue is its value.
only show the objects which match the nameValue in name property and typeValue in type property simultaneously.
I Want a more generalized filter which takes any property[propertyValue] and can filter output accordingly.
This is a way you could do it. Just give the filter pipe an array of the fields with the values you want to filter it with.
The Pipe:
#Pipe({
name: 'filter',
pure: false
})
export class FilterPipe implements PipeTransform {
transform(values: Array<any>, args:any[]):any {
return values.filter((value) => {
for (let i = 0; i < args.length; i++) {
if (value[args[i][0]] != args[i][1]) {
return false;
}
}
return true;
});
}
}
The code in the template:
<h3>Only Type A:</h3>
<div *ngFor="let elm of arr | filter:[['type', 'A']]">
<span>Name: {{elm.name}}</span> | <span>Type: {{elm.type}}</span>
</div>
<h3>Name bbb and Type B:</h3>
<div *ngFor="let elm of arr | filter:[['type', 'B'], ['name', 'bbb']]">
<span>Name: {{elm.name}}</span> | <span>Type: {{elm.type}}</span>
</div>
Plunker for working example

Resources