Angular 14 get data from unrelated component - javascript

I need help with understanding data sharing between components... I have a project that has a cart page and a footer that is a standalone component and is included in every page of my app... In the footer I have a cart icon and on that icon I'm trying to show the number of items in cart... I get the data of items in cart from an API... Do I have to call this api and all of its data in the footer component with the same function as in the cart page also or is there a shorter simpler way that uses less code?
In the image bellow you can see this footer component (NOTE: 11 is hardcoded at the moment):
Here is the the API response how the cart data looks like:
{
"total_products": 0,
"totals": null,
"notes": null,
"shipping_method": null,
"products": []
}
Here is the code of cart.page.ts that I use to show data in my app:
ngOnInit() {
this.getCart();
}
getCart() {
this.getCartSubscription = this.cartService.getCart().subscribe(
(data: any) => {
const productsData = data.body.products;
const totalProducts = data.body.total_products;
const totalCartPrice = data.body.totals;
this.products = productsData.map(products => products);
this.totalProducts = totalProducts;
this.totalCartPrice = totalCartPrice;
console.log('Products in cart:', data.body);
},
error => {
console.log('Error', error);
});
}
How do I approach to showing total products in my footer component?
Is my code even good? Is there a way to optimize it? This is my first real Angular project and I would like to do this as propperly as possible :)
**EDIT: I have read about and tried using BehaviourSubject but am unsure of it implementation in my case...
Thank you very much :)

Use BehaviorSubject. It is the simplest way in angular to share data between unrelated component.
https://www.infragistics.com/community/blogs/b/infragistics/posts/simplest-way-to-share-data-between-two-unrelated-components-in-angular
https://medium.com/codex/how-to-share-data-between-components-in-angular-a-shopping-cart-example-b86ce8254965

You could decorate the page-component with #Output and the footer with #Input. See this page on angular.io
I've created a simple example you can find on StackBlitz. However, I think as your application grows it will be better just to use a service the call the backend (as you are doing in your cart-component) and have all interested component subscibe to it.

Related

Data from store not reactive in Vue.js 3

This is my first attempt at building a web app with Vuejs. I've been trying to get data from an external JSON API and display it on my app. The JSON fetch etc is working fine. but I can't get the data to be displayed reactively on my component.
As you can read in Appraisal.js given an API link some data is populated in Appraisal.app_data. The data always has an array called items (that's just how the API is. I'll add validation later). As a proof of concept I'm trying to display the number of elements in the items array.
Since other components in my app will also use this data, I'm using an external store as the data source everywhere. One of the components calls Appraisal.setLink() on getting some user input. That part is working as expected. However the DOM contents don't change at all.
I referred to State Management for setting up the external store. I also referred to some other answers on StackOverflow with a similar issue and got the following suggestions:
The data should be initialized to undefined or null instead of {} for reactivity to work.
Properties of objects are not reactive. But by my understanding this was changed in Vue3 where it doesn't matter because proxies are in use. Either way I tried using the Object.assign({}, ..., ...) method but it did not help.
Arrow functions cannot be used in methods for reactive objects. If I remove the arrow function and put the body inside .then(function(data) {...}) it complains that this is not defined for the second then function on fetch
// --- src/components/AppraisalView.vue
<script setup>
import ItemView from './ItemView.vue';
</script>
<template>
<div v-if="app_data">{{app_data.items.length}} items in appraisal</div>
<div v-else>Enter link to get quote</div>
</template>
<script>
import {Appraisal} from '../stores/Appraisal.js';
export default {
data() {
return {
app_data: Appraisal.app_data,
}
},
}
</script>
// ---- src/store/Appraisal.js
import {reactive} from 'vue'
import {BuybackCalculator} from './BuybackCalculator.js';
export const Appraisal = reactive({
link: '',
app_data: undefined,
methods: {
setLink(value) {
if (this.link == value) return;
this.link = value;
console.log('Updating appraisal with: '+this.link);
fetch(this.link+".json")
.then((response) => response.json())
.then((data) => {
this.app_data = data;
console.log(this.app_data);
BuybackCalculator.methods.calculate_buyback(this.app_data);
});
}
}
});

Vue: Handover id to other component

Community
In my application I render a a list of doctors. This list is displayed in my DoctorView component like this:
<div
class="col-12 m-2"
v-for="recommendation in recommendations"
:key="recommendation"
>
<DoctorListItem :recommendation="recommendation" />
</div>
What I am trying to do is, that when a user clicks on an entry in this list, I want the application to open a detail page about this doctor. So in my DoctorListItem component I use a router-link:
<router-link
:to="`/doctors/${recommendation.doctor.doctor_id}`"
class="stretched-link"
></router-link>
Now I need to give this id to the ReadDoctorView, which displays the detailed informations about the selected doctor. Because I need this information to load the details of the selected doctor. This id is in my DoctorView and in my DoctorListItem available, but I can't get it to my ReadDoctorView component. I tried this with props but i am an absolute beginner and I am not sure where to define the props and how to handle the data.
In my ReadDoctorView I need a method like the one below to get the selected doctor:
methods: {
getDoctorById() {
this.id = this.recommendation.doctor.doctor_id;
console.log(this.id);
return axios
.get('http://URL/doctors/${this.id}')
.then((response) => {
this.doctordetails = response.data;
console.log(id);
})
.catch((error) => {
console.log("Ein Fehler ist aufgetreten: " + error.response);
});
},
},
I hope my explanation makes sense and you can understand what I am trying to accomplish.
Thank you in advance for any tips!
PS: I receive these warnings because vue router doesn't know these routes. Any idea how to stop these warnings:
In Vue router params from the route can be accessed by accessing $router object try accessing by using solution I hope it will work:
this.id = this.$route.params.id

How to handle vue composition api with lazy and route components?

In my vue application I have a component page that contains five components. I also use vue composition api.
each component is lazy, it resolve when the component is in the viewport. because of that I need to write the logic only inside the component. (not in the parent).
so far so good, but two of components inside product.vue depend of the route params value. say /product/2.
the product.vue looks like that:
<intro> -> just a raw text (no need to fetch from the server, not need to wait to load)
<foo> -> need data from the server base on the productId from the route.params.
<txt> -> just a raw text not need to get the data from the server
<bar> -> need data from the server base on the productId from the route.params.
Important notes:
In case I don't need data from the server like intro I want to render it immediately. so for all similar components in this page.
for example intro render right way, the foo will render when it enter to the viewport and display a loader and when the data is resolved it change to the data.
I want to see the page ASAP. so each component have its own loader.
Here I become with some problems:
One: I need to know if the product exist. if not it need to be redirect to 404 page. so where to put this logic? each component or the parent component? I think the parent component, but when it take 2 min to load the data I'll get a blank screen seince the components are not ready and not be render. which is conflict with "Important notes".
Two: when the productId is change (navigate to another product) the foo and bar need to resolve the data, so in those two components I need to listen to the route change (which makes duplication in my code) (and slow performance?) and still I need to check if the productId is exist or not.
in this example the items don't change because it need to trigger the useProduct function.
setup() {
const { route, router } = useRouter();
const productIdToLoad = computed(() => route.value.params.productId);
const { items } = useProduct({ productId: productIdToLoad });
return { items }
}
and if I do it in watch I lose the reference
setup() {
const { route, router } = useRouter();
const items = ref([]);
const productIdToLoad = computed(() => route.value.params.productId);
watch(productIdToLoad, (v) => {
items = useProduct({ productId: v });
});
return { items }
}

React Router -- history push state not refreshing with new state object

When Promise.all resolves and the new activity is saved, the user should be routed to /activities to view their newly created activity. Everything works as expected, however I currently need to refresh /activities page (once) after being routed in order to view the new activity in the table.
const handleSaveActivity = e => {
e.preventDefault();
Promise.all([
addActivity(),
saveActivity()
]).then(() => {
props.history.push('/activities');
})
};
I'm not sure how to re-render the page automatically after pushing a new history state, so the user does not need to manually refresh the page to see the new state. Happy to provide more code snippets if I left out something critical.
Hi i must be a little late to answer this, but this issue can be due to the wrong use of useEffect, if you have lets say a todo list and you wanna fetch data with axios for example, it would look like this:
useEffect(()=>{
axios.get(`${YOUR_URL}/todos`)
.then((res)=>{
setTodos(todos=res.data)
})
},[])
now as you can see we have initial value of an empty array, so this is acting as a ComponentDidMount, what you might want is to re render the component after it gets a new value, so you want to have a ComponentDidUpdate effect, so you would just not initialize the value as an empty array, therefore it would look like this:
useEffect(()=>{
axios.get(`${YOUR_URL}/todos`)
.then((res)=>{
setTodos(todos=res.data)
})
})
Hope this helps someone, couse i landed here due to the same issue and came to solve it this way.
just to run this.setState({whateverKey:whateverValue})?
In your activities page (call it Activities component) you should call API to get the updated data every time browser hit this component URL.
With class based style, you should do it in componentDidMount life cycle hook
class Activities extends Component {
// ...
componentDidMount() { loadActivities() }
// ...
}
With function based style, you should do it in useEffect hook
import React, { useEffect } from 'react'
const Activities = () => {
useEffect(() => { loadActivities() });
}
https://github.com/supasate/connected-react-router Please use this package, it solves the problem.
This issue I've faced a few minutes ago...however I finally found the solution by manually using the vanilla javascript. => for refreshing the page you can use
=> window.location.reload(false); after using the push property.

Advice about webapp architecture and reactJs

This question is more to know your opinions about the way I'm trying to solve this issue.
I would need a little bit of ReactJs expertise here as I'm quite new.
First a little bit of context. I'm developing a web application using ReactJs for the frontend part.
This webapp is going to have many translations, so for maintenance I thought it would be better to store all the translations in a database instead of having them into a file. This way I could manage them using sql scripts.
I'm using a MySQL database for the backend, but for performance reasons, I have added ElasticSearch as second database (well, it is more a full text search engine).
So once the application starts, the translations are automatically loaded into ElasticSearch. Every translation has a code, and a text, so in elastic search I only load the translations for one locale (by default english), and when a user switchs the locale, a call is done to load all the translations for the selected locale and update their corresponding text.
This way from the fronted I can reference a translation only by the code and I will get the text translated in the correct locale.
Now, how do I do that in react?
So far I have written a component TranslatedMessage which is basically looking for a given code and displaying it whereever this component is rendered.
Below the code of the component:
import React from 'react';
export class TranslatedMessage extends React.Component {
constructor() {
super();
this.render = this.render.bind(this);
this.componentDidMount = this.componentDidMount.bind(this);
this.state = {message: ''};
}
render() {
return (<div>{this.state.message}</div>);
}
componentDidMount() {
var component = this;
var code=this.props.code;
var url="data/translation?code="+code;
$.get(url, function (result) {
component.setState({message: result.text});
});
}
};
And then I use it in the application whis way, for example to translate the title of an 'a' link:
<TranslatedMessage code="lng.dropdown.home"/><i className="fa fa-chevron-down" />
So far is working fine but the problem is that I need to refresh the whole page to get the new translations displayed, because I'm not updating the state of the component.
So now my questions:
1)Every time that we find in a page the component TranslatedMessage, a new instance of that component is created right? so basically if I have 1000 translations, 1000 instances of that component will be created? And then React has to take care and watch all these instances for changes in the state? Would that be very bad for performance? Do you find any more efficient way to do it?
2) I don't think forcing the whole page to reload is the most proper way to do it, but how can I update the states of all that components when a user switch the locale? I've been reading about a framework (or pattern) called Flux, and maybe that could fit my needs, what do you thing, would you recommend it?
3) What do you think about storing translations on db, I'm a bit concern about sending a query to the db for every translation, would you recommend or not this approach?
Any suggestions, ideas for improvement are very welcome!
Thank you for taking your time to read it or for any help!
I use what is basically a Flux store for this purpose. On initialisation the application requests the whole language file to use (which is JSON) and that gets shoved into memory in the store, something like this (I'm going to assume a totally flat language file for now, and I'm using ES2015/16 syntax, I've omitted error checking etc etc for brevity):
class I18n {
constructor() {
this.lang = await fetch( 'lang_endpoint' )
.then( res => res.json() )
}
get( translation ) {
return this.lang[ translation ] || null
}
}
Somewhere my app starts during a ReactDOM.render( <App /> ) or some variation and this renders the whole thing top-down (I try to eliminate state as much as possible). If I needed to switch languages then I'd bind a change handler such that the store emits a change event which is heard by some code that triggers a ReactDOM.render. This is fairly standard Flux practise for changing the app state, the key is to try and eliminate state from your components and store it inside your stores.
To use the I18n class simply instantiate it somewhere (I normally have it as a singleton exported from a file, e.g. module.exports = new I18n(), require that file into your components and use the get method (this assumes some sort of packager such as browserify or webpack but it looks like you have that complexity all sorted):
import 'i18n' from 'stores/i18n'
class MyComponent extends React.Component {
constructor() { ... }
render() {
return (
<span>{ i18n.get( 'title' ) }</span>
)
}
}
This component could also be simplified to
const MyComponent = props => <span>{ i18n.get( 'title' ) }</span>

Categories

Resources