How to catch errors with Ngxs Websocket plugin? - websocket

I am trying to build an application using Angular 6 + Ngxs + WebSocket. However, some how I am not able to connect with the WebSocket plugin provided by Ngxs.
In the documentation, Ngxs mentioned that we can catch the error using WebsocketMessageError action. (I hope I understood the documentation correctly)
Documentation Link: https://ngxs.gitbook.io/ngxs/plugins/web-socket
But I am trying to import this action in my Service class, then it says that Action is not available.
Here is the code of my WebSocketService class.
import { Injectable } from '#angular/core';
import { Store, Actions, ofActionDispatched } from '#ngxs/store';
import { WebsocketMessageError, ConnectWebSocket } from '#ngxs/websocket-plugin';
import * as Rx from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class WebSocketService {
private subject: Rx.Subject<MessageEvent>;
constructor(private store: Store, private actions$: Actions) {
this.actions$
.pipe(ofActionDispatched(WebsocketMessageError))
.subscribe(({ payload }) => {
console.log('Got action: ', payload);
});
}
public connect() {
this.store.dispatch(new ConnectWebSocket());
}
}
I am getting following error:
module #ngxs/websocket-plugin/ngxs-websocket-plugin"' has no exported member
'WebsocketMessageError'.
import WebsocketMessageError

This looks like a bug. Please report it in the github repository:
https://github.com/ngxs/store/issues

Related

Can't get Firebase emulators to work with AngularFire 7

Good talk yesterday at the Firebase Summit about emulators! I was able to get the Functions emulator to work with AngularFire 6. I can't get the Firestore emulator or the Functions emulator to work with AngularFire 7. Here's my app.module.ts:
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { AppComponent } from './app.component';
import { initializeApp,provideFirebaseApp } from '#angular/fire/app';
import { environment } from '../environments/environment';
import { provideFirestore,getFirestore } from '#angular/fire/firestore';
import { USE_EMULATOR as USE_FIRESTORE_EMULATOR } from '#angular/fire/compat/functions';
import { USE_EMULATOR as USE_FUNCTIONS_EMULATOR } from '#angular/fire/compat/functions';
import { FormsModule } from '#angular/forms';
#NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
FormsModule,
provideFirebaseApp(() => initializeApp(environment.firebase)),
provideFirestore(() => getFirestore()),
],
providers: [
{ provide: USE_FIRESTORE_EMULATOR, useValue: environment.useEmulators ? ['localhost', 8080] : undefined },
{ provide: USE_FUNCTIONS_EMULATOR, useValue: environment.useEmulators ? ['localhost', 5001] : undefined }
],
bootstrap: [AppComponent]
})
export class AppModule { }
There's a smell here. I'm initializing Firebase using AngularFire 7 but I'm importing the emulator from AngularFire 6.1.0. Firebase can be initialized with AngularFire 6 or AngularFire 7 but not both, i.e., you can't mix AngularFire 6 and 7.
How do I import the emulators without using AngularFire 6?
In environments.ts I made a property useEmulators:
export const environment = {
firebase: {
projectId: 'my-awesome-project',
appId: '1:234567890:web',
storageBucket: 'my-awesome-project.appspot.com',
apiKey: 'ABCdef',
authDomain: 'my-awesome-project.firebaseapp.com',
messagingSenderId: '0987654321',
},
production: false,
useEmulators: true
};
My Cloud Function runs great in the cloud but doesn't run in the emulators.
Each time I make a change in a Cloud Function, deploy the update to the cloud, wait a minute for the deploy to propagate, test my function, and wait for the logs to show up in the Firebase Console is ten minutes. I'm looking forward to using the emulators to speed up this development cycle.
Here's the rest of my code. I doubt there's anything wrong with these files.
The Cloud Function triggers from writing a message to Firestore, changes the message to uppercase, and writes the uppercase message to a new field in the document.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.uppercaseMe = functions.firestore.document('Triggers/{docId}').onCreate((snap, context) => {
var original = snap.data().message;
functions.logger.log('Uppercasing', context.params.docId, original);
var uppercase = original.toUpperCase();
return snap.ref.set({ uppercase }, { merge: true });
});
The HTML view has a form for submitting a message. It displays the data that was written to Firestore and then displays the results from the Cloud Function.
<form (ngSubmit)="triggerMe()">
<input type="text" [(ngModel)]="message" name="message" placeholder="Message" required>
<button type="submit" value="Submit">Submit</button>
</form>
<div>{{ data$ }}</div>
<div>{{ upperca$e }}</div>
The app.component.ts controller writes the message to Firestore, reads back the message from Firestore, then sets up a document listener to wait for the cloud function to write a new field to the document.
import { Component } from '#angular/core';
import { Firestore, doc, getDoc, collection, addDoc, onSnapshot } from '#angular/fire/firestore';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
data$: any;
docSnap: any;
message: string | null = null;
upperca$e: string | null = null;
unsubMessage$: any;
constructor(public firestore: Firestore) {}
async triggerMe() {
try {
// write to Firestore
const docRef = await addDoc(collection(this.firestore, 'Triggers'), {
message: this.message,
});
this.message = null; // clear form fields
// read from Firestore
this.docSnap = await getDoc(doc(this.firestore, 'Triggers', docRef.id));
this.data$ = this.docSnap.data().message;
// document listener
this.unsubMessage$ = onSnapshot(doc(this.firestore, 'Triggers', docRef.id), (snapshot: any) => {
this.upperca$e = snapshot.data().uppercase;
});
} catch (error) {
console.error(error);
}
}
}
Firebase emulators work independently of Angular or other apps! I reread the documentation and learned that you just spin up the emulators,
firebase emulators:start
open your browser to http://localhost:4000, and you can write data in Firestore and then see the results of your function appear in Firestore. You can also read the logs. This only works with triggerable functions, not with callable functions.
Amazing what you can learn by reading the documentation. :-)

nest js websocket connection is not working with angular 11

Created a nest js websocket and trying to connect to that from angular app version 11. Not able to connect to socket from angular 11. I am using latest version of socket.io-client.
In websocket server log says connect and disconnects.
nest js websocket file:
import { Logger } from '#nestjs/common';
import { OnGatewayConnection, OnGatewayDisconnect, OnGatewayInit, SubscribeMessage, WebSocketGateway, WsResponse } from '#nestjs/websockets';
import { Socket,Server } from 'socket.io';
import { EventPattern } from '#nestjs/microservices';
#WebSocketGateway(3001)
export class AppGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect{
private Logger = new Logger('AppGateway');
afterInit(server: Server) {
this.Logger.log("App Gateway Initialized");
}
handleConnection(client: Socket, ...args: any[]){
this.Logger.log(`New client connected...: ${client.id}`);
client.emit('connected', 'Successfully connected to the server.');
}
handleDisconnect(client: Socket) {
this.Logger.log(`Client disconnected: ${client.id}`);
}
#SubscribeMessage('msgToServer')
handleMessage(client:Socket, text:string):WsResponse<string> {
this.Logger.log(`got new event`);
return {event: 'msgToClient', 'data': text};
}
}
angular websocket service file:
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs';
import { io } from 'socket.io-client';
#Injectable({
providedIn: 'root'
})
export class WebSocketService {
socket: any;
constructor() {
this.socket = io('http://localhost:3001');
debugger;
this.socket.on('connected', function() {
console.log("connected !");
});
}
listen(eventName: string) {
return new Observable((subscriber) => {
this.socket.on(eventName, (data) => {
subscriber.next(data);
})
});
}
emit(eventName: string, data:any) {
this.socket.emit(eventName, data);
}
}
angular app.component.ts file:
import { Component, OnInit } from '#angular/core';
import { WebSocketService } from './web-socket.service';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit{
title = 'dashboard';
constructor(
private webSocketService: WebSocketService
) {}
ngOnInit() {
/*this.webSocketService.listen('msgToClient').subscribe((data) => {
console.log(data);
})*/
this.webSocketService.listen('connected').subscribe((data) => {
console.log(data);
})
}
}
I am using latest version of socket.io-client.
I believe you're using socketIO client v4. (If you're working with v3, the following would still be true)
Based on NestJS Websocket documentation, the NestJS socketIO server is still in v2.
#nestjs/platform-socket.io currently depends on socket.io v2.3 and socket.io v3.0 client and server are not backward compatible. However, you can still implement a custom adapter to use socket.io v3.0. Please refer to this issue for further information.
If you check the version compatibility, you will see that socketIO server v2 is not compatible with socketIO client v4.
However, socketIO server v3 is compatible with socketIO client v4. So I believe you can take a look into this issue (as mentioned in NestJS docs) and try to convert your NestJS socketIO server to support socketIO client v3. Hopefully, that would also support socketIO client v4 as well. (I didn't test this though!)
Hope this helps you. Cheers 🍻 !!!

Possible memory leak in NativeScript app if user reopens his app multiple times

I'm not sure where is the bug, maybe I'm using rxjs in a wrong way. ngDestroy is not working to unsubscribe observables in NativeScript if you want to close and back to your app. I tried to work with takeUntil, but with the same results. If the user close/open the app many times, it can cause a memory leak (if I understand the mobile environment correctly). Any ideas? This code below it's only a demo. I need to use users$ in many places in my app.
Tested with Android sdk emulator and on real device.
AppComponent
import { Component, OnDestroy, OnInit } from '#angular/core';
import { Subscription, Observable } from 'rxjs';
import { AppService } from './app.service';
import { AuthenticationService } from './authentication.service';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent implements OnDestroy, OnInit {
public user$: Observable<any>;
private subscriptions: Subscription[] = [];
constructor(private appService: AppService, private authenticationService: AuthenticationService) {}
public ngOnInit(): void {
this.user$ = this.authenticationService.user$;
this.subscriptions.push(
this.authenticationService.user$.subscribe((user: any) => {
console.log('user', !!user);
})
);
}
public ngOnDestroy(): void {
if (this.subscriptions) {
this.subscriptions.forEach((subscription: Subscription) => subscription.unsubscribe());
}
}
async signIn() {
await this.appService.signIn();
}
async signOut() {
await this.appService.signOut();
}
}
AuthenticationService
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs';
import { shareReplay } from 'rxjs/operators';
import { AppService } from './app.service';
#Injectable({
providedIn: 'root',
})
export class AuthenticationService {
public user$: Observable<any>;
constructor(private appService: AppService) {
this.user$ = this.appService.authState().pipe(shareReplay(1)); // I'm using this.users$ in many places in my app, so I need to use sharereplay
}
}
AppService
import { Injectable, NgZone } from '#angular/core';
import { addAuthStateListener, login, LoginType, logout, User } from 'nativescript-plugin-firebase';
import { BehaviorSubject, Observable } from 'rxjs';
import { distinctUntilChanged } from 'rxjs/operators';
const user$ = new BehaviorSubject<User>(null);
#Injectable({
providedIn: 'root',
})
export class AppService {
constructor(private ngZone: NgZone) {
addAuthStateListener({
onAuthStateChanged: ({ user }) => {
this.ngZone.run(() => {
user$.next(user);
});
},
});
}
public authState(): Observable<User> {
return user$.asObservable().pipe(distinctUntilChanged());
}
async signIn() {
return await login({ type: LoginType.PASSWORD, passwordOptions: { email: 'xxx', password: 'xxx' } }).catch(
(error: string) => {
throw {
message: error,
};
}
);
}
signOut() {
logout();
}
}
ngOnDestroy is called whenever a component is destroyed (following regular Angular workflow). If you have navigated forward in your app, previous views would still exist and would be unlikely to be destroyed.
If you are seeing multiple ngOnInit without any ngOnDestroy, then you have instantiated multiple components through some navigation, unrelated to your subscriptions. You should not expect the same instance of your component to be reused once ngOnDestroy has been called, so having a push to a Subscription[] array will only ever have one object.
If you are terminating the app (i.e. force quit swipe away), the whole JavaScript context is thrown out and memory is cleaned up. You won't run the risk of leaking outside of your app's context.
Incidentally, you're complicating your subscription tracking (and not just in the way that I described above about only ever having one pushed). A Subscription is an object that can have other Subscription objects attached for termination at the same time.
const subscription: Subscription = new Subscription();
subscription.add(interval(100).subscribe((n: number) => console.log(`first sub`));
subscription.add(interval(200).subscribe((n: number) => console.log(`second sub`));
subscription.add(interval(300).subscribe((n: number) => console.log(`third sub`));
timer(5000).subscribe(() => subscription.unsubscribe()); // terminates all added subscriptions
Be careful to add the subscribe call directly in .add and not with a closure. Annoyingly, this is exactly the same function call to make when you want to add a completion block to your subscription, passing a block instead:
subscription.add(() => console.log(`everybody's done.`));
One way to detect when the view comes from the background is to set callbacks on the router outlet (in angular will be)
<page-router-outlet
(loaded)="outletLoaded($event)"
(unloaded)="outletUnLoaded($event)"></page-router-outlet>
Then you cn use outletLoaded(args: EventData) {} to initialise your code
respectively outletUnLoaded to destroy your subscriptions.
This is helpful in cases where you have access to the router outlet (in App Component for instance)
In case when you are somewhere inside the navigation tree you can listen for suspend event
Application.on(Application.suspendEvent, (data: EventData) => {
this.backFromBackground = true;
});
Then when opening the app if the flag is true it will give you a hint that you are coming from the background rather than opening for the first time.
It works pretty well for me.
Hope that help you as well.

WebSockets on specific route in Nest.js

I'd like to create specific API route which will be used only WebSocket (/api/events) but in all examples of implementing WebSockets on Nest.js I stumbled upon module is imported in AppModule and client is emitting events toward the root URL, which I can't do because I have this middleware;
frontend.middleware.ts
import { Request, Response } from 'express';
import { AppModule } from '../../app.module';
export function FrontendMiddleware(
req: Request,
res: Response,
next: Function,
) {
const { baseUrl } = req;
if (baseUrl.indexOf('/api') === 0) {
next();
} else {
res.sendFile('index.html', { root: AppModule.getStaticAssetsRootPath() });
}
}
Here is the EventGateway and EventModule:
event.gateway.ts
import {
SubscribeMessage,
WebSocketGateway,
WebSocketServer,
WsResponse,
} from '#nestjs/websockets';
import { from, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { Client, Server } from 'socket.io';
#WebSocketGateway({ namespace: 'events' })
export class EventGateway {
#WebSocketServer()
server: Server;
#SubscribeMessage('events')
findAll(client: Client, data: any): Observable<WsResponse<number>> {
return from([1, 2, 3]).pipe(map(item => ({ event: 'events', data: item })));
}
#SubscribeMessage('identity')
async identity(client: Client, data: number): Promise<number> {
return data;
}
}
event.module.ts
import { Module } from '#nestjs/common';
import { EventGateway } from './event.gateway';
#Module({
components: [EventGateway],
})
export class EventModule {}
Is there a way to create controller which will allow server-client communication via /api/events?
Yes, it is possible to create the WebsocketGateway on another path. You can just use the options of the WebsocketGateway to configure the underlying IO-Connection:
E.g:
import {
SubscribeMessage,
WebSocketGateway,
WebSocketServer,
WsResponse,
} from '#nestjs/websockets';
import { from, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { Client, Server } from 'socket.io';
#WebSocketGateway({ path: '/api/events', namespace: 'events' })
export class EventGateway {
#WebSocketServer()
server: Server;
#SubscribeMessage('events')
findAll(client: Client, data: any): Observable<WsResponse<number>> {
return from([1, 2, 3]).pipe(map(item => ({ event: 'events', data: item })));
}
#SubscribeMessage('identity')
async identity(client: Client, data: number): Promise<number> {
return data;
}
}
This will start the IO-Connection on http://localhost/api/events
Remember to change the connection-path also in your client. It won't be the default /socket.io path anymore, it will be /api/events in your sample.
Websocket is running on the server, not an endpoint. Therefore you cannot have it listen to requests under a specific route, rather just a port, which for Nest's default configuration happens to be the same as the HTTP one.
You could use a reverse proxy like Nginx to redirect the requests towards /api/events facing the Websocket server and also handle the redirection to index.html without changing even the Websocket server's port. Then you would not need the FrontendMiddleware class at all. It is also better since the application does not take the burden of managing request redirections.

Nativescript update http response when app launches

I have an app I have inherited that is getting data from an API endpoint. We have found that when we change data on the API, the changes are not reflected in the app. If we uninstall and re-install the app on a mobile device, then the new data from the API is displayed. Here is an example of the Building Detail page:
import { Component, OnInit } from '#angular/core';
import { ActivatedRoute } from "#angular/router";
import { switchMap } from 'rxjs/operators';
import { Building } from "../shared/building/building";
import { HttpService } from "../services/http/http.service";
import {
getString,
setString
} from "application-settings";
#Component({
moduleId: module.id,
selector: 'building-detail',
templateUrl: 'building-detail.component.html',
styleUrls: ["./building-detail-common.css"],
providers: [ Building, HttpService ]
})
export class BuildingDetailComponent implements OnInit {
paramName: string;
constructor(
private route: ActivatedRoute,
public building: Building,
private httpService: HttpService) {
this.route.params.subscribe(
(params) => {
this.paramName = params['name']
}
);
}
ngOnInit() {
console.log("ON INIT FIRED " + this.paramName);
let buildingInfo = JSON.parse(getString("buildingInfo"));
for (let item of buildingInfo) {
if (item.attributes.title === this.paramName) {
this.building.name = item.attributes.title;
this.building.desc = item.attributes.body.value;
let imageEndpoint = "file/file/" + item.relationships.field_building_image.data.id;
let imageUrl = this.httpService.getData(imageEndpoint)
.subscribe(data => {
this.building.image = "https://nav.abtech.edu" + data['data'].attributes.url;
console.log("The building image URL is " + this.building.image);
}, (error) => {
console.log("Error is " + error);
});
}
}
}
}
I am happy to share other files/code if you would like to look at those. Thanks!
The reason your data is not being updated is not because the ngOnInit is not being executed, it's because you're caching the old value and reloading it each time the app is run. You're caching the data persistently across app runs with appSettings and that's why you are seeing the values stay the same until you uninstall.
If you don't want to show a cached value then don't read from the app settings, or at least don't read from appSettings until you've refreshed the data once.
ngOnInit is something that is executed only when your component is created, it will never be executed again.
Also there is difference between app launch and resume, if you want to update data every time when user opens the app, you should listen to resume event and perform apis calls inside ngZone
You may even use push notification / data message if you want to notify user immediately when data changes on backend

Resources