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

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

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: data retrieved from API only appears when app is first time launched

I just have two components, and the app is connected to firebase (firestore). The data called in ngOnInit disappears when I navigate and only appears again if I insert a Post or a User (forms).Why? Can someone help me out?
The routes:
const routes: Routes = [
{ path: "", component: HomeComponent, pathMatch: "full" },
{
path: "admin",
component: AdminComponent
},
{
path: "**",
redirectTo: "/",
pathMatch: "full"
}
];
The Service
itemsCollection: AngularFirestoreCollection<Post>;
items: Observable<Post[]>;
itemDoc: AngularFirestoreDocument<Post>;
constructor(public afs: AngularFirestore) {
this.itemsCollection = this.afs.collection('posts', ref =>
ref.orderBy('date', 'desc')
);
this.items = this.itemsCollection.snapshotChanges().map(changes => {
return changes.map(a => {
const data = a.payload.doc.data() as Post;
data.id = a.payload.doc.id;
return data;
});
});
}
getItems() {
return this.items;
}
Its Component and the subscription
private postService: PostsService,
) {}
ngOnInit() {
this.postService.getItems().subscribe(data => {
this.postInput = data;
});
}
Create posts property in your service.
Then subscribe to your observable in your service, set posts to what u get from firebase.
Also dont write your code inside constructor but wrap in in a function and use it in OnInit in apropriate component. And lastly create a Subject send with it the current posts and subscribe to them in onInit inside desired components
posts = Post[]
postsChanged = new Subject<Post[]>()
getItems(){
this.itemsCollection.snapshotChanges().map(changes => {
return changes.map(a => {
return {
id: a.payload.doc.id,
...a.payload.doc.data()
}
}).subscribe(posts => {
this.posts = posts
this.postsChanged.next([...this.posts])
})
}
Based on Kase44 hints i managed to find a solution: wrapped the capture of the ID in a function, called it in the service constructor and in the ngOnInit():
The Service
export class UsersService {
usersCollection: AngularFirestoreCollection<Users>;
userDoc: AngularFirestoreDocument<Users>;
users: Observable<Users[]>;
user: Observable<Users>;
constructor(public afs: AngularFirestore) {
this.usersCollection = this.afs.collection('employees', ref =>
ref.orderBy('name', 'asc')
);
this.getUserID();
}
getUserID(){
this.users = this.usersCollection.snapshotChanges().map(changes => {
return changes.map(a => {
const data = a.payload.doc.data() as Users;
data.id = a.payload.doc.id;
return data;
});
});
}
And the component:
ngOnInit() {
this.userService.getUsers().subscribe(data => {
this.emp = data;
});
this.userService.getUserID();
}

Angular2 EventEmitter subscription does not work after navigate and come back

I emitted the data from MerchantComponent
and subscribed via EventEmitterService from MerchantPaymentChannelComponent, its OK when route directly opens this page. But you see there is other tabs which every tab has its own components. When I change the tab to different one, then come back to this MerchantPaymentChannelComponent its not subscribe again.
NOTE: I'm doing unsubscription on NgOnDestroy event
Here is codes;
MerchantListDetailService (SHARING SERVICE VIA EVENTEMITTER)
export class MerchantListDetailService {
#Output() emittedMerchant: EventEmitter<any> = new EventEmitter();
constructor() {}
emitMerchant(data) {
this.emittedMerchant.emit(data);
}
getEmittedValue() {
return this.emittedMerchant;
}
}
MerchantComponent (TRIGGERING EMIT FROM THIS COMPONENT)
private getMerchantDetail() {
let data = {
Id: this.merchantId,
}
this.merchantsService.getMerchants(data)
.then((res) => {
// console.log(res)
if (res.Success) {
this.merchant = res.Data[0];
this.merchantListDetailService.emitMerchant(res.Data[0]);
}
})
.catch((err) => { })
}
MerchantPaymentChannelComponent (SUBSCRIBING FROM THIS COMPONENT)
ngOnInit() {
this.merchantSubscribe = this.merchantListDetailService.getEmittedValue()
.subscribe(merchant => {
this.merchant = merchant;
this.getMerchantPaymentChannels();
})
}
ngOnDestroy() {
this.merchantSubscribe.unsubscribe();
}

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