How do I determine, if the Observable is "empty"?
Better, that it has never received anything.
My code looks like this:
spots: Observable<Spot[]>;
And I've tried several things I found on Google like:
spots.isEmpty();
spots.length;
spots.length();
spots().length;
spots.first();
But none of them works like I want..
I need this functionality, to fill a list in Ionic2 with No items found until the first item is loaded.
This is how I solve it in my code:
if ( arrayOfItems && arrayOfItems.length > 0 ) {
// display the list
return arrayOfItems.map((item) => { return item; })
} else {
// show a message that nothing was found
return "Nothing to see here...";
}
This will check that the variable has some sort of positive value (ie. it is not null, false or undefined) and that the array has at least one value. If that is not the case then display a message that nothing was found.
I solved it like this:
I have an variable let isEmpty=true; and set it to false when I receive the first Object in the Observable:
spots.subscribe(() => {
this.empty = false;
...
});
Related
I want to check if updateEmail() goes through or not. If it changes an email or not and I don't know what is the return value of that function so I can check it.
I tried something like this
if(user.updateEmail(email) != null) {
msg = "Success";
}
And I am never getting a null value if it is good or not.
The return values of the User.updateEmail method is Future<void>. So while the future has no resulting value, you can check whether it succeeded or failed by checking the result of the future itself. From there, it looks like it should be something like this:
user.updateEmail(email).then(() {
msg = "Success";
})
.catchError(handleError);
I'm looking into some old code and I am seeing something I cannot figure out. The code is a controller action that returns a dynamic object:
return new
{
Result = true,
Count = data.Count(),
Students = data.Select(s => string.Format("{0}, {1}", s.LastName, s.FirstName))
};
However, the resulting JSON in the browser is not coming back as I would expect:
{
"$id":"1",
"Result":true,
"Count":1,
"Students":
{
"$id":"2",
"$values":["USER, ACTIVE"]
}
}
What I would expect, and what I normally get any other time I do this sort of thing, is more like this:
{
"Result":true,
"Count":1,
"Students":
{
["USER, ACTIVE"]
}
}
I have no idea where the $id and $values properties are coming from. I haven't seen this happen before with .Net, so I'm not sure what is causing this. It's not the dynamic object return that's causing the problem because I switched it to a named type just to test it out and it still does the same thing.
You need add this line of code to Global.asax to avoid append $id
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.PreserveReferencesHandling
= Newtonsoft.Json.PreserveReferencesHandling.None;
You need to have a .ToList() at the end of the students.
{
Result = true,
Count = data.Count(),
Students = data.Select(s => string.Format("{0}, {1}", s.LastName, s.FirstName)).ToList()
};
I have the following code:
import { Query } from 'react-apollo';
type Post = {
id: string
};
interface Data {
posts: Array<Post>;
}
class PostsQuery extends Query<Data> {}
When using the above as follows:
<PostsQuery query={POSTS_QUERY}>
{({ loading, data }) => {
...
{data.posts.map(...)}
...
}
</PostsQuery>
I get the following error from flow:
Error:(151, 27) Cannot get 'data.posts' because property 'posts' is missing in object type [1].
Any idea why?
I did use flow-typed to add apollo-client_v2.x.x.js to my project by the way
Solution to the problem
Continued from the answer explaining how to make a verifiable example and research the problem.
So it looks like this part of react-apollo isn't typed in such a way to make accessing the data contents straightforward. Okay, that's fine, we can take their recommendation on destructuring and check for empty data. At the same time, we can also add an id property to the Post type so flow stops complaining about that:
(Try - Scroll to bottom for relevant code)
type Post = {
id: string,
title: string;
};
...snip...
// Look ma, no errors
class Search extends React.Component<{}> {
render = () => (
<PostsQuery query={QUERY}>
{({ loading, error, data }) => {
if (error) {
return <p>Error</p>
}
if (loading) return <p>Loading</p>
const nonNullData = (data || {})
const dataWithAllPosts = {allPosts: [], ...nonNullData}
const {allPosts} = dataWithAllPosts
if (allPosts.length == 0) {
return <p>Empty response or something</p>
}
return (
<div>
{allPosts.map(post => {
return <div key={post.id}>{post.title}</div>;
})}
</div>
);
}}
</PostsQuery>
);
}
I'm not familiar with the react-apollo library, so I'm not sure how you want to handle that case where there are no posts. I just added a message as seen above. It's entirely possible that the case never occurs (again, you would know better than I do). If that's the case, you might want to skip some of the above steps and just assert the desired type with a cast through any.
How to make a reproducible example and research the problem
So the first thing we need to do while analyzing these types is to go lookup the typedefs in the flow-typed repo. I went ahead a copy-pasted the react-apollo typedefs into flow.org/try, modified them slightly (added an any somewhere, set gql to any), and was able to replicate your errors:
(Try - Scroll to the bottom for your code)
Referencing the relevant lines of the QueryRenderProps type, we can see why flow is throwing the error:
{
data: TData | {||} | void,
...
}
It looks like data can either be TData (probably what you want), an empty object, or undefined. Cross checking this with the typescript typings for react-apollo, we can see why the type is the way it is:
{
...
// we create an empty object to make checking for data
// easier for consumers (i.e. instead of data && data.user
// you can just check data.user) this also makes destructring
// easier (i.e. { data: { user } })
// however, this isn't realy possible with TypeScript that
// I'm aware of. So intead we enforce checking for data
// like so result.data!.user. This tells TS to use TData
// XXX is there a better way to do this?
data: TData | undefined;
...
}
Unfortunately, due to the extreme length of these links and stackoverflow's limit on answer lengths, I have to continue my answer in another answer. I guess this answer can serve as an explanation of how to start debugging the problem.
This code passes the flow check:
/* #flow */
function test (list: ?Array<string>): Promise<number> {
if(list !== null && list !== undefined) {
return Promise.resolve(list.length)
} else {
return Promise.resolve(0)
}
}
console.log(test(null))
Whereas the following gets a null check error
/* #flow */
function test (list: ?Array<string>): Promise<number> {
if(list !== null && list !== undefined) {
return Promise.resolve().then(() => list.length)
} else {
return Promise.resolve(0)
}
}
console.log(test(null))
error:
property `length`. Property cannot be accessed on possibly null value
Clearly list cannot be null so there must be something about the code structure that makes flow unable to recognise this.
I would like to understand what limitation I am hitting and how I can work around it. Thanks!
Basically, Flow doesn't know that your type refinement (null check) will hold at the time when () => list.length is executed. Inside that callback Flow only looks at the type of list – which says it can be null.
The difference between first and second snippet is that in the second snippet list is crossing a function boundary – you're using it in a different function than where you refined its type.
One solution is to extract list.length into a variable, and use that variable in the callback.
var length = list.length;
return Promise.resolve().then(() => length)
This might also work:
var list2: Array<string> = list;
return Promise.resolve().then(() => list2.length)
Note that this problem exists even for immediately invoked callbacks, e.g. when using map or forEach. There is an issue on flow's github about this, but I couldn't find it after a quick search.
I'm trying to do this relatively complex operation in BaconJs.
Basically, the idea is keep trying each check until you have a 'pass' status or they all fail. The catch is that 'pending' statuses have a list of Observables (built from jquery ajax requests) that will resolve the check. For performance reasons, you need to try each Observable in order until either they all pass or one fails.
Here's the full pseudo algorithm:
Go thru each check. A check contains an id and status = fail/pass/pending. If pending, it contains a list of observables.
If status = pass, then return the id (you're done!)
if status = fail, then try the next check
if status = pending
try each observable in order
if observable result is 'false', then try the next check
if reach end of observable list and result is 'true', then return the id (you're done!)
Here's the Bacon code. It doesn't work when the Observables are Ajax requests.
Basically, what happens is that it skips over pending checks....it doesn't wait for the ajax calls to return. If I put a log() right before the filter(), it doesn't log pending requests:
Bacon.fromArray(checks)
.flatMap(function(check) {
return check.status === 'pass' ? check.id :
check.status === 'fail' ? null :
Bacon.fromArray(check.observables)
.flatMap(function(obs) { return obs; })
.takeWhile(function(obsResult) { return obsResult; })
.last()
.map(function(obsResult) { return obsResult ? check.id : null; });
})
.filter(function(contextId) { return contextId !== null; })
.first();
UPDATE: the code works when the checks look like this: [fail, fail, pending]. But it doesn't work when the checks look like this: [fail, pending, pass]
I am more familiar with RxJS than Bacon, but I would say the reason you aren't seeing the desired behavior is because flatMap waits for no man.
It passes [fail, pending, pass] in quick succession, fail returns null and is filtered out. pending kicks off an observable, and then receives pass which immediately returns check.id (Bacon may be different, but in RxJS flatMap won't accept a single value return). The check.id goes through filter and hits first at which point it completes and it just cancels the subscription to the ajax request.
A quick fix would probably be to use concatMap rather than flatMap.
In RxJS though I would refactor this to be (Disclaimer untested):
Rx.Observable.fromArray(checks)
//Process each check in order
.concatMap(function(check) {
var sources = {
//If we pass then we are done
'pass' : Rx.Observable.just({id : check.id, done : true}),
//If we fail keep trying
'fail' : Rx.Observable.just({done : false}),
'pending' : Rx.Observable.defer(function(){ return check.observables;})
.concatAll()
.every()
.map(function(x) {
return x ? {done : true, id : check.id} :
{done : false};
})
};
return Rx.Observable.case(function() { return check.status; }, sources);
})
//Take the first value that is done
.first(function(x) { return x.done; })
.pluck('id');
What the above does is:
Concatenate all of the checks
Use the case operator to propagate instead of nested ternaries.
Fail or pass fast
If pending create a flattened observable out of check.observables, if they are all true then we are done, otherwise continue to the next one
Use the predicate value of first to get the first value returned that is done
[Optionally] strip out the value that we care about.
I agree with #paulpdaniels Rx-based answer. The problem seems to be that when using flatMap, Bacon.js won't wait for your first "check-stream" to complete before launching a new one. Just replace flatMap with flatMapConcat.
Thanks to #raimohanska and #paulpdaniels. The answer is to use #flatMapConcat. This turns what is basically a list of async calls done in parallel into a sequence of calls done in order (and note that the last "check" is programmed to always pass so that this always outputs something):
Bacon.fromArray(checks)
.flatMapConcat(function(check) {
var result = check();
switch(result.status) {
case 'pass' :
case 'fail' :
return result;
case 'pending' :
return Bacon.fromArray(result.observables)
.flatMapConcat(function(obs) { return obs; })
.takeWhile(function(obsResult) { return obsResult.result; })
.last()
.map(function (obsResult) { return obsResult ? {id: result.id, status: 'pass'} : {status: 'fail'}; });
}
})
.filter(function(result) { return result.status === 'pass'; })
.first()
.map('.id');