Angular - Delay template loading until data is received

I am currently working on a component that dynamically renders several components using the following template:

<div [saJquiAccordion]="{active: group.value['collapsed']}" *ngFor="let group of filterGroupsTemplate | keysCheckDisplay;">
    <div>
        <h4>{{group.key | i18n}}</h4>
        <form id="ibo-{{group.key}}" class="form-horizontal" autocomplete="off" style="overflow: initial">
            <fieldset *ngFor="let field of group.value | keys">
                <ng-container *ngComponentOutlet="fieldSets[field.value.template];
                                    ngModuleFactory: smartadminFormsModule;"></ng-container>
            </fieldset>
        </form>
    </div>
</div>

The challenge I'm facing is that the data required to populate these components is fetched from an API call:

      this.getFiltersSubscription = this.getFilters().subscribe(
            (filters) => {
                this.filters = filters;
                log.info('API CALL. getting filters');

                // Sending data to fieldform components
                this.iboService.updateIBOsRankList(filters['iboRank'].data);
                this.iboService.updateIBOsNewsletterOptions(filters['iboNewsletter'].data);
                this.iboService.updateIBOsTotalOrders(filters['iboTotalOrders'].data);
            }
        );

After obtaining the data, I activate a service Observable that my components subscribe to for processing the collected data.

ISSUE

If the API call happens before all components are loaded, the service methods will be triggered with no subscribers.

A potential solution would involve loading the data first, rendering the template only when the data is loaded, and then triggering the service methods (Observables).

I aim to avoid making multiple API calls for each component, considering there could be around 60 components to load. Instead, I prefer a more streamlined approach like this:

// Listens for field initialization and triggers the creation of the fieldset by initiating a service call that will be listened to by the field component
        this.iboService.initIBOsFilters$.subscribe(
            (fieldName) => {
                if (fieldName === 'IBOsRankSelectorFieldComponent') {
                    log.data('inside initIBOsFilters$ subscription, calling updateIBOsFilters()', fieldName);
                    this.iboService.updateIBOsRankList(this.filters['iboRank'].data); // HERE I'M PASSING DATA TO THE DYNAMICALLY RENDERED COMPONENT. HOWEVER, IF this.filters IS UNDEFINED, IT WILL CAUSE AN ISSUE
                }
            }
        );

To achieve this, it is crucial to ensure that this.filters is defined. This leads me to the question:

How can I delay rendering my template HTML until after the API call is completed and this.filters is defined?

I apologize for the lengthy explanation, but please feel free to ask for more details if needed.

Thank you!

Answer №1

Upon exploring various suggestions provided by people, I eventually stumbled upon the solution involving the async pipe. However, grasping how to implement it did prove to be a bit of a challenge.

Here is the solution:

// Declaring the Promise, yes! Promise!
filtersLoaded: Promise<boolean>;

// Later in the Component, where I fetch the data, I resolve() the Promise
this.getFiltersSubscription = this.getFilters().subscribe(
    (filters) => {
        this.filters = filters;
        log.info('API CALL. getting filters');

        this.filtersLoaded = Promise.resolve(true); // Resolving the Promise once the necessary data is obtained
    }
);

// In this listener triggered by the dynamic components when instanced,
// I pass the data and know that it is defined due to the template change

// Listens to field's init and creates the fieldset triggering a service call
// that will be listened by the field component
this.iboService.initIBOsFilters$.subscribe(
    (fieldName) => {
        if (fieldName === 'IBOsRankSelectorFieldComponent') {
            log.data('inside initIBOsFilters$ subscription, calling updateIBOsFilters()', fieldName);
            this.iboService.updateIBOsRankList(this.filters['iboRank'].data);
        }
    }
);

In the template, the async pipe is used with an Observable or a Promise

<div *ngIf="filtersLoaded | async">
    <div [saJquiAccordion]="{active: group.value['collapsed']}" *ngFor="let group of filterGroupsTemplate | keysCheckDisplay;">
        <div>
            <h4>{{group.key | i18n}}</h4>
            <form id="ibo-{{group.key}}" class="form-horizontal" autocomplete="off" style="overflow: initial">
                <fieldset *ngFor="let field of group.value | keys">
                    <ng-container *ngComponentOutlet="fieldSets[field.value.template];
                                    ngModuleFactory: smartadminFormsModule;"></ng-container>
                </fieldset>
            </form>
        </div>
    </div>
</div>

Important Note:

  • The async pipe requires an Observable or a Promise for functionality, which led me to create a Promise in this scenario.
  • I opted not to use the resolver approach as it is typically utilized when navigating to a component via Angular's routing system. Since this component is part of a larger one and not instantiated through routing like a regular component, I decided against using that method. (Although I did experiment with it briefly, it didn't yield the desired outcome)

Answer №2

To ensure that the data is loaded or filters are initialized before the route is activated, consider using a resolver.

Visit this link for more information:

You can also learn about resolvers in Angular by checking out this link: https://angular.io/api/router/Resolve

Answer №3

<p class="p-large">{{homeData?.meta[0].site_desc}}</p>

Using a conditional operator "?" after the variable to handle data loading from the server.

home.component.ts

import { Component, OnInit } from '@angular/core';
import { HomeService } from '../services/home.service';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {
  public homeData: any;
  constructor(private homeService: HomeService) {}

  ngOnInit(): void {
    this.homeService.getHomeData().subscribe( data => {
      this.homeData = data[0];
    }, error => {
      console.log(error);
    });
  }
}

Answer №4

To replace the use of async, you can create a variable called isLoading = true. Display a spinner provided by Angular while this variable is true instead of showing content using *ngIf. Once your subscription activates, set isLoading = false. This approach helps avoid errors related to missing data because technically the content that relies on it won't be rendered until it exists.

Answer №5

Although I may be a bit late to the party, what has been effective for me is incorporating a loading boolean into my state within the reducer.

In the reducer.ts file:

export interface State {
 things: Stuff;
 loaded: boolean;
}
export const reducers: ActionReducerMap<State> = {
    things: reducer,
    loaded: loadedReducer,
};

Ensure to export the loaded function so it switches to true once the state is returned.

export function loadedReducer(state: boolean = false, action: ThingActions): boolean {
    switch (action.type) {
        case ThingActionTypes.GetThings:
            return true;
    }
    return state;
}

Then in your Typescript file, subscribe to the loaded state.

In parent.component.ts:

this.loaded$ = this.store.pipe(select(fromReducer.loaded));

Utilize this similar to an async pipe in your template.

In parent.component.html:

        <ng-container *ngIf="(loaded$ | async); else loading">
            <child-component></child-component>
        </ng-container>
        <ng-template #loading></ng-template>

Answer №6

**Note: The following method is not ideal, but it can be used as a quick fix: **

A simple solution would be to enclose it in an ngIf statement. While this may not be the best practice, it does resolve the issue at hand.

The specific error in this case is: "Title of undefined"

<div *ngIf="form">
  {{form.Title}}
</div>

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

Tips for dynamically injecting HTML content in an Angular web application

I'm currently working on an angular website dedicated to a specific book. One of the features I want to include is the ability for users to select a chapter and view an excerpt from that chapter in HTML format. However, I'm facing a challenge wh ...

Functions have been successfully deployed, but they are not appearing on the Azure Portal

I am experiencing difficulties deploying basic Typescript functions to Azure. Despite a successful deployment using VS code and confirmation in the Output window, I cannot see any functions listed in the Azure Portal under the Function App: https://i.stac ...

How to minimize scaffolding with Redux, React, and Typescript?

Is there a way to avoid the process of instrumenting my Redux-Aware components? The level of scaffolding required seems excessive. Take, for instance, the minimal code necessary to define a redux-aware component: class _MyActualComponent extends React.Co ...

Extract the initial sentence or the opening 50 words from a data object in Typescript/JavaScript

Is there a way to extract only the initial line or first 50 words from the data retrieved by the API and store it in a variable? In the HTML File: <td *ngIf="customizedColumns?.details_of_non_conformity?.value"> <span [ngCl ...

Managing multiple `ng-content` slots in Angular can be a daunting task. Here

I am facing an issue with a component where I have declared an input as follows: @Input() isOverlay: boolean The template html for this component looks like this: <ng-template *ngIf="isOverlay" cdkConnectedOverlay [cdkConnected ...

What is the reason behind TypeScript prohibiting the assignment of a className property to a context provider?

Embarking on my first journey with TypeScript, I am in the process of reconfiguring a React application (originally built with create-react-app) to use TS. Specifically, I am working with function components and have introduced a global context named Order ...

Navigating Unknown Properties in Angular: A Guide to Iterating Through Arrays

I'm having trouble coming up with a title for my question. I want to loop through an array of dynamic objects coming from a database, but I don't know the properties of the objects. Here's an example of the array object: [{ "id": 9, ...

Angular 2 Introductory Guide - where to find the necessary files

I recently began diving into Angular 2 and I am in the learning process. However, I encountered an issue after running the command: npm install -g angular-cli. The problem is that I cannot locate the following folder on my hard drive: src/app / ./src/app/a ...

How can I add two values in Angular?

Adding two values is not giving me the expected result. For instance, 1 + 1 = 2, but instead I am obtaining 11. This is my code: this.newpoint = this.data.point + 1; console.log(this.newpoint); The value of this.data.point is 0, but it might be in stri ...

Translating from a higher-level programming language to a lower-level programming language

Is compilation effectively the transformation of high-level programming languages (HLL) into machine code or low-level language? If so, why is TypeScript (a HLL) compiled to JavaScript (also a HLL) instead of being compiled to a low-level language? ...

Step-by-step guide on releasing declaration files (.d.ts) for vscode plugins

I developed a vscode extension that provides an API for other extensions to utilize (by returning a value in the activate() function). I am interested in releasing a scoped npm package containing a declaration file (.d.ts) to help extension developers eas ...

Error: The AppModule encountered a NullInjectorError with resolve in a R3InjectorError

I encountered a strange error in my Angular project that seems to be related to the App Module. The error message does not provide a specific location in the code where it occurred. The exact error is as follows: ERROR Error: Uncaught (in promise): NullInj ...

Issue with retrieving properties in Angular template due to undefined values in HTML

As a newbie to Angular, I am dedicated to improving my skills in the framework. In my project, I am utilizing three essential files: a service (Studentservice.ts) that emits an observable through the ShowBeerDetails method, and then I subscribe to this ob ...

Tips to decrease the bundle size of Angular 2 with WebPack

After experimenting with various Angular 2 seed projects utilizing WebPack, I noticed that when I compile the bundle for production, the resulting file size is around 3MB. Is there a way to minimize the size of this file? An example of one of these proje ...

Leveraging Renderer in Angular 4

Understanding the importance of using a renderer instead of directly manipulating the DOM in Angular2 projects, I have gone through multiple uninstallations, cache clearings, and re-installations of Node, Typescript, and Angular-CLI. Despite these efforts, ...

Error number 13 encountered during Angular CLI installation process

When attempting to install npm install -g @angular/cli, I encountered the following error: npm WARN checkPermissions Missing write access to /usr/local/lib/node_modules npm ERR! path /usr/local/lib/node_modules npm ERR! code EACCES npm ERR! errno -13 npm ...

Why would npm be unable to locate a file, potentially leading to an error? Could the lack of contents in my node_modules subfolder be the root cause of this issue?

I'm encountering an issue while attempting to execute npm install in the angular project directory obtained from ASP.NET Boilerplate. The error I'm facing is related to npm's inability to locate a specific file. D:\Dev\AspNetBoiler ...

Typescript: The type 'X' does not correspond with the signature '(prevState: undefined): undefined' in any way

My React Native app, which is written in TypeScript, has been giving me a hard time with an error lately. The issue revolves around a Searchable List feature. This list starts off with an Array of values and gets updated when users type into a search bar. ...

Firebase is storing object values as 'undefined'

My goal is to retrieve user details from my firebase database while using Ionic and Typescript. Here is how I add a user: addToDatabase(user: User) { let isInstructor = user.isInstructor == null ? false : user.isInstructor; this.afDB.list("/users/").push ...

Is there a way to customize the default styles of Kendo UI for Angular?

Is it possible to customize the default datepicker styles to look like the design in the second image? https://i.sstatic.net/h8yfA.png https://i.sstatic.net/PfiSf.png ...