Can not use Async Pipe RXJS - ANGULAR - javascript

I can not display my observables directly when I want to use the Async Pipe.
I can do without the pipe async, and it works. My comment code works well, but I would really like to use the Async Pipe for Angular and unsubscribe alone.
user.service
public newsfeed: BehaviorSubject<MessageUser> = new BehaviorSubject(null);
reloadNewsFeed(): Observable<MessageUser> {
return this.http.get<MessageUser>('api/user/newsfeed').pipe(
tap((x: MessageUser)=>{
this.newsfeed.next(x)
}), switchMap(()=>{
return this.newsfeed
}))
};
newsfeed.component.ts
//public lesMessage2;
public lesMessages:Observable<MessageUser>
constructor(
private userService: UserService,
) { }
ngOnInit() {
// It does not work
this.lesMessages = this.userService.reloadNewsFeed()
// OK that works
// this.subMessage = this.userService.reloadNewsFeed().subscribe((user: MessageUser)=>{
// console.log(user);
// this.lesMessage2 = user;
// }, (err)=>{
// console.log(err);
// })
}
newsfeed.component.html
<span *ngIf="lesMessage | async; let user">
<p>Iterer sur mon Subjects : <strong> {{ user.message }} </strong> </p>
</span>

The thing is that in TS file you have property named as this.lesMessages but in template you try to subscribe to "lesMessage" (should be lesMessages). Try to change your current string:
... *ngIf="lesMessages | async" ...

Related

Reset BehaviorSubject from another component

I've made a facade service to avoid multiple calls to the API.
It call retrieveMyUser each time the request is made.
If the request has never been made it store the value usingBehaviorSubject. If it has already been made it take the value stored.
I want to clear the data of my BehaviorSubject in auth.service.ts when a user logout. My try to do that is that I call a clearUser() method from facade-service.ts.
facade-service.ts :
...
export class UserServiceFacade extends UserService {
public readonly user = new BehaviorSubject(null);
retrieveMyUser() {
console.log(this.user.value);
return this.user.pipe(
startWith(this.user.value),
switchMap(user => (user ? of(user) : this.getUserFromServer())),
take(1)
)
}
private getUserFromServer() {
return super.retrieveMyUser(null, environment.liveMode).pipe(tap(user => this.storeUser(user)));
}
public clearUser() {
console.log("cleared");
this.storeUser(null)
console.log(this.user.value); // Output null
}
private storeUser(user: V2UserOutput) {
this.user.next(user);
}
}
auth.service.ts :
...
logout() {
var cognitoUser = this.userPool.getCurrentUser();
if (cognitoUser) {
this.userServiceFacade.clearUser()
cognitoUser.signOut();
}
this._router.navigate(['/login']);
}
...
The method clearUser() in auth.service.ts is well called and print cleared correctly.
But when I login, after I logout the console.log(this.user.value); in retrieveMyUser still output the previous value. It was null when at logout though.
So, how do I clear BehaviorSubject cache or to reset BehaviorSubject from another service ?
There are many things in your code which sound weird at reading:
You shouldn't access immediately to the value of a BehaviorSubject without using the asObservable() as recommended by ESLint here.
Instead, you could use another variable which will keep the latest value for the user.
You should use the power of TypeScript in order to help you with types definition and quality code (in my opinion).
The use of a BehaviorSubject with a startWith operator can be simplified using a ReplaySubject with a bufferSize of 1 (replay the latest change)
Your subject acting like a source storage should be private in order to limit the accessibility from outside.
I took your code and make some updates from what I said above:
export class UserServiceFacade extends UserService {
private _user: V2UserOutput;
private readonly _userSource = new ReplaySubject<V2UserOutput>(1);
public get user(): V2UserOutput { // Use for accessing to the user data without the use of an observable.
return this._user;
}
constructor() {
super();
this.clearUser(); // It will make your ReplaySubject as "alive".
}
public retrieveMyUser$(): Observable<V2UserOutput> {
return this._userSource.asObservable()
.pipe(
switchMap(user => (user ? of(user) : this.getUserFromServer())),
take(1)
);
}
private getUserFromServer(): Observable<V2UserOutput> {
return super.retrieveMyUser(null, 'environment.liveMode')
.pipe(
tap(user => this.storeUser(user))
);
}
public clearUser() {
console.log('cleared');
this.storeUser(null);
}
private storeUser(user: V2UserOutput) {
this._user = user;
this._userSource.next(user);
}
}
Cheers!

Trigger observables chain only if there was a button click

I would like to execute toggleButtonOnClick$() function from the service, only when I click on a button. This function will save current state, service will later receive this change and update usersObs$.
Whenever the above mentioned happens, the toggleButtonOnClick$ gets executed a few more times, even if I didn't click on the button again.
How can I prevent this and make this function only execute when I do .next() on the clickSubject and not when userObs$ changes?
I will write an example of the whole situation
ButtonComponent
private userClick = new Subject<null>();
private obs1$: Observable<string>();
private obs2$: Observable<string>();
private obs3$: Observable<boolean>();
ngOnInit(): void {
this.obs2$ = this.obs1$.pipe(
switchMap((value) => this.someService.getSomePropBasedOnValue$(value))
);
this.obs3$ = this.obs2$.pipe(
switchMap((value) => this.someService.checksAndReturnsBoolean$(value))
this.subscriptions.add(
this.someService.toggleButtonOnClick$(this.obs2$, this.userClick).subscribe()
)
}
ngOnDestroy(): void {
this.subscriptions.unsuscribe();
}
onClick(): void {
// emit a value when click on button to start observable chain
this.userClick.next();
}
HTML
<div
[attr.tooltip]="(obs3$ | async) ?
('text_translation_1' | transloco)
: ('text_translation_2' | transloco)"
>
<span
*ngIf="(obs3$ | async) && !isHovering"
>
Something here
</span>
<span
*ngIf="(obs3$ | async) && isHovering"
>
Something here
</span>
<span
*ngIf="!(obs3$ | async)"
>
Something here
</span>
</div>
SomeService
public checksAndReturnsBoolean$(id): Observable<boolean> {
return this.userObs$.pipe(
map((users) => { users.some(((user) => user.id === id)) }
);
}
public getSomePropBasedOnValue$(id): Observable<SomeObject | null> {
return this.userObs$.pipe(
map((users) => { users.find(((user) => user.id === id)) ?? null }
);
}
public toggleButtonOnClick$(obs2$, userClick): Observable<void> {
return userClick.pipe(
switchMap(() => obs3$),
switchMap((id) => combineLatest([this.getSomeDataById$(id), of(id)]))
).pipe(
map(([data, id]) => {
// some code block that gets executed everytime an observable emits new value
})
);
Once everything finishes, I try to store the users decision after a click is made. So the userObs$ gets updated, once that happens, the block within toggleButtonOnClick$ is executed again, and not once, but 2 sometimes 3 or 4.
Btw, in the component, the obs2$ im using it on the DOM with Async pipe to show/hide stuff. Maybe that is also triggering the calls after the service observable changes.
I've tried several things already without luck.
Any tip or help or guiding would be appreciated.
Thanks.
What I was needing was to use shareReplay(1) and take(1) on different functions on my service to make it work as expected without repeating unnecessary calls.
It would end up looking like this:
public checksAndReturnsBoolean$(id): Observable<boolean> {
return this.userObs$.pipe(
map((users) => { users.some((user) => user.id === id) }),
take(1)
);
}
public getSomePropBasedOnValue$(id): Observable<SomeObject | null> {
return this.userObs$.pipe(
map((users) => { users.find((user) => user.id === id) ?? null }),
shareReplay(1)
);
}

angular 9 execute subscribe in code behind synchronously

I need to run a method with 2 parameters, each parameter is gotten through some form of subscribe function. the first is the collection which is gotten through the url from angular's page routing. The second is the dokument, this is the firebase's firestore document.
export class FirebaseDocument implements OnInit {
collection: string;
dokument: any;
//== CONSTRUCTORS
constructor(
private route: ActivatedRoute,
private _db: AngularFirestore
) {}
//== Initialize
ngOnInit() {
console.log("__loading page component");
this.route.params.subscribe(params => {
this.collection = params["collection"];
});
console.log(this.collection);//collection populated correctly
//load the document from AngularFirestore
console.log("loading the document from firebase");
let itemsCollection = this._db.collection(url).valueChanges();
//subscribe to get the dok of the first document in the collection
itemsCollection.subscribe(docArr => {
this.dokument = docArr[0];
console.log(this.dokument);//dokument is populated
});
console.log(this.dokument);//dokument is undefined
this.doMultiParameterMethod(this.collection, this.dokument);
}
}
this.collection populates perfectly fine;
this.dokument is only populated inside the subscribe method
I need this to be populated by the time the next line is run. the console.log(this.dokument);
I have been dumbstruck by this because essentially the same code is used by the 2 subscribe methods but they don't behave the same way.
Sometimes a subscribe can be synchronous. This happens when the Observable is a ReplaySubject a BehaviorSubject or an Observable which has a shareReplay() pipe. (probably other options as well.
This will make the observable immediately fire on subscription. However, you should never count on this behavior, and always continue within your subscribe.. Or use pipes like mergeMap and create other observables which you can access in your template using the async pipe.
In your case. The this.route.params is obviously a 'replaying' Observable from which you get the latest value after subscribing. Otherwise you would have to wait for the params to change again until you get a value.
Your Database call cannot return an immediate response, because it's essentially a network request.
In your example code, you can update it to this, and use the async pipe in your template
export class FirebaseDocument implements OnInit {
readonly collection$: Observable<string> = this.route.params.pipe(
map((params) => params.collection)
);
readonly doc$: Observable<any[]> = this.db.collection(this.url).valueChanges().pipe(
shareReplay({ refCount: true, bufferSize: 1 })
);
constructor(private route: ActivatedRoute, private db: AngularFirestore) {}
ngOnInit() {
// don't forget to unsubscribe
combineLatest([
this.collection$,
this.doc$
]).subscribe((collection, document) => {
this.doMultiParameterMethod(collection, document);
});
}
}
Maybe you should make the Observable a Promise, in your case would be the following :
export class FirebaseDocument implements OnInit {
collection: string;
dokument: any;
//== CONSTRUCTORS
constructor(
private route: ActivatedRoute,
private _db: AngularFirestore
) {}
//== Initialize
ngOnInit() {
console.log("__loading page component");
this.route.params.subscribe(params => {
this.collection = params["collection"];
});
console.log(this.collection); //collection populated correctly
this.getDokument().then(docArr => {
this.dokument = docArr[0];
this.doMultiParameterMethod(this.collection, this.dokument);
});
}
getDokument(): Promise<any> {
let itemsCollection = this._db.collection(url).valueChanges();
return new Promise((resolve, reject) => {
itemsCollection.subscribe((response: any) => {
resolve(response);
}, reject);
});
}
}

Cannot put subscribed observable into collection as a string

I am kind of new to typescript and javascript, and I am having a real hard time figuring out how collections and file io works. I am trying to get data from a json file, which that I am successful in although when I put it into a collection the collection does not have the data.
Here is the code:
In my service class:
private configuration = "../../assets/Configurations/testConfiguration.json";
constructor(private http: HttpClient) {}
getBlogs(blog: string): Observable<IBlogPost[]> {
return this.http.get<IBlogPost[]>(blog);
}
getConfigurations() {
var configurationsData = [];
this.http.get(this.configuration).subscribe(data => {
configurationsData.push(data);
console.log(data);
// This will work and will print all the paths
});
//This will not work and will not print them, like if the collection is empty
configurationsData.forEach(x => console.log(x));
return configurationsData;
}
Where I get my service class injected:
blogs: IBlogPost[] = [];
private blogsPaths: string[] = [];
errorMessage = "";
constructor(private appUtilityService: AppUtilityServices) {}
ngOnInit() {
//This will not work, blogsPaths will not received the full collection as it should
this.blogsPaths = this.appUtilityService.getConfigurations();
this.blogsPaths.forEach(blogPath =>
this.appUtilityService.getBlogs(blogPath).subscribe(
b =>
b.forEach(blog => {
this.blogs.push(blog);
}),
error => (this.errorMessage = <any>error)
)
);
}
testConfiguration.json:
[
"../../assets/BlogPosts/testBlog1.json",
"../../assets/BlogPosts/testBlog2.json"
]
Bonus if you include a good tutorial on how collections work in javascript and how to return them properly
Turns out to be this, yes really...
Here I made it nice:
OnInit:
this.appUtilityService.getConfigurations().subscribe(
b =>
b.forEach(x => {
this.appUtilityService.getBlogs(x).subscribe(
b =>
b.forEach(blog => {
this.blogs.push(blog);
}),
error => (this.errorMessage = <any>error)
);
}),
error => (this.errorMessage = <any>error)
);
AppUtilityService:
getBlogs(blog: string): Observable<IBlogPost[]> {
return this.http.get<IBlogPost[]>(blog);
}
getConfigurations(): Observable<string[]> {
return this.http.get<string[]>(this.configuration);
}
Make it really nice:
ngOnInit() {
this.initializeBlog();
}
private initializeBlog(): void {
this.appUtilityService.getConfigurations().subscribe(
b =>
b.forEach(x => {
this.appUtilityService.getBlogs(x).subscribe(
b =>
b.forEach(blog => {
this.blogs.push(blog);
}),
error => (this.errorMessage = <any>error)
);
}),
error => (this.errorMessage = <any>error)
);
}
Inside getConfigurations, you're invoking an asynchronous http request. So it's normal, that configurationsData is empty outside the subscribe method.
Here, I'm using the powerful of RxJS to simplify the code and avoid to use
Array.push and nested subscribe().
1) Basic async service method to get data
So getConfigurations should return an Observable:
getConfigurations(): Observable<string[]> {
return this.http.get<string[]>(this.configuration);
}
And also getBlogs should return an Observable:
getBlogs(blogsPath: string): Observable<BlogPost[]> {
return this.http.get<BlogPost[]>(blogsPath);
}
Here, to respect Angular best practice, I remove I at the beginning of each interface, so BlogPost.
2) Here is the magic of RxJS
Then, you have another method inside your service to retrieve an Observable of blog posts, which should look like this:
getBlogPosts(): Observable<BlogPost[]> {
return this.getConfigurations().pipe(
mergeAll(),
concatMap(blogsPath => this.getBlogs(blogsPath)),
concatAll(),
toArray()
)
}
Which means, retrieve all collection of blog paths, for each array of paths, for each path, get blog posts, and then return an unique array of all posts.
Finally, inside your component, you call getBlogPosts with a subscription:
ngOnInit() {
this.service.getBlogPosts().subscribe(blogs => {
...do something with your blogs array.
});
}
or even better with async pipe inside your template :
.ts file:
blogs$: Observable<BlogPost[]>;
...
this.blogs$ = this.appService.getBlogPosts();
.html file:
<ul *ngIf="blogs$ | async as blogs">
<li *ngFor="let blog of blogs">
#{{ blog.id }} - {{ blog.title }}
</li>
</ul>
Check my full demonstration of this RxJS alternative on Stackbliz.
Recommended read on medium from Tomas Trajan

Angular6 polling not returning data

I have an Angular application where I am trying to check an external data service for changes every few seconds, and update the view.
I've tried to implement Polling from rxjs but I'm not able to access the object, conversely it seems the polling function isn't working but assume this is because the returned object is inaccessible.
app.component.ts
export class AppComponent {
polledItems: Observable<Item>;
items : Item[] = [];
title = 'site';
landing = true;
tap = false;
url:string;
Math: any;
getScreen(randomCode) {
const items$: Observable<any> = this.dataService.get_screen(randomCode)
.pipe(tap( () => {
this.landing = false;
this.Math = Math
}
));
const polledItems$ = timer(0, 1000)
.pipe(( () => items$))
console.log(this.polledItems);
console.log(items$);
}
excerpt from app.component.html
<h3 class="beer-name text-white">{{(polledItems$ | async).item_name}}</h3>
excerpt from data.service.ts
get_screen(randomCode) {
return this.httpClient.get(this.apiUrl + '/tap/' + randomCode)
}
assuming that you want an array of items you could go with something like this.
// dont subscribe here but use the
// observable directly or with async pipe
private readonly items$: Observable<Item[]> = this.dataService.get_screen(randomCode)
// do your side effects in rxjs tap()
// better move this to your polledItems$
// observable after the switchMap
.pipe(
tap( () => { return {this.landing = false; this.Math = Math}; })
);
// get new items periodicly
public readonly polledItems$ = timer(0, 1000)
.pipe(
concatMap( () => items$),
tap( items => console.log(items))
)
the template:
// pipe your observable through async and THEN access the member
<ng-container *ngFor="let polledItem of (polledItems$ | async)>
<h3 class="item-name text-white">{{polledItem.item_name}}</h3>
</ng-container>
take a look at: https://blog.strongbrew.io/rxjs-polling/
if you are not awaiting an array but a single than you dont need the ngFor but access your item_name like:
<h3 class="item-name text-white">{{(polledItems$ | async).item_name}}</h3>

Categories