Angular 9 data from api call isn't populating dropdown - html

Having some problems with filling my drop down menu with data from my API. I need help figuring out what I am doing wrong because I am getting no errors in the console.
Service.ts method call:
#Injectable({
providedIn: 'root'
})
export class OrderExceptionReportService extends ApiServiceBase {
private apiRoute: string = 'exception-report';
public sessionData: ExceptionReportSessionData[];
constructor(http: HttpClient, configService: ConfigService) {
super(http, configService);
}
public async GetExceptionReportSessionData(): Promise<ExceptionReportSessionData[]> {
if (!this.appSettings) {
this.appSettings = await this.configService.loadConfig().toPromise();
}
return await this.http.get<ExceptionReportSessionData[]>(this.appSettings.apiSetting.apiAddress + this.apiRoute + '/session-data')
.pipe(
retry(1),
catchError(this.handleError)
)
.toPromise()
}
}
component.ts file:
#Component({
selector: 'app-order-exception-report',
templateUrl: './order-exception-report.component.html',
styleUrls: ['./order-exception-report.component.scss']
})
export class OrderExceptionReportComponent implements OnInit {
public sessionData: ExceptionReportSessionData[];
constructor(private orderExceptionReportService: OrderExceptionReportService) {
}
getExceptionReportSessionData() {
this.orderExceptionReportService.GetExceptionReportSessionData()
.then(
data => {
this.sessionData = data;
});
}
ngOnInit() {
}
}
html where I need the data:
<div class="input-group">
<select class="custom-select" id="inputGroupSelect01">
<option value="" *ngFor="let session of sessionData">{{session.SessionName}}</option>
</select>
</div>
Interface.ts file:
export interface ExceptionReportSessionData {
SessionName: string,
ReportFiles: Array<string>
}

From your comments, it appears the this.configService.loadConfig() returns an HTTP observable as well.
In that case, you could avoid using promises and streamline the process using RxJS method iff to check if the this.appSettings variable is defined and switchMap operator to switch to the target request once appSettings is retrieved.
Try the following
public GetExceptionReportSessionData(): Observable<ExceptionReportSessionData[]> {
return iff(() =>
this.appSettings,
of(this.appSettings),
this.configService.loadConfig().pipe(tap(appSettings => this.appSettings = appSettings))).pipe(
switchMap(appSettings => this.http.get<ExceptionReportSessionData[]>(appSettings.apiSetting.apiAddress + this.apiRoute + '/session-data')),
retry(1),
catchError(this.handleError)
);
}
tap operator is used to tap into the result and store it into this.appSettings variable. of method is used to send an observable of this.appSettings since switchMap operator expects an observable.
You could then subscribe to the function in the component
export class OrderExceptionReportComponent implements OnInit {
public sessionData: ExceptionReportSessionData[];
constructor(private orderExceptionReportService: OrderExceptionReportService) {}
getExceptionReportSessionData() {
this.orderExceptionReportService.GetExceptionReportSessionData().subscribe(
data => { this.sessionData = data; },
error => { }
);
}
ngOnInit() {
this.getExceptionReportSessionData();
}
}

Related

Ionic5/Angular - Error trying to diff '[object Object]'. Only arrays and iterables are allowed

I'm getting this error when I want to display firebase subcollections in HTML. It does display in console but not in UI.
I'm getting this error. Console image
TS file
import { Component, OnInit } from '#angular/core';
import { AuthService } from '../services/auth.service';
import { Router } from '#angular/router';
import { AngularFirestore } from '#angular/fire/compat/firestore';
#Component({
selector: 'app-my-reservations',
templateUrl: './my-reservations.page.html',
styleUrls: ['./my-reservations.page.scss'],
})
export class MyReservationsPage implements OnInit {
user: any;
userId: string;
storedData: any = [];
constructor(
private auth: AuthService,
private router: Router,
private afs: AngularFirestore
) {}
ngOnInit() {
this.auth.user$.subscribe((user) => {
this.userId = user.userId;
});
}
fetchBookings() {
this.afs
.collection('user')
.doc(this.userId)
.collection('BookingHistory')
.get()
.subscribe((querySnapshot) => {
querySnapshot.forEach((doc) => {
console.log(doc.id, ' => ', doc.data());
this.storedData = querySnapshot;
});
});
}
}
HTML file
<ion-item *ngFor="let data of storedData">
<ion-label>{{data.TimeSlot}}</ion-label>
<ion-label>{{data.BookedAt}}</ion-label>
<ion-label>{{data.BookingDate}}</ion-label>
</ion-item>
I'd try something like this,
As long as the querySnapshot returns array of storedData without any nested objects, you can set it directly if not you have to read it through the correct entry from the response structure and read the values from the list accordingly in your HTML template
fetchBookings() {
this.afs
.collection('user')
.doc(this.userId)
.collection('BookingHistory')
.get()
.subscribe((querySnapshot) => {
querySnapshot.forEach((doc) => { // or you can remove this forEach() and set the querySnapshot (if it returns an array of storedData) directly
console.log(doc.id, ' => ', doc.data());
this.storedData.push(doc);
});
});
}
}

Display Subscribe Data in Angular 4

I need help in displaying the output of subscribe from an api in Angular 4. How can i do this since I wrote data.data.data but it says property data doesnt exist on type object. How would i output it in the browser? Here's my code below and the api picture below
import { Component, OnInit } from '#angular/core';
import { NewsService } from '../news.service';
#Component({
selector: 'app-news-list',
templateUrl: './news-list.component.html',
styleUrls: ['./news-list.component.css']
})
export class NewsListComponent implements OnInit {
constructor(private newsService: NewsService) { }
ngOnInit() {
this.newsService.getNews()
.subscribe(
data => {
alert("News Success");
console.log(data);
},
error => {
alert("ERROR");
});
}
}
create a property in component
myData: any[] = [];
and in your subscriber function
import { Component, OnInit } from '#angular/core';
import { NewsService } from '../news.service';
#Component({
selector: 'app-news-list',
templateUrl: './news-list.component.html',
styleUrls: ['./news-list.component.css']
})
export class NewsListComponent implements OnInit {
constructor(private newsService: NewsService) { }
ngOnInit() {
this.newsService.getNews()
.subscribe(
(res: any) => {
alert("News Success");
this.myData = res.data;
// Where you find the array res.data or res.data.data
console.log('res is ', res.data);
},
error => {
alert("ERROR");
});
}
}
and in your template
1) option to view JSON
<pre>{{myData | json}}</pre>
2) option to loop if you got the array
<div *ngFor="let d of myData">
{{d}}
</div>
Your data is type of array,
Create a variable of type any
myData : any;
and assign the data to myData,
this.newsService
.getNews()
.subscribe(
data => {
this.myData = data.data;
},
error => {
alert("ERROR");
}
);
you can use ngFor to iterate over array and display in the HTML
<li *ngFor="let item of myData">
{{item}}
</li>
You need to do it like this
ngOnInit() {
this.newsService.getNews()
.subscribe(
data => {
data = data.json();
console.log(data.data);
},
error => {
alert("ERROR");
});
}
the data.json() part is important, it converts the response into proper json so can access its data.
Now you can assign it to instance variable like this
this.myArrayData = data.data
inside your subscribe() method
Then in your template
<div *ngFor="let data of myArrayData">
<!-- do whatever with the data properties -->
</div>

Angular2 sync up between service and Component

I have a "HomeComponent", that displays the user name in the UI.
The user name is read from a json file using a service.
I have the service provided in the AppComponent (parent to HomeComponent) and reused in HomeComponent
AppComponent.html
<router-outlet></router-outlet>
AppComponent.ts
export class AppComponent {
constructor(private userService: UserService) {
this.userService.fetchUserDetails();
}
}
UserService.ts
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { User } from '../models/user';
import 'rxjs/add/operator/first';
import 'rxjs/add/operator/toPromise';
#Injectable()
export class AppStateManagerService {
private userDetails: User;
private initializeUser(data) {
this.userDetails = new User();
this.userDetails.name = data.username;
this.userDetails.id = data.userid;
}
constructor(private http: HttpClient) {}
async fetchDeviceDetails() {
let response = await this.http
.get('./app/config/user.json')
.first()
.toPromise();
this.initializeUser(response);
return this.userDetails;
}
getUserDetails() {
return this.userDetails;
}
}
HomeComponent.html
<div>{{user && user.name}}</div>
HomeComponent.ts
export class HomeComponent {
user: User;
constructor(private userService: userService) {
this.user = this.userService.getUserDetails();
}
}
The problem I face here is, the HomeComponent gets initialized first, before the JSON parsing is complete, that is before fetchUserDetails() is complete in AppComponent, the getUserDetails() in HomeComponent is called and the user.name is null in the HTML, before being populated in the service.
Is there a way to sync this up? Without using Observable?
fetchDeviceDetails() is asynchronous so i hope you can agree with me that getUserDetails() will immediately return undefined. Simple stuff right?
So how to fix this: You need to let HomeComponent know that data is available. We do that using Observables. One example is:
fetchDeviceDetails(): Observable<any> {
return new Observable(observer => {
this.http.get(whatever).subscribe(
res => {
this.initializeUser(res);
observer.next(res);
}
);
});
}
Now you can subscribe to this event:
constructor(private userService: userService) {
this.userService.fetchDeviceDetails().subscribe(
res => this.user = res
);
}
Another option is to use a getter like this:
export class HomeComponent {
get user(): User {
return this.userService.getUserDetails();
}
constructor(private userService: userService) { }
}
This leverages Angular's change detection to ensure that the user data is set in the UI as soon as it is available.

HTTP Request Angular 2

I'm trying to make a request from an external API in angular 2.
I want to manage the data request in 2 files and display the result as json.
My data-output component looks like this:
import {Component} from '#angular/core'
import {DataService} from './datavisualisation.service'
#Component({
selector: 'app-datavisualisation-output',
template: `
`
})
export class DatavisualisationOutput {
constructor(dataservice: DataService) {
dataservice.data
.subscribe(
data => this.data = data,
console.error,
() => console.log('Look at this:' + data)
);
}
}
My second file for the service looks like this:
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import 'rxjs/add/operator/map';
#Injectable()
export class DataService {
constructor(http:Http) {
this.data = http.get('http;//...API')
.map(response => response.json());
}
}
...but the console shows the following error:
components/datavisualisation/dataservices/datavisualisation.output.service.ts:12:26
Property 'data' does not exist on type 'DataService'.
components/datavisualisation/dataservices/datavisualisation.output.service.ts:14:29
Property 'data' does not exist on type 'DatavisualisationOutput'.
components/datavisualisation/dataservices/datavisualisation.output.service.ts:16:43 Cannot find name 'data'.
components/datavisualisation/dataservices/datavisualisation.service.ts:8:13
Property 'data' does not exist on type 'DataService'.
What am i doing wrong here?
You should define the dataproperty on your DatavisualisationOutput component:
export class DatavisualisationOutput {
public data: any; //this one
constructor(dataservice: DataService) {
dataservice.data
.subscribe(
data => this.data = data,
console.error,
() => console.log('Look at this:' + data)
);
}
}
and on your DataService:
#Injectable()
export class DataService {
public data: any;
constructor(http:Http) {
this.data = http.get('http;//...API')
.map(response => response.json());
}
}
and on DatavisualisationOutput... Just.. always define any property you access with this.
As #PierreDuc already said you should define that variable inside component class to make it available inside Class context.
Also you should create a method inside a service which would be responsible for data. Just make an call same method from another component and it will return the data which has retrieved last time.
Code
#Injectable()
export class DataService {
data: any;
constructor(http:Http) {
}
getData(){
if(this.data) return Observable.of(this.data)
else
return http.get('http;//...API')
.flatMap(response => {
this.data = response.json();
return Observable.of(this.data)
);
}
}
Component
export class DatavisualisationOutput {
myData: any;
constructor(private dataservice: DataService) {
dataservice.data
.subscribe(
data => this.data = data,
console.error,
() => console.log('Look at this:' + data)
);
}
ngOnInit(){
dataservice.getData().subscribe(
data => myData = data
)
}
}

How can I sanitize css properties to use in template given from a data service

I need to generate sanitized css property to use with my component template to set the background image of the div:
<div *ngFor="let Item of Items"
[style.background-image]="Item.imageStyle
(click)="gotoDetail(Item.iditems)">
</div>
using data obtained through a data service. The component is:
import { Component } from '#angular/core';
import { Router } from '#angular/router';
import { DomSanitizer } from '#angular/platform-browser';
import { OnInit } from '#angular/core';
import { Item } from '../models/Item';
import { CollectionDataService } from '../services/CollectionData.service';
#Component({
selector: 'mainpage',
templateUrl: 'app/mainpage/mainpage.component.html',
styleUrls: ['app/mainpage/mainpage.component.css']
})
export class MainpageComponent implements OnInit {
Items: Item[];
ngOnInit() {
this.collectionDataService.getItems().subscribe(
Items => this.Items = Items
);
// Generates and sanitizes image links
this.Items.map(
(LItem) => LItem.imageStyle = this.sanitizer.bypassSecurityTrustStyle("url(template/images/"+LItem.iditems+".jpg)")
)
}
constructor(
private router: Router,
private sanitizer: DomSanitizer,
private collectionDataService: CollectionDataService
) {
}
gotoDetail($iditems: number): void {
this.router.navigate(['/viewer', $iditems]);
}
}
But it doesn't work because the statement that generates the sanitized property
this.Items.map(
(LItem) => LItem.imageStyle = this.sanitizer.bypassSecurityTrustStyle("url(template/images/"+LItem.iditems+".jpg)")
)
doesn't find the loaded data. The error that I'm seeing in the browser console is:
core.umd.js:3070 EXCEPTION: Uncaught (in promise): Error: Error in ./MainpageComponent class MainpageComponent_Host - inline template:0:0 caused by: Cannot read property 'map' of undefined
TypeError: Cannot read property 'map' of undefined
The data service is:
import { Injectable } from '#angular/core'
import { Http } from '#angular/http'
import { Item } from '../models/Item';
import { DomSanitizer } from '#angular/platform-browser';
#Injectable()
export class CollectionDataService {
constructor(
private http: Http,
private sanitizer: DomSanitizer
) { }
getItems() {
return this.http.get('app/mocksdata/items.json').map(
response => <Item[]>response.json().items
)
}
}
And the provided items.json:
{
"items": [{
"iditems": 1,
"imageStyle": ""
}, {
"iditems": 2,
"imageStyle": ""
}]
}
If I set static data in the component, instead of using the data service, everything works:
export class MainpageComponent implements OnInit {
Items: Item[];
ngOnInit() {
this.Items = [{
"iditems": 1,
"imageStyle": ""
}, {
"iditems": 2,
"imageStyle": ""
}]
// Generates and sanitizes image links
this.Items.map(
(LItem) => LItem.imageStyle = this.sanitizer.bypassSecurityTrustStyle("url(template/images/"+LItem.iditems+".jpg)")
)
}
How can I force the sanitizer statement to wait that the async data are fully loaded? Alternatively how can I generate sanitized properties directly in the service?
EDIT
The best answer comes from PatrickJane below:
Items: Item[] = [];
ngOnInit() {
this.collectionDataService.getItems().subscribe(Items => {
this.Items = Items;
this.Items.map(LItem => LItem.imageStyle = this.sanitizer.bypassSecurityTrustStyle("url(template/images/"+LItem.iditems+".jpg)"))}
});
}
I also solved this problem working directly in the service method (credits), but it is more verbose:
return this.http.get('app/mocksdata/items.json')
.map( (responseData) => {
return responseData.json().items;
})
.map(
(iitems: Array<any>) => {
let result:Array<Item> = [];
if (iitems) {
iitems.forEach((iitem) => {
iitem.imageStyle = this.sanitizer.bypassSecurityTrustStyle("url(template/images/"+iitem.iditems+".jpg)");
result.push(<Item>iitem);
});
}
return result;
}
)
The subscribe function is async so your map function called before the subscribe function run. So in this phase the array is undefined because you doesn't set any initial value.
The solution is to do this inside the subscribe function and to initialize the Items with empty array.
Items: Item[] = [];
ngOnInit() {
this.collectionDataService.getItems().subscribe(Items => {
this.Items = Items;
this.Items.map(LItem => LItem.imageStyle = this.sanitizer.bypassSecurityTrustStyle("url(template/images/"+LItem.iditems+".jpg)"))}
});
}