Search code examples
angularangular-routing

Angular router causing issues


Edit: I have uploaded the project to StackBlitz https://stackblitz.com/edit/angular-ivy-kfyhcp?file=src/app/chat-view/chat-view.component.ts

Through more testing I found out that the issue is being caused by this.router.navigate(['view']); on line 54 in file-upload.component.ts. If I remove this and just embed the ChatViewComponent on my page normally instead of within a router-outlet, the app works as expected. I will update with a solution if I figure out how to get the functionality working from within a router-outlet.

import { Component, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MessageParsingService } from '../services/message-parsing.service';
import { Router } from '@angular/router';

@Component({
  selector: 'file-upload',
  templateUrl: './file-upload.component.html',
  styleUrls: ['./file-upload.component.scss'],
})
export class FileUploadComponent implements OnInit {
  fileName = '';

  constructor(
    private messageParsingService: MessageParsingService,
    private router: Router,
    public dialog: MatDialog
  ) {}

  ngOnInit(): void {}

  readFileContent(file: File): Promise<string> {
    return new Promise<string>((resolve) => {
      if (!file) {
        resolve('');
      }

      const reader = new FileReader();

      reader.onload = (e) => {
        const text = reader.result!.toString();
        resolve(text);
      };
      reader.readAsText(file);
    });
  }

  async onFileSelected(event: any) {
    const file: File = event.target.files[0];
    const fileReader = new FileReader();

    if (file) {
      console.log(file.type + ' uploaded');
      this.fileName = file.name;
      const formData = new FormData();
      formData.append('thumbnail', file);
      const fileContent = await this.readFileContent(file);
      console.log(this.fileName);
      if (file.type == 'application/json') {
        this.messageParsingService.parseJson(fileContent);
      }
      this.router.navigate(['view']);
    }
  }
}

I have the following Angular service to parse a JSON file of messages, which works as expected, and is using an EventEmitter to send the parsed messages to a display component, which is not working as it's supposed to:

import { EventEmitter, Injectable } from '@angular/core';
import { Message } from '../models/message';
import { ParseEvent } from './parse-event';

@Injectable({
  providedIn: 'root'
})
export class MessageParsingService {

  public onParseComplete: EventEmitter<ParseEvent> = new EventEmitter<ParseEvent>();
  public messages: Message[];

  constructor() {
    this.messages = [];
  }

  public parseJson(jsonString: string) {
    let jsonObj = JSON.parse(jsonString);
    this.messages = jsonObj.chats[0].messages.map((item: { timestamp: any; fromMe: any; text: any; }) => {
      var msg = {
        timestamp: item.timestamp,
        fromMe: item.fromMe,
        text: item.text,
      }
      this.onParseComplete.emit(new ParseEvent(msg));
      return msg;
    });
    console.log("Messaged parsed by service: " , this.messages);
  }
}

Sample JSON file I'm using for testing:

{
    "chats": [
        {
            "contactName": "Test",
            "messages": [
                {
                    "timestamp": "2022-12-11T17:00:12Z",
                    "id": "35A8984427489ACE786A9F5DFA81E7",
                    "fromMe": true,
                    "type": "text",
                    "text": "Hello!"
                },
                {
                    "timestamp": "2022-12-11T21:34:04Z",
                    "id": "009715FBED647B52CD4EA6AE9A7CA4",
                    "fromMe": false,
                    "type": "text",
                    "text": "How are you?"
                },
                {
                    "timestamp": "2022-12-11T21:34:23Z",
                    "id": "479AFD522FEC7444991CCA578EDF9F",
                    "fromMe": false,
                    "type": "text",
                    "text": "What are you doing today?"
                }
            ]
        }
    ]
}

Code for the ParseEvent:

import { Message } from "../models/message";

export class ParseEvent {
    message: Message;

    constructor(m: Message){
        this.message = m;
    }
}

Component to view messages:

import { Component, OnInit } from '@angular/core';
import { Message } from '../models/message';
import { MessageParsingService } from '../services/message-parsing.service';
import { ParseEvent } from '../services/parse-event';

@Component({
  selector: 'app-chat-view',
  templateUrl: './chat-view.component.html',
  styleUrls: ['./chat-view.component.scss']
})
export class ChatViewComponent implements OnInit {
  private _serviceSubscription;
  messages: Message[];

  constructor(private messageParsingService: MessageParsingService) {
    this.messages = [];
    this._serviceSubscription = this.messageParsingService.onParseComplete.subscribe({
      next: (event: ParseEvent) => {
          console.log('Received message from parseEvent', event.message);
          this.messages.push(event.message);
      }
    })  
  }

  public logMessages(){
    console.log("Log current messages: ", this.messages);
  }

  ngOnInit(): void {
    console.log('chat view component msgs: ', this.messages);
  }
}

HTML for display component:

<app-message
    *ngFor="let m of messages"
    [timestampInput]="m.timestamp"
    [fromMeInput]="m.fromMe"
    [textInput]="m.text"
>
</app-message>

<button mat-raised-button (click)="logMessages()">Log current messages</button>

In my view component, I have subscribed to the above event emitter from the parsing service. Each time an event is triggered, I am pushing the message attached to the event onto the list in ChatViewComponent.ts - I can see the messages passing through via the console.log statements, as shown in the screenshot below. However when I try and access the list from outside of the subscribe block, for example in the logMessages() button, or within ChatViewComponent.html, it returns an empty list. I tried refactoring my code to use a BehaviorSubject and Observable instead of EventEmitters, but faced the same issue where the list stays empty. I also tried changing the list in ChatViewComponent to type 'any' just in case the objects were being formatted incorrectly, but that didn't help either.

enter image description here


Solution

  • After checking your code on stackblitz, I recoment you to refactor the code a little

    • Don't keep data in components. You should move the function to a Service
    • I created the service and added "add" method in one
    • Data moves beetwen service and component with help an observable

    so, your refactored code is below https://stackblitz.com/edit/angular-ivy-exyyf6?file=src%2Fapp%2Fservices%2Fmessage.service.ts