Custom Endpoint with #NGRX/Data - angular-ngrx-data

I need to modify my endpoint using #NGRX/Data. I have written a dataservice that is described in the documentation that does change the endpoint, the problem is that the data is not loaded into the store. How do I use this custom dataservice and load the data into the store once it is received?
#Injectable()
export class OfficeCustomService extends DefaultDataService<Office> {
userId: number;
territoryId: number;
constructor(http: HttpClient ) {
const url = new DefaultHttpUrlGenerator(new DefaultPluralizer([appPluralNames]));
url.registerHttpResourceUrls({Label: {entityResourceUrl: 'office', collectionResourceUrl: 'offices'}});
super('Office', http, url, defaultDataServiceConfig);
}
protected execute(method: HttpMethods, url: string, data?: any, options?: any): Observable<any> {
const regex = /offices/gi;
if (method === 'GET' && url.search(regex) > 0) {
url = url + 'userId/' + this.userId + '/territoryId/' + this.territoryId;
}
return super.execute(method, url, data, options);
}
getAllByUserAndTerritory(userId: number, territoryId: number): Observable<Office[]> {
this.userId = userId;
this.territoryId = territoryId;
return super.getAll();
}
}
why doesn't the super.getAll() function load the data into the store?

I havent' been able to figure out why the super.getAll() doesn't work but I found a work around. Instead of passing the userId and territoryId to the custom function, I'm just pulling them from the store in the dataservice. Not what I wanted to do but it does work.
#Injectable()
export class OfficeCustomService extends DefaultDataService<Office> {
userData: User;
constructor(
http: HttpClient,
store: Store ) {
const url = new DefaultHttpUrlGenerator(new DefaultPluralizer([appPluralNames]));
url.registerHttpResourceUrls({Label: {entityResourceUrl: 'office', collectionResourceUrl: 'offices'}});
super('Office', http, url, defaultDataServiceConfig);
store.select(fromState.getUser).subscribe((user: User) => {
this.userData = user;
});
}
protected execute(method: HttpMethods, url: string, data?: any, options?: any): Observable<any> {
const regex = /offices/gi;
if (method === 'GET' && url.search(regex) > 0) {
url = url + 'userId/' + this.userData.id + '/territoryId/' + this.userData.territoryId;
}
return super.execute(method, url, data, options);
}
}

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

Observable to Array *ngFor saying undefined

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.

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'.

how to save and retrieve data from Angular2 local storage?

I was able store an auth token in the browser's localstorage, but I wasn't able retrieve it as string. I can't find any examples on how to do that.
You could write yourself a service to encapsulate the serializing and deserializing:
export class StorageService {
write(key: string, value: any) {
if (value) {
value = JSON.stringify(value);
}
localStorage.setItem(key, value);
}
read<T>(key: string): T {
let value: string = localStorage.getItem(key);
if (value && value != "undefined" && value != "null") {
return <T>JSON.parse(value);
}
return null;
}
}
Add it to your providers either in the bootstrap call:
bootstrap(App, [ ..., StorageService]);
or in your root component:
#Component({
// ...
providers: [ ..., StorageService]
})
export class App {
// ...
}
Then in the component where you need it, just inject it in the constructor:
export class SomeComponent {
private someToken: string;
constructor(private storageService: StorageService) {
someToken = this.storageService.read<string>('my-token');
}
// ...
}

Resources