Change variable value from different component - Angular 4 - html

I have two components, one is the home.component, and one is loading.component (loading screen). The loading screen has a countdown, and after the countdown, he does something. What I need to do is make the loading screen capable of custom countdown length.
I tried using the EventEmiter, but it doesn't work.
home.component.html:
<a (click)="applyChanges()" class="green" style="margin-left: 8px; cursor: pointer"><i style="margin-right: 5px;"class="fa fa-check"></i>Apply changes now</a>
home.component.ts
#Output() loadingScreenStart: EventEmitter<any> = new EventEmitter();
applyChanges(){
this.dashboard.applyChanges().catch(
err => {
console.log("There was an error: "+err.status);
console.log("There was an error: "+err.statusText);
//this.handleError(err);
return Observable.throw(err);
}
)
.subscribe(
data => {
this.loadingScreenStart.emit(34);
this.router.navigate(['/loading']);
}
);
}
loading.component.html
<div class="container container-table" (loadingScreenStarted)="onScreenStart($event)">
<div class="row vertical-10p">
<div class="container">
<img src="../../assets/img/LoginLogo.png" class="center-block logo">
<img style="width: 15em" src="../../assets/img/gears.svg" class="center-block ">
<div class="text-center col-sm-6 col-sm-offset-3">
<h1>Changes are taking place</h1>
<h4>You will be redirected in {{timeLeft}} seconds</h4>
</div>
</div>
</div>
loading.component.ts
import { Component, OnInit, Input } from '#angular/core';
import { AuthGuard } from '../guard/auth.guard';
import { Title } from '#angular/platform-browser';
#Component({
selector: 'app-loading',
templateUrl: './loading.component.html',
styleUrls: ['./loading.component.css']
})
export class LoadingComponent implements OnInit {
public timeLeft;
public intervalId;
constructor(public titleService: Title, public guard: AuthGuard) { }
onScreenStart(length) {
this.timeLeft = length;
console.log(length);
}
ngOnInit() {
this.titleService.setTitle("Loading... | something);
this.startCounter();
localStorage.removeItem('statusInfo');
}
startCounter() {
this.intervalId = setInterval(() => {
if (this.timeLeft == 1) {
clearInterval(this.interValId);
}
this.timeLeft--;
}, 1000)
}
}

There are 2 things wrong in you code.
Understand how EventEmitter works.
When you are emitting a value from child using eventEmitter, you will need to map it to some method of the parent component.
// in child component.
#Output()
myEmitter = new EventEmitter();
someMethod() {
this.myemitter.emit('somevalue');
}
Now it actually receive this value of emitter you need to map this to a method of parent compoenent.
someParentMethod(value) {
console.log(value);
}
<app-child (myEmitter)="someParentMethod(event)"></app-child>
2. Observable's error handling.
Since you are throw an Observable, you will need to handle it in the subscribe. Observable's subscribe consist of 3 parts. Response, ErrorHandling, Next. Now, you are throwing an error from Observable.throw, your res part will not run. But you are not handling error, so you won't see anything. Instead do this:
.subscribe(
data => {
this.loadingScreenStart.emit(34);
this.router.navigate(['/loading']);
},
error => console.log(error)
);

Since these two components are not in a parent/child relationship, I don't think that you're going to be able to successfully use an EventEmitter. The major problem is that you can't depend on both components being in existence at the same time.
So, the two common choices for your situation are:
pass the data in a route parameter (example at https://angular.io/tutorial/toh-pt5#navigating-to-hero-details)
communicate through a shared service (example at https://angular.io/guide/component-interaction#parent-and-children-communicate-via-a-service)

Related

i have error when i try to get data with route params. cannot read properties of undefined (reading 'imagepath')

i want to have access to data as this page load. but it's undefined from beginning. how can i fix this?
i get error:
ERROR TypeError: Cannot read properties of undefined (reading 'imagepath')
import { Component, OnInit } from '#angular/core';
import { itemData } from 'src/app/interfaces/item-data';
import { MainService } from 'src/app/main.service';
import { ActivatedRoute } from '#angular/router';
export class Aliens3rdeyeitemComponent implements OnInit {
items: itemData[] = [];
itemId: number = 0;
.....
constructor(private mainService: MainService, private route: ActivatedRoute) {
}
....
ngOnInit(): void {
this.mainService.getData2()
.subscribe(
response => {
this.items = response;
}
)
this.itemId = this.route.snapshot.params['id'];
}
}
<div *ngIf="previewMode" class="full-screen-image">
<img (click)="closePreview()" src="{{items[itemId].imagepath}}" alt="">
</div>
There aree several things happening here:
The getData2 function delivers the data asynchronous and the items are assigned in the subscribe function, this can take a while.
At the same time of subscribe you're already assigning the itemID to your class variable. This happens BEFORE the data is delivered and thus the evaluation will always be that the data is undefined.
The best thing to do in this case is to move the assigning of the itemID in the subscribe function.
.subscribe(
response => {
this.items = response;
this.itemId = this.route.snapshot.params['id'];
}
)

Angular: ERROR TypeError: Cannot read property 'choice_set' of null, while data displayed correctly

i hope you're doing well.
I am trying to implement a FormsBuilder in Angular by accessing the data from an API. The data is pushed down to its child-component via #Input().
However the data gets pushed down, are provided and shown successfully, but still I get this Error, when the first attempt from ngOnChangess tries to receive the data.
ERROR TypeError: Cannot read property 'choice_set' of null
at StyleTypeQuestionComponent.setFormValues (style-type-question.component.ts:34)
at StyleTypeQuestionComponent.ngOnChanges (style-type-question.component.ts:26)
at StyleTypeQuestionComponent.rememberChangeHistoryAndInvokeOnChangesHook (core.js:1471)
at callHook (core.js:2490)
at callHooks (core.js:2457)
at executeInitAndCheckHooks (core.js:2408)
at refreshView (core.js:9207)
at refreshEmbeddedViews (core.js:10312)
at refreshView (core.js:9216)
at refreshComponent (core.js:10358)
The data is provided through an data-service and are subscribed through an async pipe from its parent-component and as mentioned above pushed down via property binding.
I tried to use the ? operator in my template and tried to set an Timeout on the childcomponent. Also i tried to initialize the data via default values. Still thats making no sense for me right know, because the data is already available through his parent component and getting checked via an *ngIf directive.
I hope i could provided as much as information as needed.
I guess there is an initializing problem in the first seconds of ngChanges.
Parent-Component
import { Component, Input, OnChanges, OnInit } from '#angular/core';
import { Question } from '../shared/models/question';
import { QuestionStoreService } from '../shared/question-store.service';
import { Observable } from 'rxjs';
#Component({
selector: 'pc-style-type-detection',
templateUrl: './style-type-detection.component.html',
styleUrls: ['./style-type-detection.component.css'],
})
export class StyleTypeDetectionComponent implements OnInit, OnChanges {
question$: Observable<Question>;
#Input() question_Input: Question;
question_index: number = 1;
constructor(private qs: QuestionStoreService) {}
ngOnInit(): void {
this.question$ = this.qs.getSingle(1);
}
ngOnChanges(): void {}
updateBook(question: Question): void {
console.log(question);
}
}
Parent-Template
<pc-style-type-question
*ngIf="question$"
(submitQuestion)="updateBook($event)"
[question]="question$ | async"
></pc-style-type-question>
Child-Component
import {
Component,
EventEmitter,
Input,
OnChanges,
OnInit,
Output,
} from '#angular/core';
import { FormArray, FormBuilder, FormGroup } from '#angular/forms';
import { Choice, Question } from '../shared/models/question';
#Component({
selector: 'pc-style-type-question',
templateUrl: './style-type-question.component.html',
styleUrls: ['./style-type-question.component.css']
})
export class StyleTypeQuestionComponent implements OnInit, OnChanges {
questionForm: FormGroup;
#Input() question: Question;
#Output() submitQuestion = new EventEmitter<Question>();
constructor(private fb: FormBuilder) {}
ngOnChanges(): void {
this.initForm();
this.setFormValues(this.question);
}
ngOnInit(): void {
this.initForm();
}
private setFormValues = (question: Question) => {
this.questionForm.patchValue(question.choice_set);
this.questionForm.setControl(
'choice_set',
this.buildChoiceSetArray(question.choice_set)
);
};
initForm = () => {
if (this.questionForm) {
return;
}
this.questionForm = this.fb.group({
choice_set: this.buildChoiceSetArray([
{
choice_text: '',
choice_value: false,
},
]),
});
};
get choiceSet(): FormArray {
return this.questionForm.get('choice_set') as FormArray;
}
private buildChoiceSetArray = (values: Choice[]): FormArray => {
if (values) {
return this.fb.array(
values.map((choice) => {
return this.fb.control(choice.choice_value);
})
);
}
return this.fb.array(
this.question.choice_set.map((choices) =>
this.fb.control(choices.choice_value)
)
);
};
submitForm() {}
}
Child-Template
<form class="ui form" [formGroup]="questionForm" (ngSubmit)="submitForm()">
<div
formArrayName="choice_set"
*ngFor="let choiceset of choiceSet?.controls; index as i"
>
<div>
<input type="checkbox" [formControl]="choiceset" />
<label>
{{ question.choice_set[i].choice_text }}
</label>
</div>
</div>
</form>
Thank you in advance and wish you a nice weekend.
You are not using ngOnChanges the right way, its going to be triggered everytime your input change no matter what the value is which means you need to check if that value is what you expect it to be with SimpleChanges.
ngOnChanges(changes: SimpleChanges) {
if(changes.question.currentValue) {
this.initForm();
this.setFormValues(this.question);
}
}

Angular5 *ngfor not working, but there is no error

I'm facing a problem with Angular at the moment.
I want to read data from my server API and want to display it with *ngfor in a html document.
I can receive the data from the API, but i can't display it.
I took the example code from the tour of heroes tutorial and changed it:
The data gets through to my angular app. I can console.log it and see it in chrome development console.
I tried to display other data that I get from my api and it is working. You can see the data commented out in heroes.components.ts.
Who can help me with this?
If you want to see some more of the code like imports please tell me. But i guess everything needed imported as there are no error messages, i can get the data from my api and i can display some data (sadly not the data i need).
I tried several ideas to solve this from some other posts, but can't get it working.
Here are some Code Snippets:
This is my hero.service.ts
imports...
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { HttpResponse } from '#angular/common/http';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import { catchError, map, tap } from 'rxjs/operators';
import { Hero } from '../model/hero';
import { MessageService } from '../message.service';
import { Response } from '#angular/http/src/static_response';
getHeroes(): Observable<Hero[]> {
console.log("GET HEROES IN HEROES.SERVICE");
return this.http.get<Hero[]>(this.heroesUrl)
.pipe(
tap(Hero => console.log(`fetched heroes: `)),
catchError(this.handleError('getHeroes', []))
);
//I also tried to just use return this.http.get<Hero[]>(this.heroesUrl);
This is my
heroes.components.ts
import { Component, OnInit } from '#angular/core';
import { Hero } from '../../model/hero';
import { HeroService } from '../hero.service';
import { CommonModule } from '#angular/common';
import { Pipe } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import { Response } from '#angular/http/src/static_response';
// For use of map
import 'rxjs/Rx';
#Component({
selector: 'app-heroes',
templateUrl: './heroes.component.html',
styleUrls: ['./heroes.component.css']
})
export class HeroesComponent implements OnInit {
heroes: Observable<Hero[]>;
// I tried to display some data
// heroes: any[] = [
// {
// "name": "Douglas Pace"
// }
// ];
constructor(private heroService: HeroService) { }
ngOnInit() {
this.getHeroes();
// undefined
console.log("ONINIT");
console.log(this.heroes);
}
getHeroes(): void {
console.log("GET HEROES IN HEROES.COMPONENT");
this.heroService.getHeroes()
.subscribe(
function(response: Hero[]) {
console.log("RESPONSE IN HEROES COMPONENT");
console.log(this.heroes);
var res = response["data"];
// console.log(res.json());
this.heroes = res;
console.log(this.heroes);
console.log(response["data"]);
},
function(error) {
console.log("Error happened" + error)
},
function() {
console.log("the subscription is completed")
//This shows me the right data.
console.log(this.heroes[5].id);
console.log(this.heroes[5].titel);
console.log(this.heroes[5].name);
console.log(this.heroes[5].vorname);
}
);
}
My html file:
<h2>My Heroes</h2>
<!-- <input type=text ng-model="hero"> -->
// I gave it a try with and without *ngIf="heroes"
<!-- only show the list IF the data is available -->
<div *ngIf="heroes">
<h3>Heroes are available and are displayed</h3>
<li *ngFor="let hero of heroes">
{{hero.name}}
</li>
</div>
<button (click)="button()">
Suchen
</button>
<div *ngIf="heroes">
<table class="heroes">
<tr>
<th>Id</th>
<th>Titel</th>
<th>Nachname</th>
<th>Vorname</th>
</tr>
//I tried async as the data is maybe not available from the
beginning. Also tried async on hero as heroes is created on init
and single heros are added with the function getHeroes();
<tr *ngFor='let hero of heroes | async'>
<a routerLink="/detail/{{hero.id}}">
<td>{{hero.id}}</td>
<td>{{hero.titel}}</td>
<td>{{hero.name}}</td>
<td>{{hero.vorname}}</td>
</a>
<button class="delete" title="delete hero"
(click)="delete(hero)">x</button>
</tr>
</table>
</div>
<pre>{{heroes | json}}</pre>
If got a hero interface. Should be my model. Only Last and First name are needed.
export interface Hero {
id?: string;
name: string;
titel?: string;
vorname: string;
}
The JSON I returned from my API. Online Json formatter says it is valid json.
{"status":"Success","data":
[{"id":"36","name":"Hero","vorname":"Super","titel":"Dr.",},
{"id":"34","name":"Man","Spider":"Ines","titel":""}],
"message":"Retrieved all HEROES"}
this.heroService.getHeroes()
.subscribe(
function(response: Hero[]) { }
Your problem could be here. Your response is an object with (let's say, interface):
interface DataResponse {
success: string;
data?: Hero[];
}
Because you set response: Hero[] and there's no data property in your Hero interface, response["data"] returns null and you'll never get your data. If you run response.data, you'll probably get an error saying data is not defined in Hero etc...
Change to the following:
this.heroService.getHeroes()
.subscribe((response: DataResponse) => {
this.heroes = response["data"];
});
Your code seems to be ok but i see an error in your json format here
"titel":"Dr.",},
try to remove the comma after Dr and give it a try
"titel":"Dr."},

Angular 2 edit cart total after remove a product

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.

Tour of heroes Search component

I'm trying to add a div in the search component in Tour of Heroes angular 2 so, when the search component is resolving the request, a three dots appear. And, once the observable is resolved, the results are shown, or, if no results are present, a message like Not found is shown.
So far, I have this:
<div id="search-component">
<h4>Hero Search</h4>
<input #searchBox id="search-box" (keyup)="search(searchBox.value)" />
<div>
<div *ngIf="searchBox.value.length > 0 && !(heroes | async)" >...</div>
<div *ngFor="let hero of heroes | async"
(click)="gotoDetail(hero)" class="search-result" >
{{hero.name}}
</div>
</div>
</div>
If you can see, I added the following div
<div *ngIf="searchBox.value.length > 0 && !(teams | async)" >...</div>
Trying to make the three dots to appear when the search box isn't empty and when the teams is not resolved yet.
But it is not working very well since, if I try to search for something, in the meantime the request is done, I can see the three dots but, once is resolved, if I removed some letters and try again, the three dots don't appear anymore.
This is the controller, it is exactly the same as the one you can find in the your of heroes (https://angular.io/docs/ts/latest/tutorial/toh-pt6.html)
import { Component, OnInit } from '#angular/core';
import { Router } from '#angular/router';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import { HeroSearchService } from './hero-search.service';
import { Hero } from './hero';
#Component({
moduleId: module.id,
selector: 'hero-search',
templateUrl: 'hero-search.component.html',
styleUrls: [ 'hero-search.component.css' ],
providers: [HeroSearchService]
})
export class HeroSearchComponent implements OnInit {
heroes: Observable<Hero[]>;
private searchTerms = new Subject<string>();
constructor(
private heroSearchService: HeroSearchService,
private router: Router) {}
// Push a search term into the observable stream.
search(term: string): void {
this.searchTerms.next(term);
}
ngOnInit(): void {
this.heroes = this.searchTerms
.debounceTime(300) // wait for 300ms pause in events
.distinctUntilChanged() // ignore if next search term is same as previous
.switchMap(term => term // switch to new observable each time
// return the http search observable
? this.heroSearchService.search(term)
// or the observable of empty heroes if no search term
: Observable.of<Hero[]>([]))
.catch(error => {
// TODO: real error handling
console.log(error);
return Observable.of<Hero[]>([]);
});
}
gotoDetail(hero: Hero): void {
let link = ['/detail', hero.id];
this.router.navigate(link);
}
}
Do you know how can I improve that condition?
in your condition:
<div *ngIf="searchBox.value.length > 0 && !(heroes | async)" >...</div>
and you empty array when search returns no result.
but heroes empty array returns true
So you can either set heroes null or undefined
or check length instead, so try
<div *ngIf="searchBox.value.length > 0 && !((heroes&& heroes.length>0) | async)" >...</div>