Tips for executing numerous asynchronous tasks in Ionic 3 and closing a loader once all tasks are completed

Currently, I am in the process of developing an Ionic 3 application that offers the feature to cache a list of articles content on demand. The implementation involves utilizing Storage which employs promises for its operations.

The code snippet I have written is as follows:

article-service.ts

getArticleFullData(articleId: number) {
    let appSettings = this.appSettingsService.getSettings();
    let params = this.utilsService.serializeQueryParams(appSettings);
    let url = `${this.apiBasePath}Article/GetArticleFullData?articleId=${articleId}&${params}`;
    this.loggingService.logInfo("Getting article full data from url " + url); 
    return this.http.get(url)
        .map(res => res.json());
}

article-cache-service.ts

// saves a single article to the cache
private saveToCache(articleId: number): Observable<any> {

    let key = articleId;
    let obs = this.articleService.getArticleFullData(key);
    obs.subscribe(data => {
        this.storage.set(this.GetArticleFullKey(key), data as ArticleFullData);
    },
        err => {
            this.loggingService.logError("Failed to cache data for article: " + key);
        }
    );

    return obs;
}

// attempts to cache a list of articles in the cache without overwriting existing data
public saveArticleDataToCache(articles: ArticleBriefData[]): Observable<{}[]> {

    var obsArray = [];
    for (let article of articles) {
        let key = this.GetArticleFullKey(article.ArticleId);
        this.storage.get(key)
            .then(data => {
                console.log("Storage data for article " + key, data);

                if (!data) {
                    obsArray.push(this.saveToCache(article.ArticleId));
                }
            })
            .catch(err => {
                this.loggingService.logError("Failed to check storage for key " + key, err);
            });
    }

    var ret = Observable.forkJoin(obsArray);
    return ret;
}

code with loader

    this.loadingCtrl.create({ content: "Caching data. Please wait..." });
    this.loadingCtrl.present();

    var all = this.articleCacheService.saveArticleDataToCache(this.articles);
    var subscr = all
        .toPromise()
        .then(data => {
            console.log("All data: ", data);
            this.loadingCtrl.dismiss();
            this.loggingService.logInfo("Successfully cached articles ");
        })
        .catch(err => {
          this.loadingCtrl.dismiss();
          this.loggingService.logError("Failed to cache data: ", JSON.stringify(err));
        }
    );

The caching operation proceeds smoothly, however, the code within the then block executes immediately (data turns out to be undefined).

I've also experimented with the subscribe method, but even though the caching performs correctly, the code inside the subscribe doesn't seem to execute as expected:

    var all = this.articleCacheService.saveArticleDataToCache(this.articles);
    var subscr = all
        .subscribe(data => {
            console.log("All data: ", data);

            this.loadingCtrl.dismiss();
            this.loggingService.logInfo("Successfully cached articles ");
        },
        err => {
          this.loadingCtrl.dismiss();
          this.loggingService.logError("Failed to cache data: ", JSON.stringify(err));
        }
    );

I recognize that my handling of Observables requires refinement.

Question: How can I effectively carry out multiple asynchronous actions in Ionic 3 and dismiss a loader only after all processes are complete?

Answer №1

To easily accomplish this, you can utilize the forkjoin function.

Note: The following code snippet has been extracted from my application. Please adjust it based on your specific use case. If you are working with RXJS 5.5.2, make sure to update the imports as per the latest changes mentioned here.

import { Observable } from "rxjs/Observable";
import 'rxjs/add/observable/forkJoin'

let myTopicApi = this.getMytopic();
let myarticlApi = this.getMyArticles();
let myPicksAPi = this.getMyPicks();
Observable.forkJoin([myTopicApi, myarticlApi, myPicksAPi])
  .subscribe(res => {
    this.arrangeMytopics(res[0]);
    this.arrangeMyArticles(res[1]);
    this.arrangeMyPicks(res[2]);
    if (loading) { loading.dismiss(); loading = null; }//you can dismiss your loader here
  },
  error => { if (loading) { loading.dismiss(); loading = null; }//you can dismiss your loader here },
  () => { });

getMytopic() {
    return this.topicSer.getMyTopics().map((res: any) => res.json()).map((res: any) => res = res.categories)
      .catch((err: any) => { })
}

Answer №2

Sampath's solution really helped me identify the issue. The mistake I was making was in how I structured the Observable array (used in forkJoin): adding to the list inside the then function of the Promise returned by storage get.

After correcting this, my code also now includes a check for item existence to prevent unnecessary HTTP calls and data resets:

article-service.ts

getArticleFullData(articleId: number) {
    let appSettings = this.appSettingsService.getSettings();
    let params = this.utilsService.serializeQueryParams(appSettings);
    let url = `${this.apiBasePath}Article/GetArticleFullData?articleId=${articleId}&${params}`;
    return this.http.get(url)
        .map(res => res.json());
}

article-cache-service.ts

I'm using flatMap to connect the obtained Observable from checking if the storage already contains the key and the actual fetch. If the data exists, I return an empty Observable to stop the process.

private saveToCache(articleId: number): Observable<any> {

    let key = this.GetArticleFullKey(articleId);

    let storageObs = Observable.fromPromise(this.storage.get(key));
    let getArticleObs = this.articleService.getArticleFullData(articleId);

    let obs = storageObs.flatMap(data => {
        if (!data)
            return getArticleObs;
        else
            return Observable.of(null);
    });

    obs.subscribe(data => {

        if (data)
          this.storage.set(key, data as ArticleFullData);
    },
        err => {
            this.loggingService.logError("Failed to cache data for article: " + articleId);
        }
    );

    return obs;
}

public saveArticleDataToCache(articles: ArticleBriefData[]): Observable<{}[]> {

    let obsArray = [];
    for (let article of articles) {

        let obs = this.saveToCache(article.ArticleId);
        obsArray.push(obs);
    }

    let ret = Observable.forkJoin(obsArray);
    return ret;
}

actual subscription

onCacheRefresh() {
    // articles are not loaded for some reason
    if (!this.articles)
        return;

    this.loadingCtrl.create({ content: "Caching data. Please wait..." });
    this.loadingCtrl.present();

    let all = this.articleCacheService.saveArticleDataToCache(this.articles);
    let subscr = all
        .subscribe(data => {
            console.log("All data: ", data);

            this.loadingCtrl.dismiss();
            this.loggingService.logInfo("Successfully cached articles ");
        },
        err => {
          this.loadingCtrl.dismiss();
          this.loggingService.logError("Failed to cache data: ", JSON.stringify(err));
        }
    );
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The SortKey<> function is ineffective, even though the individual types it uses work perfectly fine

After my initial inquiry about TypeScript, the focus shifted to this current topic. In my previous question, I was attempting to develop a generic sort() method that could function with an array of sort keys defined by the SortKey type featured there (and ...

What is the proper syntax for defining all CSS properties as a type in TypeScript?

Using StyleProps allows for specifying size and color. I would prefer if it covered all styles. This way, I can easily pass down styles directly to specific parts of the component. What should I include to encompass all CSS properties? How can I modify ...

How can I encode and decode a base64 string using AngularJS1 and TypeScript?

I am currently working with Angular1 using TypeScript and I have a question that needs some clarification. Within the environment that I am operating in, is there a method available to encode and decode a string in base64? Despite conducting extensive re ...

How can PrimeNG PIE chart values be displayed by default instead of only on hover over the slice?

front end <td> <div class="p-col-8 ui-toolbar-group-right"> <button pButton type="button" icon="pi pi-search" (click)="populate_charts()"></button> </div> </td> TS-File ...

Encountering "environment.prod.ts path does not exist in file replacements" error while Dockerizing Angular build

I am encountering an issue with a Dockerfile that throws an error during the build process. I attempted to install the angular/cli globally and run ng build --prod using a separate RUN command, but the same error persists. Dockerfile FROM node:12.17.0-al ...

How can I combine Angular's material drag and drop feature with virtual scrolling?

I've been attempting to combine Angular material's virtual scrolling with drag and drop functionality, but I'm encountering an issue where the items are reverting back and the dragging and dropping doesn't seem to work as expected. Bel ...

Error in parsing: Unexpected token encountered. Expected a comma instead. Issue found in React with Typescript

I'm encountering a new error message that I haven't seen before... I've checked my code thoroughly and it seems to be correct, yet the error persists. Here is my code snippet: interface AuthState { token: string; user: User; } interfac ...

Dealing with documents in Django Rest Framework

For my Angular + Django project, I am looking to add files to the Ticket model. Which field should I use for this – FileField? class Ticket(models.Model): titulo = models.CharField(max_length=100, blank=True) estagio = models.ForeignKey(Estagio, ...

The use of the global icon library with FortAwesome/angular-fontawesome has been discontinued

Package.json { "name": "socialtools-frontend-apps", "version": "0.0.0", "scripts": { "ng": "ng", "start": "ng serve", "build": "ng bu ...

The map component does not render when the agm-map is placed within the component

Objective I am attempting to encapsulate an <agm-map> within my custom <app-map> component, but it is not appearing in the HTML output. The agm (angular google maps) library is properly configured and the map displays correctly when the <a ...

3 Ways to Ensure Your Picture Uploads are Visible Right Away

I am currently working on an Ionic app that enables users to upload images to Firebase storage. One issue I am encountering is that the image only changes once a new one is selected, after closing and reopening the app. I would like it to update immediatel ...

Which is better: Utilizing ASP.NET Core or ASP.NET Core MVC in tandem with Angular for the

Currently in the planning stages of developing a website or web application using .NET Core for the backend and Angular for the frontend. One aspect that is proving to be confusing is whether to use ASP.NET Core or ASP.NET Core MVC on the backend. I'm ...

What is the best way to ensure that an animation has finished before moving on to the next route?

I attempted a solution from this source, but unfortunately, it did not work for me. What I tried to do was incorporate the code from this example into my application. However, the challenge I faced was transitioning from the /login route to the /home rout ...

Tips for utilizing MUI Typography properties in version 5

I'm clear on what needs to be done: obtain the type definition for Typography.variant. However, I'm a bit uncertain on how to actually get these. interface TextProps { variant?: string component?: string onClick?: (event: React.MouseEvent&l ...

In Angular 5, you cannot assign type 'any[]' to type 'typeof User'

After extracting data from the JSON file, I encountered a typo error message stating: Type 'any[]' is not assignable to type 'typeof User'. Here is my code in app.component.ts: import { Component, OnInit } from '@angular/core&a ...

A guide to integrating a component into another component in Angular

Currently, I am encountering an issue with importing a component into another in my Ionic 5.0.0 application. Within my application, I have two separate modules: ChatPageModule and HomePageModule. My objective is to include the Chat template within the Hom ...

Problem with Angular app not loading in IE 11 due to ES6 targeting

I have encountered an issue while developing a new application with IE11 as the target browser. When I set the target to es6, the app fails to load and displays the error shown below. https://i.stack.imgur.com/FL8BG.png However, when I switch the target ...

Incorporating Copyleaks SDK into Angular: A Seamless Integration

Currently, I'm in the process of implementing the Copyleaks SDK with Angular to conduct plagiarism checks on two text area fields within an HTML form. Within the form, my goal is to incorporate two buttons: one to check for plagiarism on one text area ...

Is there a way for me to manually manipulate the advancement of the progress bar from @ngx-progressbar/core in Angular5/Ionic4?

I've been working on implementing a progress bar into my application using the @ngx-progressbar/core library. However, I'm facing an issue where I can't seem to control its progress effectively. Whenever I try to increase the progress increm ...

Comparing Input and Output Event Binding

Can you provide reasons why using @Output for events is more advantageous than passing an @Input function in Angular 2+? Utilizing @Input: Parent Template: <my-component [customEventFunction]=myFunction></my-component> Inside parent-compone ...