Observable to Array *ngFor saying undefined - arrays

I am new to Angular. I have a Node and Express backend pulling data from an MS SQL database. If I go to the endpoint URL it displays my data as JSON. I am running on localhost so I set a proxy for CORS. I have a class that defines the data, a service that pulls the data from the endpoint and a component that tries to set an array equal to the data pulled from the service. The HTML has an *ngFor that is supposed to loop through the values and display them in a grid.
If I call my data in my component through my service, so this.userService.getUsers(), and do a console.log I can see the recordset in the browser console. I try to set the array equal to the userService.getUsers() and then call the array and I get "undefined". Being that I am new, I have tried to follow the Heroes tutorial and that did not work. I spent a day searching Google and trying different solutions that I have come across but they all come up as undefined. I will attach the code here. If someone can guide me a bit, it would be much appreciated.
User class defining User:
export class User{
id: number;
ccn: string;
firstName: string;
lastName: string;
email: string;
}
User Service doing Http request:
import { Injectable } from '#angular/core';
import { User } from './user';
import { USERS } from './mock-users';
import { MessageService } from './message.service';
import { Observable, of } from 'rxjs';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { catchError, map, tap } from 'rxjs/operators';
#Injectable({
providedIn: 'root'
})
export class UserService {
private userURL = 'api/users'
//private userURL = 'localhost:5000'
httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};
constructor(
private http: HttpClient,
private messageService: MessageService) { }
//getUsers(): Observable<User[]> {
// this.messageService.add('UserService: fetched users');
// return of(USERS);
//}
/** GET users from the server */
getUsers(): Observable<User[]> {
//console.log('getting users');
return this.http.get<User[]>("http://localhost:5000/api/user")
.pipe(
tap(_ => this.log('Fetched users')),
catchError(this.handleError<User[]>('getUsers', []))
);
//return this.http.get<User[]>("http://localhost:5000/api/user");
//console.log('got users');
}
/* GET heroes whose name contains search term */
searchUsers(term: string): Observable<User[]> {
if (!term.trim()) {
// if not search term, return empty hero array.
return of([]);
}
return this.http.get<User[]>(`${this.userURL}/?ccn=${term}`).pipe(
tap(_ => this.log(`found users matching "${term}"`)),
catchError(this.handleError<User[]>('searchUsers', []))
);
}
addUser (user: User): Observable<User> {
return this.http.post<User>(this.userURL, user, this.httpOptions).pipe(
tap((newUser: User) => this.log(`added user w/ id=${newUser.id}`)),
catchError(this.handleError<User>('addUser'))
);
}
private handleError<T> (operation = 'operation', result?: T) {
return (error: any): Observable<T> => {
console.error(error);
this.log(`${operation} failed: ${error.message}`);
return of(result as T);
};
}
private log(message: string) {
this.messageService.add(`User service: ${message}`);
}
}
Display Users Component TS file:
import { Component, OnInit } from '#angular/core';
//import { USERS } from '../mock-users';
import { UserService } from '../user.service';
import { User } from '../user';
import { Observable, of } from 'rxjs';
import { catchError, map, tap } from 'rxjs/operators';
import { element } from 'protractor';
#Component({
selector: 'app-display-users',
templateUrl: './display-users.component.html',
styleUrls: ['./display-users.component.css']
})
export class DisplayUsersComponent implements OnInit {
users: User[] = [];
constructor(private userService: UserService) { }
//users$ = this.getUsers();
ngOnInit() {
this.getUsers();
console.log(this.userService.getUsers());
this.userService.getUsers().forEach(element => {
console.log(element);
});
}
getUsers(): void {
/*this.userService.getUsers()
.subscribe(users => this.users = users);*/
const userObservable = this.userService.getUsers();
userObservable.subscribe((userData: User[]) => {
this.users = userData;
});
}
}
Display Users Component HTML:
<div class="clr-row">
<div class="clr-col-lg-11 clr-col-md-11 clr-col-11 main-div">
<div class="card card-style" style="box-shadow: 0 0 0 0;">
<div class="card-header">
<h1><img src="../assets/images/BSOLOGO_gray.png" class="title-img"><span class="title"> Users</span></h1>
</div>
<div class="card-block">
<div class="card-title">
<clr-datagrid>
<clr-dg-column>CCN</clr-dg-column>
<clr-dg-column>Last Name</clr-dg-column>
<clr-dg-column>First Name</clr-dg-column>
<clr-dg-column>Email</clr-dg-column>
<clr-dg-row *ngFor="let user of users">
<clr-dg-cell>{{user.ccn}}</clr-dg-cell>
<clr-dg-cell>{{user.lastName}}</clr-dg-cell>
<clr-dg-cell>{{user.firstName}}</clr-dg-cell>
<clr-dg-cell>{{user.email}}</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>{{users.length}} users</clr-dg-footer>
</clr-datagrid>
</div>
</div>
</div>
</div>
</div>
Any help would be greatly appreciated!
UPDATED

Ypu can replace getUsers on both classes by these. HTML looks fine to me. I converted users to public too.
//userService
getUsers(callback: Function) {
return this.http.get<User[]>("http://localhost:5000/api/user")
.subscribe(
response => callback(response)
);
}
//Component
public users: User[] = [];
getUsers(): void {
this.userService.getUsers((result) => {this.users = result;})
}

If you do not need it to be Observable you can use toPromise() and using async/await makes it waaay easier
Service
async getUsers(): Promise<User[]> {
return await this.http.get<User[]>('http://localhost:5000/api/user').toPromise();
}
Component.ts
users: User[] = [];
async ngOnInit() {
this.users = await this.userService.getUsers();
}
Component.html
<clr-datagrid *ngIf="users">
<clr-dg-column>CCN</clr-dg-column>
<clr-dg-column>Last Name</clr-dg-column>
<clr-dg-column>First Name</clr-dg-column>
<clr-dg-column>Email</clr-dg-column>
<clr-dg-row *ngFor="let user of users">
<clr-dg-cell>{{user.ccn}}</clr-dg-cell>
<clr-dg-cell>{{user.lastName}}</clr-dg-cell>
<clr-dg-cell>{{user.firstName}}</clr-dg-cell>
<clr-dg-cell>{{user.email}}</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>{{users.length}} users</clr-dg-footer>
</clr-datagrid>

My issue has been resolved. In my SQL statement I was calling SELECT * FROM table FOR JSON PATH which was creating a weird object being pulled from the server. Removing the FOR JSON PATH provided JSON data. Then the second part of my issue was mapping my DB fields with my user class.
This was done like this:
request.query('SELECT * FROM Table ORDER BY myField', function (err, recordset) {
if (err) console.log(err);
const records = recordset.recordset;
const result = records.map(r => { return { id: r.tableID, field1: r.dbField1, field2: r.dbField2, field3: r.dbField3, field4: r.dbField4}});
res.send(result);
});
I hope this helps someone! Thanks to everyone that posted to help me.

Related

How to filter an observable wrapped json file response?

I am working on a angular project and my task is to filter a huge file based on the "_type" key which can take different values. Right now I want to first filter _type = "COMPETITION".
My model is defined in a competition.model.ts file which looks like this:
export interface Competition {
product: { active: true };
schemaVersion: number; // 2,
status: string; // PUBLISHED",
comp: string; // "4fc16b10-b8b4-4a99-b9f1-842f0d8b8413",
_createdDate: number; // 1594249198,
discipline: string; // "TRAP [ACTA]",
categories: any; // ["OPEN", "LADIES", "JUNIOR", "VETERAN", "CLAYS_ONLY"],
host: string; // "2",
changeSet: number; // 257,
sync: number; // 155,
seq: number; // 120,
id: string; // "4fc16b10-b8b4-4a99-b9f1-842f0d8b8413",
_type: string; // "COMPETITION",
when: number; // 1597154400,
title: string; // "ACTA Self Nom Test"
}
Here is a my service class where I am trying to implement this:
import { Injectable, OnInit } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { map } from 'rxjs/Operators';
import { Competition } from '../interfaces/competition.model';
#Injectable ({providedIn: 'root'})
export class CompetitionListService {
private loadedCompetitions: Competition[];
private url = '../../assets/data/program1.json';
constructor(private http: HttpClient) {}
public getCompetitions(): Competition[] { return this.loadedCompetitions; }
public fetchCompetition(){
return this.http
.get<Competition[]>(this.url)
.pipe(
map( (responseData) => {
const competitionsArray = [];
for (const key in responseData ) { // responseData is an object
if (responseData.hasOwnProperty(key)) {
// get the_type property
// if ( key.valueOf() === 'COMPETITION') {
competitionsArray.push(
// responseData[key]._createdDate,
responseData[key]._createdDate,
responseData[key]._type,
responseData[key].categories,
responseData[key].changeSet,
responseData[key].comp,
responseData[key].discipline,
responseData[key].host,
responseData[key].id,
responseData[key].product,
responseData[key].schemaVersion,
responseData[key].seq,
responseData[key].status
);
}
}
console.log(competitionsArray);
return competitionsArray;
})
)
.subscribe(competitions => {
console.log(competitions);
this.loadedCompetitions = competitions;
});
}
}
I attached the snapshot of the result on my console, which doesn't really do what I really want to achieve.
I see multiple issues here
You're trying to fetch the asynchronous variable this.loadedCompetitions synchronously. It is not possible. All async variables should be accessed asynchronously. You could use RxJS ReplaySubject multicast observable with buffer 1 to hold and emit the last value.
You don't to manually loop through each item of the array and create a new array with the _type === 'COMPETITION' property. You could use Array filter function to filter out the objects based on a condition.
...
import { Observable, ReplaySubject } from 'rxjs';
import { map } from 'rxjs/operators';
#Injectable ({providedIn: 'root'})
export class CompetitionListService {
private loadedCompetitions: ReplaySubject<Competition[]> = new ReplaySubject<Competition[]>(1);
private url = '../../assets/data/program1.json';
constructor(private http: HttpClient) {
this.fetchCompetition(); // <-- triggers the request and pushes value to `loadedCompetitions`
}
public getCompetitions(): Observable<Competition[]> {
return this.loadedCompetitions.asObservable();
}
public fetchCompetition() { // return nothing here
this.http.get<Competition[]>(this.url).pipe(
map(res => res.filter(item => item['_type'] !== 'COMPETITION'))
).subscribe(
res => this.loadedCompetitions.next(res),
err => console.log(err) // <-- handle error
);
}
}
Now you need to subscribe to loadedCompetitions variable to obtain notifications from it. I've used RxJS takeWhile operator with Subject to close any open subscriptions in the ngOnDestroy hook of the component.
...
import { Observable, Subject } from 'rxjs';
import { takeWhile } from 'rxjs/operators';
export class SomeComponent implements OnInit, OnDestroy {
private close$ = new Subject<any>(); // <-- use to close open subscriptions
constructor(private competitionListService: CompetitionListService) { }
ngOnInit() {
this.competitionListService.loadedCompetitions.pipe(
takeWhile(this.close$)
).subscribe(
res => {
console.log(res);
// other statements that depend on `res`
}
);
}
ngOnDestroy() {
this.close$.next(); // <-- close open subscriptions
}
}

Trying to get a length on a key value on an object which is inside an object within an array of objects

I am trying to create a score count for two teams. The score is an array which differentiates by defining if the goal was scored by a home or away team (which is a separate object within the score array). I need to get the count for the number of times a goal has been scored which carries an id of either 1 or 2.
I've tried using query selectors within the DOM, but with the request times, the score doesn't often render properly - the list length doesn't seem to get counted and stays at 0.
I have also tried to filter the count length whilst making a request post-subscription to the service within the component. But this again didn't work. The below code is an example.
This states that goalCount.filter is not a function.
getScore(): void {
this.ScoreService.getScore()
.subscribe(score => {
this.score = score;
const goalCount = this.score;
this.count = goalCount.filter((obj) => obj.side.data.id === '1').length;
});
}
then {{ count }} is bound to the view, but the above error displays.
console log of score - editor is so bad and formatting it takes ages:
I expect that when the server sends back a new goalscorer, the ngif decides which column to put it in and the length of that list in the DOM is then printed above it.
e.g
1
Smith, '89
2
Perez, '30
Taylor,'45
awayscore.component.ts
import { Component, OnInit, AfterViewInit, Input, OnDestroy } from '#angular/core';
import { Subscription } from 'rxjs/Subscription';
import { UpdateService } from '../update.service';
import { Stats } from '../stats';
import { StatsService } from '../stats.service';
import { Score } from '../score';
import { ScoreService } from '../score.service';
import { ConfigService } from '../../../shared/config/config.service';
import { Config } from '../../../shared/config/config';
import { environment } from '../../../environments/environment';
#Component({
selector: 'app-away-score',
templateUrl: './away-score.component.html'
})
export class AwayScoreComponent implements OnInit, AfterViewInit, OnDestroy {
#Input()
away: Stats;
private subscription: Subscription;
config: Config[];
stats: Stats[];
score: Score[];
imageUrl: string;
count: number;
constructor(
private StatsService: StatsService,
private ScoreService: ScoreService,
private configService: ConfigService,
private updateService: UpdateService) {
}
ngOnInit() {
this.getConfig();
this.getStat();
this.getScore();
this.imageUrl = environment.image_url;
this.subscription = this.updateService.updateObservables.subscribe((res) => {
if (res.hasOwnProperty('option') && res.option === 'call_score_component') {
console.log(res.value);
}
})
}
ngAfterViewInit() {
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
getConfig(): void {
this.configService.getConfig()
.subscribe(config => this.config = config);
}
getStat(): void {
this.StatsService.getStats()
.subscribe(stats => this.stats = stats);
}
getScore(): void {
this.ScoreService.getScore()
.subscribe(score => {
this.score = score;
});
}
}
awayscore.component.html:
<span *ngIf="away">
<ng-container *ngIf="config">
<img *ngIf="away.away_badge.data" class="avatar" [src]="imageUrl + away.away_badge.data.url">
<h4
*ngFor="let c of config;"
[style.font-family]="c.header_font.data.font_family"
[style.text-transform]="c.header_text_case.data.text_case"
[style.color]="'#' + c.secondary_color.data.hex_code">
{{ away.away_team }}</h4>
</ng-container>
<h1>{{ count }}</h1>
<ul #awayScore *ngIf="score">
<ng-container *ngFor="let s of score.data">
<li *ngIf="s.side.data.side === 'Away'">
<small>{{ s.goal_scorer }} '{{ s.time_of_goal }}</small>
</li>
</ng-container>
</ul>
</span>
score service:
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { _throw } from 'rxjs/observable/throw';
import { catchError, retry } from 'rxjs/operators';
import { environment } from '../../environments/environment';
#Injectable()
export class ScoreService {
url = environment.api_url;
private scoreURL = this.url + 'score/rows';
data: any = {};
constructor(private http: HttpClient) { }
getScore() {
return this.http.get(this.scoreURL)
.pipe(
retry(1),
catchError(this.handleError),
);
}
handleError(error) {
let errorMessage = '';
if (error.error) {
errorMessage = error.error.message;
} else {
errorMessage = error;
}
return _throw(errorMessage);
}
}
You are trying to access an array but its not. Your score contains a property data which is an array. Try this out:
getScore(): void {
this.ScoreService.getScore()
.subscribe(score => {
this.score = score.data;
const goalCount = this.score;
this.count = goalCount.filter((obj) => obj.side.data.id === '1').length;
});

Angular 2 Load data through server API : data.slice error

Im trying to load the data from my API to custom component using Angular2 ng Smart table plugin.
AS per their documentation (https://github.com/akveo/ng2-smart-table/blob/master/src/app/pages/examples/server/basic-example-load.component.ts)
i have my component like:
import { LocalDataSource } from 'ng2-smart-table';
import { ProductService } from '../../../services/product.service';
export class CategoryItemsComponent implements OnInit {
...
source: LocalDataSource;
constructor(private productService: ProductService,
private flashMessage: FlashMessagesService,
private router: Router,
http: Http) {
this.source = new LocalDataSource();
this.productService.getProductsOncategory(this.categoryid).subscribe((data) => {
this.source.load(data);
});
}
ProductService .ts
getProductsOncategory(category_id) {
let catUrl = "http://localhost:5000/products/getProductsOncategory"
let headers = new Headers();
headers.append('Content-Type', 'application/json');
let catIdObj = JSON.stringify({ category_id: category_id })
return this.http.post(catUrl, catIdObj, { headers: headers })
.map((response: Response) => response.json())
.do(data => console.log(JSON.stringify(data)))
.catch(this.handleError);
}
The above API used in the service function works perfect in my postman.
Now i need to load the dame data from that API into my custom component.
I am getting this error:
ERROR TypeError: this.data.slice is not a function
at LocalDataSource.webpackJsonp.../../../../ng2-smart-table/lib/data-source/local/local.data-source.js.LocalDataSource.getElements (http://localhost:4200/1.chunk.js:22280:30)
at LocalDataSource.webpackJsonp.../../../../ng2-smart-table/lib/data-source/data-source.js.DataSource.emitOnChanged (http://localhost:4200/1.chunk.js:22185:14)
at LocalDataSource.webpackJsonp.../../../../ng2-smart-table/lib/data-source/data-source.js.DataSource.load (http://localhost:4200/1.chunk.js:22105:14)
at LocalDataSource.webpackJsonp.../../../../ng2-smart-table/lib/data-source/local/local.data-source.js.LocalDataSource.load (http://localhost:4200/1.chunk.js:22243:38)
Ok i got it by using like:
source: LocalDataSource;
constructor(private productService: ProductService,
private flashMessage: FlashMessagesService,
private router: Router,
http: Http)
{
this.source = new LocalDataSource();
}
onChange(categoryid) {
this.productService.getProductsOncategory(categoryid).subscribe(data => {
if (data.success) {
this.source.load(data.products);
console.log('Products obtained');
} else {
console.log('Not obtained!');
}
});
}
Had the same problem. It solved when i check all match columns and add the missing in table data. For example i delared a variable
Settings = {
....
columns: {
id: {
title: 'id',
show: false,
type: 'string',
},
name: {
title: 'name',
type: 'string',
},
//compare columns in json response in same variable declaration
//for your data table [source]
}
}
And in second case your table try get data from dataTableSource before full data loading, to avoid this use setTimeout(); method and set more time.
For Example:
getChildData(): Promise<any> {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(this.childsList);
}, 4000);//<= increase this value
});
}
excuse me for my english))

How transfer data from component to another component wia Subject/Observable?

I find interesting example how transfer data from one component to another component using Subject/Observable in Service:
http://plnkr.co/edit/yMBoVkxohwhPig5COgkU?p=preview
I find it similar to broadcast event from Angular 1.X. But I notice that subscribe to data changes can only component that inishiated data change. I need somehow modify this example to get data in one component after another component changed it. Console.log dos't show any error, but data-transfer not working. Can anyone help?
Service:
#Injectable()
export class ClientsService {
private _client$: Subject<Client>;
private _clients$: Subject<Client[]>;
constructor(private http: Http) {
this._client$ = <Subject<Client>>new Subject();
this._clients$ = <Subject<Client[]>>new Subject();
}
getClients(): Promise<Client[]> {
return this.http.get('api/Client')
.toPromise()
.then(res => {
this._clients$.next(res.json());
return res.json();
})
.catch(this.handleError);
}
get client$() {
return this._client$.asObservable();
}
get clients$() {
return this._clients$.asObservable();
}
clientChangeBroadcast(objClient: Client) {
this._client$.next(objClient);
}
clientsChangeBroadcast(arrClients: Client[]) {
this._clients$.next(arrClients);
}
private handleError(error: any): Promise<any> {
return Promise.reject(error.message || error);
}
First component:
#Component({
selector: 'app',
templateUrl: './app/app.component.html'
})
export class AppComponent implements OnInit {
clients: Client[];
constructor(public clientsService: ClientsService) {
this.clients = [];
}
ngOnInit() {
}
loadClients() {
this.clientsService.getClients().then(clients => {
this.clients = clients;
this.clientsService.clientsChangeBroadcast(clients);
});
}
selectClient(objClient: Client) {
this.clientsService.getClients();
this.clientsService.clientChangeBroadcast(objClient);
}
}
Second component:
export class TestComponent implements OnInit {
client: Observable<Client>;
clients: Observable<Client[]>;
constructor(private clinetsService: ClientsService) {
}
ngOnInit() {
//this.clinetsService.getClients();
this.client = this.clinetsService.client$;
this.clients = this.clinetsService.clients$;
this.clinetsService.client$.subscribe(value => {
// try to get changes here
});
this.clinetsService.clients$.subscribe(value => {
// try to get changes here
});
}
reLoadClients() {
this.clinetsService.getClients();
}
}
I try to get data changinf in 'Second component' after data was changed by 'First component'. When I click on button for 'selectClient' method in 'First component' I expect data transfer wia service and I catch changes ob 'Second component' -> it not working. When from 'Second component' I press button for 'reLoadClients' data transfer wia service and I catch changes in all subscribe functions on 'Second component'.

Angular2 Observables - Creating my own - subscriber undefined

I can't quite tell if this is a scoping issue or an issue with how I've set up my observables, but maybe you can help.
Firstly, my logic. My overall aim is to be able to check if some data is held locally before sending off a http.get request for it. What I'd like to do is return the locally held data back if it exists, and I thought that I could make this return an observable so that I could subscribe to the output of this function regardless of if it returned local data or remote data. Any thoughts on this logic would be appreciated!
I am following this blog post by Cory Rylan to implement my own observable. I am running into a problem where I cannot get my Observer to be defined in my getData function.
Here is Cory's working demo
Here is my not-working demo - You can see in the log that this._dataObserver in the service is undefined, whereas in Cory's it is. This is what is causing me issues.
Here are snippets from the code:
App.ts
#Component({
selector: 'my-app',
template: `
<landing-page></landing-page>
`,
providers: [DataService],
directives: [LandingPageComponent]
})
export class App {
constructor() { }
}
bootstrap(App, [HTTP_BINDINGS])
.catch(err => console.error(err));
LandingPageComponent.ts
#Component({
selector: 'landing-page',
template : `
hello
`,
})
export class LandingPageComponent implements OnInit{
data : Observable<any[]>;
constructor(
private _dataService : DataService
) { }
ngOnInit() {
this.data = this._dataService.data$;
this._dataService.getData();
}
}
DataService
#Injectable()
export class DataService {
data$ : Observable<any[]>; // data stream
private _baseUrl: string;
private _dataObserver : Observer<any[]>;
private _dataStore : {
data : any[]
};
constructor (
private _http: Http
) {
this._baseUrl = 'http://56e05c3213da80110013eba3.mockapi.io/api';
this.data$ = new Observable(observer => this._todosObserver = observer).share();
this._dataStore = { data: [] };
}
/** **************
* Public functions
*****************/
getData () {
this._http.get(`${this._baseUrl}/todos`)
.map(data => data.json())
.subscribe( data => {
this._dataStore.data = data;
console.log(this._dataObserver); //<------ This is undefined for me!
this._dataObserver.next(this.data);
},
error => {
console.log('couldn\'t load data! ' + error );
});
}
}
Thank you for any thoughts
_dataObserver is null because you don't subscribe on the associated observable. Don't forget that observables are lazy...
You could try the following:
ngOnInit() {
this.data = this._dataService.data$.subscribe(data => { // <-----
console.log(data);
});
this._dataService.getData();
}
Edit
In addition, there is a typo in your code:
this.data$ = new Observable(observer => this._todosObserver = observer).share();
You should use _dataObserver instead of _todosObserver:
this.data$ = new Observable(observer => this._dataObserver = observer).share();

Resources