How to send persisted complex object from component to another while router navigate in angular 5 - angular5

I need to pass a complex object from component 1 to component 2 while router navigate. Here my object consist of a list of integer values and two integer fields. The list of integer values are obtained when user selected on the grid. And navigate to component 2 when user click on the button. I have used behavior subject to transfer the data and it is working. Problem is that data become null when page (compoent2) is refreshing in browser.
here is my service
export class CustomDataService {
constructor() {}
private customData$: BehaviorSubject < any > = new BehaviorSubject < any > (null);
setData(data: any) {
this.studentSerachData$.next(data);
}
getData() {
if (this.customData$: getValue()) {
return this.customData$: asObservable().share();
}
}
I have added this service in my shared module's provider array and imported this module in component 1 and component 2. I am getting data on component 2 when page load first time. When I refresh the route I am getting undefined.

The best way to get rid of this problem is to send your object via Queryparams , so on page refresh you wont lose the data, but if you are not a fan of queryparams and long urls so try to add your object to Localstorage so after first time you would read it from localStorage , but this way you cant have your object if you come from outside of your project or anywhere else that didnt trigger the behavior subject transfer.
Option 1 :
let params = {};
params[object]=JSON.stringify(objectyouwanttohaveincomponent2)
this.router.navigate(['whatevercomponent2Routeis'], {queryParams: params});
Then in ngOninit of component 2 :
object : any = {} ;
constructor(private router: Router,
private route: ActivatedRoute){}
ngoninit(){
this.object = JSON.parse(this.route.snapshot.queryParamMap.get('object'));
}
Option 2 :
After you getData you simply add it to localStorage and you retrive it next time :
localStorage.setItem('object',customData$);
ngoninit(){
if (localStorage.getItem('object')) {
this.object = localStorage.getItem('object');
}
}

Related

Passing data to an ancestor component vue3

I have a multi-step form in Vue3 that has the following structure:
<Formroot />
...
<formPage2 />
...
<postcodeWidget />
I have a data structure in my formRoot that holds all the field values from the child components and then uses them to make an external API call and present a result.
I use Props to pass the data down to the child components and then emit from the children to the parent.
The issue is, my autocomplete widget - which pulls from an external api - does all the autocomplete in the setup() function. I cannot figure out the best way to communicate input from that widget back up to the formRoot component.
I tried emitting from the widget but I can't access the instance from within setup, and I can't seem to access the data from setup variables within an instance method.
For example, I have a function called changePostcode that fires on input to the field:
methods: {
changePostcode(e){
//I have tried calling the input event:
this.$emit('update:postcode', e.target.value)
//I have tried accessing my setup variable:
this.$emit('update:postcode', this.selectedPostcode) //or postcode.value this is the actull value I want to emit.
//these dont work.They return nothing.
},
}
my selectedPostcode variable is set in the setup() function as follows:
setup() {
...
let selectedPostcode = ref('')
let searchTerm = ref('')
...
// searchTerm is used in a filter with data from an external API to offer suggestions. This is the ultimate source of the "location" object
const selectPostcode = (location) => {
selectedPostcode.value = location.postcode
searchTerm.value = location.locality
}
return {
searchTerm,
...
selectPostcode,
selectedPostcode,
...
}
}
I have a locality and a postcode variable because I want to populate the input with a "locality" that includes the full name of the suburb while I want to emit only the post/zip code.
My setup does a bunch of other work including calling and api for a list of suburb and filtering on user input to make suggestions. That all works fine.
In summary,
A multi step form
One step includes a nested component that needs to pass data up to the root ancestor
I cannot seem to access/emit data from setup() back up to the ancestor element
What is the right way to do this? It seems like it should be a pretty common use case.
I looked into provide/inject as well but I also couldn't understand how to send data back up to the ancestor only down to the child.
The ancestor could provide a function (e.g., a setter) that the nested component could inject to communicate a value back to the ancestor:
// RootAncestor.vue
<script setup>
import { ref, provide } from 'vue'
const postCode = ref('initial value')
const setPostCode = value => {
postCode.value = value
}
provide('setPostCode', setPostCode)
</script>
// NestedComponent.vue
<script setup>
import { inject } from 'vue'
const setPostCode = inject('setPostCode')
const save = () => {
setPostCode('12345')
}
</script>
demo

Vue.js 3 : Accessing a dynamically created component

I have a vue.js 3 application that needs to dynamically create components and access them.
The proper way to dynamically create them and injected them in the DOM seems to be th wrap them in an app using createApp, and this part works, when I call test() a new copy of the components appears at the bottom of the DOM.
Now I need to be able to get a reference to that component son I can call public (exposed) methods on it.
In my (actual) code below, what would allow me to get a reference to my component ?
import { createApp, h } from "vue";
import ConfirmModal from '#/views/components/ui/confirm-modal.vue';
function test()
{
let componentApp = createApp({
setup() {
return () => h(ConfirmModal, { title: "Fuck yeah!", type: "primary" });
}
});
const wrapper = document.createElement('div');
wrapper.setAttribute('id', 'confirm-modal-0');
componentApp.mount(wrapper);
document.body.appendChild(wrapper);
let _confirmModal: ConfirmModal = ???????????????????
_confirmModal.show();
}
EDIT : Here's my use-case: I have a helper function in a service which is used to confirm actions (like deletes) using a Modal dialog. This helper/service is pure TS, not a Vue component, but needs to instanciate the modal (which is a vue component), have it injected in the DOM and have its public methods callable (by my helper/service).
Right now, the only thing that works is to have a sungle copy of the modal component in my root layout and have to root layout foward the ref to my service, which can then use the component. But this isn't great because I need multiple instances of the dialog, and isn't good SOC to have to root layout handle a modal dialog.

Computed value based on component's created method

In my component I have acreated method where I make a request and then I want to use the data I get for a computed property.
computed: {
...mapState(["pages"]),
pageContent() {
let slug = this.$route.params.slug ? this.$route.params.slug : 'home' ;
let page = this.pages.find(page => page.slug == slug);
return page.content;
}
},
methods: {
...mapActions(['setPagesAction'])
},
created() {
this.setPagesAction();
}
The problem is that created is executed after the computed property pageContent is read, so it is undefined.
How can I make pageContent get the data from created ?
Solution
As you know, computed property will update itself when the dependent data changes. ie when this.pages is assigned in vuex. So
You have mainly three options.
Option 1
Set a variable so that till the time page content is being loaded, a loading spinner etc is being shown.
Option 2
If you are using vue-router, then use the beforeRouteEnter guard to load the data.
like here
Option 3
Load data initially (when app starts) and add it to vuex. (you can use vuex modules for seperating data store if needed)

Using one vuex module store in multiple sibling components

I have one global state with some modules.
now i have vue components for various parts of my page.
i have everything setup so /foo uses the foo store (this works).
the created method loads data from an API and writes it to the store
now i have /foo/bar as another (sibling) component, but it needs to access the same store as /foo, but i can't get it to work.
if i enter /foo/bar/ in the URL, there is nothing in the store.
but if i switch to /foo, and then back to /foo/bar, the data is in the store and being output correctly
I've tried registering /foo/bar as a child, which seemed to have no effect (and actually it's not really a child, but just another page with the same data..)
I also tried
state: {
...mapState([
'foo'
)]
}
in /foo/bar, but that doesn't seem to be the right way either
what is the best practice to
load data from API on created on any of a specified set of pages
access said data on any of those pages (i.e. sharing the same store)
i've tried all day to find a solution, but it seems I didn't understand something.
thanks for your help :)
EDIT
actually, while i read my question again, i think my whole problem is the data not being loaded (because the created method is not called). how can i make sure this happens on any page using the store and just once? i can't just write an api call in every created method, can i?
Well, I think just to summarize your problem could be called like you're not being able to access the same state between two different componentes.
What I do normally is that I make an API call from one component inside the method beforeMount, that will guarantee that once my component is created, the data will be available to be used.
Furthermore, after calling the api, I update my state so after that I can call it from everywhere.
One thing that you have to take care with is which component is loaded first?
If A is B's parent, then you should load data inside A.
However, if A and B are siblings, then you should load data inside both of them because you can access first either Component A or B, then you don't know when the data is going to be available. In that case, I would load the data in both of the components.
Also, add cache to your server so you don't need to load the same data again.
For example:
State
{
data: {}
}
Component A
export default {
name: 'Batch',
beforeMount() {
this.getDataFromAPI();
},
methods: {
// getDataFromAPI will store its return inside data with a mutation
...mapActions(['getDataFromAPI']),
randomMethod() {
// Now I can Use my state
const data = this.$store.state.data;
}
}
};
Component B
export default {
name: 'Batch',
methods: {
randomMethodB() {
// If component A was loaded first than component B and A is B's parent, then the state will be accessible in the same manner and it should be populated
const data = this.$store.state.data;
}
}
};
Actions
const getDataFromAPI = ({ commit }) => new Promise((resolve, reject) => {
// Call server
const data = await callServer();
commit('updateMyStateWithData');
resolve(data);
});
export default {
getDataFromAPI
}
Mutations
const mutations = {
updateMyStateWithData(state, newData) {
state.data = newData;
}
}
export default mutations;
Another thing that I do is to define getters, that way is a good approach to load data once, and inside the getter you update the data to return only the things that your UI needs.
I hope that it helps!

How to stop reloading the components in vue router?

I tried to find some article on it but couldn't find any.
I am using vue router and loading data via axios in created hook. The problem is, I don't want to call the created hook every time user visits to next route and come back. So when user click back, currently the created hook runs which reloads the data from database. What I want is to block re rendering of data and use already loaded previous components.
Thank you.
You can create variable e.g. initialState to maintain the data and if that is empty query the database otherwise take from the variable.
const initialState = {
counter: 0,
};
export default {
name: 'ComponentName',
created() {
initalState.counter += 1;
console.log(initalState.counter);
}
}
If you refresh the browser then variable will be reset to origin in that case you can use localstorage