Angular 6 pass value from one service to another? - javascript

I'm trying to use latitude and longitude from the geolocation service in my list service. Unfortunately this keeps returning as undefined. Not really sure what the issue could be.
list.service.ts
import { Injectable } from '#angular/core';
import { Http, Headers } from '#angular/http';
import { Observable, Subject, asapScheduler, pipe, of, from, interval, merge, fromEvent, SubscriptionLike, PartialObserver } from 'rxjs';
import { List } from '../models/list.model';
import { map } from 'rxjs/operators';
import { GeolocationService } from '../services/geolocation.service';
#Injectable()
export class ListService {
constructor(private http: Http, private geoServ: GeolocationService) { }
getLongitude() {
this.geoServ.getLongitude().subscribe((longitude) => {
console.log(longitude)
});
}
private serverApi = 'http://localhost:3000';
public getAllLists(): Observable<List[]> {
const URI = `${this.serverApi}/yelp/${longitude}/${latitude}`;
return this.http.get(URI)
.pipe(map(res => res.json()))
.pipe(map(res => <List[]>res.businesses));
}
}
geolocation.service.ts
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class GeolocationService {
constructor() { }
getGeoLocation() {
console.log('Geolocation working!');
const options = {
enableHighAccuracy: true,
timeout: 5000,
maximumAge: 0
};
const success = (pos) => {
const crd = pos.coords;
console.log(`Latitude : ${crd.latitude}`);
console.log(`Longitude : ${crd.longitude}`);
};
const error = (err) => {
console.warn(`ERROR(${err.code}): ${err.message}`);
};
navigator.geolocation.getCurrentPosition(success, error, options);
}
getLongitude() {
console.log('Geolocation working!');
const options = {
enableHighAccuracy: true,
timeout: 5000,
maximumAge: 0
};
const success = (pos) => {
const crd = pos.coords;
console.log(`Longitude : ${crd.longitude}`);
const longitude = crd.longitude;
return longitude;
};
const error = (err) => {
console.warn(`ERROR(${err.code}): ${err.message}`);
};
navigator.geolocation.getCurrentPosition(success, error, options);
}
}
thank you for taking the time to look at this. And I appreciate the further help on my additional question below. text text text text won't let me post until I add more details text text text text.

You are not returning anything from your methods. You should use a return statement. You also need to use either a callback function a promise or a observable. I'll give you a observable example:
getLongitude() {
this.geoServ.getLongitude().subscribe((longitude) => {
console.log(longitude)
});
}
And change your getLongitude:
getLongitude() {
return new Observable((observer) => {
console.log('Geolocation working!');
const options = {
enableHighAccuracy: true,
timeout: 5000,
maximumAge: 0
};
const success = (pos) => {
const crd = pos.coords;
console.log(`Longitude : ${crd.longitude}`);
const longitude = crd.longitude;
observer.next(longitude);
observer.complete();
};
const error = (err) => {
console.warn(`ERROR(${err.code}): ${err.message}`);
observer.error(err);
observer.complete();
};
navigator.geolocation.getCurrentPosition(success, error, options);
});
}
If you want to get the longitude and latitude in your getAllList method you can use the mergeMap operator:
public getAllLists(): Observable<List[]> {
return this.geoServ.getGeoLocation().pipe(
mergeMap(({ latitude, longitude}) =>
this.http.get(`${this.serverApi}/yelp/${longitude}/${latitude}`)
),
map(res => res.businesses)
);
}
I'm using the HttpClient here. This is the 'new' http module from angular. It's advised to use this one instead of the old one. It also automatically does the json() call for you.

Related

ML5.JS objectDetector.detect is not a function

I'm quite new to ml5 and p5 libraries and during implementation to my Angular project I'm receiving this error:
TypeError: this.objectDetector.detect is not a function
After logging objectDetector object console shows this:
ZoneAwarePromise {__zone_symbol__state: null, __zone_symbol__value: Array(0)}
p5 drawing working good but combined with ml5 is not working.
Here's my component code:
import { Component, OnInit } from '#angular/core';
import * as p5 from 'p5';
declare let ml5: any;
#Component({
selector: 'app-new-found',
templateUrl: './new-found.component.html',
styleUrls: ['./new-found.component.scss']
})
export class NewFoundComponent implements OnInit {
objectDetector;
img;
constructor(
) { }
ngOnInit(): void {
const sketch = (s) => {
s.preload = () => {
this.objectDetector = ml5.objectDetector('cocossd');
console.log('detector object is loaded', this.objectDetector);
this.img = s.loadImage('https://i.imgur.com/Mzh4cHR.jpg');
}
s.setup = () => {
s.createCanvas(700, 700).parent('test-canvas');
this.objectDetector.detect(this.img, this.gotResult);
s.image(this.img, 0, 0);
};
s.draw = () => {
};
}
let canvas = new p5(sketch);
}
gotResult(error, results) {
if (error) {
console.error(error);
} else {
console.log(results);
//drawResults(results);
}
}
}
ml5 library is imported in <HEAD> of my index.html file.
Does someone know how to get rid of this error?
Thank you.
Finally I figured it out. The ml5.objectDetector('cocossd'); function must be marked as await because it takes quite long time to execute. Below is working code:
import { Component, OnInit } from '#angular/core';
import * as p5 from 'p5';
declare let ml5: any;
#Component({
selector: 'app-new-found',
templateUrl: './new-found.component.html',
styleUrls: ['./new-found.component.scss']
})
export class NewFoundComponent implements OnInit {
objectDetector;
img;
constructor(
) { }
async ngOnInit(): Promise<void> {
this.objectDetector = await ml5.objectDetector('cocossd');
const sketch = (s) => {
s.preload = () => {
console.log(ml5);
console.log('detector object is loaded', this.objectDetector);
this.img = s.loadImage('https://i.imgur.com/Mzh4cHR.jpg');
}
s.setup = () => {
s.createCanvas(700, 700).parent('test-canvas');
this.objectDetector.detect(this.img, this.gotResult);
s.image(this.img, 0, 0);
};
s.draw = () => {
};
}
let canvas = new p5(sketch);
}
gotResult(error, results) {
if (error) {
console.error(error);
} else {
console.log(results);
//drawResults(results);
}
}
}
It is possible that the library has not fully loaded yet. I would create a polling technique here where you keep checking if the value has been initialized and only then proceed.
This is the code I use for polling that xola script has loaded:
let subscription = interval(1000)
.pipe(timeout(3 * 60 * 1000))
.subscribe({
next: () => {
if (this.window.xola) {
const xola = this.window.xola();
subscription.unsubscribe();
this.xolaSubject.next(xola);
}
},
error: (error) => {
if (error instanceof TimeoutError) {
console.error('Xola took too long to load, check your connection.');
}
subscription.unsubscribe();
},
});

Unable to mock a class method in Javascript/Typescript

I am not getting any clue how to mock a method. I have to write a unit test for this function:
index.ts
export async function getTenantExemptionNotes(platform: string) {
return Promise.all([(await getCosmosDbInstance()).getNotes(platform)])
.then(([notes]) => {
return notes;
})
.catch((error) => {
return Promise.reject(error);
});
}
api/CosmosDBAccess.ts
import { Container, CosmosClient, SqlQuerySpec } from "#azure/cosmos";
import { cosmosdbConfig } from "config/Config";
import { Workload } from "config/PlatformConfig";
import { fetchSecret } from "./FetchSecrets";
export class CosmoDbAccess {
private static instance: CosmoDbAccess;
private container: Container;
private constructor(client: CosmosClient) {
this.container = client
.database(cosmosdbConfig.database)
.container(cosmosdbConfig.container);
}
static async getInstance() {
if (!CosmoDbAccess.instance) {
try {
const connectionString = await fetchSecret(
"CosmosDbConnectionString"
);
const client: CosmosClient = new CosmosClient(connectionString);
// Deleting to avoid error: Refused to set unsafe header "user-agent"
delete client["clientContext"].globalEndpointManager.options
.defaultHeaders["User-Agent"];
CosmoDbAccess.instance = new CosmoDbAccess(client);
return CosmoDbAccess.instance;
} catch (error) {
// todo - send to app insights
}
}
return CosmoDbAccess.instance;
}
public async getAllNotesForLastSixMonths() {
const querySpec: SqlQuerySpec = {
// Getting data from past 6 months
query: `SELECT * FROM c
WHERE (udf.convertToDate(c["Date"]) > DateTimeAdd("MM", -6, GetCurrentDateTime()))
AND c.IsArchived != true
ORDER BY c.Date DESC`,
parameters: [],
};
const query = this.container.items.query(querySpec);
const response = await query.fetchAll();
return response.resources;
}
}
export const getCosmosDbInstance = async () => {
const cosmosdb = await CosmoDbAccess.getInstance();
return cosmosdb;
};
index.test.ts
describe("getExemptionNotes()", () => {
beforeEach(() => {
jest.resetAllMocks();
});
it("makes a network call to getKustoResponse which posts to axios and returns what axios returns", async () => {
const mockNotes = [
{
},
];
const cosmosDBInstance = jest
.spyOn(CosmoDbAccess, "getInstance")
.mockReturnValue(Promise.resolve(CosmoDbAccess.instance));
const kustoResponseSpy = jest
.spyOn(CosmoDbAccess.prototype, "getAllNotesForLastSixMonths")
.mockReturnValue(Promise.resolve([mockNotes]));
const actual = await getExemptionNotes();
expect(kustoResponseSpy).toHaveBeenCalledTimes(1);
expect(actual).toEqual(mockNotes);
});
});
I am not able to get instance of CosmosDB or spyOn just the getAllNotesForLastSixMonths method. Please help me code it or give hints. The complexity is because the class is singleton or the methods are static and private

Websocket event is being catched only by one component

I have a websocket service in Angular7
import { environment } from './../../../environments/environment.prod';
import { Injectable } from '#angular/core';
import * as Rx from 'rxjs';
import {map} from 'rxjs/operators';
#Injectable()
export class WsService{
public wsMessages: Rx.Subject<any>;
public connect(url): Rx.Subject<MessageEvent> {
if(!this.subject){
this.subject = this.create(url);
console.log("Websocket (Dashboard) successfully connected to : ", url);
}
return this.subject;
}
private create(url): Rx.Subject<MessageEvent> {
let ws = new WebSocket(
url,
[`Bearer`, `${this.token.substring(7)}`]
);
let observable = Rx.Observable.create(
(obs: Rx.Observer<MessageEvent>) => {
ws.onmessage = obs.next.bind(obs);
ws.onerror = obs.error.bind(obs);
ws.onclose = obs.complete.bind(obs);
return ws.close.bind(ws);
}
)
let observer = {
next: (data: Object) => {
if(ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(data));
}
}
}
return Rx.Subject.create(observer, observable);
}
private token: string;
constructor(){
this.token = JSON.parse(localStorage.getItem('user')).token
this.wsMessages = <Rx.Subject<any>>
this.connect(`${environment.websocketUrl}/dashboard/ws`)
.pipe(
map((response: MessageEvent): any =>{
let data = JSON.parse(response.data);
return data;
})
)
}
private subject: Rx.Subject<MessageEvent>;
}
and i have mulltiple component that subscribe to wsMessages
this.ws.wsMessages.subscribe(msg => {
this.catchWebSocketEvents(msg)
console.log("LeftBarSocket : ", msg);
})
the events are only printed on one component only and i need many components to listen to those ws events.
Ok so after doing a bit of reading on the subject, I found out there is a share() function that allows multiple subscribers sharing a source.
the solution is here :
const observable = Rx.Observable.create(
(obs: Rx.Observer<MessageEvent>) => {
this.ws.onmessage = obs.next.bind(obs);
this.ws.onerror = obs.error.bind(obs);
this.ws.onclose = obs.complete.bind(obs);
return this.ws.close.bind(this.ws);
}
).pipe(
share()
)
just need to add the .pipe(share()) to the observable, being created.

Sending objects through websocket using ws: Can't deserialize

So I tried sending an object through my websocket by translating it to json and then back when it returns. Unfortunately it gives me the below error. The console.log shows me that it is valid JSON, but somehow it gives me an error at JSON.parse in the service document. Can anyone see what I did wrong?
The error
core.js?223c:1440 ERROR SyntaxError: Unexpected token c in JSON at position 0
at JSON.parse (<anonymous>)
at WebSocket._this.ws.onmessage [as __zone_symbol__ON_PROPERTYmessage] (movie-chat.service.ts?6086:22)
at WebSocket.wrapFn (zone.js?fad3:1166)
console.log result of event.data (valid json)
{"message":"good boy","extra":"extra"}
movie-chat.service.ts
import {Injectable} from "#angular/core";
import 'rxjs/rx';
import {HttpClient} from "#angular/common/http";
import {Observable} from "rxjs/Observable";
// We need #injectable if we want to use http
#Injectable()
export class MovieChatService {
ws;
constructor(private http: HttpClient) {
}
// receive events
createObservableSocket(url:string){
this.ws = new WebSocket(url);
return new Observable(observer => {
this.ws.onmessage = (e) => {
console.log(e.data);
var object = JSON.parse(e.data);
observer.next(object);
}
this.ws.onerror = (event) => observer.error(event);
this.ws.onclose = (event) => observer.complete();
}
);
}
// send events
sendMessage(message) {
message = JSON.stringify(message);
console.log(message);
this.ws.send(message);
}
}
Back-end handling of messages
var wss = new Websocket.Server({port:3185});
var CLIENTS = [];
wss.on('connection',
function(websocket) {
CLIENTS.push(websocket);
websocket.send('connected to socket');
websocket.on('message', function (message) {
console.log('Server received:', message);
sendAll(message)
});
websocket.on('close', function(client) {
CLIENTS.splice(CLIENTS.indexOf(client), 1);
});
websocket.on('error', function(client) {
CLIENTS.splice(CLIENTS.indexOf(client), 1);
});
});
movie-chat.component.ts
import {Component, OnInit} from "#angular/core";
import { MovieChatService} from "./movie-chat.service";
#Component({
selector: 'app-movie-chat',
templateUrl: './movie-chat.component.html',
styleUrls: ['./movie-chat.component.css']
})
export class MovieChatComponent implements OnInit{
fullName;
messageFromServer;
title = 'Websocket Demo';
url;
ws;
messages = [];
constructor(private movieChatService: MovieChatService){
}
ngOnInit(){
this.fullName = localStorage.getItem('fullName');
this.url = 'ws://localhost:3185';
this.movieChatService.createObservableSocket(this.url)
.subscribe(data => {
this.messageFromServer = data;
},
err => console.log(err),
() => console.log('The observable stream, is complete'));
}
sendMessageToServer(){
console.log('Client sending message to websocket server');
this.movieChatService.sendMessage({
message: 'good boy',
extra: 'extra'
});
}
}
It seems that you are trying to parse a Json Object,
{"message":"good boy","extra":"extra"}
JSON.parse expect string parameter and you are passing an Json Object for that the exception is rised.
We try to surround the Parse with try and catch
import {Injectable} from "#angular/core";
import 'rxjs/rx';
import {HttpClient} from "#angular/common/http";
import {Observable} from "rxjs/Observable";
// We need #injectable if we want to use http
#Injectable()
export class MovieChatService {
ws;
constructor(private http: HttpClient) {
}
// receive events
createObservableSocket(url:string){
this.ws = new WebSocket(url);
return new Observable(observer => {
this.ws.onmessage = (e) => {
console.log(e.data);
try {
var object = JSON.parse(e.data);
observer.next(object);
} catch (e) {
console.log("Cannot parse data : " + e);
}
}
this.ws.onerror = (event) => observer.error(event);
this.ws.onclose = (event) => observer.complete();
}
);
}
// send events
sendMessage(message) {
message = JSON.stringify(message);
console.log(message);
this.ws.send(message);
}
}
So, I didn't actually solve it the way I wanted it, but I found out that it is possible to send arrays through a websocket. I did this and on the receiving end I transferred it into an object in the service file. It does the job for now. If anyone knows a better solution, let me know :)
createObservableSocket(url:string){
this.ws = new WebSocket(url);
return new Observable(observer => {
this.ws.onmessage = (e) => {
var obj = e.data.split(',');
console.log(obj);
obj = {
name: obj[0],
msg: obj[1]
};
console.log(obj);
observer.next(obj);
}
this.ws.onerror = (event) => observer.error(event);
this.ws.onclose = (event) => observer.complete();
}
);
}

RxJS run 2nd stream with the output from first stream

I've got a service, which determines the location, it's written as Observable
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs';
const GEOLOCATION_ERRORS = {
'errors.location.unsupportedBrowser': 'Browser does not support location services',
'errors.location.permissionDenied': 'You have rejected access to your location',
'errors.location.positionUnavailable': 'Unable to determine your location',
'errors.location.timeout': 'Service timeout has been reached'
};
#Injectable()
export class GeolocationService {
public getLocation(opts): Observable<any> {
return Observable.create(observer => {
if (window.navigator && window.navigator.geolocation) {
window.navigator.geolocation.getCurrentPosition(
(position) => {
observer.next(position);
observer.complete();
},
(error) => {
switch (error.code) {
case 1:
observer.error(GEOLOCATION_ERRORS['errors.location.permissionDenied']);
break;
case 2:
observer.error(GEOLOCATION_ERRORS['errors.location.positionUnavailable']);
break;
case 3:
observer.error(GEOLOCATION_ERRORS['errors.location.timeout']);
break;
}
}, opts);
} else {
observer.error(GEOLOCATION_ERRORS['errors.location.unsupportedBrowser']);
}
});
}
}
export var GeolocationServiceInjectables: Array<any> = [
{ provide: GeolocationService, useClass: GeolocationService }
];
Then in my HttpService I want to construct the query URL with the output from location service
import { Observable } from 'rxjs/Observable';
import { Injectable, Inject } from '#angular/core';
import { Http, Response } from '#angular/http';
import { GeolocationService } from './location.service';
import { WeatherItem } from '../weather-item/weather-item.model';
export const OpenWeatherMap_API_KEY: string = 'SOME_API_KEY';
export const OpenWeatherMap_API_URL: string = 'http://api.openweathermap.org/data/2.5/forecast';
#Injectable()
export class HttpService {
constructor(private http: Http,
private geolocation: GeolocationService,
#Inject(OpenWeatherMap_API_KEY) private apiKey: string,
#Inject(OpenWeatherMap_API_URL) private apiUrl: string) {
}
prepaireQuery(): void {
this.geolocation.getLocation({ enableHighAccuracy: false, maximumAge: 3 }).subscribe(
(position) => {
let params: string = [
`lat=${position.latitude}`,
`lon=${position.longitude}`,
`APPID=${this.apiKey}`,
].join('&');
// return `${this.apiUrl}?${params}`;
}
);
}
getWeather(): Observable<WeatherItem[]> {
return this.http.get(/*there should be the url*/)
.map((response: Response) => {
return (<any>response.json()).items.map(item => {
const city = {
city: item.city.name,
country: item.city.country,
}
return item.list.map(entity => {
return new WeatherItem({
temp: entity.main.temp,
temMin: entity.main.temp_min,
temMax: entity.main.temp_max,
weatherCond: entity.weather.main,
description: entity.weather.description,
windSpeed: entity.wind.speed,
icon: entity.weather.icon,
city,
})
})
})
})
}
}
export var HttpServiceInjectables: Array<any> = [
{ provide: HttpService, useClass: HttpService },
{ provide: OpenWeatherMap_API_KEY, useValue: OpenWeatherMap_API_KEY },
{ provide: OpenWeatherMap_API_URL, useValue: OpenWeatherMap_API_KEY }
];
The question is how to get the URL before doing request. I've seen solutions with unsubscribe(), but I think thay are not so good. I've thought about merge() but I'm not sure that it's what I really want.
You are probably looking for the mergeMap operator of RxJs.
What mergeMap does is automatically subscribes to the source observable, then lets you work with its result in your inner observable, and then finally flattens your output.
In this example, you call the firstUrl and use the result you get from that request in your second call to secondUrl:
this.http.get(`{firstUrl}`)
.mergeMap(res => this.http.get(`{secondUrl}/{res.json()}`))
.subscribe(...)
I have not made it specific for your code, as I'm not sure of exactly what you want to do. But I hope this will help you on the way!
This can be done using map/flatMap combination:
getWeather(): Observable<WeatherItem[]> {
return this.geolocation.getLocation({ enableHighAccuracy: false, maximumAge: 3 })
.map((position) => {
let params: string = [
`lat=${position.latitude}`,
`lon=${position.longitude}`,
`APPID=${this.apiKey}`,
].join('&');
return `${this.apiUrl}?${params}`;
})
.flatMap(url => this.http.get(url)
.map((response: Response) => {
return (<any>response.json()).items.map(item => {
const city = {
city: item.city.name,
country: item.city.country,
}
return item.list.map(entity => {
return new WeatherItem({
temp: entity.main.temp,
temMin: entity.main.temp_min,
temMax: entity.main.temp_max,
weatherCond: entity.weather.main,
description: entity.weather.description,
windSpeed: entity.wind.speed,
icon: entity.weather.icon,
city,
})
})
})
})
}

Categories

Resources