Observable Shorthand - javascript

I'm wondering if there is a better way (read: shorter) to write the following function in Typescript/Angular2. I find myself in a situation a lot of times where I'm caching observable server data and returning the cached value if it exists. My way (below) seems to be a bit overkill.
getUser(){
return Observable.create(s => {
if(!this.user)
this.connection.identity((err, res) => {
this.user = res;
s.next(res);
s.complete();
})
else{
s.next(this.user);
s.complete();
}
});
}

This is a little shorter and it handles errors:
import "rxjs/add/observable/of";
import "rxjs/add/observable/bindNodeCallback";
import { Observable } from "rxjs/Observable";
...
getUser() {
return this.user ?
Observable.of(this.user) :
Observable.bindNodeCallback(
this.connection.identity.bind(this.connection),
res => this.user = res
)();
}

Related

How to return a derived function that use fetched data from db based on value of another writable value in svelte/svelte kit?

I am trying to get my language table's data from db in store.js and modify that data based on the language user selected. So the selected language is a writable variable and I need to get a derived function that returns the modified data. Here is my code in store.js
import { writable, derived } from 'svelte/store';
export async function getData(endpoint){
try {
const res = await axios.get(`${baseAPI}${endpoint}`);
return res.data;
} catch (e) {
return e;
}
}
function getTypography(lang){
let typography;
try {
const texts = getData('/language');
texts.then((data)=>{
typography = data.payload;
console.log(typography);
return filterTypography(typography, lang)
}).catch((e)=>{
console.log('error', e);
})
} catch (error) {
console.log('error', error);
}
}
function filterTypography(typography, lang){
let textarray=[];
typography.forEach((element, index) => {
textarray[index]={
'keyword':element.keyword,
'value':element[lang]
}
});
return textarray
}
export const key = writable('en')
export const updateKey = (lang) => {
key.set(lang)
}
export const data = derived(key, $key => getTypography($key));
Here is the code in my +page.svelte
<script>
import { key, data, updateKey } from '$lib/store.js'
function changeLang(lang){
console.log("clicked with", lang);
updateKey(lang)
}
$: console.log($key)
</script>
<h1>{$data}</h1>
<button on:click={() => changeLang('dk')} >Change to DK</button>
<button on:click={() => changeLang('en')}>Change to EN</button>
I am getting 'undefined' data. I am just trying to print out the data variable for testing. Please note that, the console log that I am printing after getting the data from the API endpoint is showing the data successfully. Also, another thing, if I just use a function in store instead of the getTypography function, that returns different static array based on the language chosen, it works perfectly fine. So to my understanding the issue might be getting the data properly from db. What can I do here?
The derived store makes no sense in this context because the data is updated asynchronously. The problem being that getTypography is not returning anything, but being used in data as if that were the case.
Just make data a writable and update that when the language changes. I.e. use something like:
key.subscribe($key => updateData($key))
Where the updateData function is analogous to getTypography but sets the data store.
Something like:
const data = writable([]);
async function updateData(lang) {
try {
const languageData = await getData('/language');
const typography = languageData.payload;
data.set(filterTypography(typography, lang));
}
catch (error) {
console.log('error', error);
}
}
(You may want to add additional logic to invalidate previous requests to prevent race conditions.)
If data for all languages is requested at once, you can update a base store asynchronously and use a derived store just for the filtering. It should be based on both the language (key) and the store that is updated with all the language data.
An example of the principle:
import { writable, derived } from 'svelte/store';
const key = writable('en');
const allData = writable({});
const data = derived([allData, key], ([$allData, $key]) => {
return $allData[$key]; // Filter data based on language
});
REPL
You could also rewrite getTypography to actually return the promise it creates internally, but then you would have to use something like {#await} everywhere you use data.
async function getTypography(lang) {
try {
const data = await getData('/language');
const typography = data.payload;
return filterTypography(typography, lang);
}
catch (error) {
console.log('error', error);
// If you do not return anything here, the awaited store will be `undefined`
}
}

React clear LocalStorage and Asyncstorage in one Component

is it possible to clear the LocalStorage and AsyncStorage in one Component?
I want to clear the Storage if the 401 error displayed.
Need to share this for React and React-Native, is there a way to solve it?
I guess with try and catch will not work.
Hope someone can help me to fix this.
import 'whatwg-fetch';
import AsyncStorage from '#react-native-async-storage/async-storage';
async function clearMobile(){
await AsyncStorage.clear();
}
const clearWeb = () => {
localStorage.clear();
}
export const authFetch = (originalFetch => {
return (...args) => {
const result = originalFetch.apply(this, args);
return result.then(
(response) => {
if (!response.ok && response.status === 401)
try {
clearMobile();
}
catch {
clearWeb();
}
finally {
window.location.reload();
}
return response;
}
);
};
})(fetch);
export default authFetch;
You want to clear both at the same time, both functions must be used inside the try block. try...catch
try {
clearMobile();
clearWeb();
}
catch (error) {
console.log(error);
}
In this way, you will have cleaned both of them. And if there is a bug, you will debug it.
I'm sorry if the question is not what I understood.

Best way for multiple HTTP Request in Angular

I am trying to send 2 HTTP requests one by one; if the first one is succeeds, send the second one, if not display the corresponding error message regarding to the first request.
I am planning to use something like that, but not sure if it is the best option for this scenario:
import { Component } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Component({
selector: 'app-root',
templateUrl: 'app/app.component.html'
})
export class AppComponent {
loadedCharacter: {};
constructor(private http: HttpClient) {}
ngOnInit() {
this.http.get('/api/people/1').subscribe(character => {
this.http.get(character.homeworld).subscribe(homeworld => {
character.homeworld = homeworld;
this.loadedCharacter = character;
});
});
}
}
I have different requests e.g. PUT and CREATE also using this approach. I know there are other ways e.g. forkjoin, mergemap, but if this one solves my problem seems to be more readable. Any idea?
First of all, your code works and that's great - you can leave it as is and everything will be fine.
On the other hand, there is a way for multiple improvements that will help you and your colleagues in future:
try to move http-related logic to the service instead of calling http in the components - this will help you to split the code into view-related logic and the business/fetching/transformation-related one.
try to avoid nested subscribes - not only you ignore the mighty power of Observables but also tie the code to a certain flow without an ability to reuse these lines somewhere in the application. Returning the Observable might help you with "sharing" the results of the request or transforming it in some way.
flatMap/mergeMap, concatMap and switchMap work in a different way, providing you an ability to control the behaviour the way you want. Though, for http.get() they work almost similar, it's a good idea to start learning those combining operators as soon as possible.
think about how you'll handle the errors in this case - what will happen if your first call will result an error? Observables have a powerful mechanism of dealing with them, while .subscribe allows you to handle an error only in one way.
An example using the switchMap:
import { Component } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Component({
selector: 'app-root',
templateUrl: 'app/app.component.html'
})
export class AppComponent {
loadedCharacter: {};
constructor(private http: HttpClient) {}
ngOnInit() {
const character$ = this.http.get('/api/people/1').pipe(
tap(character => this.characterWithoutHomeworld = character), // setting some "in-between" variable
switchMap(character => {
return this.http.get(character.homeworld).pipe(
map(homeworld => {
return {
...character,
homeworld: homeworld
}
}
)
)
}),
catchError(errorForFirstOrSecondCall => {
console.error('An error occurred: ', errorForFirstOrSecondCall);
// if you want to handle this error and return some empty data use:
// return of({});
// otherwise:
throw new Error('Error: ' + errorForFirstOrSecondCall.message);
})
);
// you can either store this variable as `this.character$` or immediately subscribe to it like:
character$.subscribe(loadedCharacter => {
this.loadedCharacter = loadedCharacter;
}, errorForFirstOrSecondCall => {
console.error('An error occurred: ', errorForFirstOrSecondCall);
})
}
}
2 nested subscriptions are never a way to go. I recommend this approach:
this.http.get('/api/people/1').pipe(
switchMap(character => this.http.get(character.homeworld).pipe(
map(homeworld => ({ ...character, homeworld })),
)),
).subscribe(character => this.loadedCharacter = character);
Edit: For your university
this.http.get('/api/people/1').pipe(
switchMap(character => this.http.get(character.university).pipe(
map(university => ({ ...character, university})),
)),
).subscribe(character => this.loadedCharacter = character);
Or even chain university and homeworld requests
this.http.get('/api/people/1').pipe(
switchMap(character => this.http.get(character.homeworld).pipe(
map(homeworld => ({ ...character, homeworld })),
// catchError(err => of({ ...character, homeworld: dummyHomeworld })),
)),
switchMap(character => this.http.get(character.university).pipe(
map(university => ({ ...character, university})),
)),
).subscribe(character => this.loadedCharacter = character);
You can try a solution using switchmap and forkJoin for easier chaining and error handling. this will help keep the code clean in case the chain keeps growing into a deep nest.
this.http
.get("/api/people/1'")
.pipe(
catchError((err) => {
// handle error
}),
switchMap((character) => {
return forkJoin({
character: of(character),
homeworld: this.http.get(character.homeworld)
});
})
)
.subscribe(({ character, homeworld }) => {
character.homeworld = homeworld;
this.loadedCharacter = character;
});
EDIT: Scenario 2
this.http
.get("/api/people/1")
.pipe(
catchError((err) => {
console.log("e1", err);
}),
switchMap((character) => {
return forkJoin({
character: of(character),
homeworld: this.http.get(character.homeworld).pipe(
catchError((err) => {
console.log("e2", err);
})
)
});
})
)
.subscribe(({ character, homeworld }) => {
character.homeworld = homeworld;
this.loadedCharacter = character;
});
You can chain a catch error or add a separate function for error handling without it invoking the next API call. but I would recommend abstracting the backend logic to an angular service and using this method. which would help retain an easy to read structure.
You can check if the first request was successful or not by checking the status code:
ngOnInit() {
this.http.get('/api/people/1').subscribe((character: HttpResponse<any>) => {
// here you should look for the correct status code to check, in this example it's 200
if (character.status === 200) {
this.http.get(character.homeworld).subscribe(homeworld => {
character.homeworld = homeworld;
this.loadedCharacter = character;
});
} else {
// character is gonna contain the error
console.log(character)
}
});
}

Throwing error from subscription/observable - Angular2+

I am attempting to throw an error from my observed function, such that I can access the err part of my subscription, but I cannot seem to get through to that part.
I'm not sure how to do it via the map way, but here is a different way:
import { Observable, of, throwError } from 'rxjs';
ngOnInit() {
this.getObs().subscribe(data => {
console.log(data);
}, err => {
console.log(err);
});
}
getObs() {
const observable = of(6);
const isError = true;
if (isError) {
return throwError("Is an error!");
}
return observable;
}
Working example: https://stackblitz.com/edit/angular-mqp4qv?file=src/app/app.component.ts
Couple ways I would handle this. Heres one way using _throw
// auth.ts
import { _throw } from 'rxjs/observable/throw';
login( username, password ) {
const body = this.jsonifyData( username, password );
return this.http.post<any>( this.apiBaseUrl + '/auth', body )
.pipe(
map( res => {
if ( 'token' in res ) {
return res.token;
}
// Unsuccessful login returns error
return _throw( res ); // from import {_throw} from 'rxjs/observable/throw';
} )
);
}
The other would be to just catch the error in the login component
I think in your component's test spec you need to mock up your authService and the login procedure. This mock does not make http call, just throw an error via throwError(new Error('Either username or password were incorrect')) immediately if, say, password does not match some predefined value. Here is some example of how to test component-http service stuff, it is a bit outdated but still actual conceptually.

How to synchronise Angular2 http get?

I understand using observable I can execute a method when the request is completed, but how can i wait till a http get is completed and return the response using in ng2 http?
getAllUser(): Array<UserDTO> {
this.value = new Array<UserDTO>();
this.http.get("MY_URL")
.map(res => res.json())
.subscribe(
data => this.value = data,
err => console.log(err),
() => console.log("Completed")
);
return this.value;
}
the "value" will is null when its returned because get is async..
your service class: /project/app/services/sampleservice.ts
#Injectable()
export class SampleService {
constructor(private http: Http) {
}
private createAuthorizationHeader() {
return new Headers({'Authorization': 'Basic ZXBossffDFC++=='});
}
getAll(): Observable<any[]> {
const url='';
const active = 'status/active';
const header = { headers: this.createAuthorizationHeader() };
return this.http.get(url + active, header)
.map(
res => {
return res.json();
});
}
}
your component: /project/app/components/samplecomponent.ts
export class SampleComponent implements OnInit {
constructor(private sampleservice: SampleService) {
}
ngOnInit() {
this.dataset();
}
dataset(){
this.sampleservice.getAll().subscribe(
(res) => {
// map Your response with model class
// do Stuff Here or create method
this.create(res);
},
(err) => { }
);
}
create(data){
// do Your Stuff Here
}
}
By looking at the angular source (https://github.com/angular/angular/blob/master/packages/http/src/backends/xhr_backend.ts#L46), it is apparent that the async attribute of the XMLHttpRequest is not getting used. The third parameter of XMLHttpRequest needs to be set to "false" for synchronous requests.
Please find code for your problem
Below is component and service file.And Code is Working fine for synchornize
import { Component, OnInit } from '#angular/core';
import { LoginserviceService } from '../loginservice.service';
#Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
model:any={};
constructor(private service : LoginserviceService) {
}
ngOnInit() {
}
save() {
this.service.callService(this.model.userName,this.model.passWord).
subscribe(
success => {
if(success) {
console.log("login Successfully done---------------------------- -");
this.model.success = "Login Successfully done";
}},
error => console.log("login did not work!")
);
}
}
Below is service file..
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import { UserData } from './UserData';
import 'rxjs/add/operator/map'
import 'rxjs/add/operator/toPromise'
import {Observable} from 'rxjs/Rx'
#Injectable()
export class LoginserviceService {
userData = new UserData('','');
constructor(private http:Http) { }
callService(username:string,passwrod:string):Observable<boolean> {
var flag : boolean;
return (this.http.get('http://localhost:4200/data.json').
map(response => response.json())).
map(data => {
this.userData = data;
return this.loginAuthentication(username,passwrod);
});
}
loginAuthentication(username:string,passwrod:string):boolean{
if(username==this.userData.username && passwrod==this.userData.password){
console.log("Authentication successfully")
return true;
}else{
return false;
}
}
}
Another solution would be to implement a priority queue of sort.
From what I understand http requests do not get executed until you add subscribers. Therefore, you can do something like this:
Observable<Response> observable = http.get("/api/path", new RequestOptions({}));
requestPriorityQueue.add(HttpPriorityQueue.PRIORITY_HIGHEST, observable,
successResponse => { /* Handle code */ },
errorResponse => { /* Handle error */ });
This assumes that requestPriorityQueue is a service injected into your component. The priority queue would store entries in an array in the following format:
Array<{
observable: Observable<Response>,
successCallback: Function,
errorCallback: Function
}>
You would have to decide how the elements are added to your array. Finally, the following will happen in the background:
// HttpPriorityQueue#processQueue() called at a set interval to automatically process queue entries
The processQueue method would do something like this:
protected processQueue() {
if (this.queueIsBusy()) {
return;
}
let entry: {} = getNextEntry();
let observable: Observable<Response> = entry.observable;
this.setQueueToBusy(); // Sets queue to busy and triggers an internal request timeout counter.
observable.subscribe()
.map(response => {
this.setQueueToReady();
entry.successCallback(response);
})
.catch(error => {
this.setQueueToReady();
entry.errorCallback(error);
});
}
If you are able to add new dependencies you could try using the following NPM package: async-priority-queue
I looked and I couldn't find any way to make an HTTP call sync instead of async.
So the only way around this: wrap your call in a while loop with a flag. Don't let the code continue until that flag has "continue" value.
Pseudo code as follows:
let letsContinue = false;
//Call your Async Function
this.myAsyncFunc().subscribe(data => {
letsContinue = true;
};
while (!letsContinue) {
console.log('... log flooding.. while we wait..a setimeout might be better');
}
as you see, first callback waiting for a data from request and
there you can go on with your logic (or use the third one)
example:
.. subscribe( data => {
this.value = data;
doSomeOperation;
},
error => console.log(error),
() => {console.log("Completed");
or do operations here..;
}
});
How about to use $.ajax(of jQuery) or XMLHttpRequest.
It can use as asynchornize.
You should not try to make http calls behave synchronously. Never a good idea.
Coming to your getAllUser implementation it should return an observable from the function and the calling code should subscribe instead of you creating a subscription inside the method itself.
Something like
getAllUser(): Observable<UserDTO> {
return this.http.get("MY_URL")
.map(res => res.json());
}
In you calling code, you should subscribe and do whatever you want.

Categories