Adding an src from an API in Angular7 - javascript

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;
}

Related

Cannot read properties of undefined on synchronous call to a rest api

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();
});

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

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

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.

Can't bind JSON results to Component variable

I'm trying a simple component that has to pull data from a JSON file. I'm almost copying the functionality from generated Fountain App, but for some reason I can't get the desired results. I have a component like:
import {Component, Inject} from "#angular/core";
import {Http} from '#angular/http';
#Component({
selector: 'body-selector',
template: require('./body.html')
})
#Inject(Http)
export class BodyComponent {
constructor(http: Http) {
this.http = http;
this.getText('app/texts/main.json').subscribe(result => {
console.log(result);
this.texts = result;
});
console.log(this.texts);
}
getText(url) {
return this.http.get(url).map(response => response.json());
}
}
on the first console.log I have [Object object] [Object object] [Object object], which is correct as I have three entries in the JSON. On the second however I've got undefined, which turns into an error in the browser.
Error in ./BodyComponent class BodyComponent - inline template:3:6 caused by: Cannot read property 'title' of undefined
I'm looking at the example generated from the fountain app, but I can't get what I'm doing wrong.
You have multiple problems:
The first console.log is inside the callback, where this.texts has just been set. However the second one is outside the callback, so it won't have been. Therefore you'll always see undefined for that, because...
...you never set a default value for this.texts, and your template apparently doesn't have any e.g. *ngIf to handle it being null or undefined, causing errors prior to the callback being called.
Below is your code, refactored to start with an empty this.texts (assuming it should be an array; please adapt to taste) and simplifying the injection of Http. Also note the comments, and that I've used templateUrl to avoid the require and OnInit to trigger the HTTP call slightly later in the component lifecycle rather than doing it in the constructor.
import {Component, OnInit} from '#angular/core'; // note consistent quotes
import {Http} from '#angular/http';
#Component({
selector: 'body-selector',
templateUrl: './body.html',
})
export class BodyComponent implements OnInit {
texts: any[] = []; // start with an empty array
constructor(private http: Http) { } // inject Http here, no need to assign to this
ngOnInit() {
this.http
.get('app/texts/main.json')
.map(response => response.json())
.subscribe(result => {
console.log(result); // only log *inside* the callback
this.texts = result;
});
// outside the callback, the HTTP call hasn't yet finished
}
}
You could also solve this by having an ngIf in your HTML to prevent the element from being loaded before the data is.
<div class="main-container" *ngIf="texts">
...
</div>
I'd strongly recommend running through the basic Angular 2 tutorials to get on top of this stuff, see e.g. the Tour of Heroes.
You get undefined because this:
this.getText('app/texts/main.json')
Is an asynchronous call that gets the data and when it's done, it executes the code in the 'subscribe' block. So this.text is empty until that executes. That is the expected behavior.
Better way to use data or making API call use service:
import { Injectable } from '#angular/core';
import { Http, Response, Headers, RequestOptions } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import { ErrorObservable } from 'rxjs/observable/ErrorObservable';
#Injectable()
export class BodyService {
private _requestOptions: RequestOptions;
private static handleError(error: Response): ErrorObservable {
return Observable.throw(error.json().error || 'Server error');
}
constructor(private http: Http) {
const headers = new Headers({ 'Accept': 'application/json' });
this._requestOptions = new RequestOptions({ headers: headers});
}
/**
* [getJsonData will get data of json file: main.json ]
*/
getJsonData() {
return this.http.get('app/texts/main.json', this._requestOptions)
.map(res => res.json()) //needs to add map for converting data in json format
.catch(BodyService.handleError);
}
}
Now Inject this service in your component:
import {Component, OnInit} from '#angular/core';
import { BodyService } from './adminModules.service';
#Component({
selector: 'body-selector',
templateUrl: './body.html',
providers: [BodyService]
})
export class BodyComponent implements OnInit {
texts: any = []; // start with an empty array
errorMessage: any;
constructor(private _bodyService: BodyService) {}
ngOnInit() {
this._bodyService.getJsonData()
.subscribe(data => {
this.texts = data;
console.log('data', this.texts);
}, error => {
this.errorMessage = <any> error;
})
}
}
For calling service, you can call directly in constructor or create one method and call either in constructor or any place where you want to call.
Hope it will helpful for you :)

Angular 2 - Call a function that exists outside of the current class

I want to call a function that exists in HomePage class which is outside of the (class Popover) that I want to use the function on, I've already done some research, and I guess that I need to do something like dependency injection, I've tried to follow some tutorials but I was not lucky enough to solve the issue.
Popover class:
#Component({
template: `
<div>
<button ion-item *ngFor="let city of cities" (click)="switchToThisCity(city.cityName);close();">{{city.cityName | uppercase}}</button>
</div>
`
})
class MyPopover{
static get parameters(){
return [[Http], [ViewController]];
}
constructor(http, viewCtrl) {
this.http = http;
this.viewCtrl = viewCtrl;
//Async Call
var getCities = new URLSearchParams();
this.http.get('https://restApi.com/class/outlet', {headers: ParseHeaders}).subscribe(data => {
this.cities = data.json().results;
});
///
}
close() {
this.viewCtrl.dismiss();
}
switchToThisCity(currentCity){
//I WANT TO CALL THIS FUNCTION WHICH EXISTS ON HomePage CLASS
return getQueries(currentCity);
}
}
HomePage Class:
#Component({
templateUrl: 'build/pages/home/home.html',
})
export class HomePage {
static get parameters(){
return [[NavController],[Http], [NavParams]];
}
// this.cartLength = this.cart.items.length;
constructor() {}
//I NEED TO USE THIS IN THE POPOVER CLASS
getQueries(city){
var cities = new URLSearchParams();
cities.set('cityName', city);
this.http.get('https://restApi.com/classes/citiesList', { search : dishesParams, headers: ParseHeaders}).subscribe(data => {
this.getCities = data.json().results;
});
}
}
Create a Service class
cities.service
#Injectable()
export class CitiesService {
getQueries(city) {
var cities = new URLSearchParams();
cities.set('cityName', city);
return this.http.get('https://restApi.com/classes/citiesList', {
search: dishesParams,
headers: ParseHeaders
}) // here we return an observable so we can subscribe to it in our class
}
and in Popover: (Same with homepage class)
export class MyPopover{
constructor(private citiesService:CitiesService) {
}
// and this is how you use the function
this.citiesService.getQueries().subscribe(data => {
this.getCities = data.json().results;
});
}
UPDATE : have a look at this article: http://nicholasjohnson.com/blog/how-to-do-everything-in-angular2-using-es6/
First up, anything is injectable in Angular, so PetService can be just a newable function.
The Angular DI mechanism will automatically use it to create a
singleton that is local to the correct branch of the injector tree. If
you only have a root injector (made automatically by Angular 2 on
bootstrap), this will be a global singleton, just like Angular
the principle here is to create a service that handles the request and inject it, return an observable object and subscribe, then you can do whatever you want with the response...
I would extract the getQueries method into a service:
#Injectable()
export class QueryService {
constructor(http) {
this.http = http;
}
static get parameters(){
return [[Http]];
}
}
and inject it into both components:
#Component({
templateUrl: 'build/pages/home/home.html',
providers: [ QueryService ]
})
export class HomePage {
static get parameters(){
return [[NavController],[NavParams], [QueryService];
}
constructor(nav, params, service) {
this.service = service;
}
getQueries(city){
this.service.getQueries(city)...
}
}
and the same in the MyPopover class.

Categories

Resources