Can't unsubscribe from SignalR events - asp.net

I have multiple Angular components that are subscribing to a SignalR hub connection. I have wrapped the SignalR hub connection object to an angular service, hubconnection.service.ts:
import { Injectable } from '#angular/core';
import { HubConnectionBuilder, HubConnection } from '#aspnet/signalr';
#Injectable()
export class HubConnectionService {
public MyHubConnection: HubConnection;
constructor() {
this.MyHubConnection = new HubConnectionBuilder()
.withUrl('/sensorHub')
.build();
this.MyHubConnection
.start()
.then(() => console.log('[HubConnectionService] Connection started'))
.catch(err => console.log('[HubConnectionService] Error while starting connection: ' + err));
}
}
Now I have a component from where I want to subscribe to the data received through the SignalR hub, to do this I import my SignalR wrapper, and in the constructor I call the .on() method. To unsubscribe from that event I call the .off() method in the destructor:
export class RawdataComponent implements OnInit {
constructor(private hubConnectionService: HubConnectionService) { }
ngOnDestroy() {
this.hubConnectionService.MyHubConnection.off('Broadcast');
}
ngOnInit() {
this.hubConnectionService.MyHubConnection.on('Broadcast',
function(sender, message) {
console.log('New message received: ' + message);
});
}
}
When I initially access the page that is described by this component, everything works as expected (console.log() is logging for every message I receive).
When I leave the page, MyHubConnection.off('Broadcast'); is called. Then after I access the previous page again, the constructor is called and another subscription is made, but the previous one is not closed, and I get two console.log() calls.
Where's the mistake I'm making? Shouldn't MyHubConnection.off('Broadcast'); close the current subscription when I leave the page?

Related

How to monitor SignalR ConnectionState in Angular 11 after SignalR connection is established continously

I am trying to find a way to monitor a SignalR connection continuously like say every minute or every 3 minutes.
Trying to find a way to check the connection state. Do I need to set up an observable that can be subscribed to. But then that would have to be in a Component or do I do that in the app.Module or app.component ?
Is it connection.OnClose();
the Angular Code in my SignalR Service class is:
Injectable({
providedIn: 'root'
})
export class SignalrService {
connection: signalR.HubConnection;
constructor() {
this.connection = new signalR.HubConnectionBuilder()
.withUrl(environment.hubAddress)
.build();
this.connect();
}
public connect() {
if (this.connection.state === signalR.HubConnectionState.Disconnected) {
this.connection.start().catch(err => console.log(err));
}
}
public getMessage(next) {
this.connection.on('SendMessage', (message) => {
next(message);
});
}
public disconnect() {
this.connection.stop();
}
}
I was able to solve this by checking connection reconnecting(), reconnected() and onClose()events:
constructor() {
this.connection = new signalR.HubConnectionBuilder()
.withUrl(environment.hubAddress)
.build();
this.connection.reconnecting() => {}
this.connection.reconnected() => {}
this.connection.onClose() => {}
this.connect();
}

from http to Http Client, cant get my object to become a string array [duplicate]

Following Google's official Angular 4.3.2 doc here, I was able to do a simple get request from a local json file. I wanted to practice hitting a real endpoint from JSON placeholder site, but I'm having trouble figuring out what to put in the .subscribe() operator. I made an IUser interface to capture the fields of the payload, but the line with .subscribe(data => {this.users = data}) throws the error Type 'Object' is not assignable to type 'IUser[]'. What's the proper way to handle this? Seems pretty basic but I'm a noob.
My code is below:
import { Component, OnInit } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { IUsers } from './users';
#Component({
selector: 'pm-http',
templateUrl: './http.component.html',
styleUrls: ['./http.component.css']
})
export class HttpComponent implements OnInit {
productUrl = 'https://jsonplaceholder.typicode.com/users';
users: IUsers[];
constructor(private _http: HttpClient) { }
ngOnInit(): void {
this._http.get(this.productUrl).subscribe(data => {this.users = data});
}
}
You actually have a few options here, but use generics to cast it to the type you're expecting.
// Notice the Generic of IUsers[] casting the Type for resulting "data"
this.http.get<IUsers[]>(this.productUrl).subscribe(data => ...
// or in the subscribe
.subscribe((data: IUsers[]) => ...
Also I'd recommend using async pipes in your template that auto subscribe / unsubscribe, especially if you don't need any fancy logic, and you're just mapping the value.
users: Observable<IUsers[]>; // different type now
this.users = this.http.get<IUsers[]>(this.productUrl);
// template:
*ngFor="let user of users | async"
I'm on the Angular doc team and one open todo item is to change these docs to show the "best practice" way to access Http ... which is through a service.
Here is an example:
import { Injectable } from '#angular/core';
import { HttpClient, HttpErrorResponse } from '#angular/common/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/map';
import { IProduct } from './product';
#Injectable()
export class ProductService {
private _productUrl = './api/products/products.json';
constructor(private _http: HttpClient) { }
getProducts(): Observable<IProduct[]> {
return this._http.get<IProduct[]>(this._productUrl)
.do(data => console.log('All: ' + JSON.stringify(data)))
.catch(this.handleError);
}
private handleError(err: HttpErrorResponse) {
// in a real world app, we may send the server to some remote logging infrastructure
// instead of just logging it to the console
let errorMessage = '';
if (err.error instanceof Error) {
// A client-side or network error occurred. Handle it accordingly.
errorMessage = `An error occurred: ${err.error.message}`;
} else {
// The backend returned an unsuccessful response code.
// The response body may contain clues as to what went wrong,
errorMessage = `Server returned code: ${err.status}, error message is: ${err.message}`;
}
console.error(errorMessage);
return Observable.throw(errorMessage);
}
}
The component would then look like this:
ngOnInit(): void {
this._productService.getProducts()
.subscribe(products => this.products = products,
error => this.errorMessage = <any>error);
}

Angular : Unable to pass API url in SignalR HubConnection

I have developed a project in Angular6. There is a requirement to develop section for live chat. For that I am using SignalR in my asp.net core Web Apiproject. Now I want to use this Web Api reference in my Angular project.
I am using this link.
But while providing the Web Api url in App.Component.ts, I am getting below error :
Constructor of class 'HubConnection' is private and only accessible
within the class declaration.
App Component.ts :
import { HubConnection } from '#aspnet/signalr';
import { Message } from 'primeng/api';
export class AppComponent implements OnInit {
public _hubConnection: HubConnection;
msgs: Message[] = [];
constructor() {
}
ngOnInit() {
this._hubConnection = new HubConnection('http://localhost:1874/notify'); // getting error on this line.
Edit : tried below code :-
Modified App.Component.ts :
import { HubConnection } from '#aspnet/signalr';
import * as signalR from '#aspnet/signalr';
import { Message } from 'primeng/api';
export class AppComponent implements OnInit {
public _hubConnection: HubConnection;
msgs: Message[] = [];
constructor() {
}
ngOnInit() {
this._hubConnection = new signalR.HubConnectionBuilder()
.withUrl('http://localhost:1874/notify')
.configureLogging(signalR.LogLevel.Information)
.build();
Error :
Failed to load
http://localhost:1874/notify/negotiate: Response to preflight request
doesn't pass access control check: The value of the
'Access-Control-Allow-Credentials' header in the response is '' which
must be 'true' when the request's credentials mode is 'include'.
Origin 'http://localhost:4200' is therefore not allowed access. The
credentials mode of requests initiated by the XMLHttpRequest is
controlled by the withCredentials attribute.
They changed the constructors for SignalR Client HubConnection to no longer allow you to pass the route in the client constructor and made the HubConnection constructor private to enforce the change. Instead, you need to use the HubConnectionBuilder as follows:
new HubConnectionBuilder().withUrl("/YOURROUTEHERE").build();
You will want to import the HubConnectionBuilder in your header as follows:
import { HubConnection, HubConnectionBuilder } from '#aspnet/signalr';
In case it helps, I also just posted an update to my RxSignalR samples for #aspnet/signalr 1.0.2, RxJs 5.5.6 and Angular 6. Check out the ReactiveChat component for an example.
In your server code, are you using CORS?
public void ConfigureServices(IServiceCollection services)
{
...
services.AddCors(options => options.AddPolicy("CorsPolicy", builder =>
{
builder.AllowAnyMethod()
.AllowAnyHeader()
.WithOrigins("http://localhost:4200")
.AllowCredentials();
}));
...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
app.UseCors("CorsPolicy");
app.UseSignalR(routes =>
{
// your routes
});
...
}
Try like this,
private hubConnection: signalr.HubConnection;
message: string;
ngOnInit() {
this.hubConnection = new signalr.HubConnection('');
this.hubConnection
.start()
.then(() => {
console.log('Connection started!');
this.hubConnection.on('ReceiveMessage', (message) => {
alert(message);
});
})
.catch(err => console.log('Error while establishing connection :('));
}

Ionic2-Long Polling: killing http.get request while changing pages

My server is developed on Node.js. It is a long-polling service (e.g. chat): it gives the following API:
join() //listening for new events
align(fromId) //retrieving events from an id
send(data) //creating an event
The long-polling is implemented by the join(): it sends a request and the server answers when there is a new event.
Front end with Ionic2
There are 2 pages: Page1 and Page2. Where Page2 is the viewer of my events, where the long-polling communication is running.
So I start from Page1 and then I push() the second page Page2. Until now everything works fine; but if I pop() the Page2 and then push() again the Page2 then I can see that there is still running the join() of the previous instance of my Page2. This behaviour creates duplicated join(): if I push/pop Page2 many times I will have many long-polling communication with the server.
So I'm trying to find a way to kill the join() instance, which is a HTTP.get request, when leaving the page.
Let's see now my code.
This is the provider of my Ionic2 in charge of the communication with the server
import { Injectable } from '#angular/core';
import { Http, Headers } from '#angular/http';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/map';
#Injectable()
export class MyProvider {
...
constructor(private http: Http) {
this.token_access = null;
this.token_room = null;
}
...
join(){
let headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('x-access-token',this.getToken());
return Observable.create(observer =>{
this.http.get('/localhost/chat/'+this.room,{headers : headers})
.map(res => res.json())
.subscribe(
data=>{
observer.next(data);
},
(err) =>{
observer.error(err);
}
);
})
}
send(message){
let headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('x-access-token',this.getToken());
headers.append('x-chat-token',this.getRoomToken());
return Observable.create(observer =>{
this.http.post('/localhost/chat/'+this.room+'/send', JSON.stringify({
event: message
}),{headers : headers})
.map(res => res.json())
.subscribe(
data=>{
observer.next(data);
},
(err) =>{
observer.error(err);
}
);
})
}
align(from){
let headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('x-access-token',this.getToken());
headers.append('x-chat-token',this.getRoomToken());
return Observable.create(observer =>{
this.http.post('/localhost/chat/'+this.room+'/align', JSON.stringify({
fromId: from
}),{headers : headers})
.map(res => res.json())
.subscribe(
data=>{
observer.next(data);
},
(err) =>{
observer.error(err);
}
);
})
}
}
The Page1 just push the Page2 with a button that calls the the following code (page1.ts):
...
export class Page1 {
...
constructor(public navCtrl: NavController, public myProviderService: MyProvider) {
}
.....
toPage2(){
this.navCtrl.push(Page2);
}
And my Page2 is implemented by the following code:
import { Component } from '#angular/core';
import { NavController } from 'ionic-angular';
import { MyProvider } from '../../providers/myprovider';
import { Event } from '../../components/event';
#Component({
selector: 'page-chat',
templateUrl: 'chat.html'
})
export class ChatPage {
eventsList: Array<Event>;
message: any;
last_event: any;
msg: any;
constructor(public navCtrl: NavController, public myProviderService: MyProvider) {
this.last_event = -1;
this.join();
this.eventsList= new Array();
}
join(){
this.myProviderService.join().subscribe(
(data)=>{
if(data.success){
this.last_event = this.last_event + 1;
if(this.last_event == data.event.id){
//up to now all events are correctly received
this.eventsList.push(data.event);
}else{
//some events are missing
this.last_event = this.last_event - 1;
this.align();
}
this.join();
}else{
this.message=data.message;
//TBD sleep....
//this.join();
}
},
(err) => {
this.message="Connectivity with server Lost...";
//TBD sleep....
//this.join();
});
}
align(){
this.myProviderService.align(this.last_event + 1).subscribe((data)=>{
if(data.success){
for (var i=0;i<data.events.length;i++) {
this.eventsList.push(new Event(data.events[i].id,data.events[i].data,data.events[i].user));
this.last_event = this.last_event + 1;
};
}else{
this.message=data.message;
}
},
(err) => {
this.message="Failure receiving messages";
});
}
send(): void{
this.myProviderService.send(this.msg).subscribe((data)=>{
if(data.success){
this.msg='';
}else this.message=data.message;
},
(err) => {
this.message="Error while authenticating";
})
}
ionViewDidLoad() {
}
ionViewDidEnter() {
}
}
So coming back to my question:
How can I kill the join() (kill the HTTP.get request) instance of my Page2 when this is not used, in order to prevent duplicated join()?
I think that if you have a provider that is globally added to Providers section of your app (meaning that it can act as a Singleton service), then you can use the following:
Every time Page 2 calls the join() method of your provider check a hasAlreadyJoined boolean variable in your provider.
This variable is set to true every time the join() method is called.
If the join() has not been called, call it and update the variable accordingly.
So, even though every time Page 2 calls the join() method of MyProvider, this method does the actual http request only if hasAlreadyJoined is false.
For you to be sure that every time a MyProvider instance is initiated it's variables are "static", the provider should be declared at the global Providers section of your app module file, not at the page's providers section.

Catch errors from FirebaseListObservable on Angularfire2

I am using Angularfire2 along with Ionic2 and looking for a way to catch errors on FirebaseListObservable subscribe() function.
I am subscribing to fbData which is a FirebaseListObservable, and when I switch my browser/device offline, the (error) function is never called. I don't understand why.
My objective is to get data from the localStorage if the user is offline or firebase is not reachable.
Here is my simplified code:
export class MyService {
fbData: FirebaseListObservable<any[]>;
constructor(private af: AngularFire) {
this.data = af.database.list('/data', { preserveSnapshot: true });
}
updateData() {
return new Promise<any[]>((resolve, reject) => {
this.fbData.subscribe(
(snapshots) => {
resolve(snapshot.val());
},
(error) => console.log('error: ', error) // NEVER CALLED
);
});
}
Firebase will not throw an error if the network is unreachable. The SDK silently waits for the socket connection to build up. The subscription can only error out if the security rules denies read access to the query.
If you want to implement such a timeout scheme, you'll have to do everything manually: including starting a timeout when you start a query and cancelling it if the query returns a result in time.

Resources