Angular - Why after changing components, the data in the variables gets lost? - arrays

I have two components, one that creates and changes one array, and another that gets the array, the problem is that when getting the array in the second component, the array is the default one without data:
array creation:
export class AddPlayerComponent implements OnInit {
team = [
{
summonerName: '',
rank: '',
role: '',
}
];
// code that changes the array
}
the other component calling for the array:
export class WaitingComponent implements OnInit {
actualTeam;
constructor(
private addPlayerComponent: AddPlayerComponent
) { }
ngOnInit(): void {
console.log("add player team:", this.addPlayerComponent.team);
this.actualTeam = this.addPlayerComponent.team;
console.log("actual team:", this.actualTeam);
}
}
By logic when i get the array, it should come with the data that i placed, but it comes with the default empty data.
How can i get the real array with the data between the two components?

You might want to try using a behavior subject in this case. You'll want to create this in a service, which you can then inject into any number of components for easy access.
#Injectable()
export class TeamsService {
private teams: ITeam[];
private observableTeams: BehaviorSubject<ITeam[]>;
constructor() {
this.teams = new Array<ITeam>;
this.observableTeams = <BehaviorSubject<ITeam[]>>new BehaviorSubject([]);}
get teams() {
return this.observableTeams.asObservable();}
addTeam(team: ITeam) {
this.teams.push(team);
this.observableTeams.next(Object.assign({}, this.teams));}}

Create a service to share the data between the two components. Say your service appears like this
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class ShowService {
private data=new BehaviorSubject<any>();
public castTeam = this.data.asObservable();
showTeam(team){
this.data.next(team);
}
}
then import this service in both of your components and then call showTeam() in ngOnInit of your components like this
export class WaitingComponent implements OnInit {
actualTeam;
constructor(
private showdata: ShowService,
private addPlayerComponent: AddPlayerComponent
) { }
ngOnInit(): void {
console.log("add player team:", this.addPlayerComponent.team);
this.actualTeam = this.addPlayerComponent.team;
this.showdata.castTeam.subscribe(actualTeam => { this.actualTeam= actualTeam; });
console.log("actual team:", this.actualTeam);
}
}
and then import the same service in the other component and then in ngOnInit just subscribe to the service methodlike this
export class AddPlayerComponent implements OnInit {
constructor(
private showdata: ShowService
) { }
ngOnInit(): void {
this.showdata.castTeam.subscribe(actualTeam => { this.team = actualTeam; });
}
team = [
{
summonerName: '',
rank: '',
role: '',
}
];
}

Related

Angular/Typescript - Add timestamp in array services

I'm trying to add a timestamp OnInnit in order to record the time a user accessed a page. I would like to add the timestamp in an array found in my services, but I'm really struggling to find my way around this, and keep getting errors. Any help would really be appreciated.
tracker-data.ts
export class TrackerData {
entry?: number;
exit?: number;
clicks?: number;
url?: string;
}
tracker.service.ts
import { Injectable } from '#angular/core';
import { TrackerData } from '../modules/tracker-data';
#Injectable({
providedIn: 'root'
})
export class TrackerService {
trackerData: TrackerData;
websiteData: TrackerData[];
public entryTime: {entry: number} [] = [];
constructor() { }
}
tracker.component.ts
import { Component, OnInit, OnDestroy } from '#angular/core';
import { TrackerService } from '../services/tracker.service';
import { TrackerData } from '../modules/tracker-data';
#Component({
selector: 'app-tracker',
templateUrl: './tracker.component.html',
styleUrls: ['./tracker.component.scss']
})
export class TrackerComponent implements OnInit, OnDestroy {
constructor(
public trackerService: TrackerService,
private websiteData: TrackerData[]
) { }
ngOnInit(): void {
const timeOfEntry = Date.now();
this.websiteData.push(timeOfEntry); // Type 'number' has no properties in common with type 'TrackerData'.
}
I know the above code might not make sense, but I'm fairly new to this and I'm really trying.
The error is pretty clear, you are trying to add a date (the timestamp) as an instance of TrackerData, which is clearly not. In this case you need to create an instance of the TrackerData and then assign the timestamp value.
const trackerData = new TrackerData(); // create the instance
trackerData.entry = Date.now(); // assign the the entry timestamp
this.websiteData.push(trackerData); // add to array
export class TrackerData {
entry?: number;
exit?: number;
clicks?: number = 0;
url?: string;
}
import { Injectable } from '#angular/core';
import { TrackerData } from '../modules/tracker-data';
#Injectable({
providedIn: 'root'
})
export class TrackerService {
websiteData: TrackerData[];
constructor() { }
addTrackerData(trackerData: TrackerData): void {
this.websiteData.push(trackerData);
//log data on the server if you want
}
}
import { Component, OnInit, OnDestroy } from '#angular/core';
import { TrackerService } from '../services/tracker.service';
import { TrackerData } from '../modules/tracker-data';
import { Router } from '#angular/router';
#Component({
selector: 'app-tracker',
templateUrl: './tracker.component.html',
styleUrls: ['./tracker.component.scss']
})
export class TrackerComponent implements OnInit, OnDestroy {
private pageData: TrackerData = new TrackerData();
constructor(
private trackerService: TrackerService,
private router: Router
) { }
#HostListener('click', ['$event.target'])
onClick(btn) {
this.pageData.clicks++;
}
ngOnInit(): void {
this.pageData.entry = Date.now();
this.pageData.url= this.router.url;
}
ngOnDestroy(): void {
this.pageData.exit = Date.now();
this.trackerService.addTrackerData(this.pageData);
}
this is the simplest version of what you want!
if you want to keep data when the page gets refresh, you need to put data in the cache!

ANGULAR Iterate over an Array within a Model

Synopsis:
I am trying to iterate over an array that is returned as part of an object. The object has three properties 2 string, 1 array. I want to iterate over the array i my html but can't seem to get it to populate. I can get both strings to show, but cannot figure out how to iterate the inner array for values.
Policy.ts
import { Document } from './Document';
export interface Policy {
insuredName: string;
policyNumber: string;
documents: Document[];
}
Document.ts
export interface Document {
url: string,
docType: string
}
I bind the model("policy") in my parent component
#Component({
selector: 'app-search',
templateUrl: './search.component.html',
styleUrls: ['./search.component.css']
})
export class SearchComponent implements OnInit {
policy: any = {};
constructor(private policyService: PolicyService, private alertify: AlertifyService) { }
ngOnInit() {
}
loadPolicy() {
this.policyService.getPolicy(this.policy.policyNumber).subscribe((res) => {
this.policy.insuredName = res.insuredName;
this.policy.policyNumber = res.policyNumber;
this.documents = res.documents;
}, error => {
this.alertify.error(error);
})
}
I pass the data to my child component
Search.component.html
<app-documentList [policy]=policy></app-documentList>
and then bind it in the child
export class DocumentListComponent implements OnInit {
#Input() policy: Policy;
ngOnInit() {
}
but when I finally try the iteration all I get is the first property (insuredName) and nothing for the *ngFor
<div>
<div class="test">
<p>{{policy.insuredName}}</p>
<h2 *ngFor="let doc of policy.documents">{{doc.url}}</h2>
</div>
</div>
Try replacing this.documents = res.documents; with this.policy.documents = res.documents;.
Looks like you are binding the result to a wrong variable.
Also you might not have to assign values manually. You could do the following
import { Policy } from './Policy';
#Component({
selector: 'app-search',
templateUrl: './search.component.html',
styleUrls: ['./search.component.css']
})
export class SearchComponent implements OnInit {
policy: Policy = {};
constructor(private policyService: PolicyService, private alertify: AlertifyService) { }
ngOnInit() {
}
loadPolicy() {
this.policyService.getPolicy(this.policy.policyNumber).subscribe((res: Policy) => {
this.policy = res;
}, error => {
this.alertify.error(error);
});
}
}

Problem passing data from one component to another component using services in Angular 8

Am working on a Single Page Application CRUD based API whereby am passing
data from the frontend to the backend using JWT which works fine. On the backend am saving the data in a database and later fetching all the data and return as a response to the frontend which works fine.
On the frontend, I fetch the data in the response and pass to another component through a Service. Basically I pass the data from create component to show component via a service called Shared service. The data am passing (response from the backend is an array of Javascript objects).
the problem is that the data is not reaching the show component, since I want after it reaches the show component I want to loop through the array of objects and show them on the view (show.component.html) as a table
Please assist?
Create Component
import { Component, OnInit } from '#angular/core';
import { Router } from '#angular/router';
import { SharedService } from 'src/app/Services/shared.service';
#Component({
selector: 'app-create',
templateUrl: './create.component.html',
styleUrls: ['./create.component.css']
})
export class CreateComponent implements OnInit {
public form = {
sponsorFirstName: null,
sponsorSurName: null,
sponsorEmail: null,
sponsorPhone: null,
nationality: null,
childFirstName: null,
childAge: null,
childSurName: null,
childGender: null
};
public error = null;
constructor(
private router: Router,
private Shared : SharedService) { }
onSubmit(){
this.Auth.submitFormData(this.form).subscribe(
data => this.handleResponse(data),
error => this.handleError(error)
);
}
handleResponse(data){
//console.log(data.data);
//pass the data to the shared service
this.Shared.createData(data.data);
//redirect to show-data component whereby I show the data in a table
this.router.navigateByUrl('/show-data');
}
handleError(error){
this.error = error.error.errors;
}
ngOnInit() {
}
}
Shared service
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs';
import { TokenService } from './token.service';
#Injectable({
providedIn: 'root'
})
export class SharedService {
private userData = new BehaviorSubject;
checkUser$ = this.userData.asObservable();
createData(data:any){
this.userData.next(data);
}
constructor(private Token : TokenService) { }
}
Show.component.ts file
import { Component, OnInit } from '#angular/core';
import { SharedService } from 'src/app/Services/shared.service';
#Component({
selector: 'app-show',
templateUrl: './show.component.html',
styleUrls: ['./show.component.css']
})
export class ShowComponent implements OnInit {
public userData;
constructor(
private Shared : SharedService,
) { }
ngOnInit() {
this.Shared.checkUser$.subscribe(message => this.userData = message);
}
}
Seems like a race condition between the service and your component, you should try and instantiate userData with a ReplaySubject(1) like so:
private userData = new ReplaySubject(1);
This way your subject will have a buffer and even if you late subscribe to it you'll have the value.
ReplaySubject
A Subject that "replays" or emits old values to new
subscribers

Angular2 display subscription after console.log

I have different problem.
I retrieve data from Services into variable, but this data doesn't display on screen after loading (I use Subscription for this operation). All data appears on screen when I click button with function getMessages(){ console.log(this.messages);}
Can you explain why?
import {Component, NgZone, OnInit} from '#angular/core';
import {GmailApiService} from "../google/api/gmailApi.service";
import {Subscription} from "rxjs/Rx";
#Component({
selector: 'gmail-app',
templateUrl: '/app/gmail/gmail.component.html'
})
export class GmailComponent implements OnInit{
public messages: Array<string>;
subscription:Subscription;
constructor(private gmailApi: GmailApiService){
}
ngOnInit() {
this.gmailApi.checkAuthAuto('from:(xyz#zyx.com) OR to:(azx#saa.com)');
this.subscription = this.gmailApi.openMessages$
.subscribe(messages => this.messages = messages);
}
getMessages(){
console.log(this.messages);
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
Component files:
<template [ngIf]="messages">
Messages:
<h1 *ngFor="let message of messages">**{{message?.id}}**</h1>
</template>
<button id="button-get" (click)="getMessages(event)">
getMessages
</button>
---UPDATE---SOLUTIONS---
I found solutions for my problem, i add zone in subscription and now it's work correctly. Below I present part of my code after h
constructor(private zone:NgZone, private gmailApi: GmailApiService){
}
ngOnInit() {
this.gmailApi.checkAuthAuto('from:(xyz#zyx.com) OR to:(azx#saa.com)');
this.subscription = this.gmailApi.openMessages$
.subscribe(messages => {
this.zone.run(() => {
this.messages = messages;
});
});
}
I think it may be associated with lifecycle hooks. See this: https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html
I'm not sure but maybe you should implements doCheck and in subscribe call ngDoCheck method? Something like this:
export class GmailComponent implements OnInit, DoCheck {
public messages: Array<string>;
subscription:Subscription;
constructor(private gmailApi: GmailApiService){
}
ngOnInit() {
this.gmailApi.checkAuthAuto('from:(xyz#zyx.com) OR to: (azx#saa.com)');
this.subscription = this.gmailApi.openMessages$
.subscribe(messages => {
this.messages = messages;
ngDoCheck();
});
}
getMessages(){
console.log(this.messages);
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
ngDoCheck() {
//do nothing, just call
}
}

How do I ensure the data has arrived before the template tries to render it?

I made an app with angular-cli, Immutable and Redux. I followed instructions from this article which does not describe how to get data. My app needs to initialize the Redux store with data from an asynchronous http call.
I have a component for listing the data which has a template. This component gets its data from the store which depends on a service which makes the http call. The http call works but the app throws an exception that indicates the listing component is trying to get the data before it has arrived.
My repository is here
A demo of the app is here
Error message:
main.js:21 TypeError: Cannot read property 'getState' of undefined
at a.get [as objections] (https://dancancro.github.io/bernierebuttals/main.js:18:22268)
at a._View_a0.detectChangesInternal (a.template.js:189:37)
at a.detectChanges (https://dancancro.github.io/bernierebuttals/main.js:32:13138)
at a.detectViewChildrenChanges (https://dancancro.github.io/bernierebuttals/main.js:32:13774)
at a._View_a_Host0.detectChangesInternal (a.template.js:34:8)
at a.detectChanges (https://dancancro.github.io/bernierebuttals/main.js:32:13138)
at a.detectContentChildrenChanges (https://dancancro.github.io/bernierebuttals/main.js:32:13588)
at a.detectChangesInternal (https://dancancro.github.io/bernierebuttals/main.js:32:13345)
at a.detectChanges (https://dancancro.github.io/bernierebuttals/main.js:32:13138)
at a.detectViewChildrenChanges (https://dancancro.github.io/bernierebuttals/main.js:32:13774)
Here are some relevant parts of the code: (I'm working on this. The repository contains the current code)
list.component.html
...
<ul id="objection-list" [sortablejs]="store.objections" [sortablejsOptions]="options" (update)="setTouched()">
<li *ngFor="let objection of store.objections">
<list-objection
[objection]="objection"
[editable]="editable"
(onEdit)="setTouched()"
(onReordered)="setReordered(objection)"
></list-objection>
</li>
</ul>
...
list.component.ts
import { Component, OnInit, ContentChildren, QueryList } from '#angular/core';
import { Router, ActivatedRoute } from '#angular/router';
import { SortablejsOptions, SORTABLEJS_DIRECTIVES } from 'angular-sortablejs';
import Immutable = require('immutable');
import { ObjectionComponent } from './objection/objection.component';
import { ObjectionModel } from '../objection';
import { ObjectionStore } from '../objection-store';
import { DataService } from '../data.service';
import { addObjection } from '../actions';
#Component({
moduleId: module.id,
selector: 'app-list',
templateUrl: 'list.component.html',
styleUrls: ['list.component.css'],
providers: [ObjectionStore, DataService],
directives: [ObjectionComponent, SORTABLEJS_DIRECTIVES]
})
export class ListComponent implements OnInit {
private sub: any;
editable: boolean = false;
touched: boolean = false;
expanded: boolean = false;
options: SortablejsOptions = {
disabled: false
};
objectionID: number;
constructor(
private store: ObjectionStore,
private route: ActivatedRoute) {}
...
}
objection-store.ts
import { Injectable } from '#angular/core';
import { ActivatedRoute } from '#angular/router';
import Immutable = require('immutable');
import { createStore } from 'redux';
import { ObjectionAction } from './actions';
import { reducer } from './reducer';
import { ObjectionModel } from './objection';
import { DataService } from './data.service';
#Injectable()
export class ObjectionStore {
private sub: any;
store: any;
constructor(
private dataService: DataService) {
this.store = createStore(reducer, Immutable.List<ObjectionModel>(objections.json()));
});
}
data.service.ts
import { Injectable } from '#angular/core';
import { Http, Response, Headers } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/toPromise';
import { ObjectionModel } from './objection';
import { Area } from './area';
let objectionsPromise;
#Injectable()
export class DataService {
result: Object;
combined: any;
error: Object;
getUrl: string = 'https://script.google.com/macros/s/AKfycbymzGKzgGkVo4kepy9zKIyDlxbnLbp-ivCvj8mVMClmWgr-V-g/exec?json=1';
postUrl: string = 'https://script.google.com/macros/s/AKfycbymzGKzgGkVo4kepy9zKIyDlxbnLbp-ivCvj8mVMClmWgr-V-g/exec';
static getObjection(objections: any[], id: number): ObjectionModel {
return objections.filter(function(objection) {
return objection.id === id
})[0];
}
constructor(private http: Http) {
objectionsPromise = this.http.get(this.getUrl).toPromise();
}
Basic Answer
To answer the immediate question, getting the objections from the server and putting them in a template can be simply done like this:
Use async pipe to bind an observable directly into the template. Async pipe 'unboxes' observables (promises too) and updates your template when they change.
<ul id="objection-list"
[sortablejs]="store.objections"
[sortablejsOptions]="options"
(update)="setTouched()">
<li *ngFor="let objection of objections | async">
<list-objection
[objection]="objection"
[editable]="editable"
(onEdit)="setTouched()"
(onReordered)="setReordered(objection)">
</list-objection>
</li>
</ul>
Use DataService to initialize this observable when your component is initialized:
#Component({
moduleId: module.id,
selector: 'app-list',
templateUrl: 'list.component.html',
styleUrls: ['list.component.css'],
providers: [ObjectionStore, DataService],
directives: [ObjectionComponent, SORTABLEJS_DIRECTIVES]
})
export class ListComponent implements OnInit {
// ...
private objections: Observable<ObjectionModel[]>;
constructor(
private dataService: DataService,
private route: ActivatedRoute) {}
ngOnInit() {
this.objections = this.dataService.getObjections();
}
// ...
Fix up your data service:
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import { ObjectionModel } from './objection';
#Injectable()
export class DataService {
result: Object;
combined: any;
error: Object;
getUrl: string = 'https://script.google.com/macros/s/AKfycbymzGKzgGkVo4kepy9zKIyDlxbnLbp-ivCvj8mVMClmWgr-V-g/exec?json=1';
postUrl: string = 'https://script.google.com/macros/s/AKfycbymzGKzgGkVo4kepy9zKIyDlxbnLbp-ivCvj8mVMClmWgr-V-g/exec';
getObjections(private http: Http): Observable<ObjectionModel[]> {
return this.http.get(this.getUrl) // returns an observable of the response
.map(response => response.json()); // transforms it into an observable of ObjectionModels
}
}
Notes on Redux
Note that this has all been done without Redux.
In general, I like Redux and I use it a lot. However in your example you seem to be doing a couple of unorthodox things:
You are creating a store in your ObservableStore service - this suggests to me that you are planning on having several stores in your app. One of the main principles of Redux is global immutable state, meaning that there is normally only one Redux store in an application.
You seem to by trying to fetch the initial data set from the server and then creating your store when the response has come back. It's not generally a good idea to couple the store creation to an HTTP request like this. Instead I recommend creating an empty store when you initialize your app, and then updating it via reducer when the HTTP request comes back.
You can do raw Redux in Angular 2, but you may find it a little frustrating getting it to work with Angular's observable-heavy APIs. Fortunately people (including me) have done this work for you in the form of Observable-oriented redux libraries like ng2-redux and ngrx/store
If you were to use ng2-redux, things would look more like this:
Top-level app component: build your store and initialize it:
import { NgRedux } from 'ng2-redux';
import { rootReducer } from './reducers';
#Component({ /* ... */ })
class App {
constructor(private ngRedux: NgRedux<any>) {
this.ngRedux.configureStore(rootReducer, {});
}
}
List component: bind your template to a selector from the store's current data. Also trigger a data fetch on initialization.
import { NgRedux, select } from 'ng2-redux';
#Component({
moduleId: module.id,
selector: 'app-list',
templateUrl: 'list.component.html',
styleUrls: ['list.component.css'],
providers: [DataService],
directives: [ObjectionComponent, SORTABLEJS_DIRECTIVES]
})
export class ListComponent implements OnInit {
// ...
// Magic selector from ng2-redux that makes an observable out
// of the 'objections' property of your store.
#select('objections') objections: Observable<ObjectionModel[]>;
constructor(
private ngRedux: NgRedux<any>,
private dataService: DataService) {}
ngOnInit() {
this.subscription = this.dataService.getObjections()
.subscribe(objections => this.ngRedux.dispatch({
type: FETCH_OBJECTIONS_OK,
payload: objections
},
error => this.ngRedux.dispatch({
type: FETCH_OBJECTIONS_ERROR,
error: error
});
)
}
}
OK... so how does the data actually get into the store? Via the reducer. Remember in redux store state can only be changed from a reducer.
export function objectionReducer(state = [], action) {
switch(action.type) {
case FETCH_OBJECTIONS_OK: return [ ...action.payload ];
case ADD_OBJECTION: return [ ...state, action.payload ];
// etc.
}
return state;
}
We can also track errors in a reducer too if we want, how you want to structure this is up to you.
export function errorReducer(state = {}, action) {
switch(action.type) {
case FETCH_OBJECTIONS_ERROR: return { objectionFetch: action.error }
}
}
Since we have one store, we modularize the reducers instead and compose them together:
import { combineReducers } from 'redux';
import { objectionReducer } from './objection.reducer';
import { errorReducer } from './error.reducer';
export const rootReducer = combineReducers({
objections: objectionReducer,
error: errorReducer
});
More to Learn/Disclosure
Disclosure: I am one of the authors of Ng2-Redux. However Ngrx/Store is also a great alternative for doing redux with Ng2, and while the implementation is different using it is very similar to what I have described above.
I and my colleagues also maintain a number of training resources on Angular2 and on Redux, which I will provide below:
A good Angular2-specific intro to redux: http://angular-2-training-book.rangle.io/handout/redux/
An intro to Observables: http://angular-2-training-book.rangle.io/handout/observables/
Ng2-Redux documentation: https://github.com/angular-redux/ng2-redux/blob/master/README.md
Your data service is wrong. you are missing the point of the promise/observable.
you should read angular documentation about http client. at least read this part:
https://angular.io/docs/ts/latest/guide/server-communication.html#!#promises
No http call in the constructor!
this is more like it:
getHeroes (): Promise<Hero[]> {
return this.http.get(this.heroesUrl)
.toPromise()
.catch(this.handleError);
}
After you get use to that, I really recommend you read some about Observables. much cleaner and advanced.

Resources