Generate a map that returns zero elements in a Typescript/Angular function

I recently started learning Angular and am currently working on a practice app. I have a feature where the app takes in a property file as input from HTML, processes the file, and stores the values in a map using TypeScript. While I can successfully store the values in the map, I'm facing an issue accessing this map from another component.

File-reader-component.ts

export class FileReaderComponent {
    static readFileAsMap(file: File): Map<string, string> {
        let map :Map<string, string>= new Map<string, string>();

        let fileReader = new FileReader();
        fileReader.onloadend = (e) => {
            // By lines
            let lines = fileReader.result.toString().split('\n');
            for(let line = 0; line < lines.length; line++){
                if(lines[line].startsWith('#') || 
                    lines[line].startsWith('//') ||
                    ! lines[line].includes('=') ) {
                    // invalid line - ignoring it
                    continue;
                }
                let lineArr = lines[line].split('=');
                let key = lineArr[0].trim();
                let value = lineArr[1].trim();

                map.set(key, value);
                // not checking duplicate keys to let override the config
            }
        };

        fileReader.readAsText(file);
        console.log(map);
        return map;
    }
}

In the code above, the console.log(map) function works correctly. However, when calling this method in another component(as shown below), it returns a map with 0 elements.

let config: Map<string, string> = FileReaderComponent.readFileAsMap(configFile);

Answer №1

Initially, the reason for the map returning with 0 elements is due to the asynchronous nature of the onloadend function being called. readAsText function itself operates asynchronously and when it completes, it triggers the onloadend event. There is no guarantee that this event will be triggered before the return statement is executed.

To address this asynchronicity, one must consider using a promise.

Regarding the puzzling aspect of console.log appearing non-trivial, this can be attributed to the browser performing a deceptive action. When console.logging a reference, by the time you view it in the terminal, the reference has already been populated, leading the browser to display it as if it were already filled.

In order to witness this behavior firsthand, paste the following code into your terminal:

function f() { 
    const x = {}; 
    console.log(x); 
    setTimeout(() => {x[1] = 2}) 
}

After invoking f(), one would logically expect to see {}. However, due to the browser's handling, you will observe { 1: 2 } displayed instead.

Answer №2

The map variable is set asynchronously with values. One approach is to return an observable (using tools like RxJS Subject) from the function. Give this a try:

static readFileAsMap(file: File): Observable<Map<string, string>> {
  const result = new Subject<Map<string, string>>();

  let fileReader = new FileReader();
  fileReader.onloadend = (e) => {
      // Process each line
      let map: Map<string, string> = new Map<string, string>();
      let lines = fileReader.result.toString().split('\n');
      for(let line = 0; line < lines.length; line++){
          if(lines[line].startsWith('#') || 
              lines[line].startsWith('//') ||
              !lines[line].includes('=') ) {
             continue;
          }
          let lineArr = lines[line].split('=');
          let key = lineArr[0].trim();
          let value = lineArr[1].trim();

          map.set(key, value);
      }
      result.next(map);          // Push the value here
  };

  fileReader.readAsText(file);
  console.log(map);
  return result.asObservable();  // Return an observable
}

Now, you need to subscribe to the readFileAsMap() function to access the values.

let config: Map<string, string>;

FileReaderComponent.readFileAsMap(configFile).subscribe(
  value => { config = value; }
);

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

Experiencing 429 Too Many Requests error on Angular 7 while attempting to perform multiple file uploads

Whenever I attempt to upload a large number of files simultaneously, I encounter an issue. The API interface only allows for the submission of one file at a time, requiring me to call the service for each individual file. Currently, my code looks like thi ...

Is there a way for me to showcase a particular PDF file from an S3 bucket using a custom URL that corresponds to the object's name

Currently, I have a collection of PDFs stored on S3 and am in the process of developing an app that requires me to display these PDFs based on their object names. For instance, there is a PDF named "photosynthesis 1.pdf" located in the biology/ folder, and ...

Angular application utilizes role-based approach for populating elements

I am currently developing a project that involves tightly coupling UI components with the permissions assigned to the logged-in user role. (Angular 4 for front end and Spring for backend) Upon successful login, the backend server returns a user object alo ...

"Exploring Angular Testing with RouterLink and Jasmine: Overcoming the Challenge of

I encountered an issue while testing the Navbar component. Every time I try to test the button click on the logout key, Jasmine's test gets stuck in an infinite loop. describe('NavbarComponent', () => { let component: NavbarComponent; ...

Empty dialog box displayed in production for Angular material datepicker

I'm facing an issue that I can't seem to figure out. The code works perfectly fine when I run it locally using ng build and ng serve. However, when I moved it to production, it started misbehaving. When I tried to replicate the issue in my local ...

Enable child classes to overwrite using either a function attribute or a method

class Foo { foo: () => void } class Bar extends Foo { foo() {} } Is there a way in which TypeScript can be configured to allow the scenario described above? Playground The 'Foo' class defines a property 'foo' as an instance ...

When running scripts, Protractor is unable to perform a click action in Safari, even though it works perfectly in

Currently, I am in the process of developing an angular application and utilizing directconnect for Chrome and Firefox. All my test scripts are functioning as expected, however, a new requirement has been introduced to run these same tests on Safari. To ...

What is the process for uploading an image with express-fileupload?

Looking to upload an image to Cloudinary via Postman using the express-fileupload library for handling multipart forms. Here is a snippet from my index.ts file: import fileUpload from "express-fileupload"; app.use(fileUpload()); In my controller ...

When subscribed to, the BehaviorSubject will return the object twice

When working with a bank API for payments, the response expected by the banks is that Ban Pay JavaScript will perform an HTTP redirect in the top frame to a completeUrl. The question arises - what should be the completeUrl that I need to provide to the ban ...

Switching out a traditional class component with a functional component within a hook to deduce properties from T

One challenge is to subtract props from T within the withHookFn function using a function instead of a class as successfully done in the withHook function. The code contains comments explaining this issue. Dive into the code for more insights. import Reac ...

Illustrative demonstration of Vue with TypeScript

I am currently working on developing a HelloWorld application using Vue.js and TypeScript. index.html <script data-main="app.js" src="node_modules/requirejs/require.js"></script> <div id="app">{{text}}</div> app.ts import Vue f ...

Error in DraftJS: The parameter 'htmlConverter' does not match the expected type 'ContentState'

Utilizing the convertFromHTML function from draft-convert library, I transform my HTML string into an object that can be used as a parameter in EditorState.createWithContent from the draftjs package (as outlined in the README file). However, when attempti ...

Angular Component Encounters 401 Error Connecting to Youtube API

I've been working on a project in Angular that involves retrieving a list of YouTube videos using the YouTube API. Below is the service I've put together to handle this task. import { Injectable } from '@angular/core'; import { HttpClie ...

Multiple subscriptions without having to recalculate the shared components

const ex = ajax.getJSON('https://httpbin.org/get?a=24'); ex.pipe(pluck('args')).subscribe(x => console.log('Arguments: ', x)); ex.pipe(pluck('headers')).subscribe(x => console.log('Response Headers: ' ...

Subscribing to ngrx store triggers multiple emissions

Currently, I have an app with a ngrx store set up. I am experiencing an issue where, upon clicking a button, the function that fetches data from the store returns multiple copies of the data. Upon subsequent clicks, the number of returned copies grows expo ...

I'm curious about how to link a JSON field using dot notation in Angular 12 HTML

Does anyone know how to bind a JSON field using dot paths in Angular 12 HTML? For example: //Angular data: any = { name: 'x1', address: { city: 'xyz' } }; field: any = 'address.city'; //Html <input [(ngModel)]="data[ ...

Guide to incorporating Angular 2 onto a specific section of a webpage

As a student of Angular 2, I have a question about utilizing specific parts of a web page. Is it possible to use certain Angular 2 utilities, such as *ngFor, in only designated areas - for example, within just a div element? While this was feasible in An ...

Accessing URL fragments in Next JS with context during the execution of getServerSideProps

I am trying to extract a URL fragment using getServerSideProps. The URL is structured like this: http://localhost:3000/some-folder#desiredParam=value Even though I pass the context as an argument to the getServerSideProps function, I am struggling to retr ...

A guide on instantly updating displayed flat/section list elements in React Native

I am in the process of creating a screen called ContactListScreen. The direct child of ContactListScreen is ContactItems, which is a sectionList responsible for rendering each individual ContactItem. However, I have encountered a problem where my ContactIt ...

Switch from flexbox layout to begin on the next column within the same row

I am attempting to create a CSS layout where, after a specific element like :nth-child(6), the elements will break and form a separate column within the same row. The parent element should split into 2 columns after every 6th element in the same row, then ...