Why am I receiving the following error when subscribed to the patientAllergies observable? - node.js

When I select the add button in the patient-allergies.component.ts, select options for the addition of patient allergies and click save, I receive the following error:
ERROR Error: Error trying to diff '[object Object]'. Only arrays and iterables are allowed.
I cannot understand why I am receiving this error as I cast the object to an array when it is passed from the rest-api.service.ts through _patientAllergies.next().
patient-allergies.component.html
<mat-card class="mat-typography">
<mat-card-title>Allergies</mat-card-title>
<mat-card-content>
<hr>
<div *ngFor="let patientAllergy of this.patientAllergies">
<h3 *ngIf="!patientAllergy.nickname"> {{ patientAllergy.fullname }} </h3>
<h3 *ngIf="patientAllergy.nickname"> {{ patientAllergy.fullname }} ({{ patientAllergy.nickname }}) </h3>
</div>
</mat-card-content>
<mat-card-actions *ngIf="isEdit">
<button mat-button class="dark" (click)="onAdd()">ADD</button>
<button mat-button class="dark" (click)="onRemove()">REMOVE</button>
</mat-card-actions>
</mat-card>
<mat-card *ngIf="isLoading" style="display: flex; justify-content: center; align-items: center">
<mat-progress-spinner class="mat-spinner-color" mode="indeterminate"></mat-progress-spinner>
</mat-card>
patient-allergies.component.ts
import {Component, OnInit, Input, Pipe, PipeTransform} from '#angular/core';
import {RestAPIService} from 'src/app/rest-api.service';
import {ActivatedRoute} from '#angular/router';
import {MatDialog} from '#angular/material';
#Component({selector: 'app-patient-allergies', templateUrl: './patient-allergies.component.html', styleUrls: ['./patient-allergies.component.css']})
export class PatientAllergiesComponent implements OnInit {
#Input()isEdit : boolean = false;
constructor(private restAPIService : RestAPIService, private route : ActivatedRoute, public dialog : MatDialog) {}
isLoading : boolean;
patientAllergies;
subscription = null;
ngOnInit() {
this.isLoading = true;
this
.restAPIService
.getPatientAllergies(this.route.snapshot.params.id);
this.subscription = this
.restAPIService
.patientAllergies
.subscribe((patAllergies) => {
this.patientAllergies = patAllergies as [];
this.isLoading = false;
});
}
onAdd() {
let dRef = this
.dialog
.open(AllergiesDialogComponent, {
disableClose: true,
height: '800px',
width: '600px',
data: {
isAdding: true,
patientAllergies: this.patientAllergies
}
});
dRef
.afterClosed()
.subscribe((res) => {
res.forEach((ele) => {
this
.restAPIService
.addPatientAllergies(this.route.snapshot.params.id, ele.value.allergyID);
});
});
}
onRemove() {
let dRef = this
.dialog
.open(AllergiesDialogComponent, {
disableClose: true,
height: '800px',
width: '600px',
data: {
isAdding: false,
patientAllergies: this.patientAllergies
}
});
dRef
.afterClosed()
.subscribe((res) => {
res.forEach((ele) => {
this
.restAPIService
.deletePatientAllergies(this.route.snapshot.params.id, ele.value.allergyID);
});
})
}
}
import {Inject} from '#angular/core';
import {MatDialogRef, MAT_DIALOG_DATA} from '#angular/material';
import {DialogData} from 'src/app/patient/patient.component';
#Component({selector: 'app-allergies-dialog', templateUrl: './allergies-dialog.component.html', styleUrls: ['./allergies-dialog.component.css']})
export class AllergiesDialogComponent implements OnInit {
allergies = [];
filterName : string;
subscription;
constructor(public dialogRef : MatDialogRef < AllergiesDialogComponent >, #Inject(MAT_DIALOG_DATA)public data : DialogData, private restAPIService : RestAPIService) {}
ngOnInit() {
this
.restAPIService
.getAllergies();
if (this.data['isAdding']) {
this.subscription = this
.restAPIService
.allergies
.subscribe((allergies) => {
let allergiesArr = allergies as [];
this.allergies = [];
let patientAllergyNames = [];
this
.data['patientAllergies']
.forEach(patientAllergy => {
patientAllergyNames.push(patientAllergy['fullname'])
});
allergiesArr.forEach(allergy => {
if (!patientAllergyNames.includes(allergy['fullname']))
this.allergies.push(allergy);
}
);
})
} else {
this.allergies = this.data['patientAllergies'];
}
}
onClose(selectedOptions) {
if (this.data['isAdding'])
this.subscription.unsubscribe();
// either [] or the IDs of the objects to add/remove
this
.dialogRef
.close(selectedOptions);
}
}
#Pipe({name: 'filterOnName'})
export class filterNames implements PipeTransform {
transform(listOfObjects : any, nameToFilter : string) : any {
let allergyArr = listOfObjects as[];
let matchedObjects = [];
if (!listOfObjects)
return null;
if (!nameToFilter)
return listOfObjects;
allergyArr.forEach(allergyObj => {
let fullname : string = allergyObj['fullname'];
let nickname : string = allergyObj['nickname'];
let fullnameLower = fullname.toLowerCase();
let nicknameLower = fullname.toLowerCase();
let filter = nameToFilter.toLowerCase();
if (nickname) {
if ((fullnameLower.includes(filter) || nicknameLower.includes(filter)))
matchedObjects.push(allergyObj);
}
else {
if (fullnameLower.includes(filter))
matchedObjects.push(allergyObj);
}
});
return matchedObjects;
}
}
rest-api.service.ts
private _allergies;
private allergiesSubject = new Subject();
allergies = this
.allergiesSubject
.asObservable();
private _patientAllergies;
private patientAllergiesSubject = new Subject();
patientAllergies = this
.patientAllergiesSubject
.asObservable();
getPatientAllergies(patientID) {
const request = {
headers: {},
response: true
};
API
.get('DiagnetAPI', '/v1/patients/' + patientID + '/allergies', request)
.then(resp => {
this._patientAllergies = resp.data;
this
.patientAllergiesSubject
.next(this._patientAllergies);
})
.catch((err) => console.log(err))
}
addPatientAllergies(patientID, allergyID) {
const request = {
headers: {},
response: true,
body: allergyID
};
API
.post('DiagnetAPI', '/v1/patients/' + patientID + '/allergies', request)
.then(resp => {
console.log(resp.data);
this._patientAllergies = resp.data;
this
.patientAllergiesSubject
.next(this._patientAllergies);
})
.then(() => {
this.getPatientAllergies(patientID);
})
.catch((err) => console.log(err))
}
deletePatientAllergies(patientID, allergyID) {
const request = {
headers: {},
response: true
};
API
.del('DiagnetAPI', '/v1/patients/' + patientID + '/allergies/' + allergyID, request)
.then((res) => console.log(res))
.then(() => {
this.getPatientAllergies(patientID);
})
.catch((err) => console.log(err))
}

If your response from the api is not an array but an object, you can use the keyvalue pipe in your *ngFor loop.
In your case patientAllergy would be an object with two keys, the key and the value so you can access the nickname like this: patientAllergy.value.nickname
your template:
<div *ngFor="let patientAllergy of this.patientAllergies | keyvalue">
<h3 *ngIf="!patientAllergy.value.nickname"> {{ patientAllergy.value.fullname }} </h3>
<h3 *ngIf="patientAllergy.value.nickname"> {{ patientAllergy.value.fullname }} ({{ patientAllergy.value.nickname }}) </h3>
</div>

Related

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
};

Action creator is being dispatched but the new state is not showing in store

I have written a server and wired up React with Redux, also made use of container-componenent-seperation. The first action fetchSnus() is successfully dispatched, the selectors also seem to work, but for some reason all the actions that I call after the first rendering, such as fetchSnu() (that only fetches ONE single snu-object) e.g. is not accessible for me in the store, meaning: after mapping state and dispatch to props not accessible for me under this.props. I really don't understand why!
You can see the whole project here: https://github.com/julibi/shonagon
This is my container ReadSingleSnuContainer.js:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchSnus, fetchSnu, setToRead, createSnu, getSnusMatchingKeyword } from '../../actions/index';
import { unreadSnus, randomFirstSnu } from '../../selectors/index';
import ReadSingleSnu from './ReadSingleSnu';
class ReadSingleSnuContainer extends Component {
componentWillMount() {
this.props.fetchSnus();
}
render() {
return (
<ReadSingleSnu { ...this.props } />
);
}
}
const mapStateToProps = (state) => {
return {
snus: state.snus,
randomFirstSnu: randomFirstSnu(state),
candidate: state.candidate
};
}
const mapDispatchToProps = (dispatch) => {
return {
fetchSnus: () => dispatch(fetchSnus()),
getSnusMatchingKeyword,
fetchSnu,
setToRead,
createSnu
}
};
export default connect(mapStateToProps, mapDispatchToProps)(ReadSingleSnuContainer);
This is my component ReadSingleSnu.js:
import React, { Component } from 'react';
import style from './ReadSingleSnu.css'
import Typist from 'react-typist';
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
export default class ReadSingleSnu extends Component {
constructor(props) {
super(props);
this.state = { showTitles: false };
}
renderRandomFirstSnu() {
const { randomFirstSnu } = this.props;
const { showTitles } = this.state;
// preselect a keyword
// dispatch the action that searches for other keywords (action I)
if(randomFirstSnu) {
return (
<div>
<h3><Typist cursor={ { show: false } }>{ randomFirstSnu.title }</Typist></h3>
<ReactCSSTransitionGroup
transitionName="snu"
transitionAppear={ true }
transitionAppearTimeout={ 1000 }
transitionEnter={ false }
transitionLeave={ false }
>
<p>{ randomFirstSnu.text }</p>
</ReactCSSTransitionGroup>
{ !showTitles ? (
<div>
<button>Some other</button>
<button onClick={ () => this.handleDoneReading(randomFirstSnu) }>Done reading, next</button>
</div>
) : (
<ReactCSSTransitionGroup
transitionName="keywords"
transitionAppear={ true }
transitionAppearTimeout={ 1000 }
transitionEnter={ false }
transitionLeave={ false }
>
<ul>{ randomFirstSnu.keywords.map((keyword, idx) =>
<li key={ idx }>
<button onClick={ () => this.fetchNextSnu(randomFirstSnu) }>
{ keyword }
</button>
</li>) }
</ul>
</ReactCSSTransitionGroup>
)
}
</div>
);
}
return <div>Loading ...</div>
}
handleDoneReading(snu) {
const { setToRead, getSnusMatchingKeyword } = this.props;
const id = snu._id;
if (snu.keywords.length > 0 && setToRead) {
// setToRead(id, snu);
this.setState({ showTitles: true });
const randomIndex = Math.floor(Math.random() * snu.keywords.length);
const randomKeyword = snu.keywords[randomIndex];
console.log('This is the randomKeyword :', randomKeyword);
getSnusMatchingKeyword(randomKeyword);
} else {
console.log('Here will soon be the select random next snu action :)');
}
}
render() {
console.log('ReadSingleSnu, this.props: ', this.props);
return (
<div className={style.App}>
<div>{ this.renderRandomFirstSnu() }</div>
</div>
);
}
}
This is my actions file:
import axios from 'axios';
export const FETCH_SNUS = 'FETCH_SNUS';
export const FETCH_SNU = 'FETCH_SNU';
export const SET_TO_READ = 'SET_TO_READ';
export const CREATE_SNU = 'CREATE_SNU';
export function getSnusMatchingKeyword(keyword) {
const request = axios.get(`/snus/keyword/${keyword}`);
return {
type: GET_SNUS_MATCHING_KEYWORD,
payload: request
};
}
export function fetchSnus() {
const request = axios.get('/snus');
return {
type: FETCH_SNUS,
payload: request
};
}
export function fetchSnu(id) {
const request = axios.get(`/snus/${id}`);
return {
type: FETCH_SNU,
payload: request
};
}
export function setToRead(id, snu) {
const request = axios.patch(`/snus/${id}`, { title: snu.title, text: snu.text, keywords: snu.keywords, read: true });
return {
type: SET_TO_READ,
payload: request
}
}
export function createSnu(object) {
const request = axios.post('/snus', object);
return {
type: CREATE_SNU,
payload: request
};
}
A Reducer:
import { FETCH_SNUS, FETCH_SNU, SET_TO_READ, CREATE_SNU } from '../actions/index';
export default function(state = [], action) {
switch(action.type) {
case FETCH_SNUS:
return [ ...state, ...action.payload.data ];
case FETCH_SNU:
return [ ...state, ...action.payload.data ];
case SET_TO_READ:
return [ ...state, ...action.payload.data ];
case CREATE_SNU:
return [...state, ...action.payload.data ];
default:
return state;
}
}
I tested all endpoints via Postman and they work. So that should not be the problem… Please help! I cannot find a solution to this problem.

How to efficiently show a users change/update

I have a moments component which is similar to a thread/post. Users can like or dislike this moment. Once they like the moment i call the momentService to retrieve the entire list again so it refreshes and increases the like count of the moment.
However, as im only liking one post its not efficient to update all the other moments. Whats the best way to show this update/change without having to get all the moments again.
When i call the update i retrieve the updated moment object. So is there a way to update this specific moment in the moments object.
moments.component.html
<mat-card class="" *ngFor="let moment of moments">
<mat-card-header class="sub-header" fxLayout="row" fxLayoutAlign="space-between center">
<mat-card-subtitle>
{{moment.created_at}}
</mat-card-subtitle>
<mat-card-title>
<img src="http://via.placeholder.com/50x50" alt="" class="img-circle">
</mat-card-title>
<div>
<button mat-icon-button color="warn" matTooltip="Delete" (click)="delete(moment._id)">
<mat-icon>delete</mat-icon>
</button>
<button mat-icon-button>
<mat-icon>more_vert</mat-icon>
</button>
</div>
</mat-card-header>
<mat-card-content>
<p>
{{moment.body}}
</p>
</mat-card-content>
<mat-card-actions fxLayout="row" fxLayoutAlign="space-between center">
<div>
<button mat-icon-button matTooltip="Like" (click)="like(moment._id)">
<mat-icon>thumb_up</mat-icon> {{moment.likes.length}}
</button>
<button mat-icon-button matTooltip="Dislike" (click)="dislike(moment._id)">
<mat-icon>thumb_down</mat-icon> {{moment.dislikes.length}}
</button>
</div>
<button mat-icon-button matTooltip="Comments">
<mat-icon>comment</mat-icon> {{moment.comments.length}}
</button>
</mat-card-actions>
</mat-card>
moment.component.ts
import { Component, OnInit } from '#angular/core';
import { AuthService } from '../../../services/auth.service';
import { MomentService } from '../../../services/moment.service';
import { Router, ActivatedRoute, ParamMap } from '#angular/router';
import { UserService } from '../../../services/user.service';
#Component({
selector: 'app-moments',
templateUrl: './moments.component.html',
styleUrls: ['./moments.component.scss']
})
export class MomentsComponent implements OnInit {
user: any;
moments: any;
constructor(private authService: AuthService,
private userService: UserService,
private momentService: MomentService,
private route: ActivatedRoute,
private router: Router) { }
ngOnInit() {
this.route.parent.paramMap.switchMap((params: ParamMap) => {
let user_id = params.get('id');
return this.userService.get(user_id);
}).subscribe((res) => {
this.user = res;
console.log(this.user._id);
this.getMoments();
});
}
getMoments() {
this.momentService.all(this.user._id).subscribe((res) => {
this.moments = res.data;
}, (err) => {
console.log(err);
});
}
like(moment) {
let like = { 'like': this.user._id };
this.momentService.update(moment, like).subscribe((res) => {
this.getMoments();
console.log(res);
}, (err) => {
console.log(err);
});
}
dislike(moment) {
let dislike = { 'dislike': this.user._id };
this.momentService.update(moment, dislike).subscribe((res) => {
this.getMoments();
console.log(res);
}, (err) => {
console.log(err);
});
}
delete(moment) {
let id = moment;
this.momentService.delete(id).subscribe((res) => {
this.getMoments();
console.log(res);
}, (err) => {
console.log(err);
})
}
}
moments.service.ts
import { HttpParams } from '#angular/common/http';
import { Injectable } from '#angular/core';
import { ApiService } from './api.service';
#Injectable()
export class MomentService {
path = 'moment/';
constructor(private apiService: ApiService) { }
create(user_id, moment) {
let endpoint = this.path;
return this.apiService.post(endpoint, moment);
}
delete(moment_id) {
let endpoint = this.path + moment_id;
return this.apiService.delete(endpoint);
}
all(user_id) {
let endpoint = this.path;
let params = new HttpParams().set('author', user_id);
return this.apiService.get(endpoint, params);
}
get(moment_id) {
let endpoint = this.path + moment_id;
return this.apiService.get('endpoint');
}
update(moment_id, data) {
let endpoint = this.path + moment_id;
return this.apiService.put(endpoint, data);
}
search(filters) {
let endpoint = this.path;
let params = new HttpParams().set('filters', filters);
return this.apiService.get(endpoint, params);
}
}
moment.controller.js (backend api)
update(req, res) {
let id = req.params.id;
Moment.findOne({ '_id': id }, (err, moment) => {
if (req.body.body) {
moment.body = req.body.body;
}
// LIKE&DISLIKE Toggle
if (req.body.like) {
if (moment.dislikes.indexOf(req.body.like) > -1) {
moment.dislikes.remove(req.body.like);
}
if (moment.likes.indexOf(req.body.like) > -1) {
moment.likes.remove(req.body.like);
} else {
moment.likes.push(req.body.like);
}
}
if (req.body.dislike) {
if (moment.likes.indexOf(req.body.dislike) > -1) {
moment.likes.remove(req.body.dislike);
}
if (moment.dislikes.indexOf(req.body.dislike) > -1) {
moment.dislikes.remove(req.body.dislike);
} else {
moment.dislikes.push(req.body.dislike);
}
}
moment.save((err, moment) => {
if (err) {
return res.status(404).json({
success: false,
status: 404,
data: {},
mesage: "Failed to update moment"
});
}
return res.status(201).json({
succss: true,
status: 201,
data: moment,
message: "Succesfully updated moment",
});
});
})
}
Instead of passing the id to the like/dislike methods, you can pass the entire object and from there pass the id to the update method, and on success, mutate the reference.

Resources