angularexpressangular-materialjavascript-objectsnode.js-connect

Angular Post call with an Array of Objects to Express is not working, not even talking with each other


I'm working with a particular Post call that passes an array that doesn't want to talk to Express Node.js server I created to make MySQL queries. Here's the method code that starts the whole thing:

    questionsSubmit(){
    console.log('Questions Submited')
    // Initialize an array to store the answers
    const answers: {participantId: number, optionId: number, scorePoints: number}[] = [];
    
    // Iterate over the form controls
    Object.keys(this.questionsForm.controls).forEach(key => {
      const control = this.questionsForm.controls[key]
      
      // Check if the control is a FormGroup and contains 'value' property
      if (control instanceof FormControl && control.value) {

        const participantId = this.participantId
        const [optionId, scorePoints] = control.value.split('-').map(Number)
        
        // Create an Answer object and push it to the answers array
        answers.push({
          participantId,
          optionId,
          scorePoints
        })
      }
    })
    if(!this.participantId){
      this.showAlert = true
      this.step = 0
      console.log('Please complete and submit the Participant\'s profile before submitting to the Needs questionnaire' )
      return
    }
    if(this.questionsForm.valid){
      
      this.masterService.saveAnswers(answers)
    }
    // console.log(answers);
  }

This Component.ts code works just fine gathering all the necessary FormControls and assembling an array of objects. I've verified a millions times and this is always the result from console.logConsole Log of the Array is assembled Then it calls a Service.ts method to Post the array to the Server. Here's the method:

saveAnswers(answers: {participantId: number, optionId: number, scorePoints: number}[]){

 return this.http.post<{participantId: number, optionId: number, scorePoints: number}[]>(`http://localhost:3000/hello`, answers, this.httpOptions)
 .pipe(catchError(this.errorHandlerService.handleError<any>("post")));
 
}   

But here is where all communications go to die. No errors in the browser console, and no errors in the Backend terminal. I've put a million console.logs in every place and nothing, Nada!!!. The crazy thing is that I have GETs, PUTs, DELETEs, and other POSTs and they all work fine. The Server POST to the Database works just fine too. I used Postman to push the same data and it works.

My Server consists of 6 files (An Index.js for the Middleware to start the Server, 1 Route file, a Controller to handle Requests and Responses, and a Model file for Queries to the Database, the last 2 are just to handle the connection with the Database)

Index.js

const express = require('express')
const bodyParser = require('body-parser');
const app = express()
const ports = process.env.PORT || 3000
const participantRoutes = require('./routes/participant-routes')
const questionRoutes = require('./routes/question-routes')
const errorController = require('./controllers/error')

// Middleware to parse JSON bodies
app.use(express.json())
// app.use(bodyParser.json())

app.use((req, res, next) => {
console.log(`Incoming request: ${req.method} ${req.url}`);
next();
});

app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*')
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE')
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization')
next()
})

// Routes
app.use('/participants', participantRoutes);

app.use('/hello', questionRoutes);
// http://localhost:3000/questions-options-answers

app.use(errorController.get404)
app.use(errorController.get500)


app.listen(ports, () => console.log(`Hello_Kitty (Server): listening on port ${ports}`))

Router.js

const express = require('express')
const controller = require('../controllers/controller')
const router = express.Router()

// Question Routes
router.get('/', controller.getAllQuestionsWithOptions);
router.post('/', controller.postAnswersFromOptions);
console.log('Hello From Routes')

module.exports = router

Controller.js

const Question = require('../models/question-model')

exports.getAllQuestionsWithOptions = async (req, res, next) => {
  try {
    const questionsWithOptions = await Question.fetchAllQuestions()
    res.status(200).json(questionsWithOptions)
  } catch (error) {
    console.error('Error fetching questions with options:', error)
    res.status(500).json({ error: 'Internal server error' })
  }
}

exports.postAnswersFromOptions = async (req, res, next) => {
  try {
    console.log('From postAnswersFromOptions')
    console.log(req.body)
    const postAnswersWithOptions = await Question.postsAnswers(req.body)
    res.status(200).json(postAnswersWithOptions)
  } catch (error) {
    console.error('Error fetching questions with options:', error)
    res.status(500).json({ error: 'Internal server error' })
  }
}

Model.js

const db = require('../util/database')
const Query = require('./query-builder')
const mysql = require('mysql');

module.exports = class Question {
constructor(question, options) {
 this.question = question;
 this.options = options;
}

static async fetchAllQuestions() {
 try {
   // Fetch questions from the database
   const [questions] = await db.execute('SELECT * FROM Questions');

   // Fetch options for each question
   const questionsWithOptions = [];
   for (const question of questions) {
     const [options] = await db.execute('SELECT * FROM Options WHERE questionId = ?', [question.questionId]);
     const formattedOptions = options.map(option => ({ id: option.optionId, option: option.questionOption,
                                                       points: option.points}));
     questionsWithOptions.push({ question: question.question, options: formattedOptions });
   }

   return { questions: questionsWithOptions };
 } catch (error) {
   console.error('Error fetching questions with options:', error);
   throw error
 }
}

static async postsAnswers(answers){
 
 try {
   const columns = Object.keys(answers[0]);

   const values = answers.map(obj => `(${columns.map(col => mysql.escape(obj[col])).join(', ')})`).join(', ');

   const query = `INSERT INTO Answers (${columns.join(', ')}) VALUES ${values}`;

   console.log('Query for post answers')
   console.log(query)
   console.log(values)
   
   return await db.execute(query, [...Object.values(values)])
   
 } catch (error) {
   console.error(error)
   throw error 
 }
 
}

}

If I use Postman with the same Array I showed earlier the Server calls work just fine and the Database too, the issue is that it doesn't want to work with my Angular app. The Express server works fine with other calls except for the one I mentioned, and the biggest problem is that I get no errors from anywhere. Not from the Browser console, not from the Terminal, etc. Please can you tell me what I'm missing or point me in the right direction? I've been using ChatGPT heavily so I've done some extensive troubleshooting. I know that the problem might be small, as they often are, but I plead with you guys to help.


Solution

  • When you call saveAnswers, it returns an Observable<...>.

    Observables only execute when they are subscribed to (see https://angular.io/guide/observables#subscribing).

    When you call this.masterService.saveAnswers(answers) in your first snippet, it really is calling the saveAnswers method but no data is actually transmitted as this.http.post() is waiting for a listener before doing any work.

    Your frontend code is correct and does not throws any errors as you can do the following :

    let answersObservable: Observable<any>;
    if (this.questionsForm.valid) {
        answersObservable = this.masterService.saveAnswers(answers)
    } else {
        answersObservable = this.masterService.countWrongAnswers(answers)
    }
    
    answersObservable.subscribe({
        next: _ => {
            /* Only do the following when we received a response from the server, can be either saveAnswers or countWrongAnswers */
        }
    })
    

    In your snippet, you can swap your following code :

        if(this.questionsForm.valid){
          this.masterService.saveAnswers(answers)
        }
    

    with this if you don't care about the server response :

        if(this.questionsForm.valid){
          this.masterService.saveAnswers(answers).subscribe(_=>{})
        }
    

    or if you want to handle the response :

            if(this.questionsForm.valid){
              this.masterService.saveAnswers(answers).subscribe({
                next: result => {
                    displayResult(result)
                },
                error: error => {
                    showError(error)
                }
              })
            }