I'm new to this. I want to get data from Rest API. Loading data from the endpoint is ok, but I want to use it later, outside the method. For example I want to sum one of the attributes of the todos in another function. In funcion loadTodos() the first console log shows the data, but the second one shows only "undefined". How can I save the values what loadTodos() gives back and use it later?
import { Component, OnInit } from '#angular/core';
import { Router } from '#angular/router';
import { TodoDomainService } from '../services/todo-domain.service';
import { Todo } from 'app/model/todo';
#Component({
selector: 'app-todo-listing',
templateUrl: './todo-listing.component.html',
styleUrls: ['./todo-listing.component.scss']
})
export class TodoListingComponent implements OnInit {
todo: Todo;
constructor(private todoService: TodoDomainService, private router:Router) { }
public todos;
ngOnInit() {
this.loadTodos();
this.todo = new Todo();
}
private loadTodos() {
this.todoService.getTodos().subscribe(
data => { this.todos = data },
err => console.error(err),
() => console.log("todos loaded." +this.todos)
);
console.log(this.todos)
}
}
private getSum(todos) {
var sum = 0;
for(var i = 0; i < todos.length; i++){
sum += todos.price[i]}
return this.aggregatedSales;
}
console.log("todos loaded." +this.todos) will show a response because it is executed after the observable has completed.
console.log(this.todos) after your .subscribe(...) shows undefined because the observable hasn't yet finished, that is, the line data => { this.todos = data } hasn't been executed.
You are saving the data correctly for use. If you update your next called for the subscription to look like the following then the sum will execute:
// from
data => { this.todos = data }
// to
data => {
this.todos = data;
this.getSum(this.todos);
}
Here is a stackblitz example of fetching a todos array and adding up the userId values into a sum variable then displaying the value.
Related
I'm new to angular and I wasn't sure how to implement synchronous api calls. I implemented async/await from a few articles I read but it still seems like the variables are undefined meaning the console is printing before even initializing the variable. I need it to be synchronous because code further down the cycle function depends on accurate variables.
I'm making a small program where people can upload their own images and it will be displayed on the stage component. I'm saving the images as a blob on a mysql database and retrieving them one at a time depending on the names provided in my nameList array variable
What am I doing wrong when calling the api via synchronous call?
stage.component.html
<div class="container">
<div class="slideshow" *ngIf="retrievedImage">
<ng-container>
<img [src]="retrievedImage"/>
<h1 *ngIf="!database_populated" style="color: red;">No Photo's to show. Please go back and upload</h1>
</ng-container>
</div>
</div>
stage.component.ts
import { HttpClient } from '#angular/common/http';
import { Component, OnInit } from '#angular/core';
import { interval } from 'rxjs';
import { ImagingService } from '../../services/imaging.service';
#Component({
selector: 'app-stage',
templateUrl: './stage.component.html',
styleUrls: ['./stage.component.css']
})
export class StageComponent implements OnInit {
constructor(private httpClient: HttpClient, private imageService: ImagingService) { }
retrieveResponse: any;
public namesList: any;
imageName: string = "eating.jpg";
base64Data: any;
retrievedImage: any = null;
currentImage = 0;
public database_populated: boolean = false;
totalImages: any;
ngOnInit(): void {
this.checkCount().then(count => {
if (count > 0 ) {
this.database_populated = true
console.log("database is populated. going to cycle")
this.cycle()
}
else {
this.database_populated = false;
}
}) }
cycle(){
console.log("entering cycle")
interval(10000).subscribe(x =>
{
// update how many images there are in the database
this.checkCount().then(data => {
this.totalImages = data
})
console.log(this.totalImages)
//update the list of image names found in the database
this.updateNamesList().then(nameList => {
this.namesList = nameList;
})
console.log(this.namesList)
if (this.currentImage == this.totalImages){
console.log("inside mod")
this.currentImage = this.currentImage % this.totalImages
}
else
{
console.log("printing pictures")
// display the Nth image in the list
this.imageName = this.namesList[this.currentImage]
// increment the image count in case there is another image added to the database
this.currentImage = this.currentImage + 1
this.getImage()
}
});
}
getImage() {
//Make a call to Sprinf Boot to get the Image Bytes.
this.httpClient.get('http://localhost:8080/halloween/get/' + this.imageName)
.subscribe(
res => {
this.retrieveResponse = res;
this.base64Data = this.retrieveResponse.picByte;
this.retrievedImage = 'data:image/jpeg;base64,' + this.base64Data;
}
);
}
async updateNamesList(){
return await this.imageService.updateNamesList()
}
async checkCount(){
return await this.imageService.checkCount()
}
}
imaging.service.ts
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Injectable({
providedIn: 'root'
})
export class ImagingService {
constructor(private httpClient: HttpClient) { }
public updateNamesList() {
return this.httpClient.get('http://localhost:8080/halloween/allnames').toPromise();
}
public checkCount() {
return this.httpClient.get('http://localhost:8080/halloween/check').toPromise();
}
}
this is a snippet of the browser console errors and it shows the variables as undefined even though I place the promise prior to the console.log
Your code will not work with asynch. Here is the order of execution.
// command 1
this.checkCount().then(data => {
//command 3
this.totalImages = data
});
// command 2, totalImages will be undefined.
console.log(this.totalImages)
There is no guarantee about time at command 2, because we fetch data through network, so delay time may take few seconds.
You can await the result of checkCount to make sure we have data through rest api.:
this.totalImages = await this.checkCount();
Or you can do other things after rest api have an data.
this.checkCount().then(data => {
this.totalImages = data
doSomethingWithTotalImagesHere();
});
I follow this guide, and i try to do something similar at Unrelated Components: Sharing Data with a Service paragraph
Data Service:
#Injectable()
export class MyDataService{
private messageSource = new BehaviorSubject(null);
currentMessage = this.messageSource.asObservable();
constructor(private http: HttpClient) {
setInterval(() => { this.changeMessage(this.resultFromRestCall()); }, 10 * 1000);
}
changeMessage(message: object) {
this.messageSource.next(message);
}
resultFromRestCall(){
const json;
this.http.get<object>(myApiUrl).subscribe(res =>
json['data'] = res['data'] //this is an example
);
return json;
}
Component:
export class MyComponent implements OnInit {
constructor(private dataservice: MyDataService) {}
ngOnInit() {
this.dataservice.currentMessage.subscribe(
message => {this.handleVarChange(message); }
);
}
handleVarChange(message) {
console.log(message.data);
}
With this code i got "undefined" in handleVarChange log
Instead of calling this.handleVarChange(message); in subscribe I write console.log(message) i got my result correctly.
So, my question is if it's possible use the value coming from data service in some function of my component.
Thanks in advance
With:
resultFromRestCall(){
const json;
this.http.get<object>(myApiUrl).subscribe(res =>
// takes x amount of time to populate json
json['data'] = res['data'] //this is an example
);
// executed instantly after above request has been called
return json;
}
You are returning json before it has been populated, since the request is asynchronous.
Instead you can flip it around a bit, and call resultFromRestCall() first, and when you get the response, then call changeMessage():
setInterval(() => {
this.resultFromRestCall().subscribe((data) => {
this.changeMessage(data);
});
}, 10 * 1000);
where resultFromRestCall simply returns an observable:
resultFromRestCall(){
return this.http.get<object>(myApiUrl);
}
Also remember to clearInterval in OnDestroy!
DEMO
Omit the .data in handleVarChange:
Instead of
handleVarChange(message) {
console.log(message.data);
}
write
handleVarChange(message) {
console.log(message);
}
i have a class and a function named getStationDetail and i want to send a request to server and get the value and save it to dataFromServer variable
and i want to return that but when i cal that i get empty array
export class StationService {
dataFromServer: any = [];
constructor(private rest: RestService) { }
getStationsDetail() {
this.rest.sendRequest('GET', 'station', null).subscribe(
value => {
this.dataFromServer = value['Station'];
// return this.dataFromServer;
},
);
return this.dataFromServer;
}
}
and i cal it
export class StationComponent implements OnInit(){
mm: any;
ngOnInit() {
this.mm = this._stationService.getStationsDetail().subscribe();
console.log(this.mm);
}
}
but map not worked? how to cal subscribe?
When a request is sent to server then cursor doesn't stop to execute next lines of codes if we want some operations after receiving the response from server then we use observables and do these methods in subscribe(). so for examples:
ngOnInit() {
this._stationService.getStationsDetail()
.subscribe(stationDetails => {
console.log('Response array:' , stationDetails);
});}
I have some data that I want to be shared with my entire app so I have created a service like so..
user.service
userDataSource = BehaviorSubject<Array<any>>([]);
userData = this.userDataSource.asObservable();
updateUserData(data) {
this.userDataSource.next(data);
}
then in my component Im getting some data from an api and then sending that data to userDataSource like so..
constructor(
private: userService: UserService,
private: api: Api
){
}
ngOnInit() {
this.api.getData()
.subscribe((data) => {
this.userService.updateUserData(data);
})
}
now that all works but.. I want to be able to add data to the end of the array inside the userDataSource so basically the equivalent of a .push am I able to just call the updateUserData() function and add more data or will doing that overwrite what is currently in there?
Any help would be appreciated
You can add a new method to your service like addData in which you can combine your previous data with new data like.
import {Injectable} from '#angular/core';
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
#Injectable()
export class UserService {
userDataSource: BehaviorSubject<Array<any>> = new BehaviorSubject([]);
userData = this.userDataSource.asObservable();
updateUserData(data) {
this.userDataSource.next(data);
}
addData(dataObj) {
const currentValue = this.userDataSource.value;
const updatedValue = [...currentValue, dataObj];
this.userDataSource.next(updatedValue);
}
}
For someone that may come accross this issue with a BehaviorSubject<YourObject[]>.
I found in this article a way to properly add the new array of YourObject
import { Observable, BehaviorSubject } from 'rxjs';
import { YourObject} from './location';
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class ObjService {
private theObjData: BehaviorSubject<YourObject[]> = new BehaviorSubject<YourObject[]>(null);
constructor() {
}
public SetObjData(newValue: YourObject[]): void {
this.theObjData.next(Object.assign([], newValue));
}
}
How to update data:
// inside some component
this.api.userData().subscribe((results:YourObject) =>
this.objService.SetObjData(results);
)
How to observe changes on other component
// inside another component
ngOnInit() {
this.objService.GetAccountStatements().subscribe((results) =>
...
)
}
Normally Observables and Subjects are meant to be streams of data, not an assignment of data. BehaviorSubjects are different because they hold their last emitted value.
Normally Subjects or BehaviorSubjects inside of a contained class (like a Service) do not want to expose themselves publicly to any other classes, so it's best practice to access their properties with getters or methods. This keeps the data stream cold to all subscribers.
However, since the BehaviorSubject holds the last emitted value, there's a few options here. If all subscribers need a concatenated stream of data from every emission, you could access the last emitted value and append to it:
userDataSource = BehaviorSubject<any[]>([]);
userData = this.userDataSource.asObservable();
updateUserData(data) {
this.userDataSource.next(this.userDataSource.value.push(data));
}
...or, in what might be considered better practice, Subscribers to this Subject could do their own transformation on the stream:
this.api.userData()
.scan((prev, current) => prev.push(current). [])
.subscribe((data) => {
this.concatenatedUserData = data;
});
Use concat to add object
userDataSource = BehaviorSubject<Array<any>>([]);
updateUserData(data) {
this.userDataSource.next(this.userDataSource.value.concat(data));
}
Use filter to remove object
removeUserData(data) {
this.userDataSource.next(this.userDataSource.value.filter(obj => obj !== data));
}
On my web-app written in angular I am posting data to a Database and I am displaying this data in a table on the same html. Each data record has an ID. And every time I am adding new data, the ID is going to be increased. The first input field shows the actual ID, see the screenshot below:
In my ngOnInit-method I am initialising the id and I call the function fbGetData() in order to display the data.
But now I am facing one odd problem:
Everytime I starting the application the initial value which is displayed in the ID-field is NaN.
Obviously I cannot post any data to the database because the ID is not a number. So I have to switch to another page on my application and then switch back. After that the correct ID is displayed. I also tried to move my methods from the ngOnInit-method to the constructor but this didn't help.
Somehow I think that I need to implement the methods asynchronously, but I have no idea how to do this, since I am quite new to Angular/Typscript.
I hope you guys can help me with this problem or give me any hint or idea.
I appreciate your answers!
Here is my .ts Code:
import { Component, OnInit, ViewEncapsulation } from '#angular/core';
import { Router, ActivatedRoute, Params } from '#angular/router';
import { DataService } from '../data.service';
import { Http } from '#angular/http';
import { rootRoute } from '#angular/router/src/router_module';
import { SearchNamePipe } from '../search-name.pipe';
import { LoginComponent } from '../login/login.component';
import {NavbarService} from '../navbar.service';
declare var firebase: any;
const d: Date = new Date();
#Component({
selector: 'app-business-function',
templateUrl: './business-function.component.html',
styleUrls: ['./business-function.component.css'],
encapsulation: ViewEncapsulation.None,
providers: [DataService, SearchNamePipe, LoginComponent]
})
export class BusinessFunctionComponent implements OnInit {
id;
name: String;
descr: String;
typ: String;
bprocess: String;
appsystem: String;
applications: String;
datum: String;
liste = [];
bprocessliste = [];
applicationliste = [];
appsystemliste = [];
isDesc: boolean = false;
column: String = 'Name';
direction: number;
loginName: String;
statusForm: Boolean = false;
private idlist = [];
constructor(
private dataService: DataService,
private router: Router,
private route: ActivatedRoute,
private searchName: SearchNamePipe,
private navbarService: NavbarService
) {
this.datum = Date().toString();
}
ngOnInit() {
this.navbarService.show();
firebase.database().ref().child('/AllID/').
on('child_added', (snapshot) => {
this.idlist.push(snapshot.val()
)})
this.id = this.idlist[0];
console.log("ID: "+this.id);
console.log("IDlist: "+this.idlist[0]);
this.id++;
console.log("ID: "+this.id);
this.fbGetData();
}
fbGetData() {
firebase.database().ref().child('/BFunctions/').orderByChild('CFlag').equalTo('active').
on('child_added', (snapshot) => {
//firebase.database().ref('/BFunctions/').orderByKey().on('child_added', (snapshot) => {
// alter code ... neuer Code nimmt nur die Validen mit dem X Flag
this.liste.push(snapshot.val())
});
// firebase.database().ref().child('/ID/').on('child_added', (snapshot) => {
//Bprocess DB Zugriff
firebase.database().ref().child('/BProcess/').orderByChild('CFlag').equalTo('active').
on('child_added', (snapshot) => {
this.bprocessliste.push(snapshot.val())
});
//Appsystem DB Zugriff
firebase.database().ref().child('/Appsystem/').orderByChild('CFlag').equalTo('active').
on('child_added', (snapshot) => {
this.applicationliste.push(snapshot.val())
})
//Application DB Zugriff
firebase.database().ref().child('/Application/').orderByChild('CFlag').equalTo('active').
on('child_added', (snapshot) => {
this.applicationliste.push(snapshot.val())
});
console.log(this.applicationliste);
}
You need to update the id inside your callback:
firebase.database().ref().child('/AllID/').on('child_added', (snapshot) => {
this.idlist.push(snapshot.val())
this.id = this.idlist[0];
console.log("ID: "+this.id);
console.log("IDlist: "+this.idlist[0]);
this.id++;
console.log("ID: "+this.id);
this.fbGetData();
})
Otherwise id retains it initial undefined value. This is because the call to firebase is asynchronous.
Here is what happens in your original code:
call to firebase API... wait your response
set id to this.idlist[0], which is empty (undefined)
...some time later, getting response from firebase
id does not get updated because the code in point 2. has already been executed.
Anything that you need to do when you get the result from an asynchronous call, must be executed inside the callback function.