How can I get a single element from a json? - json

I am creating a marketplace in angular and I have created the marketplace page which is populated by a remote json created with mockapi. The problem is that in the homepage I want to display a single item (possibly random) from the same json but with *ngFor it displays all the items.
This is my code:
export class DashboardComponent implements OnInit {
nfts: any;
constructor(
private http: HttpClient,
) {
}
ngOnInit(): void {
this.getNfts()
}
getNfts() {
this.http.get('https://63bd1526fa38d30d85d88179.mockapi.io/NFT/v1/metadata').subscribe((data) => {
this.nfts = data
})
}
}
// HTML
<div class="card cards card-p" *ngFor="let nft of nfts">
<img src="{{nft.image}}" class="card-img-top">
<div class="card-body">
<h4 class="nft-title">{{nft.name}}</h4>
<a class="nft-collection mb-3" routerLink="/">NFT collection</a>
<p>Price: <span>300</span></p>
<button class="button heart text-end"><i class="fa-solid fa-heart"></i></button>
<a routerLink="/nft-details/:id" class="stretched-link"></a>
</div>
</div>
I hope someone can help me! Thank you!

If all you want to do is display a single element (random) from an array then you can use something like "Math.floor(Math.random() * this.nfts.length)" to get a random element from the array and display that array.
so the html will look something like this
<div class="card cards card-p">
<img src="{{nfts[randomIndex].image}}" class="card-img-top">
<div class="card-body">
<h4 class="nft-title">{{nfts[randomIndex].name}}</h4>
<a class="nft-collection mb-3" routerLink="/">NFT collection</a>
<p>Price: <span>300</span></p>
<button class="button heart text-end"><i class="fa-solid fa-heart"></i></button>
<a routerLink="/nft-details/:id" class="stretched-link"></a>
</div>
</div>
The Javascript
export class DashboardComponent implements OnInit {
nfts: any;
randomIndex = 0;
constructor(
private http: HttpClient,
) {
}
ngOnInit(): void {
this.getNfts()
}
getNfts() {
this.http.get('https://63bd1526fa38d30d85d88179.mockapi.io/NFT/v1/metadata').subscribe((data) => {
this.nfts = data
this.randomIndex = Math.floor(Math.random() * this.nfts.length)
})
}
}

Use Object.map and iterate the elements that use the same syntax.

Related

Best way to implement a pageable to Angular Project

I have products in my database and I have created controller on my backend app that I tested and works really good, so now I need to implement that to my frontend. I have product.component.ts file that looks like this
import { Component, OnInit } from "#angular/core";
import { FormControl, FormGroup } from "#angular/forms";
import { debounceTime, switchMap } from "rxjs";
import { ProductService } from "./product.service";
#Component({
selector: 'app-product',
templateUrl: './product.component.html'
})
export class ProductComponent implements OnInit {
products: any[] = [];
productSearchForm!: FormGroup;
page: number = 0;
size: number = 4;
constructor(
private productService: ProductService
) { }
loadMore() {
this.page = this.page+1;
this.productSearchForm.get('searchTerm')?.valueChanges
.pipe(
debounceTime(500),
switchMap(value => {
return this.productService.searchByTermPageable(value, this.page, this.size);
})
).subscribe(data => {
this.products = data;
}, error => {
console.log(error);
// this.products = [];
});
}
ngOnInit(): void {
this.initializeForm();
this.productSearchForm.get('searchTerm')?.valueChanges
.pipe(
debounceTime(500),
switchMap(value => {
return this.productService.searchByTermPageable(value, this.page, this.size);
})
).subscribe(data => {
this.products = data;
}, error => {
console.log(error);
// this.products = [];
});
}
private initializeForm(): void {
this.productSearchForm = new FormGroup({
searchTerm: new FormControl(null)
});
}
}
searchTerm is a query param that is used to find products with name starting with that term. I call function from file product.service.ts that looks like this
import { HttpClient } from "#angular/common/http";
import { Injectable } from "#angular/core";
import { Observable } from "rxjs";
import { environment } from "src/environments/environment";
#Injectable({ providedIn: 'root'})
export class ProductService {
constructor(private httpClient: HttpClient) { }
searchByTerm(searchTerm: string): Observable<any> {
const url = `${environment.apiUrl}product/search/by-name-or-desc?term=${searchTerm}`;
return this.httpClient.get(url);
}
searchByTermPageable(searchTerm: string, page: number, size: number): Observable<any> {
const url = `${environment.apiUrl}product/search/by-name-or-desc?term=${searchTerm}&page=${page}&size=${size}`;
return this.httpClient.get(url);
}
}
When I click the button load more I want to load next 4 products from database, but keep the first 4 products on my html page, so what is the best way to do this? This is my html page, and also I am using Bulma as my css framework
<div class="mt-5">
<form>
<div class="field" [formGroup]="productSearchForm">
<div class="control has-icons-left">
<input class="input" type="text" formControlName="searchTerm" placeholder="Find products">
<span class="icon is-small is-left">
<i class="fas fa-search"></i>
</span>
</div>
</div>
<hr />
</form>
</div>
<div class="columns">
<div class="column" *ngFor="let product of products">
<div class="card">
<div class="card-content">
<div class="media">
<div class="media-content">
<p class="title is-4">{{ product.name }}</p>
<p class="subtitle is-6">{{ product.category?.name}}</p>
</div>
</div>
<div class="content">
{{ product.description }}
</div>
</div>
</div>
</div>
</div>
<button type="button" class="button is-primary" (click)="loadMore()">Load more...</button>
You can to have a new function in your service that return the elements of the page plus the other elements search. If you has a function that return the paginate elements
searchByTermPageable(search:string,page:number,size:number)
{
...
}
Some like
product:any[]=[] //define an empty array where you store the product
searchOld:string=null //and a variable to store the search
getData(search:string,page:number,size:number)
{
if (search!=this.searchOld) //if is a new search
{
this.searchOld=search
this.product=[]
}
return this.searchByTermPageable(search,page,size).pipe(
map((res:any[])=>[...this.product,...res]), //concat the before store product
tap((res:any[])=>{
this.product=res //store in the product the new array
})
)
}
Allow you some like
<div *ngFor="let product of product$|async">
{{product.name}}
</div>
<button (click)="more()">more</button>
pageIndex:number=-1;
product$:Observable<any[]>
more()
{
this.pageIndex++
this.product$=this.dataService.getData("",this.pageIndex,4)
}
The other solution is search all the product and simple use slice pipe
<div *ngFor="let product of allProduct$|async |slice:0:(index+1)*pageSize">
{{product.name}}
</div>
<button (click)="index=index+1">more</button>
a litle stackblitz

Displaying Data without *Ngfor

I'm trying to display data without using a ngFor loop. It works perfectly but shows all of the quote information from multiple customers. The CSS is laid out in a way that has the discount div next to the customerinfo div Here is the HTML
<hr />
<div class="info">
<div id="CustomerInfoInline" *ngIf="quotes" >
<div *ngFor="let q of quotes">
<h6>Name: {{q.firstName}} {{q.lastName}}</h6>
<h6>Address: {{q.address}}</h6>
<h6>City, State, Zip: {{q.city}}, {{q.state}}, {{q.zip}}</h6>
<h6>SSN: {{q.SSN}}</h6>
<h6>DOB: {{q.dateOfBirth}}</h6>
<h6>Email: {{q.email}}</h6>
<h6>Prev. Carrier: {{q.previousCarrier}}</h6>
<h1>-----------------------------------------------------------------------------------------------</h1>
</div>
</div>
<div *ngIf="quotes">
<div id="CustomerDiscountsInline" *ngFor="let q of quotes">
<h6 id="customerBold">Customer Discounts</h6>
<h4 id="DiscountsID">discounts will be applied here</h4>
</div>
</div>
</div>
<hr />
and the respective TS
import { Component, OnInit } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { environment } from '#environments/environment';
import { Quote } from '#app/_models/quote';
import { Router } from '#angular/router';
#Component({
selector: 'app-quote-summary',
templateUrl: './quote-summary.component.html',
styleUrls: ['./quote-summary.component.css']
})
export class QuoteSummaryComponent implements OnInit {
apiUrl: string = environment.apiUrl
quotes: Quote[]
//TODO: implement submitted quote view later
//submittedQuotes: Quote[]
constructor(private http: HttpClient, private router: Router) { }
ngOnInit(): void {
this.getQuotes()
}
// #region API Calls
getQuotes() {
var httpRequest = this.http.get<Quote[]>(`${this.apiUrl}/quotes`)
httpRequest.subscribe(returnedQuotes => {
this.quotes = returnedQuotes
})
}
}
If you need to show only one customer you can use indexer for quotes like quotes[0]:
Don't forgot to check quotes.length > 0:
<div class="info">
<div id="CustomerInfoInline"
<div *ngIf="quotes && quotes.length > 0">
<h6>Name: {{quotes[0].firstName}} {{quotes[0].lastName}}</h6>
<h6>Address: {{quotes[0].address}}</h6>
....
</div>
</div>
</div>

Rendering card content on a separate route [Angular]

I am learning MEAN stack. I've deployed my application here. Right now If you click on any card i.e. Read more button, it will automatically take you to a division where contenets are displayed. But I want to show all that content on a separate route or page because now I am planning to provide some more useful options such as Like, Report, Upvote,Donwvote`, etc. So I made some changes. Please look at them.
articles.component.html
<div class="row mt-5">
<div class="col-md-4 mb-3" *ngFor="let article of articles; let i = index;">
<div class="card text-center">
<div class="card-body">
<h5 class="card-title">{{article.title}}</h5>
<a (click)="onPress(i)" class="btn btn-primary">Read More</a>
</div>
<div class="card-footer text-muted">
{{article.date}}
</div>
</div>
</div>
</div>
articles.component.ts
import { Component, OnInit } from '#angular/core';
import { ArticlesService } from '../articles.service'; <---- SERVICE FOR READING FROM MONGODB
import {Router, ActivatedRoute }from '#angular/router';
#Component({
...
})
export class ArticlesComponent implements OnInit {
articles=[]
constructor(private _articleService: ArticlesService, private router: Router) { }
ngOnInit() {
this._articleService.getAllArticles()
.subscribe(
res => this.articles = res,
err => console.log(err)
)
}
onPress(id) {
console.log(id); <--- THIS PRINTS INDEX NO OF THE CARD
this.router.navigate(['/options',id]);
}
}
And all the options I mentioned above I've kept them in a separate component.
options-panel.component.html
<div style="margin: 0 auto; width:50vw">
<p> {{data }} </p>
<button (click)="back()"> Back</button>
<div style="display:flex; margin-top:1rem;padding:1rem;">
<button style="margin:0.5rem;"> upVote </button>
<button style="margin:0.5rem;"> DownVote </button>
...
</div>
</div>
options-panel.component.ts
import { Component, OnInit } from '#angular/core';
import {ActivatedRoute,Router } from '#angular/router';
#Component({
...
})
export class OptionsPanelComponent implements OnInit {
private data;
constructor(private router: Router,private activatedRoute: ActivatedRoute) { }
ngOnInit() {
this.activatedRoute.paramMap.subscribe(id =>{
this.data = id.get('id');
})
}
back(){
this.router.navigate(['../'])
}
}
And please review my app-routing.module.ts
...
import { ArticlesComponent } from './articles/articles.component';
import { OptionsPanelComponent } from './options-panel/options-panel.component';
const routes: Routes = [
{path: 'articles', component: ArticlesComponent},
{path:'',redirectTo: 'articles', pathMatch:'full'},
{path: 'options/:id', component:OptionsPanelComponent}
];
#NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
PS: I read documentation and dependancy injection in Angular. I also tried this stackblitz project.
But I'm getting no errors but blank page.
Please tell me what else I should do now.
What you showed in your picture was working, but you are printing just the id. You must get the article with that id from somewhere, like you service.
There is some information missing about your project, but you could make something like this.
Get the id from the route.
Retrieve your article from your service.
Show it on front using async if possible.
options-panel.component.ts
#Component({
...
})
export class OptionsPanelComponent implements OnInit {
private id: number;
private article$: Observable<Article>;
constructor(private router: Router, private activatedRoute: ActivatedRoute, private _articleService: ArticlesService) { }
ngOnInit() {
this.id = Number(this.activateRoute.snapshot.params['id']);
if (this.id) {
this.article$ = this._articleService.getArticle(this.id);
}
}
back(){
this.router.navigate(['../'])
}
}
options-panel.component.html
<div style="margin: 0 auto; width:50vw" *ngIf="article$ | async as article">
<p> {{ article.content }} </p>
<button (click)="back()"> Back</button>
<div style="display:flex; margin-top:1rem;padding:1rem;">
<button style="margin:0.5rem;"> upVote </button>
<button style="margin:0.5rem;"> DownVote </button>
...
</div>
</div>
If you are seeing that Welcome to my app! and logo, it must be on the index.html file.
If I am not wrong then, you want to create a separate link for each read more. In that case, you have to add router-link in your html and you have to update your routes as well. Try this one:
Your html:
<div class="row mt-5">
<div class="col-md-4 mb-3" *ngFor="let article of articles; let i = index;">
<div class="card text-center">
<div class="card-body">
<h5 class="card-title">{{article.title}}</h5>
<a [routerLink]="['/'+article.id]" routerLinkActive="router-link-active" (click)="onPress(i)" class="btn btn-primary">Read More</a>
</div>
<div class="card-footer text-muted">
{{article.date}}
</div>
</div>
</div>
</div>
this html will generate links for each read more. you also have to update your router and have to put your router-outlet in your desired position.
const routes: Routes = [
{
path: 'articles', component: ArticlesComponent,
children: [
{
path: 'id',
component: 'Read more component'
}
]
},
{ path: '', redirectTo: 'articles', pathMatch: 'full' },
{ path: 'options/:id', component: OptionsPanelComponent }
];

Binding data to another component and use it in angular using #Input, #Output and EventEmitter

I am trying to pass the one team from array of team in another component and use it. I am getting selected team in same component(team) as console.log but not getting in the another component(team-detail)
Actually, I want to use team id to fetch details of other detail about team from API. please help me around this TIA
Team.component.ts
export class TeamsComponent implements OnInit {
#Output() selectedTeam = new EventEmitter<any>();
constructor(private general: GeneralService) {
}
teamsObject: any;
teams: [];
ngOnInit() {
this.loadTeams();
}
loadTeams() {
this.general.getTeams().subscribe(data => {
this.teamsObject = data;
this.teams = this.teamsObject.teams;
});
}
onSelectTeam(team: any) {
this.selectedTeam.emit(team);
}
}
Team.component.html
<div class="container-fluid " >
<div class="row " >
<div class="col-xs-12" *ngFor="let team of teams">
<div class="card border-dark " style="width: 250px; height: 450px; margin: 10px;" (click)="onSelectTeam(team)">
<img class="card-img-top embed-responsive" src="{{team.crestUrl}}" alt="Card image cap">
<div class="card-body">
<h5 class="card-title">{{team.name}}</h5>
<p class="card-text">{{team.address}}</p>
Visit Website
</div>
</div>
</div>
</div>
<app-team-detail *ngIf="selectedTeam" [team]="selectedTeam"></app-team-detail>
</div>
Team-detail component
export class TeamDetailComponent implements OnInit {
#Input() team: any;
constructor() {
}
ngOnInit() {
}
}
team-detail template(this template only for demo purpose.)
<p>
{{team.name}}
{{team.crestUrl}}
{{team.address}}
{{team.website}}
</p>
“Output” identifies the events a component can fire to send information up the hierarchy to its parent. In you case you want to send information from parent component to child. So you don't need to use Output.
Team.component.ts
export class TeamsComponent implements OnInit {
selectedTeam:any;
constructor(private general: GeneralService) {
}
teamsObject: any;
teams: [];
ngOnInit() {
this.loadTeams();
}
loadTeams() {
this.general.getTeams().subscribe(data => {
this.teamsObject = data;
this.teams = this.teamsObject.teams;
});
}
onSelectTeam(team: any) {
this.selectedTeam = team;
}
}
team.component.html
<app-team-detail *ngIf="selectedTeam" [team]="selectedTeam"></app-team-detail>
team-details template
<p *ngIf="team">
{{team.name}}
{{team.crestUrl}}
{{team.address}}
{{team.website}}
</p>
create your property with getter and setter in you child component , rest of code remain as is
_team: any;
get team(): any {
return this._team;
}
#Input()
set team(value: any) {
this._team = value;
}
in child
<p *ngIf="team">
{{team.name}}
{{team.crestUrl}}
{{team.address}}
{{team.website}}
</p>

How to show a modal window with *ngFor for multiple items in Angular4

I am experimenting with a simple project in order to learn Angular and I have currently a problem:
I am loading some dummy people with a service and then displaying boxes with the persons' names. And when I click on a box, a modal popup comes up and displays more info about this person - currently just a string of a short bio.
The problem is that I have an *ngFor to itterate over the persons and then what I suspect happens is that I also create a modal window for each and every one. Then the modal window does not know which is the currently selected person, so it just shows me the bio of the the first person from the list ...
So the question is how do I make it work for each currently selected person; i.e. when I click on person with id = 3, the modal displays the bio of that same person.
I guess this needs to be done programatically, that's why I am not using stuff like href="#modal" data-toggle="modal" to bind the modal window to an event.
Any better ideas?
Here is what I have: PersonComponent
#Component({
selector: 'person',
templateUrl: './person.component.html'
})
export class PersonComponent implements OnInit {
people: Person[];
selectedPerson: Person;
personBio: string;
#ViewChild('personModal') private personModal;
constructor(private router: Router,
private activatedRoute: ActivatedRoute,
private stateService: StateService,
private personService: PersonService) {
}
ngOnInit() {
this.loadAllPeople();
}
private loadAllPeople() {
this.personService.getPeople()
.subscribe(result => {
this.people = result;
}, error => {
this.console.log(error);
});
}
goToPersonEditComponent(person: Person) {
this.stateService.person = this.selectedPerson;
this.router.navigate(['../' + FrontendRoute.EDIT], {relativeTo: this.activatedRoute});
}
loadPersonBioModal(person: Person) {
if (this.personModal) {
this.selectedPerson = person;
jQuery(this.personModal.nativeElement).modal('show');
}
this.personService.getPersonBio(person.id)
.subscribe((bio) => this.personBio = bio);
}
closeModal() {
if (this.personModal) {
jQuery(this.personModal.nativeElement).on('hide.bs.modal', (evt) => {
this.selectedPerson = null;
});
}
}
}
Person edit component: I am not showing the whole thing, to keep it simple, but the idea is that I get the selected person and from the id attribute I can edit it's bio.
#Component({
selector: 'person-edit',
templateUrl: './person-edit.component.html'
})
export class PersonEditComponent implements OnInit {
person: Person;
constructor(private router: Router,
private activatedRoute: ActivatedRoute,
private packService: PackService,
private orderService: PackOrderService,
private stateService: StateService,
private eventBusService: EventBusService,
private loggingService: LoggingService) {
}
ngOnInit() {
this.person = this.stateService.person;
}
}
A simple service for passing stuff between components (I wanted it this way instead of with #Input):
#Injectable()
export class StateService {
private _person: Person;
constructor() {
}
get person() {
return this._person;
}
set person(person: Person) {
this._person = person;
}
clear() {
this._person = null;
}
}
And here is my template where I have the modal:
<div>
<h2 class="h2">People</h2>
<div class="item-collection item-collection-3-columns item-collection-tablet item-collection-wide">
<div class="items">
<article *ngFor="let person of people"
(click)="loadPersonBioModal(person)">
<div class="content">
<div class="headline">
<span>{{person.name}}</span>
<span data-original-title="Show Person's Bio"></span>
</div>
</div>
</article>
</div>
</div>
</div>
<div #personModal tabindex="-1" role="dialog" class="modal fade" style="display: none;"
aria-hidden="true">
<div role="document" class="modal-dialog modal-lg modal-has-header modal-has-footer">
<div class="modal-content">{{personBio}}</div>
<div class="modal-footer">
<div class="section-btns">
<button type="button" data-dismiss="modal"
(click)="closeModal()">Close
</button>
<button type="button" data-dismiss="modal"
(click)="goToPersonEditComponent(selectedPerson)">Edit Bio
</button>
</div>
</div>
</div>
</div>
And here is the PersonService, which makes a http call:
#Injectable()
export class PersonService {
constructor(private http: HttpClient, private router: Router) {
}
getPeople(): Observable<Person[]> {
return this.http.get<Person[]>(BackendRoute.PERSON_DATA)
.catch(error => {
return Observable.throw(new Error('An unexpected error occurred' + error));
});
}
getPersonBio(): Observable<Person> {
return this.http.get<Person>(BackendRoute.PERSON_BIO)
.catch(error => Observable.throw(new Error(error)));
});
}
}
Okay, given your comments, I think what's happening likely is you are showing your modal before the getPersonBio() returns the person information and assigns it to personBio property.
A likely easy way to tackle this would be to use the async pipe to asynchronously update the values for you. Try this,
in your PersonComponent,
personBio: Observable<Person>;
loadPersonBioModal(person: Person) {
if (this.personModal) {
this.selectedPerson = person;
// directly assigning the returned observable of Person type and let async pipe do its magic in the template
this.personBio = this.personService.getPersonBio(person.id);
jQuery(this.personModal.nativeElement).modal('show');
}
}
In your template,
<div class="modal-content">{{ personBio | async }}</div>
You have to import CommonModule for this to work as probably seen from the docs. Also, using jQuery methods directly in angular is not recommended unless there is no other supported way of doing it.
Let me know if that helped.