I have two components: recipe and recipe-detail.
Recipe is displaying list of recipes from my database and recipe-detail should display details of given recipe. What I want to achieve is to whenever someone click on recipe name I want to route him to recipe-detail component.
This is my recipe html:
<section class="columns">
<div class="column" *ngFor="let recipe of RecipeList">
<h2><a routerLink="recipes/{{recipe.Id}}" routerLinkActive="true">{{recipe.name}}</a></h2>
<p>{{recipe.recipeBody}}</p>
</div>
</section>
My routing file:
{path:'recipes', component: RecipeComponent},
{path: 'recipes/:id', component: RecipeDetailsComponent}
And my recipe-detail ts:
export class RecipeDetailsComponent implements OnInit {
#Input() recipe! : any;
constructor(private route: ActivatedRoute,
private sharedService: SharedService) { }
ngOnInit(): void {
this.getRecipe();
}
getRecipe() : void{
const id = Number(this.route.snapshot.paramMap.get('id'));
this.sharedService.getRecipeById(id).subscribe(recipe => this.recipe == recipe);
}
}
Simple html for test:
<h2>Name : {{recipe.name}}</h2>
<h2>RecipeBody: {{recipe.recipeBody}}</h2>
<h2>IntendedUse: {{recipe.IntendedUse}}</h2>
<h2>CoffeeId: {{recipe.coffeeId}}</h2>
Results:
When I click on recipe name (recipe.component) it redirects me to: http://localhost:4200/recipes/recipes when it sould be for example http://localhost:4200/recipes/1
And in the console i get: TypeError: Cannot read properties of undefined (reading 'name')
Edit:
How i fetch it:
getRecipes() : Observable<any[]> {
return this.http.get<any>(this.ApiUrl + '/Recipes')
}
getRecipeById(val:any){
return this.http.get<any>(this.ApiUrl + '/Recipes/', val);
}
I would recommend doing it as below by subscribing
HTML
<h2><a [routerLink]="['/recipes', recipe.Id]" routerLinkActive="true">{{recipe.name}}</a></h2>
import { ActivatedRoute } from '#angular/router';
constructor(private route: ActivatedRoute,private sharedService: SharedService) {
ngOnInit() {
this.route.params
.subscribe(
(params: Params) => {
this.sharedService.getRecipeById(params['id']).subscribe(recipe => {
this.recipe = recipe;
})
}
);
}
}
or even make it better with switchMap
You need to use = to assign the values but not ==.
From:
this.sharedService.getRecipeById(id).subscribe(recipe => this.recipe == recipe);
To:
this.sharedService.getRecipeById(id).subscribe((recipe:any) => this.recipe = recipe);
Related
with *ngFor, I cannot fetch the data from my component.ts to my component.html
The same method works for one class but does not work for another class.
Here is my service class
export class FoodListService {
private url = environment.serverURL;
constructor(private http: HttpClient) {
}
//get all products from one store
getAllProducts(storeId: Number): Observable<FoodListModelServer[]> {
return this.http.get<FoodListModelServer[]>(this.url + 'foodlist/' + storeId);
}
Here is my component class
export class FoodListComponent implements OnInit {
foodlist: FoodListModelServer[] = [];
constructor(private foodListService: FoodListService, private router: Router, private route: ActivatedRoute) {}
ngOnInit(): void {
this.foodListService.getAllProducts(this.storeId).subscribe(food => {
this.foodlist = food;
console.log(this.foodlist);
});
}
}
Here is my component.html
<div class="col-md-8 col-lg-10 col-sm-6 card">
<li *ngFor="let foodlist of foodlist">
{{foodlist.food_name}}
</li>
</div>
Console.log(this.foodlist)
I get and object {count: 5, stores: Array(5)}
Why do I get a count included forming an object instead of just the Array?
How do I get only the array?
I have same code with the other component and it works fine. I tried everything mentioned online with no progress.
Why do I get a count included forming an object instead of just the
Array?
it depends on the implementation of API on the backend side
How do I get only the array?
create interface for the actual response from API and use here this.http.get<FoodListModelServerResponse>
then we can extract value from response via RxJs map operator - map(response => response.stores) (find more info here: https://www.learnrxjs.io/learn-rxjs/operators/transformation/map)
that is it, you can subscribe to getAllProducts and you will get the array
import { map } from 'rxjs/operators';
export interface FoodListModelServerResponse {
count: number;
stores: FoodListModelServer[];
}
export class FoodListService {
private url = environment.serverURL;
constructor(private http: HttpClient) {
}
getAllProducts(storeId: Number): Observable<FoodListModelServer[]> {
return this.http.get<FoodListModelServerResponse >(this.url + 'foodlist/' + storeId)
.pipe(map(response => response.stores));
}
then you can use your implementation
ngOnInit(): void {
this.foodListService.getAllProducts(this.storeId).subscribe(food => {
this.foodlist = food;
console.log(this.foodlist);
});
}
}
Use RxJs pluck operator to extract stores out of response object.
Declare foodlist variable as foodlist$: Observable<FoodListModelServer[]> so that it is assignable to observable.
In foodService return Observable<any> like
getAllProducts(storeId: Number): Observable<any>
import { pluck} from 'rxjs/operators';
import { Observable } from 'rxjs';
export class FoodListComponent implements OnInit {
foodlist$: Observable<FoodListModelServer[]>;
constructor(private foodListService: FoodListService, private router: Router, private route: ActivatedRoute) {}
ngOnInit(): void {
this.foodlist$ = this.foodListService.getAllProducts(this.storeId).pipe(
pluck('stores')
);
}
}
In template use Async pipe, it will take care of subscribing and unsubscribing to your foodListService.getAllProducts
<div class="col-md-8 col-lg-10 col-sm-6 card">
<li *ngFor="let foodlist of foodlist$ | async">
{{foodlist.food_name}}
</li>
</div>
this problem is driving me crazy.
I have an array defined within a service, which is used in 3 other components:
This is the service, file products.service.ts (notice the product array of Products)
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Product } from './../models/Product';
import { ProductForm, productFormToProduct } from './../models/ProductForm';
// #Injectable({
// providedIn: 'root'
// })
const apiUrl = 'http://localhost:3000/products';
#Injectable()
export class ProductsService {
public products: Product[] = [];
constructor(private http: HttpClient) {}
getProducts() {
return this.http.get(apiUrl)
}
deleteProduct(p: Product) {
// this.products = this.products.filter(prod => prod.id !== p.id);
const i = this.products.indexOf(p);
this.products.splice(i,1);
return this.http.delete(apiUrl + "/" + p.id)
}
storeNewProduct(pf: ProductForm) {
const idList = this.products.map((x) => {return x.id});
const i = Math.max(...idList) + 1;
const p = productFormToProduct(pf);
p.id = i;
this.products.push(p);
return this.http.post(apiUrl, p)
}
}
This is the component where i subscribe to getProducts, and fill the array (file products.component.ts):
import { Component, OnInit } from '#angular/core';
import { ProductsService } from '../../shared/services/products.service';
import { Product } from '../../shared/models/Product';
#Component({
selector: 'app-products',
templateUrl: './products.component.html',
styleUrls: ['./products.component.scss']
})
export class ProductsComponent implements OnInit {
products: Product[] = [];
searchText: string = "";
constructor(private productsService: ProductsService) {}
ngOnInit(): void {
this.productsService.getProducts()
.subscribe((data: Product[]) => {
this.productsService.products = data;
this.products = this.productsService.products;
})
}
}
And this is the component where i subscribe to deleteProduct (file product-card.component.ts):
import { Component, Input, OnInit } from '#angular/core';
import { ProductsService } from '../../services/products.service';
import { Product } from './../../models/Product';
#Component({
selector: 'app-product-card',
templateUrl: './product-card.component.html',
styleUrls: ['./product-card.component.scss']
})
export class ProductCardComponent implements OnInit {
constructor(private productsService: ProductsService) {}
ngOnInit(): void {
}
#Input() product: Product
public buttonDeleteFunction() {
this.productsService.deleteProduct(this.product).subscribe();
}
}
The problem is, when i click on some delete product button, i have this weird behaviour:
Before click:
After click:
Here is the products.component.html file:
<div class="products__header">
<h3 class="products__heading">
Listado de productos ({{ products.length }})
</h3>
<input
class="products__search"
placeholder="Buscador"
type="search"
[(ngModel)]="searchText"
/>
</div>
<p *ngFor="let p of products">{{ p.name }}</p>
<p>{{ products }}</p>
<div class="products__list">
<app-product-card
*ngFor="let p of products | filterNames: searchText"
[product]="p"
></app-product-card>
</div>
Why do i get the expected behaviour in only two of the four places where i use the products list?
I know i can use an Output to manually remove the item from the list when i click the button, but i have been told that services are used instead of Inputs/Outputs when i want to share between multiple components, so i'd rather not use an Output for this
When you use your approach with common data on service layer then a common pitfall is that Angular does not detect the changes that affect your component. In that case you must inform your component for those changes using an emmiter.
Use an emmiter on service
productUpdated :EventEmitter = new EventEmitter();
deleteProduct(p: Product) {
// this.products = this.products.filter(prod => prod.id !== p.id);
const i = this.products.indexOf(p);
this.products.splice(i,1);
this.productUpdated.emit(this.products);
return this.http.delete(apiUrl + "/" + p.id)
}
And then listen for that change ProductsComponent
export class ProductsComponent implements OnInit {
products: Product[] = [];
searchText: string = "";
constructor(private productsService: ProductsService) {}
ngOnInit(): void {
this.productsService.getProducts()
.subscribe((data: Product[]) => {
this.productsService.products = data;
this.products = this.productsService.products;
})
this.productsService.productUpdated.subscribe( (data) => {
this.products = data;
});
}
frisur-details.component.ts
hier am trying to fetch a single data and display it
in this detail-component
frisur: Frisur;
subs: Subscription;
constructor(
private service: FrisurService,
private route: ActivatedRoute,
) { }
ngOnInit() {
this.subs = this.service.getFrisur(this.route.snapshot.params['id'])
.subscribe(
res => {
this.frisur = res
}
);
}
ngOnDestroy(){
this.subs.unsubscribe();
}
}
frisur.service.ts
the service to fetch a list of data
and a single data
#Injectable({
providedIn: 'root'
})
export class FrisurService {
private apiUrl = './assets/frisur.json'
frisure: Frisur[];
frisur: Frisur;
constructor(private http: HttpClient) { }
getFrisurList(): Observable<Frisur[]> {
return this.http.get<Frisur[]>(this.apiUrl);
}
getFrisur(id: number){
return this.http
.get<Frisur>( `${this.apiUrl}/${id}` )
.pipe(
map(res => this.frisur = res)
)
}
}
frisur-detail.component.html
i'm getting a 404 Error Not found
<div class="container container1" *ngIf="frisur">
<div class="innen-div">
<div><img class="image-div" src='{{frisur.avatar}}'></div>
<p class="spacer mat-body-1"><b>{{ frisur.style }}</b></p>
<p class="spacer mat-body-1">{{ frisur.model }}</p>
<a mat-stroked-button color="primary" routerLink="/">Go Back</a>
</div>
</div>
I don't know the response exactly but I'm assuming that you get something like this
// response from the API.
{
id: 1,
avatar: 'imageurl.png',
style: 'string',
model: 'string'
}
First, edit your service so it can return the response as it is.
in your getFrisur() method in frisur.service.ts
getFrisur(id: number){
return this.http.get<Frisur>(`${this.apiUrl}/${id}`);
}
Also, another thing is in your html component
instead of this
<img class="image-div" src='{{frisur.avatar}}'>
Replace it with
<img class="image-div" [src]="frisur.avatar">
How to update component when route changes. I have this component :
import { Component, OnInit } from '#angular/core';
import { ActivatedRoute } from '#angular/router';
import { ListService } from '../list/list.service';
#Component({
selector: 'view',
template: `
<div *ngIf="!entity">
<p>Select <b (click)="showRow()">row {{entity}}</b>!</p>
</div>
<div *ngIf="entity">
<p >{{entity.id}}</p>
<p >{{entity.name}}</p>
<p >{{entity.weight}}</p>
<p >{{entity.symbol}}</p>
</div>
`,
styles: []
})
export class ViewComponent implements OnInit {
constructor(
private route: ActivatedRoute,
private service: ListService
) {
this.route.params.subscribe(params => {
const id = parseInt(params['id']);
if (id) {
const entity = this.service.getRow(id);
this.entity = entity
}
});
}
entity;
showRow() {
console.log(this.entity);
}
ngOnInit() {
}
}
in this.entity inside constructor i have desired object but when i execute showRow this.entity is undefined, what i'm doing wrong ? I have tried to change property to different name, and it didn't work as expected, if any one knows how to resolve this or point me to right direction.
EDIT:
getRow from service
getRow(id) {
console.log(id, 'test');
return this.datasource.find(row => row.id === id);//returns good row
}
Move your code to ngOnInit() method and check will you get value or not.
ngOnInit() {
this.route.params.subscribe(params => {
const id = parseInt(params['id']);
if (id) {
const entity = this.service.getRow(id);
this.entity = entity
}
});
}
I believe you need to define/initialize the entity that is positioned above showRow, such as:
const entity = Entity;
or something along those lines. apologies I am quite new to angular as well.
I found answer to my problem, i just needed to put router-outlet in template just like that :
....
....
template: `
<router-outlet>
<div *ngIf="row?.id; else elseBlock">
<div>
<p>{{row.id}}</p>
<p>{{row.name}}</p>
<p>{{row.weight}}</p>
<p>{{row.symbol}}</p>
</div>
</div>
</router-outlet>
`,
I am trying to pass the string value of this.title from my LandingPage.component to my ResultPage.component.
I retrieve the list.show value, and send it to my TitleService in like so in my:
landingpage.component.html
<ol>
<li (click)="selectShow(list.show)" [routerLink]="['/details', list.id]" *ngFor="let list of shows">{{list.show}}
</li>
</ol>
landingpage.component.ts
import { TitleService } from '../../services/title.service';
constructor(private TitleService: TitleService) {}
selectShow(show) {
this.TitleService.fetchTitle(show)
}
The above sends the list.show value to my:
title.service.ts
// this gives us the name of the clicked show, which we send to TitleResolver
#Injectable()
export class TitleService {
fetchTitle(title) {
console.log("title is " + title); // this outputs correctly
return title;
}
}
And here is how I manage the routing in my:
app-routing.module.ts
import { TitleService } from './services/title.service';
const routes: Routes = [
{ path: '', component: LandingPage },
{
path: 'details/:id', component: ResultPage
}
];
#NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
providers: [TitleService]
})
My question
Once I receive the title.show value in my service component, I'm unsure how to then send it to my receiving component (resultpage.component)
How can I send my title value from my service to my ResultPage.component?
Make the title a public property of the service like this:
// this gives us the name of the clicked show, which we send to TitleResolver
#Injectable()
export class TitleService {
selectedTitle: string;
fetchTitle(title) {
console.log("title is " + title); // this outputs correctly
this.selectedTitle = title;
return title; // No need to return it.
}
}
Then any other component can inject this service and access this.titleService.selectedTitle
In title.service.ts you can declare a variable called title and have setter and getter:
title: string ="";
// replace fetchTitle with setTitle
// remember to change it in the component too
setTitle(title) {
this.title = title;
}
getTitle() {
return this.title;
}
Then, when ResultPage.component is initialized, call getTitle() from TitleService and set the result to a variable declared in the component.
Here's an example of sharing data via shared services.
Separation of concerns... Your landing page is used to select the list item and navigate to the result page. Let it do just that and only that. Let the ResultPage.component do the rest. Note: Other answers recommend storing the value of the last title in the TitleService. It's not a good idea to store state in a service. Then TitleService cannot be used as a generic way to get any title separate from your current navigation, without side effects.
Remove (click) event. Add 'show' as a QueryParam.
landingpage.component.html
<li [routerLink]="['/details', list.id]"
[queryParams]="{show: list.show}"
*ngFor="let list of shows">
{{list.show}}
</li>
Subscribe to router params and queryparams to get the id and show.
resultpage.component.ts
import { Component, OnInit, OnDestroy } from '#angular/core';
import { ActivatedRoute, Router } from '#angular/router';
import { TitleService } from '../../services/title.service';
#Component({
...
})
export class ResultPageComponent implements OnInit, OnDestroy {
itemId: string;
show: string;
subParams: any; // infinite Observable to be unsubscribed
subQueryParams: any; // infinite Observable to be unsubscribed
constructor(
...
private TitleService: TitleService,
protected route: ActivatedRoute,
protected router: Router,
...
) {}
ngOnInit() {
this.subParams = this.route.params.subscribe(this.onParams);
this.subQueryParams = this.route.queryParams(this.onQueryParams);
}
ngOnDestroy() {
// Delete active subscribes on destroy
this.subParams.unsubscribe();
this.subQueryParams.unsubscribe();
}
onParams = (params: any) => {
this.itemId = params['id'];
}
onQueryParams = (data: any) => {
this.show = data.show;
if(this.show) {
this.TitleService.fetchTitle(this.show)
}
}