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

Navigating the store in Ionic Angular using Ngrx

Currently, I am in the process of developing an app using Ionic Angular Cordova. My issue lies in trying to display the state of my application, specifically all the objects within it. However, despite my efforts, the objects that I have added cannot be lo ...

Hello there! I am just starting to learn about Bootstrap and I'm excited to create a responsive layout that will be centered on the page

I am aiming for this specific layout design. Here is the current code snippet that I have: <div class="container"> <h2 class="title mt-3 mb-4">Title</h2> <div class="container"> <div class="row"> <div cl ...

Customizing the entire application's style based on conditions in Angular 4

Looking to implement a dark mode button for the app. Create a toggle switch for "dark mode". This switch will change a boolean value, "dark-ui", between true and false. When the app component detects dark-ui as true, add the class "dark" to a parent-leve ...

Error encountered while installing node modules within an angular workspace

Currently, I am facing an issue with my workspace where the command npm install is giving me a series of errors that I cannot seem to resolve. I have tried running it as an admin, manually deleting the node_modules folder, asking for help from a senior col ...

Issue: (SystemJS) Unable to find solutions for all parameters in $WebSocket: ([object Object], [object Object], ?)

Upon running the code snippet below, an error is thrown: Error: (SystemJS) Can't resolve all parameters for $WebSocket: ([object Object], [object Object], ?). app.component.ts import { Component } from '@angular/core'; import {$WebSocket} ...

Facing a challenge with handling HTTP data in a TypeScript-based Angular web application

I am currently working on developing a web application using Angular and the SpringMVC Framework. One of the tasks I'm facing is loading a list of users (referred to as "consulenti" in the code). While the backend HTTP request works fine, I encounter ...

Encountering an error of ExpressionChangedAfterItHasBeenCheckedError while trying to refresh the

I'm encountering an issue that I need help with: https://i.stack.imgur.com/4M54x.png whenever I attempt to update the view using *ngIf to toggle on an icon display. This is what my .ts file looks like: @Component({ selector: 'app-orders&ap ...

Access the child component within an @ChildComponent directive in Angular

Is it possible to retrieve child components of another component? For instance, consider the following QueryList: @ContentChildren(SysColumn) syscolumns: QueryList<SysColumn>; This QueryList will contain all instances of the SysColumns class, which ...

Repeated calls to Angular's <img [src]="getImg()"> frequently occur

Can someone help me figure out what's going on here? Recently, I set up a new Angular project and in app.component.html, I have the following code: <img [src]="getDemoImg()"> In app.component.ts: getDemoImg(){ console.log('why so m ...

Angular sending data to ASP.NET MVC results in null object properties

My issue involves sending data from Angular (v15) to ASP.NET on the .NET Framework 4.72. On the client side: var name = this.inName; var queue = new Queue(); queue.QueueId = 6; queue.CustomerFullName = name; queue.Status = 5; var d = new Date(); const da ...

Error message stating 'Module not found' is displaying in the browser console

As a beginner with Angular CLI, I recently executed the following commands at the root of my Angular project. issue-management\src\webui>ng generate module pages\dashboard issue-management\src\webui>ng generate component pag ...

Having trouble with the lodash find function in my Angular application

npm install lodash npm install @types/lodash ng serve import { find, upperCase } from 'lodash'; console.log(upperCase('test')); // 'TEST' console.log(find(items, ['id', id])) // TypeError: "Object(...)(...) is un ...

javascript + react - managing state with a combination of different variable types

In my React application, I have this piece of code where the variable items is expected to be an array based on the interface. However, in the initial state, it is set as null because I need it to be initialized that way. I could have used ?Array in the i ...

Encountering difficulties when attempting to upload a file to Google Cloud Platform using Multer in a Node.js

I am currently experimenting with uploading a single file using Multer and the "multipart/form-data" content type to a Google Cloud Storage bucket. For this task, I am utilizing "Multer.memoryStorage()" and "@google-cloud/storage" try { const docume ...

Is it possible to utilize the lighten css property within ngStyle in Angular?

Having some trouble with the lighten property in ngStyle and it's not working as expected. I have a color variable within my component that I want to utilize like so: <div [ngStyle]="{color: schedule.group.color, background: 'lighten(' ...

Facing a problem with running npm start on jHipster

I am currently working on a jhipster project on my MacBook Pro running macOS Mojave v.10.14.4. After successfully compiling with npm start, the code continues to compile multiple times without any changes. Eventually, it throws the following error: DONE ...

Disabling breakpoints without bounds during TypeScript debugging in Visual Studio Code

While working on my Ubuntu machine using VS Code to debug a Nest.js TypeScript project, I am encountering issues with unbound breakpoints that are not being hit. Despite making various changes in the launch.json and tsconfig.json files, as well as trying o ...

Setting up ReactJS and TypeScript in a fresh MVC5 project from scratch

After extensively trying out various tutorials, I have yet to successfully run a basic MVC5 project using TypeScript and ReactJS. For reference, I have created these projects from scratch in Visual Studio 2015 with .NET 4.6.1, using the ASP.NET Web Applic ...

Navigating with the Angular router to a child route is causing a redirection to the 404

I'm facing a challenge with navigating to a child component from the parent view. This is how my app-routing configuration looks: const routes: Routes = [ { path: '', redirectTo: 'home', pathMatch: 'fu ...

The matInput value remains stagnant and fails to update

I am encountering an issue with a form Input field for username and password. Here is the code snippet: <mat-form-field class="mdb-form-field-modal form-adjustments"> <input (keydown)="emailBtnFocus($event)" tabindex="0" matInput placeholder ...