How to manage list among components in angular 4 - javascript

I have a BookingSearch component.
//imports
#Component({
selector: 'booking-search',
templateUrl: './booking-search.component.html',
styleUrls: ['./booking-search.component.css']
})
export class BookingSearchComponent implements OnInit {
constructor(private bookingRetrieveService: BookingRetrieveService,
private bookingTreeService: BookingTreeService) {
}
ngOnInit() { }
loadBooking() {
this.bookingRetrieveService.retrieveBooking(bookingId, firstName, lastName)
.subscribe(
response => {
let openBookingUrl = '/booking?b=' + this.bookingId;
window.open(openBookingUrl, '_self');
},
error => { }
);
}
}
Once the booking is loaded it needs to add into a booking tree and page redireds to BookingDetail component.
#Injectable()
export class BookingTreeService {
constructor() { }
addBooking(booking) { }
}
This booking tree needs to be used in a BookingDetail component.
#Component({
moduleId: module.id,
selector: 'booking-detail',
templateUrl: 'booking-detail.component.html',
styleUrls: ['booking-detail.component.css']
})
export class BookingDetailComponent implements OnInit {
booking: any = {};
constructor(private bookingService: BookingTreeService) {
}
ngOnInit() {
//how to get updated booking tree here
}
}
How to update booking tree when search new booking and how can it be used in BookingDetailComponent.
Any suggestions are appreciated.
Thank You!

You can store the booking tree inside a service and access it from your booking-details component.
Like:
#Injectable()
export class BookingTreeService {
bookingTree: Booking[] = [];
constructor() { }
addBooking(booking) {
this.bookingTree.push(booking);
}
}
Then in your bookingDetails component:
constructor(private bookingService: BookingTreeService) {
this.bookings = this.bookingService.bookingTree;
}

Related

Angular removing elements from a list shared with a service

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;
});
}

How to transfer variables from a ts fie to another, angular

I defined a property here in my function
evs: string
...
openArticle(url){
this.evs = url
console.log(this.evs)
this.navCtrl.navigateForward('/url-page')
}
And I a trying to pass the value of 'this.evs' to another ts file and use its value but I do not know how to do this. I tried exporting it like this.
export const webpage = this.evs
but this.evs has no value until someone performs the openArticle function ad so I keep getting the error. "Cannot read property 'evs' of undefined"
What i need to do is tranfer the variable to the 'url-page' page and use the value of this.evs only after the openArticle function has bee called. How do I go about this?
As per my understanding you are trying to share data between two components.
So choose one of them as per your requirements.
Parent to Child: Sharing Data via Input().
Child to Parent: Sharing Data via Output() and EventEmitter.
Unrelated Components: Sharing Data with a Service.
This link will be helpful.
If the components have a parent/child relationship, You can share data between them via #Inpput() and #Output() decorators.
Sharing data from Parent to Child using #Input() :
<h3>Parent Component</h3>
<label>Parent Component</label>c
<input type="number" [(ngModel)]='parentValue'/>
<p>Value of child component is: </p>
<app-child [value]='parentValue'></app-child>
And in the child component, the 'parentValue' can be received as :
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'app-child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit {
#Input() value: number;
constructor() { }
ngOnInit() {
}
}
Now, in the case of sending data from Child to Parent, we can use an #Output() event emitter. So the parent would have a function to receive the emitted data from child as :
parent-app.component.html
<app-child [value]="parentValue" (childEvent)="childEvent($event)"></app-child>
parent-app.component.ts
childEvent(event) {
console.log(event);
}
And, the child.component.ts would look like :
import { Component, OnInit, Input, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'app-child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit {
#Input() PData: number;
#Output() childEvent = new EventEmitter();
constructor() { }
onChange(value) {
this.childEvent.emit(value);
}
ngOnInit() {
}
}
If the components do not have a parent/child relationship, a shared service can be used, say, SharedService which has a BehavioralSubject, that emits value from either component, and the other component can then catch the changed value.
Eg:
import { Injectable } from '#angular/core';
import { BehaviorSubject } from "rxjs/BehaviorSubject";
#Injectable()
export class SharedService {
comp1Val: string;
_comp1ValueBS = new BehaviorSubject<string>('');
comp2Val: string;
_comp2ValueBS = new BehaviorSubject<string>('');
constructor() {
this.comp1Val;
this.comp2Val;
this._comp1ValueBS.next(this.comp1Val);
this._comp2ValueBS.next(this.comp2Val);
}
updateComp1Val(val) {
this.comp1Val = val;
this._comp1ValueBS.next(this.comp1Val);
}
updateComp2Val(val) {
this.comp2Val = val;
this._comp2ValueBS.next(this.comp2Val);
}
And component1 as follows :
import { Injectable } from '#angular/core';
import { BehaviorSubject } from "rxjs/BehaviorSubject";
#Injectable()
export class SharedService {
comp1Val: string;
_comp1ValueBS = new BehaviorSubject<string>('');
comp2Val: string;
_comp2ValueBS = new BehaviorSubject<string>('');
constructor() {
this.comp1Val;
this.comp2Val;
this._comp1ValueBS.next(this.comp1Val);
this._comp2ValueBS.next(this.comp2Val);
}
updateComp1Val(val) {
this.comp1Val = val;
this._comp1ValueBS.next(this.comp1Val);
}
updateComp2Val(val) {
this.comp2Val = val;
this._comp2ValueBS.next(this.comp2Val);
}
Component 2 :
import { Component, AfterContentChecked } from '#angular/core';
import { SharedService } from "../../common/shared.service";
#Component({
selector: 'app-component2',
templateUrl: './component2.component.html',
styleUrls: ['./component2.component.css']
})
export class Component2Component implements AfterContentChecked {
comp1Val: string;
comp2Val: string;
constructor(private sharedService: SharedService) {
this.sharedService.comp2Val = "Component 2 initial value";
}
ngAfterContentChecked() {
this.comp1Val = this.sharedService.comp1Val;
}
addValue(str) {
this.sharedService.updateComp2Val(str);
}
}
You can find more on different types of subjects here

#Input decorator returning undefined in child class

I have two Components StudentsComponent and UnderGradsComponent. In the students component, I created a method to get me the names of students who are "undergrads" in a list. Then im sending this list ot the undergrads component. However, I am always getting that list as undefined inside the undergrads component.
Here is the code for my StudentsComponent
import { Component, OnInit } from "#angular/core";
#Component({
selector: "app-students",
templateUrl: "./students.component.html",
styleUrls: ["./students.component.css"]
})
export class StudentsComponent implements OnInit {
students = [
{ name: "Marwa", level: "undergrad" },
{ name: "Heba", level: "undergrad" },
{ name: "Amal", level: "postgrad" }
];
public undergradsList = this.undergrads();
constructor() {}
ngOnInit() {
this.undergrads();
console.log(this.undergrads);
}
undergrads() {
var Arrayres = new Array();
for (var i = 0; i < this.students.length; i++) {
if (this.students[i].level === "undergrad") {
Arrayres.push(this.students[i].name);
}
}
console.log(Arrayres);
return Arrayres;
}
}
Here is the html for the StudentsComponent
<app-under-grads *ngIf="undergradsList" [studentList]="undergradsList">
</app-under-grads>
Here is the code for the UndergradsComponent
import { Component, OnInit, Input } from "#angular/core";
#Component({
selector: "app-under-grads",
templateUrl: "./under-grads.component.html",
styleUrls: ["./under-grads.component.css"]
})
export class UnderGradsComponent implements OnInit {
#Input() public studentList;
constructor() {}
ngOnInit() {
console.log(this.studentList);
}
}
Finally here is the code for my HTML UnderGradsCompoenent
<h2>UnderGrads</h2>
<div>
<ul>
<li *ngFor="let x of studentList">{{ x }}</li>
</ul>
</div>

Searchbar inside navbar calls an HTTP request, want returned data to populate in another component

I am building a site that allows one to search for a beer and it returns data about that beer. The user clicks the search button, and it runs the http request I have setup on a service. All displays fine. But what I am trying to do is move my search form from the displaying component, to be inside the navbar. How do I link the search form on the navbar to the viewing component?
here is the home.component where the search form currently sits(clicking search runs the "searchBeer" function below passing the beer name being searched for:
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
constructor(private beerSearchService:BeerSearchService) { }
beerName:string;
beers:{};
selectedBeer: {};
searchBeer(beerName){
this.beers=null;
this.beerSearchService.searchBeer(beerName)
.subscribe(data => console.log(this.beers=data));
this.selectedBeer=null;
}
onSelect(beer): void {
this.selectedBeer = beer;
console.log(beer);
}
ngOnInit() {
}
}
EDIT... Had wrong service before.....
beer-search.service:
import { Injectable } from '#angular/core';
import {HttpClient} from '#angular/common/http';
#Injectable({
providedIn: 'root'
})
export class BeerSearchService{
constructor(private http: HttpClient) { }
searchBeer(name){
let data= {
query: `{beerSearch(query:"`+name+`"){items{id, name, style {description}, description, overallScore,
imageUrl, abv, brewer {name, facebook, web}}}}`,
variables:"{}",
operationName:null
}
return this.http.post('https://api.r8.beer/v1/api/graphql/', data, {
headers:{
'x-api-key': '<API-KEY>'}
});
}
}
If I move the search bar to the navbar component, how do I call this searchBeer function?
You store the results of API call to BehaviorSubject in the service, from navbar call the method to get beers from API and in component instead of subscribing to API result, subscribe to Observable (from BehaviorSubject of BeerS - your data):
BeerSearchService
export class BeerSearchService {
private _beers = new BehaviorSubject<Beer[]>(null);
constructor(private http: HttpClient) { }
searchBeer(beerSearch?: string) {
// do something with beerSearch parameter
let data = {
query: ` {topBeers{items{id, name, style {description}, description,
overallScore, imageUrl, abv, brewer {name, facebook, web}}}}`,
variables:"{}",
operationName:null
};
this.http.post('https://api.r8.beer/v1/api/graphql/', data, {
headers: {'x-api-key': '<api-key>'}
}).subscribe(data => {
this._beers.next(data);
});
}
get beers$() {
return this._beers.asObservable();
}
}
navbar.ts
export class NavbarComponent implements OnInit {
constructor(private beerSearchService: BeerSearchService) {}
searchBeer(beerName) {
this.beerSearchService.searchBeer(beerName);
}
}
Component.ts
export class HomeComponent implements OnDestroy {
beers:{};
sub: Subscription;
constructor(private beerSearchService: BeerSearchService) {
this.sub = this.beerSearchService.beers$.subscribe(beers => {
this.beers = beers;
});
}
ngOnDestroy() {
this.sub.unsubscribe();
}
}

Angular 5 - HTTP Client - converting resp.body to Array

I am trying to get my JSON response from the HttpClient service into an array so that I can loop through using *ngFor in my html. I've tried using "this" to loop through but *ngFor will not accept it. Below is the code for my service.ts component and the main component.ts.
I just need some way to convert an array from "resp.body" into an exportable Array to be used for string interpolation in the html. Any help would be much appreciated!
races.component.ts
import { Component, OnInit } from '#angular/core';
import {Race, RacesService} from './races.service';
import { HttpClient } from '#angular/common/http';
#Component({
selector: 'dh-races',
templateUrl: './races.component.html',
providers: [ RacesService ],
styleUrls: ['./races.component.scss']
})
export class RacesComponent {
error: any;
headers: string[];
race: Race;
raceM: any[];
constructor(private racesService: RacesService) {
var raceM = [];
var raceArray = [];
this.racesService.getRaceResponse()
.subscribe(resp => {
raceArray.push(resp.body);
for (let obj of raceArray) {
for (let i in obj) {
raceM.push({
"ID": obj[i].id + ",",
"Date": obj[i].activityStartDate,
"RaceName": obj[i].assetName,
"Website": obj[i].website
})
}
console.log(raceM);
return raceM;
}
});
}
races.service.ts
#Injectable()
export class RacesService {
constructor(private httpClient: HttpClient) { }
getRace() {
return this.httpClient.get(activeApiURL).pipe(
retry(3),
catchError(this.handleError)
);
}
getRaceResponse(): Observable<HttpResponse<Race>> {
return this.httpClient.get<Race>(
activeApiURL, {
observe: 'response'
});
}
To fix the issue, you need to create an interface that matches the data you get from the server, I will call this interface IRace.
Then in the component I will create a variable named races, I will assign the returned value from the server response i.e. resp.body to the races variable.
I'd change the service to look like this:
export interface IRace {
// Your response from server object's properties here like so:
id: Number;
assetName: string;
...
}
export class RacesService {
constructor(private httpClient: HttpClient) { }
getRace() {
return this.httpClient.get(activeApiURL).pipe(
retry(3),
catchError(this.handleError)
);
}
getRaceResponse(): Observable<HttpResponse<Array<Race>>> {
return this.httpClient.get<Array<Race>>(
activeApiURL, {
observe: 'response'
});
}
}
Finally, I'd change the race component to this:
import { Component, OnInit } from '#angular/core';
import { Race, RacesService, IRace } from './races.service';
import { HttpClient } from '#angular/common/http';
#Component({
selector: 'dh-races',
templateUrl: './races.component.html',
providers: [ RacesService ],
styleUrls: ['./races.component.scss']
})
export class RacesComponent {
error: any;
headers: string[];
races: IRace[];
constructor(private racesService: RacesService) {
this.racesService.getRaceResponse()
.subscribe(resp => {
this.races = resp.body;
});
}
}
I hope this helps.

Categories