Angular 2 display newly added item to http in other component - javascript

This is probably easy for someone, but I just can't get it. So I have a list of items to display:
My service that fetches the data, configService.ts
ngOnInit() {
getConfig(): Observable<any> {
return this.http.get<any>('/api/config').map(res => res);
}
}
My savedSearhes component that populates data in the ngFor:
this.configService.getConfig().subscribe(data => {
this.userSavedSearches = data.isr.me.savedSearches //get an array of items
});
the html to display data:
<div *ngFor="let savedsearch of userSavedSearches">
{{savedsearch.name }}
</div>
The main issue I have, is I have another component that I use to add a new item to the same server.
saveSearch() {
this.saveSearchObject = {
name: this.SearchName,
description: this.SearchDescription,
context: this.theContext,
}
this.searchService.createSavedSearch(this.saveSearchObject).subscribe(data => {
console.log(data) // newly added item to server
})
}
The service that posting new item to server:
createSavedSearch(search: SavedSearch): Observable<SavedSearch> {
return this.http.post<SavedSearch>('/api/searches/', search)
}
When I add a new item, the item actually gets added to the server. But I don't see the "savedSearches" component display added item, only when I reload the page I can see new item added.
How to add new item to the server and see its being added with new item in other component without reloading the page.

You can achieve it by Creating a subject where saveSearch function lies
let subjectUserSavedSearches = new Subject();
let obsrvUserSavedSearches = subjectUserSavedSearches.AsObservable();
saveSearch() {
this.saveSearchObject = {
name: this.SearchName,
description: this.SearchDescription,
context: this.theContext,
}
this.searchService.createSavedSearch(this.saveSearchObject).subscribe(data => {
this.userSavedSearches = data;
this.subjectUserSavedSearches.next(this.userSavedSearches);
})
}
Now watch that obsrvUserSavedSearches on the component you need to show data.
The best way is to move methods getConfig() and saveSearch() in a service and just create a subject for userSavedSearches and an Observable to watch the same.

You won't see it unless you do a getConfig() again. Use a .switchMap() to chain your http calls.
saveSearch() {
this.saveSearchObject = {
name: this.SearchName,
description: this.SearchDescription,
context: this.theContext,
}
this.searchService.createSavedSearch(this.saveSearchObject)
.switchMap(data => {
console.log(data) // newly added item to server
return this.configService.getConfig();
})
.subscribe(data => {
this.userSavedSearches = data.isr.me.savedSearches //get an array of items
})
}
Otherwise, unless your savedSearches() actually returned a newly refreshed list, you can do it in your subscribe:
saveSearch() {
this.saveSearchObject = {
name: this.SearchName,
description: this.SearchDescription,
context: this.theContext,
}
this.searchService.createSavedSearch(this.saveSearchObject).subscribe(data => {
this.userSavedSearches = data
})
}

Related

Custom merge function is not being called after updating field with cache.modify

I have written a custom merge function for the field products on type Session. It seems the merge function is only being called when I initialise the object Session:1 with its products, and not when I update products later using cache.modify.
My merge function:
const client = new ApolloClient({
uri: 'http://localhost:8081/graphql',
cache: new InMemoryCache({
typePolicies: {
Session: {
fields: {
products: {
merge (existing, incoming) {
// this is only being called on useQuery(HydrateSession), not useMutation(UpsertProduct)
console.log('existing', JSON.stringify(existing, null, 2))
console.log('incoming', JSON.stringify(incoming, null, 2))
// remove duplicates when latestProduct has the same id as an existing product — [..., latestProduct]
if (incoming.filter(p => p.id === incoming[incoming.length - 1].id).length > 1) return existing
return incoming
}
}
}
}
}
})
})
Initialisation of Session:
const HydrateSession = gql`
query {
session {
id
products {
id
}
}
}
`
...
useQuery(HydrateSession)
Updating products later using cache.modify:
const UpsertProduct = gql`
mutation UpsertProduct($product: ProductInput!) {
upsertProduct(product: $product) {
id
}
}
`
...
const [upsertProductMutation] = useMutation(UpsertProduct)
const onClick = async () => {
await upsertProductMutation({
variables: {
product: {
id: 2
}
},
update: (cache, mutationResult) => {
cache.modify({
id: 'Session:1',
fields: {
products: previous => [...previous, mutationResult.data.upsertProduct]
}
})
}
})
}
I have a full working example here https://github.com/jsindos/apollo-play, run npm i and then start two separate processes with npm start and npm serve. After clicking the button triggering the mutation, the merge function is not run (as seen by the absence of console.log statements in the console).
modify circumvents any merge functions you've defined, which means that fields are always overwritten with exactly the values you specify.
https://www.apollographql.com/docs/react/caching/cache-interaction/#using-cachemodify
Reading documentation is a good thing.

Duplicate array items in Vuex state (using Socket.io)

So I have this app built in Vue and using Vuex. I connect to a Node/Express backend with Socket.Io to be able to push data from the server to client instantly when needed.
The data pushed to the clients are in the form of an object which is then stored in an array in VUEX. Each data object pushed into the array has a unique string attached to it.
This string is used to compare the objects already pushed into the array in VUEX. If there are duplicates they won't be stored. If not equal = they are stored.
I then use ...mapGetters to get the array in Vuex and loop through it. For each object a component is rendered.
HOWEVER - sometimes the same object is rendered twice even though the array in VUEX clearly only shows one copy.
Here is the code in the VUEX Store:
export default new Vuex.Store({
state: {
insiderTrades: [],
},
mutations: {
ADD_INSIDER_TRADE(state, insiderObject) {
if (state.insiderTrades.length === 0) {
// push object into array
state.insiderTrades.unshift(insiderObject);
} else {
state.insiderTrades.forEach((trade) => {
// check if the insider trade is in the store
if (trade.isin === insiderObject.isin) {
// return if it already exists
return;
} else {
// push object into array
state.insiderTrades.unshift(insiderObject);
}
});
}
},
},
getters: {
insiderTrades(state) {
return state.insiderTrades;
},
},
Here is the some of the code in App.vue
mounted() {
// //establish connection to server
this.$socket.on('connect', () => {
this.connectedState = 'ansluten';
this.connectedStateColor = 'green';
console.log('Connected to server');
});
//if disconnected swap to "disconneted state"
this.$socket.on('disconnect', () => {
this.connectedState = 'ej ansluten';
this.connectedStateColor = 'red';
console.log('Disconnected to server');
});
// recieve an insider trade and add to store
this.$socket.on('insiderTrade', (insiderObject) => {
this.$store.commit('ADD_INSIDER_TRADE', insiderObject);
});
},
Your forEach iterates the existing items and adds the new item once for every existing item. Use Array.find:
ADD_INSIDER_TRADE(state, insiderObject) {
const exists = state.insiderTrades.find(trade => trade.isin === insiderObject.isin);
if (!exists) state.insiderTrades.unshift(insiderObject);
},
You don't need the initial length check

Vuejs return data from component to Laravel

I am trying to make a search bar to collect a list of products which the user will then be able to select an array of products and add it to an order.
So far the products can be searched, added to a "products" array via an "add" button and they are able to see the data within products. They are also able to remove products that they do not want.
My issue is that I am trying to send the data from the "products" array with a parent form. The search component is located within the form as well as the list. But I do not know how I would send the array with the form.
I do not need to send the entire product object, but just the ID which will be used to link up the products with the order.
Here is where the component is:
<div class="uk-margin" id="search">
<label for="Search" class="uk-form-label">Search products to add</label>
<search input="ess"></search>
</div>
Here is the vuejs component data:
export default {
data() {
return {
keywords: null,
results: [],
products: []
};
},
watch: {
keywords (after, before) {
this.fetch();
}
},
methods: {
fetch() {
axios.get('/search', { params: { keywords: this.keywords } })
.then(response => this.results = response.data)
.catch(error => {});
},
addProduct (product) {
let duplicate = false;
this.products.forEach((item, index) => {
if (item.ID === product.ID) {
duplicate = true;
}
});
if (!duplicate) {
this.products.push(product);
}
},
removeProduct (product) {
Vue.delete(this.products, product);
}
}
}
Everything works fine, but my question is.. How am I able to pass the data back to the html / laravel to use it while sending data to the controller. I only need to send the "products" array, I have tried using input with the data but it isn't working. Does anyone know how I could do it, or is the best way to do so, using JavaScript and add them to an array by finding all the elements which are being displayed?
Many thanks.

ReactJS: Updating array inside object state doesn't trigger re-render

I have a react hooks function that has a state object apiDATA. In this state I store an object of structure:
{
name : "MainData", description: "MainData description", id: 6, items: [
{key: "key-1", name : "Frontend-Test", description: "Only used for front end testing", values: ["awd","asd","xad","asdf", "awdr"]},
{key: "key-2", name : "name-2", description: "qleqle", values: ["bbb","aaa","sss","ccc"]},
...
]
}
My front end displays the main data form the object as the headers and then I map each item in items. For each of these items I need to display the valuesand make them editable. I attached a picture below.
Now as you can see I have a plus button that I use to add new values. I'm using a modal for that and when I call the function to update state it does it fine and re-renders properly. Now for each of the words in the valuesI have that chip with the delete button on their side. And the delete function for that button is as follows:
const deleteItemFromConfig = (word, item) => {
const index = apiDATA.items.findIndex((x) => x.key === item.key);
let newValues = item.value.filter((keyWord) => keyWord !== word);
item.value = [...newValues];
api.updateConfig(item).then((res) => {
if (res.result.status === 200) {
let apiDataItems = [...apiDATA.items];
apiDataItems.splice(index, 1);
apiDataItems.splice(index, 0, item);
apiDATA.items = [...apiDataItems];
setApiDATA(apiDATA);
}
});
};
Unfortunately this function does not re-render when I update state. And it only re-renders when I update some other state. I know the code is a bit crappy but I tried a few things to make it re-render and I can't get around it. I know it has something to do with React not seeing this as a proper update so it doesn't re-render but I have no idea why.
It is not updating because you are changing the array items inside apiDATA, and React only re-render if the pointer to apiDATA changes. React does not compare all items inside the apiDATA.
You have to create a new apiDATA to make React updates.
Try this:
if (res.result.status === 200) {
let apiDataItems = [...apiDATA.items];
apiDataItems.splice(index, 1);
apiDataItems.splice(index, 0, item);
setApiDATA(prevState => {
return {
...prevState,
items: apiDataItems
}
});
}
Using splice isn't a good idea, since it mutates the arrays in place and even if you create a copy via let apiDataItems = [...apiDATA.items];, it's still a shallow copy that has original reference to the nested values.
One of the options is to update your data with map:
const deleteItemFromConfig = (word, item) => {
api.updateConfig(item).then((res) => {
if (res.result.status === 200) {
const items = apiDATA.items.map(it => {
if (it.key === item.key) {
return {
...item,
values: item.value.filter((keyWord) => keyWord !== word)
}
}
return item;
})
setApiDATA(apiData => ({...apiData, items});
}
});
}

How can i assign computed properties to data with Vue.js?

In my CMS system, I can add pages. I want to also edit the existing content of these pages.
What I am trying to achieve is:
User edits page
Page updates in same firebase document where it was created
Page displays new content
Right now, I have set up getter and setter in computed where getter is getting data from the firebase collection and is providing the corresponding text I want to edit, and setter will commit to the changes I do with this text.
data: () => ({
title: null,
content: null
}),
computed: {
pages: {
get() {
return this.pageIndex
},
set() {
this.title
this.content
}
}
},
To update the document in firebase I am triggering this method:
methods: {
async update(id) {
return db
.collection('PAGES')
.doc(id)
.set({
title: this.title,
content: this.content
})
.then(() => {
return this.fetchPage()
})
},
}
But it posts this in my firebase document:
title: null
content: null
In the console, it shows undefined. When posting the data as with array or objects, it does post whatever changes I made. But that is my problem, I do not want to post it as an object or array, I am trying to post it as field names inside a document.
The current way I am doing it results in this:
What can I change to post the updated title and content?
Edit:
If I do it this way it will post the data but it will post as an object and an array:
data: function() {
return {
updatedPayload: {
title: null,
content: null
}
}
},
computed: {
pages: {
get() {
return this.pageIndex
},
set() {
this.updatedPayload = { title: this.title, content: this.content }
}
}
},
methods: {
async update(id) {
// return db.doc(`pages/${page.id}`)
const self = this
const pages = self.pages
return db
.collection('PAGES')
.doc(id)
.set({ pages })
.then(() => {
return this.fetchPage()
})
}
}
Here is the HTML
<tbody v-for="(page, idx) in pages" :key="page" class="">
<b-input v-model="page.title"></b-input>
<VueEditor v-model="page.content"></VueEditor>
</tbody>
So the dilemma is, I can only update data with an object, but I can not update data with field name, as it appears.
The OP of this thread is trying to achieve the same outcome as me, populate data for an input field, where as input shows existing information, and can populate new data. In my case, I need 2 inputs to populate new data, it already displays existing data from firebase.

Categories

Resources