I try to consume an observable through an async pipe, here is my template :
<input #address type="text" id="address" placeholder="{{placeHolder}}" (keyup)="addTerm(address.value)" />
<div *ngFor="let place of possiblesPlaces | async">
{{place.label}}
</div>
Here is the component part :
export class LocationFieldComponent implements OnInit{
private searchTerms = new Subject<string>();
#Input()
placeHolder:string;
possiblesPlaces:Observable<Array<{label:string,value:any}>>;
addTerm(address:string) {
this.searchTerms.next(address);
}
ngOnInit(): void {
this.possiblesPlaces=this.searchTerms.filter(t=>t.length>2).debounceTime(300).distinctUntilChanged()
.flatMap(term =>
Observable.of(this.fetchResults(term))
)
.catch(error => {
// TODO: real error handling
console.log(error);
return Observable.of([]);
});
this.possiblesPlaces.subscribe(val => console.log(val))
}
fetchResults(address:string) :Array<{label:string,value:any}> {
var result=new Array;
var geocoder = new google.maps.Geocoder();
console.log("search with"+address);
geocoder.geocode( { 'address': address}, (results, status) => {
if (status == google.maps.GeocoderStatus.OK) {
for(let i=0;i<results.length;i++){
result.push( {label:results[i].formatted_address,value:null});
}
} else {
console.log("Geocode was not successful for the following reason: " + status);
}
});
return result;
}
}
I can see all the new values of the possiblesPlaces oberservable on the console but the async pipe won't show any results.
Sometimes i can see the results of the previous observable value for half a second when typing a new letter in the address field, i don't understand why?
EDIT: i discovered if i wait like 20 seconds, the results appear correctly. The request to the google api is quick but the async pipe seems to take a while. Any ides?
This should work
fetchResults(address:string) :Array<{label:string,value:any}> {
var subj = new Subject();
var geocoder = new google.maps.Geocoder();
console.log("search with"+address);
geocoder.geocode( { 'address': address}, (results, status) => {
if (status == google.maps.GeocoderStatus.OK) {
for(let i=0;i<results.length;i++){
subject.next( {label:results[i].formatted_address,value:null});
}
} else {
console.log("Geocode was not successful for the following reason: " + status);
}
});
return subj.asObservable();
}
ngOnInit(): void {
this.possiblesPlaces=this.searchTerms.filter(t=>t.length>2).debounceTime(300).distinctUntilChanged()
.flatMap(term =>
this.fetchResults(term).scan([], acc, x) => acc.concat(x));
)
.catch(error => {
// TODO: real error handling
console.log(error);
return Observable.of([]);
});
this.possiblesPlaces.map(val => console.log(val))
}
fetchResults() returns an observable. In your question you return an empty array and then only fill it later when the response from Geocoder arrives. That doesn't look too reliable.
ngOnInit() uses this.possiblePlaces.subscribe() but subscribe returns a Subscription not an Observable and | async only works with Promise and Observable but not with Subscription. If you use map instead of subscribe an Observable is returned.
Ok i figured out!
You can"t use http request like geocoder.geocode which use a callback function, you have to deal with the angular way using http.get with an url
Related
I'm experimenting with gjs and webkit2, how can i get the http headers of a request made with load_uri
i have the following code
const Gtk = imports.gi.Gtk, WebKit=imports.gi.WebKit2, contentManager=new WebKit.UserContentManager,
view = WebKit.WebView.new_with_user_content_manager(contentManager);
Gtk.init(null);
let win = new Gtk.Window(), Response=new WebKit.URIResponse();
contentManager.add_script (new WebKit.UserScript("alert ('test');",0,1,null,null));
view.load_uri('https://www.gnome.org');
win.add(view);
win.set_title("test");
win.set_icon_from_file("/games/aptdaemon-resolve.png");
win.connect('destroy', () => { Gtk.main_quit(); });
win.set_size_request(640, 480);
win.show_all();
view.connect("load-changed",function (instance,state)
{
if (state == 3)
{
log ("URL"+Response.get_uri());
view.run_javascript ("alert (document.body.innerHTML)",null,null);
}
});
Gtk.main();
for example Response.get_uri returns an empty string, how to access response headers, and how to exchange messages between scripts injected with view.run_javascript and gjs. i want the body html be sent to gjs-?
got it
const Gtk = imports.gi.Gtk;
const WebKit=imports.gi.WebKit2;
Gtk.init(null);
const win = new Gtk.Window(), contentManager=new WebKit.UserContentManager, view = WebKit.WebView.new_with_user_content_manager(contentManager);
let response_STR;
contentManager.connect("script-message-received::pipe", function (instance, message)
{
message=message.get_js_value().to_string ();
log (message);
});
contentManager.register_script_message_handler("pipe");
view.load_uri('https://www.gnome.org');
win.add(view);
win.set_title("test");
win.connect('destroy', () => { Gtk.main_quit(); });
win.set_size_request(640, 480);
win.show_all();
view.connect("load-changed",function (instance,status)
{
let headers, response_STR="";
if (status == 3)
{
/* WebKitView.get_main_resource -> returns WebResource
WebResource.get_response -> returns URIResponse
URIResponse.get_http_headers -> returns Soup.MessageHeaders */
headers=view.get_main_resource().get_response().get_http_headers();
response_STR="";
headers.foreach ((name, value) => { response_STR+=name+": "+value+"\n"});
view.run_javascript('window.webkit.messageHandlers.pipe.postMessage(document.body.innerHTML);', null, null);
log (response_STR);
}
});
Gtk.main();
I am trying to return an observable inside an async arrow function passed to a flatMap, but the returned observable is not being called.
protected buildUseCaseObservable(params: LoginUserParams): Observable<Session> {
return this.userRepository.getUserByName(params.getUsername())
.pipe(flatMap(async user => {
if (!user) {
throw new Error(Errors.USER_DOESNT_EXIST);
}
const match = await this.cypher.compare(params.getPassword(), user.password);
if (!match) {
throw new Error(Errors.WRONG_PASSWORD);
}
return Observable.create((subscriber: Subscriber<Session>) => {
subscriber.next(new Session("token test", "refreshToken test"));
subscriber.complete();
});
}));
}
Does anyone knows why does it happen and how can I solve it? Thanks in advance.
Solved, I just turned the promise into an observable and did flatMap it.
protected buildUseCaseObservable(params: LoginUserParams): Observable<Session> {
return this.userRepository.getUserByName(params.getUsername())
.pipe(flatMap(storedUser => {
if (!storedUser) {
throw new Error(Errors.USER_DOESNT_EXIST);
}
return from(this.cypher.compare(params.getPassword(), storedUser.password));
})).pipe(flatMap(match => {
if (!match) {
throw new Error(Errors.WRONG_PASSWORD);
}
return Observable.create((subscriber: Subscriber<Session>) => {
subscriber.next(new Session("token test", "refreshToken test"));
subscriber.complete();
});
}));
}
I am looking for simply fetching an object as a JSON, not as an observable.
So far I could:
fbGetCarById(car_id: string){
var car_json;
var car_obs: FirebaseObjectObservable<any>;
car_obs = this.db.object('/cars/'+car_id, { preserveSnapshot: true });
car_obs.subscribe(snapshot => {
console.log(snapshot.val())
car_json = snapshot.val();
});
return car_json;
}
However, .subscribe is an async function which does not return the snapshot linearly, so everything ends a mess =/.
How can I simply look for an object and have a simple JSON object as a response?
You could return a Promise.
fbGetCarById(car_id: string):Promise<string>{
const promise = new Promise<string>((resolve,reject)=>{
var car_obs: FirebaseObjectObservable<any>;
car_obs = this.db.object('/cars/'+car_id, { preserveSnapshot: true });
car_obs.subscribe(snapshot => {
resolve(snapshot.value())
});
}
return promise;
}
I've been testing on http calls with meteor, I used nitrous (because I had no access to my dev laptop during the weekend) and it worked fine.
But when I tried to run from my local pc it returns:
Exception in delivering result of invoking 'getMatch': TypeError:
Cannot read property 'duration' of undefined.
Any ideas of what could be the cause?
Method definition:
Dota = {};
Dota.getMatch = function() {
if (!Meteor.settings.steamToken)
throw new Meteor.Error(500, 'Enter a valid Steam Token in Meteor.settings');
var matchResponse = Meteor.http.get(
"https://api.steampowered.com/IDOTA2Match_570/GetMatchDetails/V001/?",
{
params:{
"match_id": "1305454585",
"key": Meteor.settings.steamToken
}
}
);
if (matchResponse.statusCode === 200) {
return matchResponse.data.result
}
else {
throw new Meteor.Error(500, "getMatch failed with error: "+matchResponse.statusCode);
}
}
Meteor.methods({
'getMatch': function(){
return Dota.getMatch();
}
})
Calling the method:
Meteor.call('getMatch', function(error, result){
var duration = numeral(result.duration).format('00:00:00');
Session.set('duration', duration);
var winner = Meteor.myFunctions.getWinner(result.radiant_win);
Session.set('winner', winner);
});
Template.layout.helpers({
winner: function () {
return Session.get('winner');
},
duration: function () {
return Session.get('duration');
}
});
Found a solution, I changed the location of
Meteor.methods({
'getMatch': function(){
return Dota.getMatch();
}
})
to server/server.js (I had it in packages/dota/dota.js) and now it works! Thanks #user3374348 for helping!
On the client I need a helper method that returns true or false depending on whether the user is eligible for a payment request.
However, I can't really use a Meteor.method for this, because they don't return a value on the client.
Instead, I have done this and would like to know if this poses any security holes or if there is a preferable approach
Server:
...
// Constants
//
_.extend(Payments, {
MINIMUM_REQUIRED_FOR_REQUEST: 100
});
// Public
//
Meteor.methods({
});
canRequestPayment = function(userId) {
var user = Meteor.users.findOne(userId, { fields: { earnings: 1 } });
if (_.isUndefined(user)) { throw new Meteor.Error('user-not-found', 'User not found'); }
return hasEnoughCreditForRequest(user) && hasNoPendingPayments(user);
};
// Private
//
var hasNoPendingPayments = function(user) {
return Payments.find({ userId: user._id, state: 'pending' }).count() === 0;
};
var hasEnoughCreditForRequest = function(user) {
var period = user.earnings.period;
return period >= Payments.MINIMUM_REQUIRED_FOR_REQUEST;
};
As can be seen, I have created two helper methods with var, to mimic private behavior, and then I have the canRequestPayment method which is accessable outside of the file, and that I call on the client instead of a Meteor.method
Client:
Template.payments.helpers({
eligibleForPaymentRequest: function() {
return canRequestPayment(Meteor.userId());
},