Angular - Data is loaded - Initial value of ID is NaN - javascript

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.

Related

Why is it showing Error : CastError: Cast to ObjectId failed for value "$this.categoryId" at path "category" for model "Product"?

I am using nodejs with MongoDB in the backend to create my application and Angular to the front-end. As I try to get the value from the postman for the 'products' under a specific 'category' the Postman responds with success message and delivers the values. But the real problem arises when I try to view through the front end.
It shows the following error once I try to get the products inside the specific category:
CastError: Cast to ObjectId failed for value "$this.categoryId" at path "category" for model "Product"
at model.Query.exec
I have checked the backend and it runs smoothly without any error. I guess the error meant'categoryId' parameter is not getting the valid value but it can't be as my localhost is redirected to the individual categoryId link http://localhost:4200/categories/5f004ae05ca53a0da8d5c043 but it is not able to load the product inside the category.
Here is how I have written code in my TypeScript category.component.ts,
import { Component, OnInit } from '#angular/core';
import { ActivatedRoute } from '#angular/router' ;
import { RestApiService } from '../rest-api.service' ;
import { DataService } from '../data.service';
#Component({
selector: 'app-category',
templateUrl: './category.component.html',
styleUrls: ['./category.component.scss']
})
export class CategoryComponent implements OnInit {
categoryId: any;
category: any;
page = 1;
constructor(
private data: DataService,
private activatedRoute: ActivatedRoute,
private rest: RestApiService
) { }
ngOnInit() {
this.activatedRoute.params.subscribe(res => {
this.categoryId = res['_id'];
this.getProducts();
});
}
get lower() {
return 10 * (this.page - 1) + 1;
}
get upper() {
return Math.min(10 * this.page, this.category.totalProducts);
}
async getProducts(event ?: any) {
if (event) {
this.category = null;
}
try {
const data = await this.rest.get(
'http://localhost:397/api/categories/$this.categoryId?page=${this.page -1}'
);
data['success']
? (this.category = data)
:this.data.error(data['message']);
}catch (error) {
}
}
}
How could I fix this issue? I am hopeful for positive responses.
At the try catch block towards the end of your code you seem to have mistakenly wrapped the request URL in normal quotes '' instead of back ticks ``.
Which is why mongoose is showing a cast error, as it is unable to parse $this.categoryId. Try this instead:
const data = await this.rest.get(
`http://localhost:397/api/categories/${this.categoryId}?page=${this.page -1}`
);

Unable to load variable from service in Angular 9

I am trying to create a web-app for a game that involves starting and joining rooms. I am using Angular and Socket.io for the project. I have components named Startpage and Lobby. Startpage contains text boxes for player name and Room ID. It also has buttons for "Start Game" and "Join Game". Clicking on the "Start Game" button triggers the startThisGame() function.
//startpage.component.ts
import { Component, OnInit, Output, NgZone } from '#angular/core';
import { Router } from '#angular/router';
import { SocketioService } from '../socketio.service';
#Component({
selector: 'app-startpage',
templateUrl: './startpage.component.html',
styleUrls: ['./startpage.component.css']
})
export class StartpageComponent implements OnInit {
constructor( private router: Router, public zone: NgZone, private socketService: SocketioService ) {}
ngOnInit() {
this.socketService.setupSocketConnection();
}
testRoomId = 'xxxx'
startThisGame() {
var playerName = (document.getElementById("nameInput") as HTMLInputElement).value;
if (playerName) {
this.testRoomId = this.socketService.startGame(playerName);
alert("In component: " + this.testRoomId)
this.zone.run(() => {this.router.navigate(['/lobby']); });
}
else{
alert("Enter a player name!")
}
}
}
The code inside socketService goes as follows.
import { Injectable } from '#angular/core';
import * as io from 'socket.io-client';
import { environment } from 'src/environments/environment';
#Injectable({
providedIn: 'root'
})
export class SocketioService {
socket;
public roomId: string;
public playerName: string;
constructor() {}
setupSocketConnection() {
this.socket = io(environment.SOCKET_ENDPOINT)
alert("Connection set-up")
}
startGame(inputPlayerName) {
this.socket.emit('startGame', {name: inputPlayerName})
this.playerName = inputPlayerName
this.socket.on('recieveRoomId', (data: any) => {
this.roomId = data.code
alert("In service startGame: " + this.roomId)
})
return this.roomId
}
getRoomId() {
alert("In service getRoomId: " + this.roomId)
return this.roomId;
}
}
The server code creates a unique room ID and passes it to the roomId variable in socketService. My first question starts here. If you notice, I have left a trail of alert messages along the way. I would expect the order of alert messages to be alert("In service startGame: " + this.roomId) and then alert("In component: " + this.testRoomId). However, I get the alert messages in the opposite order and the roomId's are undefined even though the server generates and emits a unique roomId. I do not seem to understand why this is happening.
Secondly, you can see that the startThisGame() function causes socketService to store the generated roomId and playerName inside the class and then reroutes the app to the lobby component. The lobby code goes as follows.
import { Component, OnInit, OnChanges, AfterContentInit, SimpleChanges, Input } from '#angular/core';
import { SocketioService } from '../socketio.service';
#Component({
selector: 'app-lobby',
templateUrl: './lobby.component.html',
styleUrls: ['./lobby.component.css']
})
export class LobbyComponent implements OnInit, AfterContentInit {
constructor(private socketService: SocketioService) { }
#Input() roomId: string;
ngOnInit(): void {
// alert("On init fetched")
this.socketService.setupSocketConnection()
this.roomId = this.socketService.getRoomId()
if (this.roomId) {
alert("Value obtained in init: " + this.roomId)
}
else {
alert("No value obtained inside init")
}
document.getElementById("generated_code").innerHTML = this.roomId
}
}
Here, OnInit, the socketService.getRoomId() function returns an undefined value. If I press back, go to the startpage again, enter a playerName and start a new game again, the previously generated roomId is being rendered. What am I missing here? How do I load the roomId and display the same when I am rerouted to the lobby?
What you are missing is the concept of asynchronous calls and observable.
Try:
service.ts
export class SocketioService {
socket;
public roomId: string;
public playerName: string;
private roomData$ = new BehaviorSubject<string>(null);
constructor() {}
setupSocketConnection() {
this.socket = io(environment.SOCKET_ENDPOINT)
alert("Connection set-up")
}
startGame(inputPlayerName) : Observable<string>{
this.socket.emit('startGame', {name: inputPlayerName})
this.playerName = inputPlayerName
this.socket.on('recieveRoomId', (data: any) => {
this.roomId = data.code;
this.roomData$.next(this.roomId); // <=== emitting event when the roomId is set.
alert("In service startGame: " + this.roomId)
})
return this.roomData$.asObservable(); // <== returns observable
}
getRoomId(){
alert("In service getRoomId: " + this.roomId)
return this.roomData$.asObservable(); // <== returns observable
}
}
StartpageComponent.ts
startThisGame() {
// not sure why you are getting value in this way. I can't comment because
// I dont have access to HTML code
var playerName = (document.getElementById("nameInput") as HTMLInputElement).value;
if (playerName) {
// subscribe to the observable and get the values when prepared.
// make sure to unsubscribe it in "ngOnDestroy" to avoid memory leaks
this.socketService.startGame(playerName).subscribe(data => {
this.testRoomId = data;
alert("In component: " + this.testRoomId)
// again, not sure why would you need "this.zone.run()",
// Handle input values as Angular way and you wont need these patches.
this.zone.run(() => {this.router.navigate(['/lobby']); });
});
}
else{
alert("Enter a player name!")
}
}
In LobbyComponent.ts
// You can also use RouteParam to pass RoomId rather than using Observable as I have done.
ngOnInit(): void {
this.socketService.setupSocketConnection()
// make sure to unsubscribe in ngOnDestroy
this.socketService.getRoomId().subscribe(data => {
this.roomId = data;
document.getElementById("generated_code").innerHTML = this.roomId;
})
}
I have added lot of comments to explain you why I have done those changes. I would recommend you to read about Observables and Promises in Javascript to have better understanding of async calls.
You need to wait for the values to come before you use that value. That is why your code is not running in sequence as you are expecting.
Happy learning :)
The problem here is that that every socket is asynchronous.
The reason why you are getting the alerts in reversed orer is that you wrote
if (playerName) {
this.testRoomId = this.socketService.startGame(playerName);
alert("In component: " + this.testRoomId)
this.zone.run(() => {this.router.navigate(['/lobby']); });
}
this.socket.on('recieveRoomId', (data: any) => {
this.roomId = data.code
alert("In service startGame: " + this.roomId)
})
This way the alert is emitted on the event of reciving a new room id which is asnyc. This way the the code finishes to execute the startgame function and the returns to the main function which then alerts the 'In component' alert and when the socket receives the event it alerts the dialog.
The solution what I think shouléd be the the best is to work with RxJs and put roomId into a ReplaySubject.
So modify the service to
private roomId: ReplaySubject<string>=new ReplaySubject();
startGame(inputPlayerName) {
this.socket.emit('startGame', {name: inputPlayerName})
this.playerName = inputPlayerName
this.socket.on('recieveRoomId', (data: any) => {
this.roomId.next(data.code)
alert("In service startGame: " + this.roomId)
})
}
getRoomId() {
alert("In service getRoomId: " + this.roomId)
return this.roomId.asObservable();
}
And in the component
ngOnInit(): void {
// alert("On init fetched")
this.socketService.setupSocketConnection()
this.socketService.getRoomId().subscribe((roomId)=>{
this.roomId=roomId;
//More logic here when getting a new roomId
});
}
And dont forget to call the startGame method :)
If you want to learn more about ReplaySubject check out its awesome docs.

Extract URL Query Parameters from Request URL in Angular 8

Is there any way I could fetch the GET URL Query String Parameters from any certain URL in my Angular Service?
For E.g. Suppose I have a URL = "http:localhost/?id=123&name=abc";
or URL = "http://www.myexamplewebsite.com?id=123&name=abc";
// in my service.ts
public myFunction(): Observale<any>
{
let getVariable= this.http.get(URL);
return getVariable.pipe(map(response => <Object>response), catchError(error => this.handleError(error)));
}
So either in my component.ts or service.ts is there any way I could extract this id & name? I am new with this topic.
Note: I am not running that URL in my route. So this.route.snap.params function didn't help.
Try to use Angular's HttpParams. Import this: import { HttpClient,HttpParams } from '#angular/common/http'; and use get(paramName)or getAll()as String[].
Here is another example of how you can get the Params: https://www.tektutorialshub.com/angular/angular-pass-url-parameters-query-strings/
You can get the params by injecting Activatedroute in the component constructor:
constructor(activatedRoute: ActivatedRoute) {
let id = activatedRoute.snapshot.queryParams['id'];
let name = activatedRoute.snapshot.queryParams['name'];
}
in your component do like this.
import it.
import { ActivatedRoute } from '#angular/router';
then in constructor
private route: ActivatedRoute,
and then you can get param like this.
this.route.queryParams.subscribe(data => {
console.log(data)
});
working demo
try this url and check console. you will get query params.
let me know if you still have issue.
you can also check on this url - https://angular-routing-query-params.stackblitz.io/?abc=test&xyz=test2
output will be like this
The ActivatedRoute class has a queryParams property that returns an observable of the query parameters that are available in the current url.
export class ProductComponent implements OnInit {
order: string;
constructor(private route: ActivatedRoute) { }
ngOnInit() {
this.route.queryParams
.filter(params => params.order)
.subscribe(params => {
console.log(params); // {order: "popular"}
this.order = params.order;
console.log(this.order); // popular
});
}
}
Click here for complete article, its easy and best

Save data from Javascript subscription

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.

Adding an src from an API in Angular7

This is my component.ts where when it's loaded I get the data from the api, which I can see in the console.log, I do infact get my array of 10 objects (they come in groups of 10 on the api). I have the correct path in the API for the source code of the first image in the array of 10 which I typed to out the correct path for in normal http/javascript format of data.hits.hits[n]._source.images[n].urls.original. However when I try to put it in angular it can't read the data value as it is right now since it's out of scope, but I can't figure out how to word it in a better way.
import { Component, OnInit } from '#angular/core';
import { ConfigService } from '../../config.service';
import { Observable } from 'rxjs';
#Component({
selector: 'app-property-binding',
templateUrl: './property-binding.component.html',
styleUrls: ['./property-binding.component.css']
})
export class PropertyBindingComponent implements OnInit {
private isHidden : boolean;
public zeroImage : string;
private Photos : Observable<Object>;
constructor(private configService: ConfigService) { }
ngOnInit() {
//doing the API call
this.Photos = this.configService.getConfig();
this.Photos.subscribe((data) => console.log(data));
}
toggle() : void {
this.isHidden = !this.isHidden;
if(this.isHidden){
//var zeroImg = document.createElement("img");
this.zeroImage.src = data.hits.hits[0]._source.images[0].urls.original;
}
}
}
Here is the Angular html page that should property bind the src with the variable that I want.
<p>
View Artworks
</p>
<button class="btn btn-info" (click)="toggle()">Show Artwork</button>
<div class="col-md-4" *ngIf="isHidden">
<img [src]="zeroImage">
</div>
Here is the service method that I have the method that makes the API call
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { HttpHeaders } from '#angular/common/http';
#Injectable({
providedIn: 'root'
})
export class ConfigService {
private httpOptions = {
headers: new HttpHeaders({
'ApiKey': 'my_personal_key'
})
};
private configUrl = 'https://api.art.rmngp.fr/v1/works';
constructor(private http: HttpClient) { }
getConfig(){
let obs = this.http.get(this.configUrl, this.httpOptions)
console.log("Inside the getConfig method, before subscribe, doing API call" +
obs);
//might not need to subscribe here??
//obs.subscribe((response) => console.log(response))
return obs;
//return this.http.get(this.configUrl, this.httpOptions);
}
}
And slightly unrelated code, this is the normal http/javascript where I wrote the code Outside of Angular, which works perfectly fine.
function displayPhoto(){
fetch('https://api.art.rmngp.fr/v1/works/, {headers: {ApiKey: "my_personal_key"}})
.then(function(response){
return response.json();
})
.then(function(data){
document.getElementById("zeroImg").src = data.hits.hits[0]._source.images[0].urls.original;
Again, the API call in Angular works, I can see I am pulling the data successfully, I however can not set the image to the first image in the set of data and have been struggling with it. any help will help
You are not doing anything with the data when you subscribe
this.Photos.subscribe((data) => console.log(data));
You have not done anything with the data here.
zeroImg.src = data.hits.hits[0]._source.images[0].urls.original;
zeroImg is a string and makes no sense to set a src property on it and data is undefined at the point. The only place there is a data variable is in your subscription function but it is not available here.
The following will set the src of the image
this.Photos.subscribe((data) => {
this.zeroImg = data.hits.hits[0]._source.images[0].urls.original;
});
Make the toggle function just toggle the isHidden flag and get rid of the rest.
ngOnInit() {
//doing the API call
this.Photos = this.configService.getConfig();
this.Photos.subscribe((data) => {
this.zeroImg = data.hits.hits[0]._source.images[0].urls.original;
});
}
toggle() : void {
this.isHidden = !this.isHidden;
}

Categories

Resources