I have a list of todos that are fetched from MongoDB and they are displayed on one page and when I click on one it opens on another page with URL that equals clicked todo. Now I am trying to get the id from URL and send it to node server but I can't get it.
ngOnInit() {
this.route.params
.subscribe(
(params: Params) => {
let todoId = params['userId'];
console.log(todoId);
}
);
}
console returns undefined.
I found one solution where id is fetched by this line of code, but it only gets id once and when I click on another todo it doesn't log anything.
let id = this.route.snapshot.paramMap.get('id');
console.log(id)
And when I want to send request to server with this:
let id = this.route.snapshot.paramMap.get('id');
this.todoService.getSingleTodo(id)
.subscribe(
(todo: Todo) => {
this.todo = todo;
console.log(todo);
}
);
I got this error in console "message":"Cast to ObjectId failed for value \":id\" at path \"_id\"
Service looks like this:
getSingleTodo(id) {
return this.http.get('http://localhost:3000/todos/:id')
.map( response => response.json().obj)
.map( todo => todo.map(todo => new Todo(todo.todoHeadline,
todo.todoDescription, todo._id)));
}
And node file:
router.get('/:id', (req, res, next) => {
console.log(req.params.id);
Todo.findById(req.params.id, (err, singleTodo) => {
if (err) {
return res.status(500).json({
title: 'An error occured',
error:err
});
}
res.status(201).json({
message: 'Success',
obj: singleTodo
});
});
});
Also this console prints :id.
Main routing file
const APP_ROUTES: Routes = [
{ path: '', redirectTo: '/auth', pathMatch: 'full' },
{ path: 'todos', component: TodoComponent, children: TODO_ROUTES},
{ path: 'auth', component: AuthenticationComponent, children: AUTH_ROUTES }
];
And children routes for todos
export const TODO_ROUTES: Routes = [
{path: 'todo/add', component: TodoAddComponent},
{path: ':id', component: TodoListComponent},
{path: 'edit', component: TodoEditComponent}
];
HTML where all todos are displayed lools like this
<ul class="list-group">
<li class="list-group-item"
*ngFor="let todo of todos;"
[routerLink]="['/todos', todo.todoId]">{{todo.todoHeadline}}
</li>
</ul>
What could be the problem?
It's this line right here that's wrong:
let todoId = params['userId'];
In your Routing, you defined that the path variable would be named "id":
{path: ':id', component: TodoListComponent},
So when you try to access a path variable userId, it obviously returns undefined.
Related
I have an express.js backend that handles routes and some mock data that is accessed via certain routes. Additionally, there is a get request and post request for receiving and adding documents respectively to the Firestore collection, "books".
const express = require('express');
const app = express();
const port = 8000
const cors = require("cors")
const db = require('./firebase');
app.use(express.json());
app.use(cors());
const stores = [
{
author: 'John Snape',
title: 'Random Book'
},
{
author: 'Herman Melville',
title: 'Moby Dick'
},
{
author: 'William Shakespeare',
title: 'Hamlet'
},
{
author: 'Homer',
title: 'The Iliad'
},
{
author: 'Albert Camus',
title: 'The Stranger'
},
{
author: 'George Eliot',
title: 'Middlemarch'
},
{
author: 'Charles Dickens',
title: 'Great Expectations'
},
{
author: 'William Faulkner',
title: 'The Sound and the Fury'
},
]
//Getting documents in collection "books"
app.get("/books/get", async (req, res) => {
const snapshot = await db.collection("books").get();
const books = [];
snapshot.forEach((doc) => {
books.push({ ...doc.data(), id: doc.id });
});
res.send(books);
});
//Post request for adding document to Firestore Collection "books"
app.post("/books/add", async (req, res) => {
const { title, author } = req.body;
const resp = await db.collection("books").add({
title: req.body.title,
author: req.body.author,
});
res.sendStatus(200);
});
//accessing the mock data
app.get("/api/stores", function(req, res){
res.json(stores)
})
//querying for a specific title
app.get("/api/stores/:title", function(req, res){
const query = req.params.title;
var result=null;
for(var index = 0; index<stores.length; index++){
var item = stores[index];
if(item.title === query){
result = stores[index];
}
}
res.send(result);
});
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
});
I know that the above requests for the express server are working properly as I have tested each request out and used Postman for the Post request.
Now, for the frontend via react, I have displayed the titles and authors from stores (mock data) in a list and given each element in the list a button, which will be used for saving it to the Firestore collection via the POST request from express ('/books/add'). Here is the React code for that component:
import React, { Component } from 'react';
export default class Search extends Component{
constructor(props){
super(props)
this.state = {
results: [],
author: '',
title: '',
};
}
//the below fetch is a get request that gets the elements from the stores variable in the express server.
componentDidMount(){
fetch('/api/stores')
.then(res => res.json())
.then(results => this.setState({results}, ()=> console.log('Books fetched...', results)));
}
render() {
return (
<div>
<h2>result</h2>
<ul>
{this.state.results.map(resu =>
<li key={resu.id}>{resu.title} - {resu.author}
<button/>
</li>
)}
</ul>
</div>
);
}
}
Does anybody know a way to have the POST request take place with the Button's onClick so that the corresponding title and author at that element in the list is passed as a document to the Firestore collection?
This should work. You need to call a function to do post request on the click of the button.
import React, { Component } from 'react';
export default class Search extends Component{
constructor(props){
super(props)
this.state = {
results: [],
author: '',
title: '',
};
}
//the below fetch is a get request that gets the elements from the stores variable in the express server.
componentDidMount(){
fetch('/api/stores')
.then(res => res.json())
.then(results => this.setState({results}, ()=> console.log('Books fetched...', results)));
}
handleSave(data) {
fetch('/books/add'
{
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
method: "POST",
body: JSON.stringify(data)
})
.then(function(res){ console.log(res) })
}
render() {
return (
<div>
<h2>result</h2>
<ul>
{this.state.results.map(resu =>
<li key={resu.id}>{resu.title} - {resu.author}
<button onClick={() => this.handleSave(resu)}/>
</li>
)}
</ul>
</div>
);
}
}
I have a MERN stack application that is modified from a great tutorial I completed. In the original app, transactions were rendered in a list populated from an API call to Mongo Atlas DB. I converted the list to a react-data-table-component and am now trying to figure out how to delete a table row/transaction. The original app had this as part of the transaction component with an onClick button. When I attempt to use the deleteTransaction function, I receive a "TypeError: Cannot read property '_id' of undefined". I can see that the data table renders via the object {transactions}, but cannot figure out why it does not recognize the _id.
Other info: state is managed through the React Context API, with a Router.js and Reducer.js.
TransactionTable.js
import React, { useContext, useEffect } from "react";
// Data table imports
import IconButton from "#material-ui/core/IconButton";
import DeleteIcon from "#material-ui/icons/Delete";
import Card from "#material-ui/core/Card";
import DataTable from "react-data-table-component";
// import transaction component and context provider
import { GlobalContext } from "../context/GlobalState";
// create data table component
export const TransactionTable = () => {
const { transactions, getTransactions, deleteTransaction } = useContext(
GlobalContext
);
// react-data-table-component Columns for back-end data
const columns = [
{
name: "Transaction",
selector: "text",
sortable: true
},
{
name: "Amount",
selector: "amount",
sortable: true,
// conditionally render amount if positive or negative
conditionalCellStyles: [
{
when: row => row.amount > 0,
style: {
color: "green"
}
},
{
when: row => row.amount < 0,
style: {
color: "red"
}
}
]
},
{
// where I'm attempting to pass the transactions prop and apply the deleteTransaction function
// using the delete button that renders in each row
cell: ({ transactions }) => (
<IconButton
aria-label="delete"
color="secondary"
onClick={() => deleteTransaction(transactions._id)}
>
<DeleteIcon />
</IconButton>
)
}
];
useEffect(() => {
getTransactions();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<div>
<Card style={{ height: "100%" }} p={2} mx="auto">
<DataTable
title="Transactions"
columns={columns}
data={transactions}
defaultSortField="Transactions"
//actions={actions}
pagination={true}
highlightOnHover={true}
dense={true}
/>
</Card>
</div>
);
};
./controllers/transactions.js - this is where the deleteTransaction function is
const Transaction = require('../models/Transaction');
// #desc Get all transactions
// #route GET /api/v1/transactions
// #access Public
exports.getTransactions = async (req, res, next) => {
try {
const transactions = await Transaction.find();
//const result = result.transaction.toString()
return res.status(200).json({
success: true,
count: transactions.length,
data: transactions
});
} catch (err) {
return res.status(500).json({
success: false,
error: 'Server Error'
});
}
}
// #desc Add transaction
// #route POST /api/v1/transactions
// #access Public
exports.addTransaction = async (req, res, next) => {
try {
const { text, amount } = req.body;
const transaction = await Transaction.create(req.body);
return res.status(201).json({
success: true,
data: transaction
});
} catch (err) {
if(err.name === 'ValidationError') {
const messages = Object.values(err.errors).map(val => val.message);
return res.status(400).json({
success: false,
error: messages
});
} else {
return res.status(500).json({
success: false,
error: 'Server Error'
});
}
}
}
// #desc Delete transaction
// #route DELETE /api/v1/transactions/:id
// #access Public
exports.deleteTransaction = async (req, res, next) => {
try {
const transaction = await Transaction.findById(req.params.id);
if(!transaction) {
return res.status(404).json({
success: false,
error: 'No transaction found'
});
}
await transaction.remove();
return res.status(200).json({
success: true,
data: {}
});
} catch (err) {
return res.status(500).json({
success: false,
error: 'Server Error'
});
}
}
According to the docs https://www.npmjs.com/package/react-data-table-component#custom-cells, each cell is passed an object named row by convention (you can name it to whatever you want)..
This row object should have the _id you need..
// react-data-table-component Columns for back-end data
const columns = [
// ... column items,
{
cell: row => (
<IconButton
aria-label="delete"
color="secondary"
onClick={() => deleteTransaction(row._id)}
>
<DeleteIcon />
</IconButton>
)
}
]
Each row basically represents a single transaction.
I'm having a problem identifying a 'task' in mongoDB from my frontend angular.
This question is the most similar to my question but here it just says req.body.id and doesn't really explain how they got that.
This question involves what I am trying to do: update one document in a collection upon a click. What it does in the frontend isn't important. I just want to change the status text of the Task from "Active" to "Completed" onclick.
First I create a task and stick it in my database collection with this code:
createTask(): void {
const status = "Active";
const taskTree: Task = {
_id: this._id,
author: this.username,
createdBy: this.department,
intendedFor: this.taskFormGroup.value.taskDepartment,
taskName: this.taskFormGroup.value.taskName,
taskDescription: this.taskFormGroup.value.taskDescription,
expectedDuration: this.taskFormGroup.value.expectedDuration,
status: status
};
this.http.post("/api/tasks", taskTree).subscribe(res => {
this.taskData = res;
});
}
When I make this post to the backend, _id is magically filled in!
I'm just not sure how I can pass the id to the put request in nodejs router.put('/:id') when I'm pushing it from the frontend like this:
completeTask(): void {
const status = "Completed";
const taskTree: Task = {
_id: this._id,
author: this.username,
createdBy: this.department,
intendedFor: this.taskFormGroup.value.taskDepartment,
taskName: this.taskFormGroup.value.taskName,
taskDescription: this.taskFormGroup.value.taskDescription,
expectedDuration: this.taskFormGroup.value.expectedDuration,
status: status
};
console.log(taskTree);
this.http.put("/api/tasks/" + taskTree._id, taskTree).subscribe(res => {
this.taskData = res;
console.log(res);
});
}
In the template I have a form that's filled in and the data is immediately outputted to a task 'card' on the same page.
When I send the put request from angular, I get a response in the backend just fine of the response I ask for in task-routes.js:
router.put("/:id", (req, res, next) => {
const taskData = req.body;
console.log(taskData);
const task = new Task({
taskId: taskData._id,
author: taskData.author,
createdBy: taskData.createdBy,
intendedFor: taskData.intendedFor,
taskName: taskData.taskName,
taskDescription: taskData.taskDescription,
expectedDuration: taskData.expectedDuration,
status: taskData.status
})
Task.updateOne(req.params.id, {
$set: task.status
},
{
new: true
},
function(err, updatedTask) {
if (err) throw err;
console.log(updatedTask);
}
)
});
The general response I get for the updated info is:
{
author: 'there's a name here',
createdBy: 'management',
intendedFor: null,
taskName: null,
taskDescription: null,
expectedDuration: null,
status: 'Completed'
}
Now I know _id is created automatically in the database so here when I click create task & it outputs to the 'card', in the console log of task after I save() it on the post request, taskId: undefined comes up. This is all fine and dandy but I have to send a unique identifier from the frontend Task interface so when I send the 'put' request, nodejs gets the same id as was 'post'ed.
I'm quite confused at this point.
So I finally figured this out...In case it helps someone here's what finally worked:
First I moved my update function and (patch instead of put) request to my trigger service:
Trigger Service
tasks: Task[] = [];
updateTask(taskId, data): Observable<Task> {
return this.http.patch<Task>(this.host + "tasks/" + taskId, data);
}
I also created a get request in the trigger service file to find all the documents in a collection:
getTasks() {
return this.http.get<Task[]>(this.host + "tasks");
}
Angular component
Get tasks in ngOnInit to list them when the component loads:
ngOnInit() {
this.triggerService.getTasks().subscribe(
tasks => {
this.tasks = tasks as Task[];
console.log(this.tasks);
},
error => console.error(error)
);
}
Update:
completeTask(taskId, data): any {
this.triggerService.updateTask(taskId, data).subscribe(res => {
console.log(res);
});
}
Angular template (html)
<button mat-button
class="btn btn-lemon"
(click)="completeTask(task._id)"
>Complete Task</button>
// task._id comes from `*ngFor="task of tasks"`, "tasks" being the name of the array
//(or interface array) in your component file. "task" is any name you give it,
//but I think the singular form of your array is the normal practice.
Backend Routes
GET all tasks:
router.get("", (req, res, next) => {
Task.find({})
.then(tasks => {
if (tasks) {
res.status(200).json(tasks);
} else {
res.status(400).json({ message: "all tasks not found" });
}
})
.catch(error => {
response.status(500).json({
message: "Fetching tasks failed",
error: error
});
});
});
Update 1 field in specified document (status from "Active" to "Completed"):
router.patch("/:id", (req, res, next) => {
const status = "Completed";
console.log(req.params.id + " IT'S THE ID ");
Task.updateOne(
{ _id: req.params.id },
{ $set: { status: status } },
{ upsert: true }
)
.then(result => {
if (result.n > 0) {
res.status(200).json({
message: "Update successful!"
});
}
})
.catch(error => {
res.status(500).json({
message: "Failed updating the status.",
error: error
});
});
});
Hope it helps someone!
I have two directories where vue, node exist. And I have the vue build file in the node folder.
I am currently processing requests from nodes in the vue. However, the event occurs but the data does not cross.
I have the following code, I sent form data via create, but the return data is empty. Also, in mongodb, title and content are require: true, so I get an error like the title.
Please help me.
node/routes/api
...
const Post = require('../db/post');
router.post('/new', (req, res) => {
const post = new Post({
title: req.body.title,
content: req.body.content
});
post.save((err) => {
if (err) {
console.error(err);
res.json({ result: 0 });
return;
}
res.json({ result: 1 });
});
});
...
vue/src/component/new
<template>
<div id="form-group">
name : <input v-model="post.title">
content : <input v-model="post.content">
<button v-on:click="new" >New</button>
</div>
</template>
<script>
export default {
data: function () {
return {
post: {}
}
},
methods: {
new: function (evt) {
this.$http.post('/api/post/new', {
post: this.post
})
.then((response) => {
if (response.data.result === 0) {
alert('Error')
}
if (response.data.result === 1) {
alert('Success')
this.$router.push('/')
}
})
.catch(function (error) {
alert('error')
})
}
}
}
</script>
Hey guys I have this simple handler that reads a mysql table and returns the json obj to the route like so.
handler
var PostStore = {};
PostStore.getAllPosts = function(){
conn.query('SELECT * FROM posts',function(err, result){
if(err){
console.log(err);
}
console.log(JSON.parse(JSON.stringify(result)));
return JSON.parse(JSON.stringify(result));
});
}
module.exports = PostStore;
router
{
path: '/',
method: 'GET',
handler: function (request, reply) {
console.log(PostStore.getAllPosts);
reply.view('index', {
title: 'My home page',
posts: PostStore.getAllPosts
});
}
}
index.html
<h1>{{title}}</h1>
{{#each posts}}
<h1>{{this.title}}</h1>
{{/each}}
Here is what the console output looks like
[Function]
[ { id: 1,
title: 'Hello World',
body: 'My First Post on this cool Hapi Blog!',
date: null } ]
As you can see the sql result is parsed in to a JSON obj but not read from handlebars. Also note that the {{title}} is displaying "My home page" as expected.
Any help would be much appreciated! Thank you.
PostStore.getAllPosts is async, you need to render the view inside it's callback function.
PostStore.getAllPosts(function (err, posts) {
// render the view
});
The only way to render async method in view is to call this methods in a pre handler and assign the return value so you can render the returned data inside the view. See here in hapi's documents for further explanation http://hapijs.com/api#route-prerequisites.
In PostStore routines this code
PostStore.getAllPosts = function(callback){
conn.query('SELECT * FROM posts',function(err, result){
if(err){
return callback(err);
}
return callback(result);
});
}
Then in handlers code
const getPosts = function (request, reply) {
... // do something async to set posts
PostStore.getAllPosts(function(err, results) {
if (err) {
return reply('Error occurred')
}
reply(results);
};
};
server.route({
method: 'GET',
path: '/',
config: {
pre: [
{ method: getPosts, assign: 'posts' }
],
handler: function (request, reply) {
reply.view('index', {
title: 'My home page',
posts: request.pre.posts,
});
}
}
});