Angular 2 edit cart total after remove a product - function

i'm stucked on a problem and i don't know how to get out of it.
I have two component sibiling:
One that show a list of products with a button for each product that delete from the cart the single product with a POST call at one REST API.
And another component that simple call a REST API for get the totals of the cart and show it.
The problem is that when i delete correctly the item from the cart, obviously the cart total doesn't update itself.
So, i've searched on the communities and i think that there are two different solutions:
Use a shared service;
Use #Input and #Output
I've tried using the first option, but without success, i tried also with #input and #Output but i don't think that i really understand how to use it between two components that aren't Parent > Child or opposite.
What i need is to call the function GetTotals inside the CARTTOTAL.COMPONENT from the CARTITEMS.COMPONENT for updating the prices.
I've tried to inject the same service in both components and call the function from the first one, but seems doesn't work.
Here the code:
cartitems.component.ts
import { Component, OnInit, Inject, Output } from '#angular/core';
import { ManageGuestCartService } from '../manageCartServices/addtoguestcart.service';
//import { CarttotalComponent } from '../carttotal/carttotal.component';
// Service for guest total cart
import { TotalguestcartService } from '../manageCartServices/totalguestcart.service';
#Component({
selector: 'app-cartitems',
templateUrl: './cartitems.component.html',
styleUrls: ['./cartitems.component.css'],
providers: [ManageGuestCartService, TotalguestcartService]
})
export class CartitemsComponent implements OnInit {
itemofcart:any[];
constructor(private _guestcartservice: ManageGuestCartService, private _totalguestcart: TotalguestcartService) { }
ngOnInit() {
this.listCartItems();
}
listCartItems() {
return this._guestcartservice.getCartDetails()
.subscribe(data => {
this.itemofcart = data;
//console.log(this.itemofcart);
},
(err) => {
//alert('vuoto');
});
}
removeProductFromCart(itemid) {
return this._guestcartservice.deleteProductFromCart(itemid)
.subscribe(data => {
// this.itemofcart = data;
// console.log(this.itemofcart);
this.listCartItems();
this._totalguestcart.getTotals();
},
(err) => {
alert('errore');
});
}
}
carttotals.component.ts
import { Component, OnInit, Input} from '#angular/core';
// Service for guest total cart
import { TotalguestcartService } from '../manageCartServices/totalguestcart.service';
#Component({
selector: 'app-carttotal',
templateUrl: './carttotal.component.html',
styleUrls: ['./carttotal.component.css'],
providers: [TotalguestcartService]
})
export class CarttotalComponent implements OnInit {
constructor(private _totalguestcart: TotalguestcartService) { }
totals:any[];
ngOnInit() {
this.retrieveTotals();
}
retrieveTotals() {
return this._totalguestcart.getTotals()
.subscribe(data => {
this.totals = data;
console.log(this.totals);
},
(err) => {
alert(err);
});
}
}
totalguestcart.service.ts
import { Injectable } from '#angular/core';
import { Http, Headers, RequestOptions} from '#angular/http';
#Injectable()
export class TotalguestcartService {
constructor(public http: Http) { }
public getTotals() {
let cartid;
cartid = localStorage.getItem("guestCartId");
let contentHeaders = new Headers();
contentHeaders.append('Accept', 'application/json');
contentHeaders.append('Content-Type', 'application/json');
return this.http.get(URL, { headers: contentHeaders})
//.map(res => res.json())
.map((res) => {
if(res.status == 404) {
return res.status;
} else {
return res.json();
}
});
}
}
Can someone give me the correct way to find a solution to this issue? all the feeds are accepted :)
Thanks in advance!

As mentioned, you need to mark your providers in the module, so that you share the same service between your components.
Shared Service would be best here. From my understanding you want to fire an event in the carttotals component, when deleting an item in the cartitems component.
Well we can set up Observable which will fire that event. So in your totalguestcart service add this:
private fireEvent = new Subject<boolean>();
event = this.fireEvent.asObservable();
emitEvent(bool: boolean) {
this.fireEvent.next(bool);
}
Here we are just using boolean values, as you do not need to pass any specific values, but only fire an event.
Then when you are performing the deletion, let's notify the other component, which subscribes to this, that the method should be fired.
removeProductFromCart(itemid) {
return this._guestcartservice.deleteProductFromCart(itemid)
.subscribe(data => {
this.itemofcart = data;
this.listCartItems();
this._totalguestcart.emitEvent(true); // add this!!
},
(err) => {
alert('error');
});
}
And in your cart totals, subscribe to this in your constructor, and execute the getTotals method:
constructor(private _totalguestcart: TotalguestcartService) {
_totalguestcart.event.subscribe(res => {
this.retrieveTotals();
})
}
this.retrieveTotals will then be fired each time you are deleting an item. Of course this can be used in other methods as well, like adding and updating (if you need it).
Hope this helps! :)

Throw out the service TotalguestcartService out of the providers of your components and put it into the providers of the app-module: What is happening: each component is getting a local copy of the service, so they cannot exchange information, as there are TWO services injected. Putting it global (app.module) provides it for every component as long as the component doesn't do an own provider.

Related

save data with Angular and Firebase [duplicate]

This question already has an answer here:
Updating Data in Firebase using React and Axios
(1 answer)
Closed 2 years ago.
i'm creating a Deckbuilder, i have this decks of cards and a data-storage.service to store and fetch the decks in Firebase.
i have a component deck-details that shows the details of a selected deck and allows to remove or add cards to the deck.
i'm trying to store the modified cards in the selected deck in firebase
this is the data-storage service
#Injectable({ providedIn: "root" })
export class DataStorageService {
constructor(private http: HttpClient, private deckService: DeckService) {}
storeDecks() {
const decks = this.deckService.getDecks();
this.http
.put("https://ang-cards.firebaseio.com/decks.json", decks)
.subscribe((response) => {
console.log(response);
console.log("stored");
});
}
fetchDecks() {
return this.http
.get<Deck[]>("https://ang-cards.firebaseio.com/decks.json")
.subscribe((decks) => {
decks
? this.deckService.setDecks(decks)
: this.deckService.setDecks([]);
console.log("fetching", decks);
});
}
storeCards(i: number, cards: Card[]){
this.http
.put("https://ang-cards.firebaseio.com/decks/" + i + "/deckCards", cards)
.subscribe((response) => {
console.log(response);
console.log("cards stored");
});
}
}
storeDecks and fetchDecks work, but i have a problem with storeCards function called in the deck-details component in the onCardsEdit() function.
this is the deck-detail component
import { Card } from "./../../card/card.model";
import { Deck } from "./../../deck/deck.model";
import { Component, OnInit, Input } from "#angular/core";
import { DeckService } from "src/app/deck/deck.service";
import { ActivatedRoute, Params, Router } from "#angular/router";
import { Subscription } from "rxjs";
import { DataStorageService } from 'src/app/shared/data-storage.service';
#Component({
selector: "app-deck-details",
templateUrl: "./deck-details.component.html",
styleUrls: ["./deck-details.component.scss"],
})
export class DeckDetailsComponent implements OnInit {
paramsSubscription: Subscription;
id: number;
decks: Deck[];
deck: Deck;
constructor(
private deckService: DeckService,
private route: ActivatedRoute,
private dataStorageService: DataStorageService
) {}
ngOnInit() {
this.decks = this.deckService.getDecks();
this.id = this.route.snapshot.params["id"];
this.paramsSubscription = this.route.params.subscribe((params: Params) => {
this.id = params["id"];
this.deck = this.decks.find((deck) => deck.id === this.id);
});
}
onDeleteCard(i){
this.deckService.deleteCard(this.deck, this.deck.deckCards[i])
}
onCardsEdit(){
this.dataStorageService.storeCards(this.decks.indexOf(this.deck), this.deck.deckCards)
}
ngOnDestroy() {
this.paramsSubscription.unsubscribe();
}
}
when i try to store the cards i get these 3 errors:
Access to XMLHttpRequest at 'https://ang-cards.firebaseio.com/decks/1/deckCards' from origin 'http://localhost:4200' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
PUT https://ang-cards.firebaseio.com/decks/1/deckCards net::ERR_FAILED
core.js:5882 ERROR HttpErrorResponse {headers: HttpHeaders, status: 0, statusText: "Unknown Error", url: "https://ang-cards.firebaseio.com/decks/1/deckCards", ok: false, …}
To interact with the REST API of the Firebase Realtime Database, your URLs must end with .json. Without that, you're trying to access the Firebase console, which returns a cross-origin error when you do.
So the code should look something like:
this.http
.put("https://ang-cards.firebaseio.com/decks/" + i + "/deckCards.json", cards)
...

http observable<any> - Angular 4

I need to display the data on html that I get from web service. I am able to see the data in a format that I want, but I can't display properly on html. I think -any- in http.get is the problem. I can read data in console without -any- but it works fine with . When it works with it, it still does not print in html properly. Can anyone provide advice on this?
html
<div>{{this.res}}</div>
app.component.ts
import { Component, OnInit } from '#angular/core';
//import { IMovie } from './movie';
import { AppService } from './app.service';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
res: any[] ;
errorMessage: string;
constructor(private _appService: AppService) { }
ngOnInit(): void { this.getData(); }
getData(): void {
this._appService.getData()
.subscribe(
(res: any []) => this.res = res,
(error: any) => this.errorMessage = <any>error);
}
}
app.service.ts :
Injectable()
export class AppService {
private urlNorth = '';
constructor(private http: HttpClient) { }
getData(): Observable<any> {
const headers = new HttpHeaders();
headers.set('Content-Type', 'text/sml');
headers.set('Accept', 'text/xml');
headers.set('Content-Type', 'text/xml');
return this.http.get<any>(this.urlNorth,{responseType:'text', headers: headers})
.do(data => {
// console.log(data)
var dataParsed = data.replace('<string xmlns="service">', '').replace('</string>', '').replace(/</g, '<').replace(/>/g, '>');
// console.log(dataParsed);
parseString(dataParsed, (err, res) => {
if (err) {
return console.dir('invalid XML');
}
else {
console.log(res);
console.log(res.NewDataSet.Table[0].DataPointName[0]);
}
})
})
.catch(this.handleError);
}
**data in console w/o any **
{{this.res}} in html
I'm pretty sure you don't have to put any at this line in app.service.ts
return this.http.get<any>(this.urlNorth,{responseType:'text', headers: headers})
because get method expects 0 type arguments.
Type any is not the problem. It's just TypeScript annotation to organise your code. The problem is you are refering to the res in inline template as this.res, but you should just res. However it won't work as you think. Looking at your data structure You will have to iterate throught this data due to Table is an array. Additionaly I Highly suggest to always represnt your data as class
export class Apps {
public Table: Array<any>; //here should be another type instead of "any"
/* rest of data if any */
}
Back to your question you should have in your html file <div>{{res}}</div> but that's just print your object as string if I good remember. So to properly access your data you should iterate through table using *ngFor
<div *ngFor="let el of res.NewDataSet.Table">
<span>{{el.BackColor}}</span>
<!-- all other data -->
</div>
It looks as though the data is coming back. I'll answer your initial question first (since you added a few issues in comments):
My guess is when you get data back, it's not showing because it's HTML, and angular doesn't like injecting html.
Add this to your TS:
import { DomSanitizer, SafeHtml } from '#angular/platform-browser';
res[]: safeHTML;
And change your html to this:
<div [innerHTML]="res"></div>
As mentioned in a previous answer, this is a solution for a single return of res, not an array of different htmls. If it's an array, you'll have to handle it accordingly. for instance:
<ng-container *ngFor="let r of res">
<div [innerHTML]="r">
</ng-container>

Angular doesn't pass HTTP GET params properly

So I figuring out my way around Angular. Just started with a OpenWeather API based application using a simple GET method.
So here is my app.component.ts:
import { Component } from '#angular/core';
import { WeatherService } from './weather.service';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
providers: [WeatherService]
})
export class AppComponent {
title = 'Ng-Weather';
cityName: string;
constructor(private weather: WeatherService) { }
search() {
this.weather.getWeatherbyName(this.cityName);
}
}
As you can guess, the cityName variable is two way binded. The search() function is invoked onclick of a button and the data is passed to the weatherservice. The contents of weather service is:
import { Injectable } from '#angular/core';
import { Http, Response, URLSearchParams } from '#angular/http';
import { Observable } from 'rxjs';
import { Weather } from './weather';
#Injectable()
export class WeatherService {
APIurl = "http://api.openweathermap.org/data/2.5/weather";
Appid = "xxx";
constructor(private Http: Http) { }
getWeatherbyName(name: string): Observable<any> {
let myParams = new URLSearchParams();
myParams.append('q', name);
myParams.append('appid', this.Appid);
// actual http request should look like this: http://api.openweathermap.org/data/2.5/weather?appid=xxx&q=Chennai
return this.Http.get(this.APIurl, { search: myParams})
.map(this.extractData)
.catch(this.handleError);
}
private extractData(res: Response) {
console.log(res.json());
let body = res.json();
return body.data;
}
private handleError(error: Response | any) {
console.error(error.message || error);
return Observable.throw(error.message || error);
}
}
But I get no error in my console or during the compile process. What is being done wrong? Also, how can I map the JSON I get to my class and give back that instance to the app.component?
Following is my class:
export class Weather {
city: String;
max_temp: String;
min_temp: String;
description: String;
}
And this is a sample JSON I receive:
{
"coord":{
"lon":80.28,
"lat":13.09
},
"weather":[
{
"id":803,
"main":"Clouds",
"description":"broken clouds",
"icon":"04n"
}
],
"base":"stations",
"main":{
"temp":304.15,
"pressure":1008,
"humidity":79,
"temp_min":304.15,
"temp_max":304.15
},
"visibility":6000,
"wind":{
"speed":3.1,
"deg":160
},
"clouds":{
"all":75
},
"dt":1504629000,
"sys":{
"type":1,
"id":7834,
"message":0.0029,
"country":"IN",
"sunrise":1504571272,
"sunset":1504615599
},
"id":1264527,
"name":"Chennai",
"cod":200
}
As you can see all I need is some data from the JSON and not the whole thing.
Your main problem here is that you are not subscribing to the observable that is being produced by your getWeatherbyName function. Observables returned by Http are cold:
Cold observables start running upon subscription, i.e., the observable sequence only starts pushing values to the observers when Subscribe is called. (…) This is different from hot observables such as mouse move events or stock tickers which are already producing values even before a subscription is active.
In order to subscribe to this observable, you can simply update your search function to the following:
search() {
this.weather.getWeatherbyName(this.cityName)
.subscribe();
}
This is by no means the complete solution to your problem - You will want to do something in the subscription, such as assign the information received to properties of your component so that they can be rendered in the UI.
You appear to have other issues in your linked project, but I suggest you ask separate questions on Stack Overflow if needed, or even better, your favorite search engine should be able to help.
Try passing a RequestOptions object to the http get instead:
import { RequestOptions } from '#angular/http';
getWeatherbyName(name: string): Observable<any> {
let myParams = new URLSearchParams();
myParams.append('q', name);
myParams.append('appid', this.Appid);
let options = new RequestOptions({ search: myParams}); //<----- NEW
// actual http request should look like this: http://api.openweathermap.org/data/2.5/weather?appid=xxx&q=Chennai
return this.Http.get(this.APIurl, options) //<<----- NEW
.map(this.extractData)
.catch(this.handleError);
}

http with Observable in Angular 2 cant use data

i am new to angular 2 and to observables but i wanted to give it a shot. So i have installed the angular-cli and made a simple test project.
All i wanted it to do is read a json file and work with the data inside of a component (the first intention was to make a service but i wanted to start on a low basis).
So i have created a json file in the assets/json folder (testjson.json):
{
"teststring": "test works"
}
then i have imported the http from angular and the rxjs map stuff inside of my content.component.ts file:
import { Component, OnInit } from '#angular/core';
import { Http } from '#angular/http';
import 'rxjs/add/operator/map';
#Component({
selector: 'app-content',
templateUrl: './content.component.html',
styleUrls: ['./content.component.css']
})
export class ContentComponent implements OnInit {
title: string = "Default";
data;
constructor(private http:Http) {
http.get('assets/json/testjson.json').map(res => res.json()).subscribe(data => {this.data = data; this.title = data.teststring; console.log(this.data);});
}
ngOnInit() {
}
}
So far so good, the app prints out the following:
app works!
test works [object Object]
But i want to use this data in the whole component, not only in the constructor. but if i try to console.log "this.data" outside of the constructor (inside the ngOnInit function), it prints undefined in the console.
I know, that it must have something to do with asynch loading but unfortunately i have no clue how to tell the app to wait until this.data is filled.
I hope you can help me with that. Of course in the future i want a service which does that kind of stuff and more than one component should grab data from it.
Thanks in advance!
You should move the initialization code to the initialization method.
Your data becomes available once the callback completes. In your template you can use *ngIf to execute code inside a block once there is data. As long as the *ngIf does not eval to true the inner code will not run.
The only way you can run console.log(data) is from inside the callback or called from the callback because you have to wait until the data is loaded.
content.component.html
<div *ngIf="data">
<span>{{data.teststring}}</span>
</div>
content.component.ts
export class ContentComponent implements OnInit {
title: string = "Default";
data: any = null;
constructor(private http:Http) {
}
ngOnInit() {
this.http.get('assets/json/testjson.json')
.map(res => res.json())
.subscribe(data => {
this.data = data;
this.title = data.teststring;
console.log(this.data);
});
}
}
Edit
In response to the comment below If you abstract out the http call to a service you can see the exact same logic still applies. You are still using the concept of a promise of data and that you can subscribe to that promise once it has completed. The only difference here is the http call is abstracted to a different class.
content.component.ts
export class ContentComponent implements OnInit {
title: string = "Default";
data: any = null;
// inject service
constructor(private contentService:ContentService) {
}
ngOnInit() {
this.contentService.getData()
.subscribe(data => {
this.data = data;
this.title = data.teststring;
console.log(this.data);
});
}
Service
export class ContentService {
constructor(private http:Http) {
}
getData(): IObservable<{teststring:string}> { // where string can be some defined type
return http.get('assets/json/testjson.json')
.map(res => res.json() as {teststring:string});
}

Google Places with Observables in Angular2

I try to use Google Places with Observables in Angular 2.
To do that, I included the Google scripts in the index.html and then I get some inspiration with Observables from http://blog.thoughtram.io/angular/2016/01/06/taking-advantage-of-observables-in-angular2.html
<!-- Script included in index.html -->
<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places"></script>
You can see the whole application there: https://embed.plnkr.co/LQaag2/
I think there is an issue with the events. For example, when the user type "P", nothing appears. But if he clicks on the page or he types "a", then he will see the results of places starting by "P".
Do you have an idea why?
app/main.ts
import { platformBrowserDynamic } from '#angular/platform-browser-dynamic';
import { AppModule } from './app.module';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/switchMap'
platformBrowserDynamic().bootstrapModule(AppModule);
app/app.module.ts
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { JsonpModule } from '#angular/http';
import { ReactiveFormsModule } from '#angular/forms';
import { AppComponent } from './app.component';
import { GoogleSearchComponent } from './google-search.component'
import { GoogleService } from './google.service';
#NgModule({
imports: [BrowserModule, JsonpModule, ReactiveFormsModule],
declarations: [AppComponent, GoogleSearchComponent],
providers: [GoogleService],
bootstrap: [AppComponent]
})
export class AppModule {}
app/app.component.ts
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: 'app/app.component.html'
})
export class AppComponent { }
app/app.component.html
<google-search></google-search>
app/google-place.ts
export class GooglePlace {
constructor(public id: string,
public description: string
) {}
}
app/google-search.component.ts
import { Component } from '#angular/core';
import { FormControl } from '#angular/forms';
import { GoogleService } from './google.service';
import { GooglePlace } from './google-place';
#Component({
selector: 'google-search',
template: `
<div>
<h2>Google Search</h2>
<input type="text" [formControl]="term">
<ul>
<li *ngFor="let item of items | async">{{item.description}}</li>
</ul>
</div>
`
})
export class GoogleSearchComponent {
items: Observable<Array<GooglePlace>>;
term = new FormControl();
constructor(private googleService: GoogleService) {}
ngOnInit() {
this.items = this.term.valueChanges
.debounceTime(400)
.distinctUntilChanged()
.switchMap(term => this.googleService.search(term));
}
}
app/google.service.ts
import { Injectable } from '#angular/core';
import { GooglePlace } from './google-place';
import { Observable } from 'rxjs/Observable';
declare var google: any;
#Injectable()
export class GoogleService {
search(term: string) {
return new Observable<GooglePlace[]>(observer => {
let result: GooglePlace[] = [];
let displaySuggestions = function(predictions: any, status: string) {
if (status != google.maps.places.PlacesServiceStatus.OK) {
alert(status);
return;
}
predictions.forEach(function(prediction: any) {
result.push(new GooglePlace(prediction.place_id, prediction.description));
});
observer.next(result);
observer.complete();
};
if (term) {
let service = new google.maps.places.AutocompleteService();
service.getQueryPredictions({ input: term }, displaySuggestions);
}
});
}
}
don't know if you're still interested but I was facing the same issue today with the bootstrap typeahead. I think I found a solution although I don't think it's the way one should do it.
Anyway, my approach was to gather the data and let the data display as if it was static.
ngOnInit(): void {
//this.recursiveTimeout();
this.items = this.searchTermStream
.debounceTime(300)
.distinctUntilChanged()
.switchMap((term: string) => this.placesService.search(term))
.catch(() => {
this.searchFailed = true;
return Observable.of([])
}
)
this.items.subscribe(res => {
this.places = res;
//places is a string array and stores all found places , in your case it
would be an array of GooglePlace
console.log(this.places);
});
}
Then you sould be able to access the data as soon as it is available.
I just had a very similar problem with google maps. I will share here my answer, all the same, although it is so late.
The problem is because the callback function displaySuggestions of the google maps getQueryPredictions is called outside of the 'angular zone', and so angular doesn't correctly detect the changes inside of it.
The solution is relatively simple. Just 4 little changes to the app/google.service.ts. See the comments.
// import NgZone
import { Injectable, NgZone } from '#angular/core';
import { GooglePlace } from './google-place';
import { Observable } from 'rxjs/Observable';
declare var google: any;
#Injectable()
export class GoogleService {
// Inject NgZone in the constructor
constructor(private _ngZone: NgZone) {}
search(term: string) {
// save 'this' to a constant or alternatively bind it to the callback function
const self = this;
return new Observable<GooglePlace[]>(observer => {
const result: GooglePlace[] = [];
const displaySuggestions = function(predictions: any, status: string) {
if (status !== google.maps.places.PlacesServiceStatus.OK) {
console.log('GoogleService search: ', status);
return;
}
// Wrap the prediction in the zone
self._ngZone.run(function() {
predictions.forEach(function(prediction: any) {
result.push(
new GooglePlace(prediction.place_id, prediction.description)
);
});
observer.next(result);
observer.complete();
});
};
if (term) {
const service = new google.maps.places.AutocompleteService();
service.getQueryPredictions({ input: term }, displaySuggestions);
}
});
}
}
Edit: Perhaps you should take out your API key from the plunker, although i suppose that it might not be to serious of a problem, if it is a free one and was created exclusively for the purpose of the example...
I found an awful solution. In app/google-search.component.ts, I've added the following function :
recursiveTimeout(ms: number = 1000): void {
setTimeout(() => {
this.recursiveTimeout(ms);
}, ms);
}
Then in the ngOnInit function, I call recursiveTimeout:
ngOnInit(): void {
this.recursiveTimeout();
// ...
}
With this solution, when the user type "P" (for example):
The result will be fetched on the Google API
The result will be displayed just after the event recursiveTimeout is triggered (maximum 1000 ms)
I am open to any better solution ;)