fetch data async right after save and reload the component - javascript

I'm retrieving data from an API, right now there are only two endpoints, one for adding users and other one for get the users added. The users are added using a button and its textbox.
let [showAlert, setAlertState] = useState(false);
let [twitterAccount, setTwitterAcount] = useState("Twitter Account here");
let [dataInfo, setDataInfo] = useState([]);
const getCurrentSpiedUsers = async () => {
fetch("https://localhost:7021/GetCurrentSpiedUsers")
.then((res) => res.json())
.then((res) => {setDataInfo(res)});
}
useEffect(function () {
getCurrentSpiedUsers();
}, []);
This part works as expected, the first time I enter into the website it loads data from the API.
This one is the other method for adding users.
const addTwitterAccount = (account) => {
fetch(`https://localhost:7021/AddTwitterAccount?account=${account}`, {
method:'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
}
)
.then((res) => res.json())
.then((res) => { });
}
and this is the function inside of the onClick event button.
const activeAlert = () => {
addTwitterAccount(twitterAccount);
getCurrentSpiedUsers();
setAlertState(true);
setTimeout(() => {
setAlertState(false);
}, 5000);
};
but this is not working as expected, once I click the button I add the account correctly, but can't reload the component using the getCurrentSpiedUsers(); function. I noticed that when I click twice, I can get the last one but not the actual one, so I assume it's because the code it's executed faster than my function is retrieving the data from the API.
I tried using async/await for both methods, but the result is always the same. What can I do?
Updated with the server-side:
AddTwitterAcccount endpoint:
[ApiController]
[Route("[controller]")]
public class AddTwitterAccount : ControllerBase
{
private readonly ISpiedAccounts _spiedAccounts;
public AddTwitterAccount(ISpiedAccounts spiedAccounts)
{
_spiedAccounts = spiedAccounts;
}
[HttpPost(Name = "AddTwitterAccount")]
public void AddAccount([FromQuery] string account)
{
_spiedAccounts.AddTwitterAccount(account);
}
}
SpiedAccounts class:
public interface ISpiedAccounts
{
public void AddTwitterAccount(string accountName);
public List<TwitterAccount> GetTwitterAccounts();
}
public class SpiedAccounts : ISpiedAccounts
{
private List<TwitterAccount> accounts = new();
private ResponseMessage _responseMessage;
public void AddTwitterAccount(string accountName)
{
if (accounts.Any(account => account.ScreenName == accountName))
{
_responseMessage = ResponseMessage.UserExists;
return;
}
int maxUsersSpied = 5;
if (accounts.Count <= maxUsersSpied)
{
accounts.Add(new TwitterAccount
{
ScreenName = accountName
});
_responseMessage = ResponseMessage.Added;
return;
}
_responseMessage = ResponseMessage.LimitUsersExceeded;
}
public string GetResponseMessage()
{
return _responseMessage switch
{
ResponseMessage.Added
=> "The account was added correctly.",
ResponseMessage.LimitUsersExceeded
=> "Only can be spied 6 accounts at the time. Please, wait for one of them to be free.",
ResponseMessage.UserExists
=> "This account is currently being spied.",
_
=> string.Empty
};
}
public List<TwitterAccount> GetTwitterAccounts()
=> accounts;
}
public enum ResponseMessage
{
Added,
LimitUsersExceeded,
UserExists
}

There's a race condition between the WRITE (addTwitterAccount) and the READ (getCurrentSpiedUsers).
In other words, when you call addTwitterAccount() it will request the addition of a new twitter account, but nothing guarantees such operation has been completed (aka the account has been added) before getCurrentSpiedUsers() reads info from the datasource.
In fact, given you don't await for addTwitterAccount(), getCurrentSpiedUsers() will likely never bring the new account in its results.
What to do?
First, await for the result of the POST (addTwitterAccount()) before proceeding to the GET (addTwitterAccount()):
const activeAlert = async () => { // added async here
await addTwitterAccount(twitterAccount); // added await here
getCurrentSpiedUsers();
...
For that to work, make sure the addTwitterAccount() returns the Promise:
const addTwitterAccount = (account) => {
return fetch(`https://loca......ount?account=${account}`, { // added return
Second, edit the (server-side) endpoint at https://localhost:7021/AddTwitterAccount to guarantee** it only returns after the account has been completely added.
** There are other alternatives to this. You can make this endpoint be async, but you would need to use other tactics to know the user has been added. But all these other tactics would involve way more work, and you likely don't need the scalability they would provide you at the moment. Example tactics: WebSockets, SSE, long-polling. Making the endpoint block until the account is created, as I suggested above, is likely the more cost-effective solution at this point.

you can't use multiple setState in one event it will do only the first one,
my suggestion is to use only one state with an object that contains your data

Related

How to get data from 2 collection in firebase at a time?(similar to aggregate lookup in MongoDB) [duplicate]

I have a Cloud Firestore DB with the following structure:
users
[uid]
name: "Test User"
posts
[id]
content: "Just some test post."
timestamp: (Dec. 22, 2017)
uid: [uid]
There is more data present in the actual DB, the above just illustrates the collection/document/field structure.
I have a view in my web app where I'm displaying posts and would like to display the name of the user who posted. I'm using the below query to fetch the posts:
let loadedPosts = {};
posts = db.collection('posts')
.orderBy('timestamp', 'desc')
.limit(3);
posts.get()
.then((docSnaps) => {
const postDocs = docSnaps.docs;
for (let i in postDocs) {
loadedPosts[postDocs[i].id] = postDocs[i].data();
}
});
// Render loadedPosts later
What I want to do is query the user object by the uid stored in the post's uid field, and add the user's name field into the corresponding loadedPosts object. If I was only loading one post at a time this would be no problem, just wait for the query to come back with an object and in the .then() function make another query to the user document, and so on.
However because I'm getting multiple post documents at once, I'm having a hard time figuring out how to map the correct user to the correct post after calling .get() on each post's user/[uid] document due to the asynchronous way they return.
Can anyone think of an elegant solution to this issue?
It seems fairly simple to me:
let loadedPosts = {};
posts = db.collection('posts')
.orderBy('timestamp', 'desc')
.limit(3);
posts.get()
.then((docSnaps) => {
docSnaps.forEach((doc) => {
loadedPosts[doc.id] = doc.data();
db.collection('users').child(doc.data().uid).get().then((userDoc) => {
loadedPosts[doc.id].userName = userDoc.data().name;
});
})
});
If you want to prevent loading a user multiple times, you can cache the user data client side. In that case I'd recommend factoring the user-loading code into a helper function. But it'll be a variation of the above.
I would do 1 user doc call and the needed posts call.
let users = {} ;
let loadedPosts = {};
db.collection('users').get().then((results) => {
results.forEach((doc) => {
users[doc.id] = doc.data();
});
posts = db.collection('posts').orderBy('timestamp', 'desc').limit(3);
posts.get().then((docSnaps) => {
docSnaps.forEach((doc) => {
loadedPosts[doc.id] = doc.data();
loadedPosts[doc.id].userName = users[doc.data().uid].name;
});
});
After trying multiple solution I get it done with RXJS combineLatest, take operator. Using map function we can combine result.
Might not be an optimum solution but here its solve your problem.
combineLatest(
this.firestore.collection('Collection1').snapshotChanges(),
this.firestore.collection('Collection2').snapshotChanges(),
//In collection 2 we have document with reference id of collection 1
)
.pipe(
take(1),
).subscribe(
([dataFromCollection1, dataFromCollection2]) => {
this.dataofCollection1 = dataFromCollection1.map((data) => {
return {
id: data.payload.doc.id,
...data.payload.doc.data() as {},
}
as IdataFromCollection1;
});
this.dataofCollection2 = dataFromCollection2.map((data2) => {
return {
id: data2.payload.doc.id,
...data2.payload.doc.data() as {},
}
as IdataFromCollection2;
});
console.log(this.dataofCollection2, 'all feeess');
const mergeDataFromCollection =
this.dataofCollection1.map(itm => ({
payment: [this.dataofCollection2.find((item) => (item.RefId === itm.id))],
...itm
}))
console.log(mergeDataFromCollection, 'all data');
},
my solution as below.
Concept: You know user id you want to get information, in your posts list, you can request user document and save it as promise in your post item. after promise resolve then you get user information.
Note: i do not test below code, but it is simplify version of my code.
let posts: Observable<{}[]>; // you can display in HTML directly with | async tag
this.posts = this.listenPosts()
.map( posts => {
posts.forEach( post => {
post.promise = this.getUserDoc( post.uid )
.then( (doc: DocumentSnapshot) => {
post.userName = doc.data().name;
});
}); // end forEach
return posts;
});
// normally, i keep in provider
listenPosts(): Observable<any> {
let fsPath = 'posts';
return this.afDb.collection( fsPath ).valueChanges();
}
// to get the document according the user uid
getUserDoc( uid: string ): Promise<any> {
let fsPath = 'users/' + uid;
return this.afDb.doc( fsPath ).ref.get();
}
Note: afDb: AngularFirestore it is initialize in constructor (by angularFire lib)
If you want to join observables instead of promises, use combineLatest. Here is an example joining a user document to a post document:
getPosts(): Observable<Post[]> {
let data: any;
return this.afs.collection<Post>('posts').valueChanges().pipe(
switchMap((r: any[]) => {
data = r;
const docs = r.map(
(d: any) => this.afs.doc<any>(`users/${d.user}`).valueChanges()
);
return combineLatest(docs).pipe(
map((arr: any) => arr.reduce((acc: any, cur: any) => [acc].concat(cur)))
);
}),
map((d: any) => {
let i = 0;
return d.map(
(doc: any) => {
const t = { ...data[i], user: doc };
++i;
return t;
}
);
})
);
}
This example joins each document in a collection, but you could simplify this if you wanted to just join one single document to another.
This assumes your post document has a user variable with the userId of the document.
J

Cloud Firestore: Query two collection [duplicate]

I have a Cloud Firestore DB with the following structure:
users
[uid]
name: "Test User"
posts
[id]
content: "Just some test post."
timestamp: (Dec. 22, 2017)
uid: [uid]
There is more data present in the actual DB, the above just illustrates the collection/document/field structure.
I have a view in my web app where I'm displaying posts and would like to display the name of the user who posted. I'm using the below query to fetch the posts:
let loadedPosts = {};
posts = db.collection('posts')
.orderBy('timestamp', 'desc')
.limit(3);
posts.get()
.then((docSnaps) => {
const postDocs = docSnaps.docs;
for (let i in postDocs) {
loadedPosts[postDocs[i].id] = postDocs[i].data();
}
});
// Render loadedPosts later
What I want to do is query the user object by the uid stored in the post's uid field, and add the user's name field into the corresponding loadedPosts object. If I was only loading one post at a time this would be no problem, just wait for the query to come back with an object and in the .then() function make another query to the user document, and so on.
However because I'm getting multiple post documents at once, I'm having a hard time figuring out how to map the correct user to the correct post after calling .get() on each post's user/[uid] document due to the asynchronous way they return.
Can anyone think of an elegant solution to this issue?
It seems fairly simple to me:
let loadedPosts = {};
posts = db.collection('posts')
.orderBy('timestamp', 'desc')
.limit(3);
posts.get()
.then((docSnaps) => {
docSnaps.forEach((doc) => {
loadedPosts[doc.id] = doc.data();
db.collection('users').child(doc.data().uid).get().then((userDoc) => {
loadedPosts[doc.id].userName = userDoc.data().name;
});
})
});
If you want to prevent loading a user multiple times, you can cache the user data client side. In that case I'd recommend factoring the user-loading code into a helper function. But it'll be a variation of the above.
I would do 1 user doc call and the needed posts call.
let users = {} ;
let loadedPosts = {};
db.collection('users').get().then((results) => {
results.forEach((doc) => {
users[doc.id] = doc.data();
});
posts = db.collection('posts').orderBy('timestamp', 'desc').limit(3);
posts.get().then((docSnaps) => {
docSnaps.forEach((doc) => {
loadedPosts[doc.id] = doc.data();
loadedPosts[doc.id].userName = users[doc.data().uid].name;
});
});
After trying multiple solution I get it done with RXJS combineLatest, take operator. Using map function we can combine result.
Might not be an optimum solution but here its solve your problem.
combineLatest(
this.firestore.collection('Collection1').snapshotChanges(),
this.firestore.collection('Collection2').snapshotChanges(),
//In collection 2 we have document with reference id of collection 1
)
.pipe(
take(1),
).subscribe(
([dataFromCollection1, dataFromCollection2]) => {
this.dataofCollection1 = dataFromCollection1.map((data) => {
return {
id: data.payload.doc.id,
...data.payload.doc.data() as {},
}
as IdataFromCollection1;
});
this.dataofCollection2 = dataFromCollection2.map((data2) => {
return {
id: data2.payload.doc.id,
...data2.payload.doc.data() as {},
}
as IdataFromCollection2;
});
console.log(this.dataofCollection2, 'all feeess');
const mergeDataFromCollection =
this.dataofCollection1.map(itm => ({
payment: [this.dataofCollection2.find((item) => (item.RefId === itm.id))],
...itm
}))
console.log(mergeDataFromCollection, 'all data');
},
my solution as below.
Concept: You know user id you want to get information, in your posts list, you can request user document and save it as promise in your post item. after promise resolve then you get user information.
Note: i do not test below code, but it is simplify version of my code.
let posts: Observable<{}[]>; // you can display in HTML directly with | async tag
this.posts = this.listenPosts()
.map( posts => {
posts.forEach( post => {
post.promise = this.getUserDoc( post.uid )
.then( (doc: DocumentSnapshot) => {
post.userName = doc.data().name;
});
}); // end forEach
return posts;
});
// normally, i keep in provider
listenPosts(): Observable<any> {
let fsPath = 'posts';
return this.afDb.collection( fsPath ).valueChanges();
}
// to get the document according the user uid
getUserDoc( uid: string ): Promise<any> {
let fsPath = 'users/' + uid;
return this.afDb.doc( fsPath ).ref.get();
}
Note: afDb: AngularFirestore it is initialize in constructor (by angularFire lib)
If you want to join observables instead of promises, use combineLatest. Here is an example joining a user document to a post document:
getPosts(): Observable<Post[]> {
let data: any;
return this.afs.collection<Post>('posts').valueChanges().pipe(
switchMap((r: any[]) => {
data = r;
const docs = r.map(
(d: any) => this.afs.doc<any>(`users/${d.user}`).valueChanges()
);
return combineLatest(docs).pipe(
map((arr: any) => arr.reduce((acc: any, cur: any) => [acc].concat(cur)))
);
}),
map((d: any) => {
let i = 0;
return d.map(
(doc: any) => {
const t = { ...data[i], user: doc };
++i;
return t;
}
);
})
);
}
This example joins each document in a collection, but you could simplify this if you wanted to just join one single document to another.
This assumes your post document has a user variable with the userId of the document.
J

javascript callback is called twice in an impossible invocation

I built a TS, MongoDB Client wrapper. for some reason when I call the function that gets the connection, its callback is called twice.
There are 2 calls in total to the get() function, 1 before the export as you can see and another from a mocha test.
I am pretty new to TS and JS in general, but this seems a bit off.
import {Db, MongoClient} from "mongodb";
import {MongoConfig} from '../config/config'
class DbClient {
private cachedDb : Db = null;
private async connectToDatabase() {
console.log('=> connect to database');
let connectionString : string = "mongodb://" + MongoConfig.host + ":" + MongoConfig.port;
return MongoClient.connect(connectionString)
.then(db => {
console.log('=> connected to database');
this.cachedDb = db.db(MongoConfig.database);
return this.cachedDb;
});
}
public async get() {
if (this.cachedDb) {
console.log('=> using cached database instance');
return Promise.resolve(this.cachedDb);
}else{
return this.connectToDatabase();
}
}
}
let client = new DbClient();
client.get();
export = client;
where the console output is:
=> connect to database
=> connected to database
=> connected to database
Any particular reason this is misbehaving?
There are 2 calls in total to the get() function, 1 before the export as you can see and another from a mocha test.
I suspect the output has an additional => connect to database. As I said in the comments: There's a "race condition" where get() could be called multiple times before this.cachedDb is set which would lead to multiple connections/instances of Db being created.
For example:
const a = client.get();
const b = client.get();
// then
a.then(resultA => {
b.then(resultB => {
console.log(resultA !== resultB); // true
});
});
Solution
The problem can be fixed by storing the promise as the cached value (also, no need to have the async keyword on the methods as Randy pointed out, as there's no values being awaited in any of the methods so you can just return the promises):
import {Db, MongoClient} from "mongodb";
import {MongoConfig} from '../config/config'
class DbClient {
private cachedGet: Promise<Db> | undefined;
private connectToDatabase() {
console.log('=> connect to database');
const connectionString = `mongodb://${MongoConfig.host}:${MongoConfig.port}`;
return MongoClient.connect(connectionString);
}
get() {
if (!this.cachedGet) {
this.cachedGet = this.connectToDatabase();
// clear the cached promise on failure so that if a caller
// calls this again, it will try to reconnect
this.cachedGet.catch(() => {
this.cachedGet = undefined;
});
}
return this.cachedGet;
}
}
let client = new DbClient();
client.get();
export = client;
Note: I'm not sure about the best way of using MongoDB (I've never used it), but I suspect connections should not be so long lived as to be cached like this (or should probably only be cached for a short time and then disconnected). You'll need to investigate that though.

How to join multiple documents in a Cloud Firestore query?

I have a Cloud Firestore DB with the following structure:
users
[uid]
name: "Test User"
posts
[id]
content: "Just some test post."
timestamp: (Dec. 22, 2017)
uid: [uid]
There is more data present in the actual DB, the above just illustrates the collection/document/field structure.
I have a view in my web app where I'm displaying posts and would like to display the name of the user who posted. I'm using the below query to fetch the posts:
let loadedPosts = {};
posts = db.collection('posts')
.orderBy('timestamp', 'desc')
.limit(3);
posts.get()
.then((docSnaps) => {
const postDocs = docSnaps.docs;
for (let i in postDocs) {
loadedPosts[postDocs[i].id] = postDocs[i].data();
}
});
// Render loadedPosts later
What I want to do is query the user object by the uid stored in the post's uid field, and add the user's name field into the corresponding loadedPosts object. If I was only loading one post at a time this would be no problem, just wait for the query to come back with an object and in the .then() function make another query to the user document, and so on.
However because I'm getting multiple post documents at once, I'm having a hard time figuring out how to map the correct user to the correct post after calling .get() on each post's user/[uid] document due to the asynchronous way they return.
Can anyone think of an elegant solution to this issue?
It seems fairly simple to me:
let loadedPosts = {};
posts = db.collection('posts')
.orderBy('timestamp', 'desc')
.limit(3);
posts.get()
.then((docSnaps) => {
docSnaps.forEach((doc) => {
loadedPosts[doc.id] = doc.data();
db.collection('users').child(doc.data().uid).get().then((userDoc) => {
loadedPosts[doc.id].userName = userDoc.data().name;
});
})
});
If you want to prevent loading a user multiple times, you can cache the user data client side. In that case I'd recommend factoring the user-loading code into a helper function. But it'll be a variation of the above.
I would do 1 user doc call and the needed posts call.
let users = {} ;
let loadedPosts = {};
db.collection('users').get().then((results) => {
results.forEach((doc) => {
users[doc.id] = doc.data();
});
posts = db.collection('posts').orderBy('timestamp', 'desc').limit(3);
posts.get().then((docSnaps) => {
docSnaps.forEach((doc) => {
loadedPosts[doc.id] = doc.data();
loadedPosts[doc.id].userName = users[doc.data().uid].name;
});
});
After trying multiple solution I get it done with RXJS combineLatest, take operator. Using map function we can combine result.
Might not be an optimum solution but here its solve your problem.
combineLatest(
this.firestore.collection('Collection1').snapshotChanges(),
this.firestore.collection('Collection2').snapshotChanges(),
//In collection 2 we have document with reference id of collection 1
)
.pipe(
take(1),
).subscribe(
([dataFromCollection1, dataFromCollection2]) => {
this.dataofCollection1 = dataFromCollection1.map((data) => {
return {
id: data.payload.doc.id,
...data.payload.doc.data() as {},
}
as IdataFromCollection1;
});
this.dataofCollection2 = dataFromCollection2.map((data2) => {
return {
id: data2.payload.doc.id,
...data2.payload.doc.data() as {},
}
as IdataFromCollection2;
});
console.log(this.dataofCollection2, 'all feeess');
const mergeDataFromCollection =
this.dataofCollection1.map(itm => ({
payment: [this.dataofCollection2.find((item) => (item.RefId === itm.id))],
...itm
}))
console.log(mergeDataFromCollection, 'all data');
},
my solution as below.
Concept: You know user id you want to get information, in your posts list, you can request user document and save it as promise in your post item. after promise resolve then you get user information.
Note: i do not test below code, but it is simplify version of my code.
let posts: Observable<{}[]>; // you can display in HTML directly with | async tag
this.posts = this.listenPosts()
.map( posts => {
posts.forEach( post => {
post.promise = this.getUserDoc( post.uid )
.then( (doc: DocumentSnapshot) => {
post.userName = doc.data().name;
});
}); // end forEach
return posts;
});
// normally, i keep in provider
listenPosts(): Observable<any> {
let fsPath = 'posts';
return this.afDb.collection( fsPath ).valueChanges();
}
// to get the document according the user uid
getUserDoc( uid: string ): Promise<any> {
let fsPath = 'users/' + uid;
return this.afDb.doc( fsPath ).ref.get();
}
Note: afDb: AngularFirestore it is initialize in constructor (by angularFire lib)
If you want to join observables instead of promises, use combineLatest. Here is an example joining a user document to a post document:
getPosts(): Observable<Post[]> {
let data: any;
return this.afs.collection<Post>('posts').valueChanges().pipe(
switchMap((r: any[]) => {
data = r;
const docs = r.map(
(d: any) => this.afs.doc<any>(`users/${d.user}`).valueChanges()
);
return combineLatest(docs).pipe(
map((arr: any) => arr.reduce((acc: any, cur: any) => [acc].concat(cur)))
);
}),
map((d: any) => {
let i = 0;
return d.map(
(doc: any) => {
const t = { ...data[i], user: doc };
++i;
return t;
}
);
})
);
}
This example joins each document in a collection, but you could simplify this if you wanted to just join one single document to another.
This assumes your post document has a user variable with the userId of the document.
J

Angular Transfer State not preventing repeat http calls

I have the http request being made a service which is injected onto my component and subscribed to from there. Since I introduced server side rendering with angular universal to my application, the results on the page are repeated at least twice.
I have method which is called on click, which performs the http request to facebook's api
getAlbum(albumId: number) {
this.albumPhotos = this.state.get(ALBUM_PHOTOS_KEY, null as any);
if (!this.albumPhotos) {
this.facebookService.getBachadiffAlbumPhotos(albumId).subscribe(res => {
this.bachataPicsArray = res;
this.state.set(ALBUM_PHOTOS_KEY, res as any);
});
}
}
I declared the const variable below the imports
const ALBUM_PHOTOS_KEY = makeStateKey('albumPhotos');
And I also declared the property
albumNames: any;
I am assuming I have done all of the imports right I have the code on github in the gallery component.
You are on the right pass, you just need to handle your service differently if you are on the server or the browser side to perform your queries only once and not twice.
Pseudo logic:
If server -> Do http request -> Set value in transfer-state
If browser -> Get value from transfer-state
To do so, you could for example enhance your Service like following:
#Injectable()
export class FacebookEventsService {
const ALBUM_PHOTOS_KEY: StateKey<number>;
constructor(#Inject(PLATFORM_ID) private platformId: Object, private http: HttpClient) {
this.ALBUM_PHOTOS_KEY = makeStateKey('albumPhotos');
}
getBachaDiffFacebookEvents(): Observable<CalendarEvent[]> {
// Here we check if server or browser side
if (isPlatformServer(this.platformId)) {
return this.getServerBachaDiffFacebookEvents();
} else {
return this.getBrowserBachaDiffFacebookEvents();
}
}
getServerBachaDiffFacebookEvents(): Observable<CalendarEvent[]> {
return this.http.get(this.facebookEventsUrl)
.map(res => {
// Here save also result in transfer-state
this.transferState.set(ALBUM_PHOTOS_KEY, calendarEvents);
});
}
getBrowserBachaDiffFacebookEvents(): Observable<CalendarEvent[]> {
return new Observable(observer => {
observer.next(this.transferState.get(ALBUM_PHOTOS_KEY, null));
});
}
}
UPDATE
To use this logic you would also need:
TransferHttpCacheModule (to be initialized in app.module.ts).
TransferHttpCacheModule installs a Http interceptor that avoids
duplicate HttpClient requests on the client, for requests that were
already made when the application was rendered on the server side.
https://github.com/angular/universal/tree/master/modules/common
ServerTransferStateModule on the server side and BrowserTransferStateModule on the client side to use TransferState
https://angular.io/api/platform-browser/TransferState
P.S.: Note that if you do so and enhance your server, of course you would not need anymore to set the value in transfer-state in your getAlbum() method you displayed above
UPDATE 2
If you want to handle the server and browser side as you did in your gallery.component.ts, you could do something like the following:
getAlbum(albumId: number) {
if (isPlatformServer(this.platformId)) {
if (!this.albumPhotos) {
this.facebookService.getBachadiffAlbumPhotos(albumId).subscribe(res => {
this.bachataPicsArray = res;
this.state.set(ALBUM_PHOTOS_KEY, null);
});
}
} else {
this.albumPhotos = this.state.get(ALBUM_PHOTOS_KEY,null);
}
}
UPDATE 3
The thing is, your action getAlbum is never called on the server side. This action is only used on the browser side, once the page is rendered, when the user click on a specific action. Therefore, using transfer-state in that specific case isn't correct/needed.
Furthermore not sure that the Observable in your service was correctly subscribed.
Here what to change to make it running:
gallery.component.ts
getAlbum(albumId: number) {
this.facebookService.getBachadiffAlbumPhotos(albumId).subscribe(res => {
this.albumPhotos = res;
});
}
facebook-events.service.ts
getBachadiffAlbumPhotos(albumId: number): Observable<Object> {
this.albumId = albumId;
this.facebookAlbumPhotosUrl = `https://graph.facebook.com/v2.11/${this.albumId}/photos?limit=20&fields=images,id,link,height,width&access_token=${this.accessToken}`;
return Observable.fromPromise(this.getPromiseBachaDiffAlbumPhotos(albumId));
}
private getPromiseBachaDiffAlbumPhotos(albumId: number): Promise<{}> {
return new Promise((resolve, reject) => {
this.facebookAlbumPhotosUrl = `https://graph.facebook.com/v2.11/${this.albumId}/photos?limit=20&fields=images,id,link,height,width&access_token=${this.accessToken}`;
let facebookPhotos: FacebookPhoto[] = new Array();
let facebookPhoto: FacebookPhoto;
const params: HttpParams = new HttpParams();
this.http.get(this.facebookAlbumPhotosUrl, {params: params})
.subscribe(res => {
let facebookPhotoData = res['data'];
for (let photo of facebookPhotoData) {
facebookPhotos.push(
facebookPhoto = {
id: photo.id,
image: photo.images[3].source,
link: photo.link,
height: photo.height,
width: photo.width
});
}
resolve(facebookPhotos);
}, (error) => {
reject(error);
});
});
}
UPDATE 4
ngOnInit is executed on the server side, this means that my very first answer here has to be use in this case.
Furthermore, also note that on the server side you doesn't have access to the window, therefore calling $
With gallery.component.ts you could do something like this to run only the http queries once but this won't solve all your problems, I think it will still need further improvements.
ngOnInit() {
if (isPlatformServer(this.platformId)) {
this.facebookService.getBachadiffFacebookVideos().subscribe(res => {
this.bachataVidsArray = res;
this.state.set(VIDEOS_KEY, res as any);
});
this.facebookService.getBachadiffFacebookLastClassPictures().subscribe(res => {
this.bachataPicsArray = res;
this.state.set(LAST_CLASS_PICTURES_KEY, res as any);
});
this.facebookService.getBachadiffAlbumNames().subscribe(res => {
this.bachataAlbumHeaderNames = res;
this.state.set(ALBUM_NAMES_KEY, res as any);
});
} else {
$('ul.tabs').tabs();
this.bachataVidsArray = this.state.get(VIDEOS_KEY, null as any);
this.bachataPicsArray = this.state.get(LAST_CLASS_PICTURES_KEY, null as any);
this.bachataAlbumHeaderNames = this.state.get(ALBUM_NAMES_KEY, null as any);
}
}

Categories

Resources