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
Related
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.
I Am trying to figure out how can I add the items to watchlist,
The steps am trying to carry out here are, when a user clicks on add button, the items should be added to the watchlist page/component which I have created.
Please see the hierarchy of the component.
I would like to show added items on the watchlist page.
Please see the code I tried.
const [watchlist, setWatchlist] = useState ([]);
const handleWatchlist = (movieData) => {
const newList = [...watchlist, movieData]
setWatchlist(newList)
console.log(newList)
}
<Button className = {classes.cardButton}size = "small" onClick = { ()=> handleWatchlist(movie) }> Add </Button>
When I try to inspect, the result is, it shows the items are added but can not pass on to the watchlist component? How can use a prop to pass this value and show them?
Any help is really appreciated.
Thanks a million
The Button doesn't pass any argument in handleWatchlist in your example. I don't know how Button component looks like, but passing the arg could look like the example below:
const Button = ({ onClick }) => {
const value = "some value";
return <button onClick={() => onClick(value)}>Button</button>;
};
const WatchList = () => {
...
return <Button onClick={handleWatchlist}>Add</Button>
Thanks for the support, but I figured out the solution by using two approaches and they are
Which is props drilling, i.e. is to perform the useState action in my App.js which is my main page where I have product and watchlist component.
This is the best way I would suggest other people use as well is by using the contextAPI and useContext hooks. A simple and easy way to pass any data anywhere in the app.
Please see the full code with the working solution below on the codesandbox.
https://codesandbox.io/s/determined-nightingale-m2mtn?file=/src/components/Products
The working app, where I can add the products to the watchlist.
working demo app
I am setting title on a template of the app component. The title has been updating by user input on the user page. How can available that title all over the app?
I have tried through local storage which works but does there any other way to make it available throughout the app?
Stackblitz URL: https://stackblitz.com/edit/angular-kfptvs
[Updates]
When I have implemented this same on my another app, I started getting error:
Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'null: Bob'. Current value: 'null: Rob'.
The above error occurs when execute the below code:
this._userService.titleObs.subscribe((title) => {
this.title = title;
})
I have fixed through below code but can anyone suggest the better way to fix it?
this._userService.titleObs.subscribe(
title => setTimeout(() => this.title = title, 0)
);
Using the service for this is a good approach. For navigation, you should use router links instead of navigating via the <a> tag. This avoids a reload of the page.
As Maryannah said you used a service which is a good way to solve the problem. The reason it doesn't work for you might be because your service is under the temp folder. If you put a service in the root folder, all components will be able to access the same data.
The issue has been fixed though adding ChangeDetectorRef
import { Component, OnInit, ChangeDetectorRef } from '#angular/core';
constructor(
private ref: ChangeDetectorRef
){}
this._userService.titleObs.subscribe((title) => {
this.title = title;
this.ref.detectChanges()
})
I've been struggling with this for a few days and was hoping for a graceful way of handling dynamic URLs with no data.
I have the following routes:
const router = new VueRouter({
routes: [
{path: '/product/:slug', component: Product},
{path: '/404', component: PageNotFound, alias: '*'}
]
});
In the Product component, I have an object of products and, depending on the slug variable, load the product to show.
The issue i'm having is when the URL is a slug that does not exist in the products dataset. I would like to load the PageNotFound component, without updating the URL.
Is this possible? It would be nice to have a consistent 404 page throughout the app and would also be good for me not to have to repeat myself with a v-if in the product table.
The closest I've got to it is this:
if(!product) {
this.$router.replace({path: '/404', query: {product: this.$route.params.slug}});
}
However, this updates the actual URL which is not very good UX.
Any clues?
You could conditionally render your PageNotFound component in Product.vue if the query returns no results, and then not have to fiddle with your router at all.
Thanks to Kyle pointing me in the right direction, this is what I came up with.
Becuase I am being slightly unorthodox and using server-side components and JavaScript, I already had my page not found component loaded - which looks like this:
const PageNotFound = {
name: 'PageNotFound',
template: `<div>
<h1>404 Page Not Found</h1>
<p>Head back to the <router-link to="/">home page</router-link> and start again.</p>
</div>`
};
I made sure the PageNotFound.js file was loaded in the HTML before my product component, so I was able to do the following:
const ProductPage = {
name: 'ProductPage',
template: `<div>
<div v-if="product"><h1>{{ product.title }}</h1></div>
<page-not-found v-if="notFound"></page-not-found>
</div>`,
components: {
PageNotFound
},
data() {
return {
notFound: false
}
},
computed: {
product() {
let product;
if(Object.keys(this.$store.state.products).length) {
product = this.$store.state.products[this.$route.params.slug];
if(!product) {
this.notFound = true;
}
}
return product;
}
}
};
Things to note in the above:
Data is being loaded asynchronously, hence the check to see if products exist
The PageNotFound component is loaded in - this is ES6 for PageNotFound: PageNotFound - Vue then automatically makes a <page-not-found></page-not-found> element
That element then has a v-if which gets triggered. As the first container would not be in existence if there is no product, only the 404 component is displayed
I don't do it based on product, as you would get a flash of the 404 if the product data was still loading via an API.
It's better practice to have the URL params as props (see docs), which I will be doing at some point!
To conclude, this allows you to show a consistent 404 page throughout your SPA (single page application) while maintaining URLs with dynamic routes. It allows you to load another component or show another component without updating the URL and also lets you have a wildcard 404 for dynamic routes.
Hope that all makes sense and helps someone in the future and saves them from wasting ~4 hours of trial, error and googling. (and yes I have "keyword" and phrase stuffed this answer to help someone find it...)
I know that it's not a default behaviour / feature of react-router to help us reload easily the current component but I really need this in my application.
My application deals with products. I have a product list that I can load, and when I click on an item, it displays the concerned product details.
On that page, I have related product links that load the same component, but with another product details, located at
<Route path="/products/:id/details" component={ProductDetail} />
I m fetching data in my componentWillMount, and it seems that if I only change the URL, a new component is NOT mounted, and so, I m always having my old data displayed, without fetching anything.
As a beginner using React, I'm looking for some help, or some tricks to reload the component concerned by that page. I mean being able to reload the ProductDetail with the good product.
I tried to look around with componentWillUpdate (a method in which I can see that the router URI changes :D) but I can't setState inside of it to make my component reload (it doesn't seem to be a good practice at all)
Any idea how can I make this work ?
EDIT : According to the first answer, I have to use onEnter. I m now stuck with the way of passing state/props to the concerned component :
const onEnterMethod = () => {
return fetch(URL)
.then(res => res.json())
.then(cmp => {
if (cmp.length === 1) {
// How to pass state / props to the next component ?
}
});
};
The way to handle it depends if you are using flux, redux or however you want to manage your actions. On top of it I would try to make use of onChange property of Route component (check React router docs):
<Route path="/products/:id/details" component={ProductDetail} onChange={someMethod} />
And then in the someMethod create the action if you are using redux or however is done in flux.
The redux would be:
<Route path="/products/:id/details" component={ProductDetail} onEnter={onEnterHandler(store)} />
And the onEnterHandler with the redux store:
function onEnterHandler(store) {
return (nextState, replace) => {
store.dispatch({
type: "CHANGEPRODUCT",
payload: nextState.params.id
})
};
}
And then in your ProductDetail component you would print the new information (It would require a bit more of learning in redux and redux-sagas libraries to complete that part).
Keep in mind that React is just the view part, trying to solve all those problems using only react is not only not recommended but also would mess up your code.