mongoose cant display private message data - node.js

I am pretty new with mongoose & node.js and I am having an issue with displaying my users messages privately.
The message recipient, sender and message content is stored in the database.
but when I want to see the message's between two users, the messages between two users are displayed on every user messages tab!.
(developing in MEAN stack environment)
The Problem: https://i.imgur.com/qfDlMml.mp4
Messages Schema:
var messageSchema = new mongoose.Schema({
id: {
type: Number,
},
senderId: {
type: Number
},
senderUsername: {
type: String
},
recipientId: {
type: Number
},
recipientUsername: {
type: String
},
content: {
type: String,
required:true
},
dateRead: {
type: Date
},
messageSent: {
type: Date
},
senderDeleted: {
type: Boolean
},
recipientDeleted: {
type: Boolean
}
},
{
collection: 'messages',
timestamps: true
},
);
module.exports = mongoose.model('Messages', messageSchema)
User route:
//Get Message Of User
usersRoute.route("/messages/thread/:username/:senderUsername").get((req, res) => {
Messages.find(({ data: { "$in" : [req.params.senderUsername,req.params.username]} }, (error, data) => {
if (error) {
console.log(error)
throw error;
}
res.json(data);
}))
})
messages.service:
getMessageThread(username: string,sender:string) {
return this.http.get<Message[]>(`${AUTH_API}messages/thread/${username}/${sender}`);
}
member-messages.ts:
import { Component, OnInit, Input } from '#angular/core';
import { NgForm } from '#angular/forms';
import { Message } from 'src/app/models/message';
import { User } from 'src/app/models/user';
import { AccountService } from 'src/app/services/account.service';
import { MessageService } from 'src/app/services/message.service';
import { Observable, take } from 'rxjs'
import { ActivatedRoute } from '#angular/router';
import { MembersService } from 'src/app/services/members.service';
#Component({
selector: 'app-member-messages',
templateUrl: './member-messages.component.html',
styleUrls: ['./member-messages.component.css']
})
export class MemberMessagesComponent implements OnInit {
#Input() username: string;
#Input() messages: Message[];
user: User;
member: any;
currentUser$: Observable<User | null>;
messageContent: string;
constructor(
private messageService: MessageService,
private accountService: AccountService,
private route: ActivatedRoute,
private memberService: MembersService
) {
this.accountService.currentUser$.pipe(take(1)).subscribe(x => {
this.currentUser$ = this.accountService.currentUser$;
this.user = x as User;
});
}
ngOnInit() {
const username = this.route.snapshot.paramMap.get('username') as string;
this.memberService.getMember(username).subscribe(member => {
this.member = member;
});
}
sendMessage(form: NgForm) {
this.messageService.sendMessage(this.username, this.messageContent, this.user)
.subscribe((message) => {
this.messages.push(message as Message);
form.reset();
})
}
}
member-messages.html:
<div class="card">
<div class="card-body">
<ng-container *ngIf="messages && messages.length; else noMessages">
<ul class="chat">
<li *ngFor="let message of messages.slice().reverse()">
<span class="chat-img float-right">
<img class="rounded-circle" *ngIf="message.senderUsername && member.username == message.recipientUsername" src="{{user.profile_img || './assets/user.jpg'}}"> <span *ngIf="message.senderUsername && member.username == message.recipientUsername" style="color:red"> {{message.senderUsername}}</span> <img class="rounded-circle" *ngIf="message.recipientUsername && member.username != message.recipientUsername " src="{{member.profile_img || './assets/user.jpg'}}"><span *ngIf="message.recipientUsername && member.username != message.recipientUsername" style="color:green"> {{member.username}}</span>
</span>
<div class="chat-body" >
<p >{{message.content}}</p>
</div>
</li>
</ul>
</ng-container>
<ng-template #noMessages>No messages Yet... say hi bu using the message box bellow</ng-template>
</div>
<div class="card-footer">
<form #messageForm="ngForm" (ngSubmit)="sendMessage(messageForm)" autocomplete="off">
<div class="input-group">
<input
name="messageContent"
required
[(ngModel)]="messageContent"
type="text"
class="form-control input-sm"
placeholder="Send a private message">
<div class="input-group-append">
<button [disabled]="!messageForm.valid" class="btn btn-primary" style="border-radius:0px 5px 5px 0px"type="submit"> Send </button>
</div>
</div>
</form>
</div>
</div>

I think the problem is that you are trying to query for the data field but this field does not exist in your data model.
Solution is to ask data model for messages which contain senderUsername and recipientUsername in the database.
usersRoute
.route("/messages/thread/:username/:senderUsername")
.get(async (req, res) => {
const query = {
senderUsername: req.params.senderUsername,
recipientUsername: req.params.username,
};
const messages = await Messages.find(query).exec();
res.json(messages);
});

Ok, so I managed to find a work around, created another query only this time the senderUser value will be the recipient username and recipientUsername value as the senderUsername :
const query2 = {
senderUsername: req.params.username,
recipientUsername: req.params.senderUsername,
};
Find with both quires while using $or:[query,query2].
the following example worked:
usersRoute
.route("/messages/thread/:username/:senderUsername")
.get(async (req, res) => {
const query = {
senderUsername: req.params.senderUsername,
recipientUsername: req.params.username,
};
const query2 = {
senderUsername: req.params.username,
recipientUsername: req.params.senderUsername,
};
const messages = await Messages.find({$or:[query,query2]}).exec();
console.log(messages);
res.json(messages);
});
Could there be a better way to do it? I would really like to understand this better.
Thank you #Paweł Antyporowicz for the help :)

Related

Unexpected token " in JSON at position 0

My Goal for this one is to Add ObjectId inside the array
In my backend Im declare schema on this code:
tchStudents: [{
type: Schema.Types.ObjectId,
ref: "Student"
}]
THen Im do adding an ObjectId to insert to the array of ObjectID:
My BackEnd is very fine
router.put('/assignAddStudents/:tchID', async (req,res) => {
try {
const searchTch = await Teacher.findOne({ tchID: req.params.tchID })
if(!searchTch){
return res.status(404).send({
success: false,
error: 'Teacher ID not found'
});
} else {
let query = { tchID: req.params.tchID }
let assignedStudentObjID = {$push:{tchStudents: req.body.tchStudents }}
Teacher.updateOne(query, assignedStudentObjID ,() => {
try{
return res.status(200).send({
success: true,
msg: 'Student ID has been assigned'
});
} catch(err) {
console.log(err);
return res.status(404).send({
success: false,
error: 'Teacher ID not found'
})
}
})
}
} catch (err) {
console.log(err)
}
})
But my Front End Not working
err: BAD REQUEST(400) Unexpected token " in JSON at position 0
import React, {useState} from 'react'
import axios from 'axios'
import { URL } from '../../utils/utils'
import { Modal, Button } from 'react-materialize';
import ListTchStudents from '../lists/ListTchStudents';
const trigger =
<Button
style={{marginLeft:'2rem'}}
tooltip="Add More..."
tooltipOptions={{
position: 'top'
}}
className="btn-small red darken-4">
<i className="material-icons center ">add_box</i>
</Button>;
const MdlAddStudents =({teacher}) => {
const [data, setData] = useState('');
const { tchStudents} = data;
const {
tchID,
} = teacher; // IF WE RENDER THIS IT TURNS INTO OBJECT
const assignedStudent = () => {
// BUT WE SENT IT TO THE DATABASE CONVERT TO JSON.STRINGIFY to make ObjectId
const requestOpt = {
method: 'PUT',
headers: { 'Content-Type': 'application/json'},
body: JSON.stringify(data)
}
axios.put(`${URL}teachers/assignAddStudents/${tchID}`, data,requestOpt)
.then(res => {
setData(res.data.data)
})
}
return (
<Modal header="Add Students" trigger={trigger}>
Please ADD and REMOVE Student ID No. for {tchID}
<div>
<ul
style={{marginBottom:'2rem'}}
className="collection">
{
Object.values(teacher.tchStudents).map(tchStudent => {
return(
<ListTchStudents
tchStudent={tchStudent}
/>
);
})
}
</ul>
<div className="row">
<div className="col s6 offset-s3"></div>
<div className="input-field">
<label
htmlFor=""
className="active black-text"
style={{fontSize:'1.3rem'}}>
Add Students here:
</label>
<input
type="text"
name="tchStudents"
value={(tchStudents)}
className="validate"
onChange={(e) => setData(e.target.value)}
/>
</div>
</div>
</div>
{/* BUT WE SENT IT TO THE DATABASE CONVERT TO JSON.STRINGIFY to send ObjectId to the database
*/}
<div className="row">
<div className="col s2 offset-s3" ></div>
<Button
onClick={assignedStudent}
tooltip="Add Students"
tooltipOptions={{
position: 'right'
}}
className="btn green darken-3">
<i className="material-icons ">add_circle</i>
</Button>
</div>
<p>There are {Object.values(teacher.tchStudents).length} student/s
assigned</p>
</Modal>
)
}
// MdlAddStudents.propTypes = {
// assignedStudents: PropTypes.func.isRequired,
// }
export default MdlAddStudents;
// export default connect(null, (assignedStudents))(MdlAddStudents);
Thank you for helping out
The problem stems from you attempting to wrap your tchStudents state property in an object named data.
My advice is to keep it very simple
// it's just a string
const [tchStudents, setTchStudents] = useState("")
const assignedStudent = () => {
// create your request payload
const data = { tchStudents }
// no config object required
axios.put(`${URL}teachers/assignAddStudents/${tchID}`, data)
.then(res => {
// not sure what you want to do here exactly but
// `res.data` should look like
// { success: true, msg: 'Student ID has been assigned' }
setTchStudents("") // clear the input ¯\_(ツ)_/¯
})
}
The only other change is to use the new setter name in your <input>...
<input
type="text"
name="tchStudents"
value={tchStudents}
className="validate"
onChange={(e) => setTchStudents(e.target.value)}
/>

MEAN - Http Put Request 404 Not Found

I am building a MEAN stack application. I am receiving a 404 response when submitting a put request.This only occurs when I am trying to edit a booking. It does not successfully edit a specific booking.
Client Side
booking.service.ts
getBooking(id: string) {
return this.http.get<{ _id: string, title: string, content: string }>("http://localhost:3000/api/bookings/" + id);
}
updateBooking(id: string, title: string, content: string){
const booking: Booking = { id: id, title: title, content: content };
this.http.put("http://localhost:3000/api/bookings/" + id, booking)
.subscribe((response) => {
const id = booking.id;
const updateBook = [...this.bookings];
const oldBookingIndex = updateBook.findIndex(b => b.id === id);
updateBook[oldBookingIndex] = booking;
this.bookings = updateBook;
this.bookingUpdate.next([...this.bookings]);
});
}
booking-create.component.html
<mat-card>
<form (submit)="onSaveBooking(bookingForm)" #bookingForm="ngForm">
<mat-form-field>
<input matInput type="date" name="title" [ngModel]="booking?.title"
required minlength="3"
#title="ngModel"
placeholder="Date">
<mat-error *ngIf="title.invalid">Please enter title</mat-error>
</mat-form-field>
<mat-form-field>
<textarea matInput name="content" [ngModel]="booking?.content" required
#content="ngModel" placeholder="Golf Course"></textarea>
<mat-error *ngIf="content.invalid">Please enter content</mat-error>
</mat-form-field>
<hr>
<button mat-button
color="accent"
type="submit">Save Booking</button>
</form>
</mat-card>
booking-create.component.ts
import { Component, OnInit } from '#angular/core';
//import {Booking } from '../booking-list/booking.model';
import { NgForm } from '#angular/forms';
import { BookingService } from '../booking.service';
import { ActivatedRoute, ParamMap } from '#angular/router';
import { Booking } from '../booking-list/booking.model';
#Component({
selector: 'app-booking-create',
templateUrl: './booking-create.component.html',
styleUrls: ['./booking-create.component.css']
})
export class BookingCreateComponent implements OnInit {
enteredTitle = '';
enteredContent = '';
booking: Booking;
private mode = 'create';
private bookingId: string;
constructor(public bookingService: BookingService, public route: ActivatedRoute) { }
ngOnInit() {
this.route.paramMap.subscribe((paramMap: ParamMap) => {
if (paramMap.has('bookingId')){
this.mode = 'edit';
this.bookingId = paramMap.get('bookingId');
this.bookingService.getBooking(this.bookingId).subscribe(bookingData => {
this.booking = {id: bookingData._id, title: bookingData.title, content: bookingData.content};
});
}
else {
this.mode = 'create';
this.bookingId = null;
}
});
}
//bookingCreated = new EventEmitter<Booking>();
onSaveBooking(form: NgForm){
if(form.invalid){
return;
}
if (this.mode === 'create') {
this.bookingService.addBooking(form.value.title, form.value.content);
}
else {
this.bookingService.updateBooking(this.bookingId, form.value.title, form.value.content);
}
form.resetForm();
}
}
Server side
app.js
app.get("/api/bookings/:id", (req, res, next) => {
Booking.findById(req.params.id).then(booking => {
if (booking) {
res.status(200).json(booking);
}
else {
res.status(404).json({ message: 'Booking not found'});
}
})
});
app.put("/api/bookings/:id", (req, res, next) => {
const booking = new Booking({
_id: req.body.id,
title: req.body.title,
content: req.body.content
});
Booking.updateOne({_id: req.params.id}, booking).then(result => {
console.log(result);
res.status(200).json({message: 'Booking updated'});
});
});
I guess when you are use using 'booking' in this line that throws an issue
Booking.updateOne({_id: req.params.id}, booking).then(result => {
You can try just to use a single params to see if it is working
{$set: {"title": req.body.title}}
Not sure if the whole object can be passed like that.

How to update the user feed after updating the post?

I have a UserFeed component and EditForm component. As soon as I edit the form, I need to be redirected to the UserFeed and the updated data should be shown on UserFeed(title and description of the post).
So, the flow is like-UserFeed, which list the posts, when click on edit,redirected to EditForm, updates the field, redirected to UserFeed again, but now UserFeed should list the posts with the updated data, not the old one.
In this I'm just redirectin to / just to see if it works. But I need to be redirected to the feed with the updated data.
EditForm
import React, { Component } from "react";
import { connect } from "react-redux";
import { getPost } from "../actions/userActions"
class EditForm extends Component {
constructor() {
super();
this.state = {
title: '',
description: ''
};
handleChange = event => {
const { name, value } = event.target;
this.setState({
[name]: value
})
};
componentDidMount() {
const id = this.props.match.params.id
this.props.dispatch(getPost(id))
}
componentDidUpdate(prevProps) {
if (prevProps.post !== this.props.post) {
this.setState({
title: this.props.post.post.title,
description: this.props.post.post.description
})
}
}
handleSubmit = () => {
const id = this.props.match.params.id
const data = this.state
this.props.dispatch(updatePost(id, data, () => {
this.props.history.push("/")
}))
}
render() {
const { title, description } = this.state
return (
<div>
<input
onChange={this.handleChange}
name="title"
value={title}
className="input"
placeholder="Title"
/>
<textarea
onChange={this.handleChange}
name="description"
value={description}
className="textarea"
></textarea>
<button>Submit</button>
</div>
);
}
}
const mapStateToProps = store => {
return store;
};
export default connect(mapStateToProps)(EditForm)
UserFeed
import React, { Component } from "react"
import { getUserPosts, getCurrentUser } from "../actions/userActions"
import { connect } from "react-redux"
import Cards from "./Cards"
class UserFeed extends Component {
componentDidMount() {
const authToken = localStorage.getItem("authToken")
if (authToken) {
this.props.dispatch(getCurrentUser(authToken))
if (this.props && this.props.userId) {
this.props.dispatch(getUserPosts(this.props.userId))
} else {
return null
}
}
}
render() {
const { isFetchingUserPosts, userPosts } = this.props
return isFetchingUserPosts ? (
<p>Fetching....</p>
) : (
<div>
{userPosts &&
userPosts.map(post => {
return <Cards key={post._id} post={post} />
})}
</div>
)
}
}
const mapStateToPros = state => {
return {
isFetchingUserPosts: state.userPosts.isFetchingUserPosts,
userPosts: state.userPosts.userPosts.userPosts,
userId: state.auth.user._id
}
}
export default connect(mapStateToPros)(UserFeed)
Cards
import React, { Component } from "react"
import { connect } from "react-redux"
import { compose } from "redux"
import { withRouter } from "react-router-dom"
class Cards extends Component {
handleEdit = _id => {
this.props.history.push(`/post/edit/${_id}`)
}
render() {
const { _id, title, description } = this.props.post
return (
<div className="card">
<div className="card-content">
<div className="media">
<div className="media-left">
<figure className="image is-48x48">
<img
src="https://bulma.io/images/placeholders/96x96.png"
alt="Placeholder image"
/>
</figure>
</div>
<div className="media-content" style={{ border: "1px grey" }}>
<p className="title is-5">{title}</p>
<p className="content">{description}</p>
<button
onClick={() => {
this.handleEdit(_id)
}}
className="button is-success"
>
Edit
</button>
</div>
</div>
</div>
</div>
)
}
}
const mapStateToProps = state => {
return {
nothing: "nothing"
}
}
export default compose(withRouter, connect(mapStateToProps))(Cards)
updatePost action
export const updatePost = (id, data, redirect) => {
return async dispatch => {
dispatch( { type: "UPDATING_POST_START" })
try {
const res = await axios.put(`http://localhost:3000/api/v1/posts/${id}/edit`, data)
dispatch({
type: "UPDATING_POST_SUCCESS",
data: res.data
})
redirect()
} catch(error) {
dispatch({
type: "UPDATING_POST_FAILURE",
data: { error: "Something went wrong"}
})
}
}
}
I'm not sure if my action is correct or not.
Here's the updatePost controller.
updatePost: async (req, res, next) => {
try {
const data = {
title: req.body.title,
description: req.body.description
}
const post = await Post.findByIdAndUpdate(req.params.id, data, { new: true })
if (!post) {
return res.status(404).json({ message: "No post found "})
}
return res.status(200).json({ post })
} catch(error) {
return next(error)
}
}
One mistake is that to set the current fields you need to use $set in mongodb , also you want to build the object , for example if req.body does not have title it will generate an error
updatePost: async (req, res, next) => {
try {
const data = {};
if(title) data.title=req.body.title;
if(description) data.description=req.body.description
const post = await Post.findByIdAndUpdate(req.params.id, {$set:data}, { new: true })
if (!post) {
return res.status(404).json({ message: "No post found "})
}
return res.status(200).json({ post })
} catch(error) {
return next(error)
}
}

how to get sending data from restAPI after post response in angular 5?

I have a problem with the output of the json object sent from the server, for some reason I can not get and display the message contained in the response from the server. What am I doing wrong? Thanks for any help.
Below is the code for my application.
service :
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders, HttpRequest } from '#angular/common/http';
import { User } from './user';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import { ResponseObject } from './response-object';
#Injectable()
export class MntApiService {
private mntAPI = 'http://localhost:3000';
constructor(private _http: HttpClient) {
}
getUsers(): Observable<any> {
return this._http.get<any>(this.mntAPI + '/users');
}
addUser(email: string, password: string): Observable<any> {
return this._http.post<any>(this.mntAPI + '/signup', { email, password }, )
.map((response: Response) => response);
}
}
component:
import { Component, OnInit } from '#angular/core';
import { FormGroup, FormBuilder, Validators } from '#angular/forms';
import { HttpErrorResponse, HttpResponse } from '#angular/common/http';
import { Router } from '#angular/router';
import { MntApiService } from '../mnt-api.service';
import { User } from '../user';
import { ResInterceptor } from '../res-interceptor';
#Component({
selector: 'app-signup-page',
templateUrl: './signup-page.component.html',
styleUrls: ['./signup-page.component.scss']
})
export class SignupPageComponent implements OnInit {
users: any = [];
myForm: FormGroup;
response: {
body: {
succes: boolean,
message: string,
status: number
}
};
constructor(
private mntApiService: MntApiService,
private fb: FormBuilder,
public routes: Router) {
}
ngOnInit() {
this.initForm();
}
private initForm(): void {
this.myForm = this.fb.group({
// type: null,
email: [null, [
Validators.required, Validators.email
]],
password: [null, [
Validators.required
]]
});
}
isControlInvalid(controlName: string): boolean {
const control = this.myForm.controls[controlName];
const result: boolean = control.invalid && control.touched;
return result;
}
addUser() {
const val = this.myForm.value;
const controls = this.myForm.controls;
if (this.myForm.invalid) {
Object.keys(controls)
.forEach(controlName => controls[controlName].markAsTouched());
return;
} else {
val.email = val.email.trim();
val.password = val.password.trim();
if (!val.email && !val.password) {
return;
}
this.mntApiService.addUser(val.email, val.password)
.subscribe(user => {
this.users.push(user);
},
response => { this.response = response; });
console.log(this.response);
return;
}
}
}
component.html
<div class="container pt-5">
<form [formGroup]="myForm">
<div class="form-group">
<label for="email">Email address</label>
<small class="form-text text-danger">
{{response?.body.message}}
</small>
<input formControlName="email" type="email" class="form-control" id="email" placeholder="Enter email" autocomplete="email">
<small *ngIf="isControlInvalid('email')" class="form-text text-danger">неправельный формат почты</small>
</div>
<div class="form-group">
<label for="password">Password</label>
<input formControlName="password" type="password" class="form-control" id="password" placeholder="Password"
autocomplete="current-password">
<small *ngIf="isControlInvalid('password')" class="form-text text-danger">это поле не может быть пустым</small>
</div>
<button (click)="addUser()" class="btn btn-primary">Submit
</button>
</form>
</div>
and my node server code:
app.post('/signup', (req, res) => {
let sql = 'SELECT email FROM users WHERE email = ?';
let emailBody = [req.body.email];
config.query(sql, emailBody, (err, userEmail) => {
const errResponse = {
sacces: false,
message: 'Почта занята'
};
const rightResponse = {
'sacces': true,
'message': 'Пользователь создан',
'status': 201
};
if (userEmail.length < 0) {
res.status(409).send(errResponse);
console.log('mail exist!!!!');
return;
} else {
bcrypt.hash(req.body.password, 10, (err, hash) => {
if (err) {
return res.status(500).json({
error: err
});
} else {
let sql = 'INSERT INTO users ( email, password) VALUES ( ?, ?)';
let email = req.body.email;
let password = hash;
let body = [email, password];
config.query(sql, body, (err) => {
if (err) {
res.json({
"message": 'SQL Error'
});
} else {
//res.sendStatus(201);
res.status(201).send(rightResponse);
// res.status(201).json({
// 'message': "Спасибо за регестрацию"
// });
console.log('User created');
return;
}
});
}
});
}
});
});
please help who can, I'm a novice developer .
This is part of your code
this.mntApiService.addUser(val.email, val.password)
.subscribe(user => {
this.users.push(user);
},
response => { this.response = response; });
What you are doing here it that you are passing the subscribe method 2 parameters.
The first parameter is this function
user => {
this.users.push(user);
}
The second parameter is this function
response => { this.response = response; }
The function you pass to subscribe as second parameter gets executed when an error occurs, which I guess is not what you want.
Probably an implementation like this should do the trick for you
this.mntApiService.addUser(val.email, val.password)
.subscribe(response => {
response => { this.response = response; });
// this.users.push(user); CHECK THIS LINE - YOU ARE NOT RECEIVING A USER FROM THE SERVER BUT A MESSAGE
};

How can I use a dropdown in my angular2 form to submit an object

I have an application that will include Topics which contain Articles. When the user creates an article, I want them to select a topic to associate the article with from a dropdown that presents the available topics. The UI side of this presents the user with a form that looks correct at the moment, however, the topic object is not being passed with the form and I cant understand why. Can someone please help me understand how this should be done?
the relevant pieces here are below:
This is the select statement in my form that properly displays the options for topics that I want to present to the user.
<select [ngFormControl]="myForm.find('topic')" id="topic" class="form-control" required>
<option *ngFor="let a of topics" [value]="a">{{a.title}}</option>
</select>
When I try to verify that I received the data I'm looking for I get an 'undefined' error with this line:
console.log(this.myForm.value.topic.title);
If I do this I get [object, object]
console.log(this.myForm.value.topic);
what gets submitted to the service is this:
Object { content: "8th content", title: "8th title", articleId: "57588eaf1ac787521d15ac34", username: "Jason", userId: Object, topicId: undefined }
Can someone please help me understand what I'm missing here to be able to send the result of this form select tag into my Angular2 form?
My entire article-input.component.ts file
import { Component, OnInit } from '#angular/core';
import {Article} from './article';
import {ArticleService} from "./article.service";
import {ErrorService} from "../errors/error.service";
import { FormBuilder, ControlGroup, Validators, Control } from '#angular/common';
import {TopicService} from "../topics/topic.service";
import {Topic} from "../topics/topic";
#Component({
selector: 'my-article-input',
template: `
<section class="col-md-8 col-md-offset-2">
<form [ngFormModel]="myForm" (ngSubmit)="onSubmit()">
<div class="form-group">
<label for="title">Title</label>
<input [ngFormControl]="myForm.find('title')" type="text" id="title" class="form-control" #input [value]="article?.title">
</div>
<div class="form-group">
<label for="content">Content</label>
<input [ngFormControl]="myForm.find('content')" type="text" id="content" class="form-control" #input [value]="article?.content">
</div>
<select [ngFormControl]="myForm.find('topic')" id="topic" class="form-control" required>
<option *ngFor="let a of topics" [value]="a">{{a.title}}</option>
</select>
<button type="submit" class="btn btn-primary" [disabled]="!myForm.valid">{{ !article ? 'Add Article' : 'Save Article' }}</button>
<button type="button" class="btn btn-danger" (click)="onCancel()" *ngIf="article">Cancel</button>
</form>
</section>
`
})
export class ArticleInputComponent implements OnInit {
myForm: ControlGroup;
article: Article = null;
constructor(private _fb:FormBuilder, private _articleService: ArticleService, private _errorService: ErrorService, private _topicService: TopicService ) {}
topics: Topic[];
onSubmit() {
console.log(this.myForm.value.topic.title);
if (this.article) {
// Edit
this.article.content = this.myForm.value.content;
this.article.title = this.myForm.value.title;
this.article.topicId = this.myForm.value.topic.topicId;
this._articleService.updateArticle(this.article)
.subscribe(
data => console.log(data),
error => this._errorService.handleError(error)
);
this.article = null;
} else {
const article: Article = new Article(this.myForm.value.content, this.myForm.value.title, null, 'DummyArticleInput', this.myForm.value.topic);
this._articleService.addArticle(article)
.subscribe(
data => {
console.log(data);
this._articleService.articles.push(data);
},
error => this._errorService.handleError(error)
);
}
}
onCancel() {
this.article = null;
}
ngOnInit() {
this.myForm = this._fb.group({
title: ['', Validators.required],
content: ['', Validators.required],
topic: ['', Validators.required]
});
this._articleService.articleIsEdit.subscribe(
article => {
this.article = article;
}
);
this._topicService.getTopics().subscribe(
topics => {
this.topics = topics;
this._topicService.topics = topics
},
error => this._errorService.handleError(error)
);
}
}
So the answer to this is to not use the myForm element with ngFormControl for objects/form field that are going to be references to other objects within the application. Instead what needed to be done was as follows.
Create topics, topic, and a selectedTopic
topics: Topic[];
topic = '';
selectedTopic = '';
Add a select tag with an option tag to the form for a user to be able to select the topic they want by binding with ngModel to 'topic'
<select [ngModel]="topic" (ngModelChange)="onChange($event)">
<option [ngValue]="i" *ngFor="let i of topics">{{i.title}}</option>
</select>
Create an onChange method that will update this.selectedTopic based on the user selecting a topic in the form
onChange(newValue) {
this.selectedTopic = newValue;
console.log(this.selectedTopic);
console.log(this.selectedTopic.topicId);
}
Then in the onSubmit method use the myForm.value's for the info coming from the actual form, and use the databinding on this.selectedTopic for the topic that is being selected
onSubmit() {
if (this.article) {
// Edit
this.article.content = this.myForm.value.content;
this.article.title = this.myForm.value.title;
this.article.topicId = this.selectedTopic;
this._articleService.updateArticle(this.article)
.subscribe(
data => console.log(data),
error => this._errorService.handleError(error)
);
this.article = null;
} else {
const article: Article = new Article(this.myForm.value.content, this.myForm.value.title, null, 'dummyUserName', 'dummyUserId', this.selectedTopic.topicId);
this._articleService.addArticle(article)
.subscribe(
data => {
// console.log('what comes back from addArticle is: ' + JSON.stringify(data));
this._articleService.articles.push(data);
},
error => this._errorService.handleError(error)
);
}
From where we started, we have now gotten to a place where everything works and the object getting created looks like this:
{ "_id" : ObjectId("5758c2e173fd33e04092c87e"), "content" : "c66", "title" : "t66", "user" : ObjectId("5755e5be96162f52a4f01dd8"), "topic" : ObjectId("57572d92e802307d199f0afa"), "__v" : 0 }
For reference, the entire (working) article-input.component.ts file looks like this:
import { Component, OnInit } from '#angular/core';
import {Article} from './article';
import {ArticleService} from "./article.service";
import {ErrorService} from "../errors/error.service";
import { FormBuilder, ControlGroup, Validators, Control } from '#angular/common';
import {TopicService} from "../topics/topic.service";
import {Topic} from "../topics/topic";
#Component({
selector: 'my-article-input',
template: `
<section class="col-md-8 col-md-offset-2">
<form [ngFormModel]="myForm" (ngSubmit)="onSubmit()">
<div class="form-group">
<label for="title">Title</label>
<input [ngFormControl]="myForm.find('title')" type="text" id="title" class="form-control" #input [value]="article?.title">
</div>
<div class="form-group">
<label for="content">Content</label>
<input [ngFormControl]="myForm.find('content')" type="text" id="content" class="form-control" #input [value]="article?.content">
</div>
<select [ngModel]="topic" (ngModelChange)="onChange($event)">
<option [ngValue]="i" *ngFor="let i of topics">{{i.title}}</option>
</select>
<button type="submit" class="btn btn-primary" >{{ !article ? 'Add Article' : 'Save Article' }}</button>
<button type="button" class="btn btn-danger" (click)="onCancel()" *ngIf="article">Cancel</button>
</form>
</section>
`
})
export class ArticleInputComponent implements OnInit {
myForm: ControlGroup;
article: Article = null;
constructor(private _fb:FormBuilder, private _articleService: ArticleService, private _errorService: ErrorService, private _topicService: TopicService ) {}
topics: Topic[];
topic = '';
selectedTopic = '';
onChange(newValue) {
this.selectedTopic = newValue;
console.log(this.selectedTopic);
console.log(this.selectedTopic.topicId);
}
onSubmit() {
if (this.article) {
// Edit
this.article.content = this.myForm.value.content;
this.article.title = this.myForm.value.title;
this.article.topicId = this.selectedTopic;
this._articleService.updateArticle(this.article)
.subscribe(
data => console.log(data),
error => this._errorService.handleError(error)
);
this.article = null;
} else {
const article: Article = new Article(this.myForm.value.content, this.myForm.value.title, null, 'dummyUserName', 'dummyUserId', this.selectedTopic.topicId);
this._articleService.addArticle(article)
.subscribe(
data => {
// console.log('what comes back from addArticle is: ' + JSON.stringify(data));
this._articleService.articles.push(data);
},
error => this._errorService.handleError(error)
);
}
}
onCancel() {
this.article = null;
}
ngOnInit() {
this.myForm = this._fb.group({
title: ['', Validators.required],
content: ['', Validators.required],
topic: ['', Validators.required]
});
this._articleService.articleIsEdit.subscribe(
article => {
this.article = article;
}
);
this._topicService.getTopics().subscribe(
topics => {
this.topics = topics;
this._topicService.topics = topics
},
error => this._errorService.handleError(error)
);
}
}
my topic.ts angular model looks like this:
export class Topic {
description: string;
title: string;
username: string;
topicId: string;
userId: string;
constructor (description: string, title: string, topicId?: string, username?: string, userId?: string) {
this.description = description;
this.title = title;
this.topicId = topicId;
this.username = username;
this.userId = userId;
}
}
the article.service.ts looks like this:
addArticle(article: Article) {
const body = JSON.stringify(article);
console.log(body);
const headers = new Headers({'Content-Type': 'application/json'});
const token = localStorage.getItem('token') ? '?token=' + localStorage.getItem('token') : '';
return this._http.post('http://localhost:3000/article' + token, body, {headers: headers})
.map(response => {
const data = response.json().obj;
let article = new Article(data.content, data.title, data._id, data.user.firstName, data.user, data.topicId);
return article;
})
.catch(error => Observable.throw(error.json()));
}
an in the backend in Node my article.js looks like this:
router.post('/', function(req, res, next) {
var decoded = jwt.decode(req.query.token);
User.findById(decoded.user._id, function(err, doc) {
if (err) {
return res.status(401).json({
title: 'An Error occured',
error: err
});
}
var article = new Article({
content: req.body.content,
title: req.body.title,
user: doc,
topic: req.body.topicId
});
console.log(req.body);
article.save(function(err, result){
if (err) {
return res.status(404).json({
title: 'An Error occured',
error: err
});
}
doc.articles.push(result);
doc.save();
res.status(201).json({
article: 'Saved Article',
obj: result
});
});
});
});
I hope this helps others who had the same issue as I did.

Resources