Conditionally chain observable - javascript

For the following TypeScript (using rxjs):
getRegularData(): Observable<MyData> {
return WS.loadRegularData();
}
getAlternateData(): Observable<MyData> {
return WS.loadAlternateData();
}
how can a new method be implemented to satisfy the following pseudocode:
getData(): Observable<MyData> {
// try to use getRegularData, and return observable for result.
// if getRegularData returns null, get data from getAlternateData()
// instead and return observable for result.
}

There are many ways you can implement this, one would be to use a switchMap that contains your condition:
getData(): Observable<MyData> {
return getRegularData()
.switchMap(data => {
if (data != null) {
return Observable.of(data);
} else {
return getAlternateData();
}
});
}

Related

RxJs custom operator cant access 'this'

I created a custom operator that access the this but it seems that it's always undefined, even though I passed to the function this with bind
custom operator
function shouldLoadNewOptimizationData() {
return filter(function([p1,p2,p3]) {
// some logic
if(){
this.store.dispatch()...
}
})
}
custom operator usage
effect = createEffect(()=> this.actions$.pipe(
ofType(//action type),
withLatestFrom(
//...selectors
),
shouldLoadNewOptimizationData().bind(this),
// more operators..
)
)
Generally a rxjs custom operator is in the way
function myOperator<T>(source: Observable<T>) {
...some logic...
return source; //or return source.pipe(....)
}
Or with arguments
function myOperator<T>(...args) {
return function<T>(source: Observable<T>): Observable<T> {
..some logic..
return source //or return source.pipe(....)
};
}
See this great link about this
Pass the store to shouldLoadNewOptimizationData. This way you avoid having to bind this every time you use the function:
function shouldLoadNewOptimizationData(store) {
return filter(function([p1,p2,p3]) {
// some logic
if(){
store.dispatch()... // -> use the provided store instead of `this.store`
}
})
}
effect = createEffect(()=> this.actions$.pipe(
ofType(//action type),
withLatestFrom(
//...selectors
),
shouldLoadNewOptimizationData(this.store),
// more operators..
);
Change to arrow function
function shouldLoadNewOptimizationData() {
return filter(([p1,p2,p3]) => {
// some logic
if(){
this.store.dispatch()...
}
})
}
If you want to go full angular 15 (who needs this, decorators, ngmodules, constructors, ...)
import { Store } from '#ngrx/store';
...
const shouldLoadNewOptimizationData = () => {
const store = inject(Store)
return filter(([p1,p2,p3]) => {
// some logic
if(){
store.dispatch()...
}
})
}

subscribing two observables i.e. observable inside another observable HTTP

I am new to angular. I am trying to post and get data at a time. I am using map and subscribing the observable while the outer observable is subscribed in component.
add(data) {
return this.postModel(`pathHere`, data)
.pipe(map((resp) => {
if(resp.models){
return this.getRoomById(resp.models.id).subscribe();
}
}));
}
I guess you need to use flatMap instead of map as
add(data) {
return this.postModel(`pathHere`, data)
.pipe(flatMap((resp) => {
if(resp.models){
return this.getRoomById(resp.models.id);
}
}));
}
add(data) {
return this.postModel(`pathHere`, data)
.pipe(
switchMap((resp) => {
if (resp.models) {
return this.getRoomById(resp.models.id);
}
return of(resp.models);
})
);
}
You can use switchMap to emit another Observable.
As you used if statement for checking resp.models, you have to handle the case when resp.models is not truthy.

Returning inner $http promise

I have two services:
ProductService
CartService
And a Controller:
ShoppingController
ShoppingController needs to download the cart from the server. In order for CartService to do this, ProductService must first download the products.
ProductService.js
ProductService.DownloadProducts = function(){
if(alreadyHaveDownloadedProducts){
return {
success: function (fn) {
fn(products);
}
};
}else{
if(getProductsPromise== null){
getProductsPromise= $http.post("Api/GetProducts")
}
return getProductsPromise;
}
CartService.js
CartService.DownloadCart = function(){
ProductService.DownloadProducts().success(function(){
if(alreadyHaveDownloadedCart){
//Dont need to go to server
return {
success: function (fn) {
fn(cart);
}
};
}else{
if(getCartPromise == null){
getCartPromise = $http.post("Api/GetCart")
}
return getCartPromise; //<= The promise I actually want to return
}
})
}
ProductService.DownloadProducts
ShoppingController.js
CartService.DownloadCart().success(function(){DisplayCart()});
This approach works nicely so far, because if the ProductService has already been called on a different page, I don't need to go back to the server. Same for cart service.The issue is I currently can't return the getCartPromise as it hasn't been created until the async GetProducts has returned
Is it possible to structure this so I can return the inner promise to ShoppingController while keeping the nice .success() syntax?
My way is similar to yours, but instead of saving some alreadyHaveDownloadedCart (boolean flag), i'm caching the promise it self, and then returns it.
I'm caching the promise for the data on the service class, and then returning it if it is exists otherwise initialize a server call.
Something like that:
class ProductService {
constructor() {
this.productsPromise = null;
}
DownloadProducts() {
if(!this.productsPromise) {
this.productsPromise = $http.post('Api/GetProducts');
this.productsPromise.then(products => this.products = products);
}
return this.productsPromise.then(() => this.products);
}
}
class CartService {
constructor(ProductService) {
this.ProductService = ProductService;
this.cartPromise = null;
}
DownloadCart() {
return this.ProductService.DownloadProducts().success(() => {
if (!this.cartPromise) {
this.cartPromise = $http.post('Api/GetCart');
this.cartPromise.then((cart) => {
this.cart = cart;
});
}
return this.cartPromise.then(() => this.cart);
});
}
}

handle a function that returns a promise or a null value

I have defined a function as following :
function getCurrentComponent(){
if($rootRouter._currentInstruction){
return $rootRouter.recognize($rootRouter._currentInstruction.urlPath).then(function (data) {
return data.component.componentType;
});
}else{
return null;
}
}
To call this function I did as following :
factory.getCurrentComponent().then(function (data) {
...
});
The problem is when the getCurrentComponent function returns a null value, the following error is generated :
Cannot read property 'then' of null
How can I solve this ?
Edit:
I forgot to say that I'm limited to use ES5, so I can't work with the object Promise
Use Promise.reject() function.
function getCurrentComponent() {
if ($rootRouter._currentInstruction) {
return $rootRouter.recognize($rootRouter._currentInstruction.urlPath).then(function(data) {
return data.component.componentType;
});
} else {
return Promise.reject('_currentInstruction is fale');
}
}
factory.getCurrentComponent().then(function(data) {
...
}).catch(function(e) {
console.log(e); // Output: _currentInstruction is fale
});
Resource
Promise.reject()
If you're unable to use Promise you can return an object with a function then.
function getCurrentComponent() {
if ($rootRouter._currentInstruction) {
return $rootRouter.recognize($rootRouter._currentInstruction.urlPath).then(function(data) {
return data.component.componentType;
});
} else {
var helperThen = { then: function(fn) { fn(null) } };
return helperThen;
}
}
factory.getCurrentComponent().then(function(data) {
// Check if data is null.
...
});
I cant use the object Promise in ES5.
Use the AngularJS $q Service:
function getCurrentComponent(){
if($rootRouter._currentInstruction){
return $rootRouter.recognize($rootRouter._currentInstruction.urlPath).then(function (data) {
return data.component.componentType;
});
}else{
̶r̶e̶t̶u̶r̶n̶ ̶n̶u̶l̶l̶;̶
return $q.reject(null);
}
}
AngularJS modifies the normal JavaScript flow by providing its own event processing loop. This splits the JavaScript into classical and AngularJS execution context. Only operations which are applied in the AngularJS execution context will benefit from AngularJS data-binding, exception handling, property watching, etc.
The $q Service is a Promises/A+-compliant implementation of promises/deferred objects thst is integrated with the AngularJS framework and its digest cycle.
Convert the function to use a Promise.
function getCurrentComponent(){
return new Promise((resolve, reject)=>{
if($rootRouter._currentInstruction){
resolve( $rootRouter.recognize($rootRouter._currentInstruction.urlPath).then(function (data) {
return data.component.componentType;
}));
} else{
reject(null);
})
}
Now you can check resolve and reject with then() function,
factory.getCurrentComponent().then(function (resolved) {
//handle success here
}, function(rejected) {
// handle rejection here
});
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

Angular 2 async service cache

In my Angular 2 app, I have implemented a simple service cache using promises, and it works fine. However, I am not yet satisfied with my implementation:
export class Cache<T extends Cacheable<T, I>, I> {
items: T[];
init(items: T[]): void {
this.items = items;
}
isInitialised(): boolean {
return this.items != null;
}
all(): Promise<T[]> {
return Promise.resolve(this.items);
}
get(id: I): Promise<T> {
return Promise.resolve(this.items.find(item => item.id == id));
}
put(item: T): void {
this.items.push(item);
}
update(item: T): void {
this.get(item.id)
.then(cached => cached.copy(item));
}
remove(id: I): void {
let index = this.items.findIndex(item => item.id == id);
if (index > -1) {
this.items.splice(index, 1);
}
}
}
I would like the cache to initialise no matter which method is called, whether it is .all, .get etc. But, the key is that I want the first call to this.items to force the cache to initialise. Any subsequent call should somehow wait until the cache is initialised before manipulating ´this.items´.
To do so, I thought of using a proxy method .itemsProxy() which would return a promise, and replace any call to .items with that method and adapt code consequently. This would require having a cache state which is either NOT_INITIALSED, INITIALSING or INITIALSED.
The proxy method would look like this:
export class Cache<T extends Cacheable<T, I>, I> {
constructor(private service: AbstractService) {}
initPromise: Promise<T[]>;
state: NOT_INITIALISED;
itemsProxy(): Promise<T> {
if (this.state == INITIALISED)
return Promise.resolve(this.items);
if (this.state == NOT_INITIALISED) {
this.initPromise = this.service.all().then(items => {
// Avoid override when multiple subscribers
if (this.state != INITIALISED) {
this.items = items;
this.state = INITIALISED;
}
return this.items;
});
this.state = INITIALISING;
}
return this.initPromise;
}
...
}
Notice how initialisation is done via the this.service.all(), which looks like this:
all(): Promise<T[]> {
if (this.cache.isInitialised()) {
return this.cache.all();
}
return this.http
.get(...)
.toPromise()
.then(response => {
this.cache.init(
response.json().data.map(item => new T(item))
);
return this.cache.all();
}); // No catch for demo
}
Note that new T(...) is not valid, but I simplified the code for demo purposes.
The above solution does not seem to work like I'd expect it to, and asynchronous calls are hard to debug.
I thought of making this.items an Observable, where each call would subscribe to it. However, my understanding of observables is quite limited: I don't know if a subscriber can modify the data of the observable it is subscribed to (i.e. are observables mutable like a T[] ?). In addition, it seems to me that observables "produce" results, so after the cache is initialised, would any new subscriber be able to access the data produced by the observable ?
How would you implement this kind of "synchronization" mechanism ?

Categories

Resources