Property always undefined when accessing it in Event Handler from SignalR - javascript

I have built an angular application and I am using signalR to get notifications.
For the signalR stuff i have installed "#aspnet/signalr": "^1.0.4".
In the Angular App I have built a service that handles all notifications that are sent over signalR:
private _hubConnection: HubConnection;
numberX: number = 0;
constructor() {
this.createConnection();
this.registerOnServerEvents();
this.startConnection();
}
private createConnection() {
this._hubConnection = new HubConnectionBuilder().withUrl(someURL).build();
}
private startConnection(): void {
this._hubConnection.start();
}
private registerOnServerEvents(): void {
this._hubConnection.on('Notify', (data: any) => {
this.numberX++;
});
}
When I get an event from signalR the event handler is actually called, when I send something to the console output it is actually written to the console.
But when I try to access a member property (in this case numberX), I always get an exception telling me that numberX is undefined, even I define it at the very start.
Could this be some kind of scope thing?
EDIT:
The solution from Kenzk447 actually works for my given scenario. But this would not work when I go one step further by not just increasing a number, but using an EventEmitter that can be consumed by components:
In the service:
someEvent: EventEmitter<any> = new EventEmitter();
private registerOnServerEvents(): void {
const $self = this;
this._hubConnection.on('ErrorMessage', (data: any) => {
$self.someEvent.emit(data);
});
}
In the component:
notificationCount: number = 0;
this.notificationService.someEvent.subscribe(this.onSomeEvent);
onSomeEvent(data: any) {
this.notificationCount++;
}
When doing this, 'this' in the component is undefined and I cannot access properties of the component.
EDIT 2:
Ok I got it working with
this solution
The trick is to bind the eventHandler.
Nevertheless I will accept the answer because it worked in my given example and pointed me to the right direction.

I was researched on SignalR repo, and i found:
methods.forEach((m) => m.apply(this, invocationMessage.arguments));
Issue here: m.apply(this,..., SignalR trying to impose 'this' as HubConnection class instance. So, my solution:
private registerOnServerEvents(): void {
const $self = this;
this._hubConnection.on('Notify', (data: any) => {
$self.numberX++;
});
}

Related

Unit testing rxjs in Angular [duplicate]

This question already has answers here:
Angular unit test combineLatest
(2 answers)
Closed last year.
Im having a problem writing unit tests for observable in Angular... Im trying to test cases if displayPaymentsLineItem$ will be true or false depending on the values of
mobileAccountBalance$, and selectedMobileDigitalBill$... Can anyone help?
public selectedMobileDigitalBill$: Observable<MobileDigitalBill>;
public mobileAccountBalance$: Observable<MobileAccountBalance>;
private expandedLinesMap: object = {};
private expandedTaxesAndFees: boolean = false;
private devices: MobileDevice[] = [];
private destroy$: Subject<void> = new Subject();
constructor(]
private mobileStatementsTabFacade: MobileStatementsTabFacade,
private billingMobileFacade: BillingMobileFacade,
) {}
ngOnInit() {
this.mobileAccountBalance$ = this.mobileStatementsTabFacade.mobileAccountBalance$;
this.displayPaymentsLineItem$ = combineLatest([
this.mobileAccountBalance$,
this.selectedMobileDigitalBill$,
]).pipe(
map(([mobileAccountBalance, selectedMobileDigitalBill]: [MobileAccountBalance, MobileDigitalBill]) => {
const isPastDue: boolean = mobileAccountBalance?.pastdue > 0;
const hasPayments: boolean = selectedMobileDigitalBill?.payments?.length > 0;
return isPastDue && hasPayments;
})
);
}
});
You can take(1) (take one value, then complete) and subscribe to test if the emitted value is falsy. Observables need to be completed if you test them this way.
describe('The display payment line items observable', () => {
it('should emit truthy', () => {
displayPaymentsLineItem$
.pipe(take(1))
.subscribe(value =>{
expect(value).toBeTruthy();
});
}
}
That being said, displayPaymentsLineItem$ won't emit anything if the two observables inside combineLatest() aren't defined in your test. Since they come from two facades, they may need to be provided before starting your test.
Also, about your code example:
displayPaymentsLineItem$ isn't declared before the constructor.
selectedMobileDigitalBill$ is declared but is never defined before it is referenced inside combineLatest().

Calling JavaScript from Blazor: how to dispose JavaScript within DotNetObjectReference?

I have created a Blazor Webassembly Project and added a key listener in JavaScript, which is listening to every key stroke on the DOM document. Everything works as expected, however when I open the Blazor page where the key listener is registered and later open it again, the following error occurs in the Web Browser:
There is no tracked object with id '2'. Perhaps the
DotNetObjectReference instance was already disposed. (Parameter
'dotNetObjectId')
Obviously the object "dotnethelper" is disposed but the Javascript is still listening / getting called.
Basically I implemented the "Component instance .NET method helper class" from the Microsoft Documentation.
Blazor Page:
Note: The IDisposable is injected on the top and the Dispose function is getting called.
#code {
private KeyListenerInvokeHelper _keyListenerInvokeHelper;
private DotNetObjectReference<KeyListenerInvokeHelper>? objRef;
protected override async Task OnInitializedAsync()
{
objRef = DotNetObjectReference.Create(_keyListenerInvokeHelper);
await JS.InvokeVoidAsync("initializeKeyListener", objRef);
}
public void Dispose()
{
objRef?.Dispose();
}
}
Javascript File:
window.initializeKeyListener = (dotnetHelper) => {
document.addEventListener('keydown', logKey);
function logKey(e) {
dotnetHelper.invokeMethod('OnKeyDown', e.key);
console.log('key down ' + e.key);
}
}
KeyListenerInvokeHelper:
public class KeyListenerInvokeHelper
{
private readonly Action<string> action;
public KeyListenerInvokeHelper(Action<string> action)
{
this.action = action;
}
[JSInvokable("OnKeyDown")]
public void OnKeyDown(string key)
{
action.Invoke(key);
}
}
What have I tried so far?
I tried to reset the function on window.initializeKeyListener (i.e. setting window.initializeKeyListener), however this did not achieve anything
I tried removing the eventlistener on the 'keydown' event.
When you dispose of your object, you need to remove the event listener as well. You mentioned I tried removing the eventlistener on the 'keydown' event., but perhaps the way you did it was not correct?
My javascript is a little rusty, but I think you could do something like the following:
var logkey;
window.initializeKeyListener = (dotnetHelper) => {
logkey = (e) => {
dotnetHelper.invokeMethod('OnKeyDown', e.key);
console.log('key down ' + e.key);
};
document.addEventListener('keydown', logkey);
}
window.removeKeyListener = () => {
document.removeEventListener('keydown', logkey);
}
and then in your component:
#implements IAsyncDisposable
public async ValueTask DisposeAsync()
{
await JSRuntime.InvokeVoidAsync("removeKeyListener");
objRef?.Dispose();
}
Having said that, perhaps calling a static method in C# using [JSInvokable] would be better suited for your use case?

Blazor listen to javascript event

I have a javascript event called Hello:
addEventListener('hello', function () {
alert("event listener");
})
and, in another javascript function, I raise the event:
let event = new Event("hello", { bubbles: true });
document.dispatchEvent(event);
What I want to do now is let the event trigger in a javascript function.
Blazor should listen to the event, not javascript calling a Blazor method.
Hope anyone can assist me.
Regards me,
For custom events, you will need to manually utilize JavaScript/.NET interoperability.
Using the Instance Method Call method:
Pass the .NET instance by reference to JavaScript:
Make a static call to DotNetObjectReference.Create.
Wrap the instance in a DotNetObjectReference instance and call Create on the DotNetObjectReference instance. Dispose of DotNetObjectReference objects (an example appears later in this section).
Invoke .NET instance methods on the instance using the invokeMethod or invokeMethodAsync functions. The .NET instance can also be passed as an argument when invoking other .NET methods from JavaScript.
Example
Note, this is a very simplified example. You probably want to add a few things; start by IDisposable on your interop classes to avoid memory leaks.
In C#, create a helper class to manage the interop:
public class CustomEventHelper
{
private readonly Func<EventArgs, Task> _callback;
public CustomEventHelper(Func<EventArgs, Task> callback)
{
_callback = callback;
}
[JSInvokable]
public Task OnCustomEvent(EventArgs args) => _callback(args);
}
public class CustomEventInterop : IDisposable
{
private readonly IJSRuntime _jsRuntime;
private DotNetObjectReference<CustomEventHelper> Reference;
public CustomEventInterop(IJSRuntime jsRuntime)
{
_jsRuntime = jsRuntime;
}
public ValueTask<string> SetupCustomEventCallback(Func<EventArgs, Task> callback)
{
Reference = DotNetObjectReference.Create(new ScrollEventHelper(callback));
// addCustomEventListener will be a js function we create later
return _jsRuntime.InvokeAsync<string>("addCustomEventListener", Reference);
}
public void Dispose()
{
Reference?.Dispose();
}
}
In a Blazor component, add an instance of the interop class (Interop) and add a local method as a callback (HandleCustomEvent):
private CustomEventInterop Interop { get; set; }
protected override async Task OnAfterRenderAsync(bool firstRender) {
if (!firstRender)
{
return;
}
Interop = new(JS);
await Interop.SetupCustomEventCallback(args => HandleCustomEvent(args));
HasRendered = true;
}
private void HandleCustomEvent(EventArgs args) {
// ... handle custom event here
}
In JavaScript, add a method that references the DotNetObjectReference and can call the interop in C#:
function addCustomEventListener(dotNetObjectRef) {
document.addEventListener('hello', (event) => {
// Calls a method by name with the [JSInokable] attribute (above)
dotNetObjectRef.invokeMethodAsync('OnCustomEvent')
});
}
If using TypeScript, you might check out this GitHub Issue.
For anyone wondering the solution proposed by #Mister Magoo is no longer a preview for .NET 6 and is documented here with some exemples.
In a nutshell :
Create a C# class with the EventHandlerAttribute :
[EventHandler("oncityclicked", typeof(CustomSelectionCityEventArgs),
enableStopPropagation: true, enablePreventDefault: true)]
public static class EventHandlers
{
}
public class CustomSelectionCityEventArgs: EventArgs
{
public Guid Id { get; set; }
}
Add JS inside ./wwwroot/index.html and after <script src="_framework/blazor.webview.js" autostart="false"></script> :
<script>
Blazor.registerCustomEventType('cityclicked', {
createEventArgs: event => {
return {
id: event.detail
};
}
});
</script>
In you razor :
#code {
private void HandleCityClicked(CustomSelectionCityEventArgs eventArgs)
{
Console.WriteLine("Bouep");
}
}
<div id="CarteLeaflet" #oncityclicked="HandleCityClicked"></div>
And finally in the JS you can dispatch the event :
function OnMarkerClick(pId) {
const event = new CustomEvent('cityclicked', {
bubbles: true,
detail: pId
});
document.getElementById('CarteLeaflet').dispatchEvent(event);
}
Don't make the same mistake as me, the event name in the C# should start with "on" (JS : "cityclicked", C# : "oncityclicked").

WebSocket can't see Typescript class properties in event handler methods

Situation
I have following typescript files:
PlayerList.ts
GameEngineInterface.ts
interface GameEngineInterface {
onNewPlayer(socket: Socket):void
}
GameEngine.ts
class GameEngine implements GameEngineInterface {
constructor(private playerList: PlayerList = new PlayerList()){}
onNewPlayer(socket: Socket) {
playerList.addPlayer(socket)
}
}
index.ts
const gameEngine: GameEngineInterface = new GameEngine();
const httpServer = new Http.Server({}).listen(3000);
const websocketApp = new SocketIO(httpServer);
websocketApp.on("connection", gameEngine.onNewPlayerConnect);
When browser connects to this websocket server, this error is thrown
var player = this.playerList.addPlayer(socket);
TypeError: Cannot read property 'addPlayer' of undefined
If I change the last line in index.ts to
websocketApp.on("connection", (socket: Socket) => gameEngine.onNewPlayerConnect(socket));
then the error disappears.
Question
What went wrong with my code in the first place?
Why the fix worked?
Thank you for your time.
because when you pass function
websocketApp.on("connection", gameEngine.onNewPlayerConnect);
way the binding of the onNewPlayerConnect is to websocketApp, there fore the this context is point to it
in arrow function the binding onNewPlayerConnect is retained

Cannot read property of undefined when assigning websocket message data

I'm creating a chat application using Ratchet Websocket. I followed their Hello World tutorial here: http://socketo.me/docs/hello-world
When I try to assign the data of received messages through a global function or variable, I get the error:
"ERROR TypeError: Cannot read property 'recievedMessage' of undefined
at onMessage_callback (dashboard.component.ts:32)
at WebSocket.conn.onmessage [as __zone_symbol__ON_PROPERTYmessage]"
I have read many other posters with the same issues but I cant figure out how to get their answers to work like they say. I am trying to add sent and received messages to a global array so I can display them all in a chat list. I know my problem has something to do with the scope of this.conn.onmessage=function(e) but this is the first time I have used websockets or seen this syntax.
Why cant I call this.recievedMessage(data); from function onMessage_callback(data);?
Similar question (It even mentions you can assign data from callback to a global variable but this does not work for me): WebSocket returns undefined in code but not in console
I know all my backend client/chat php is working correctly due to the console showing messages for different connections.
typescript code:
export class DashboardComponent implements OnInit {
conn:WebSocket;
messageDisplay:any[] = ["Welcome To Chat"]
message: string;
constructor() {}
ngOnInit(): void {
this.conn = new WebSocket('ws://localhost:8080');
this.conn.onopen = function(e) {
console.log("Connection established!");
};
this.conn.onmessage = function(e) {
console.log(e.data);
onMessage_callback(e.data);
};
function onMessage_callback(data){
this.recievedMessage(data);
}
}
recievedMessage(message){
this.messageDisplay.push(message);
}
sendMessage(message){
this.conn.send(message);
this.messageDisplay.push(message);
console.log(this.messageDisplay);
}
}
Any help is greatly appreciated :)
The context for this changed when you are inside another function (onMessage_callback), so this is no longer referring to the parent object at this point.
TO use the reference this to the main class you must wrap this reference in another local variable (in this example, self)
ngOnInit(): void {
var self = this;
this.conn = new WebSocket('ws://localhost:8080');
this.conn.onopen = function(e) {
console.log("Connection established!");
};
this.conn.onmessage = function(e) {
console.log(e.data);
onMessage_callback(e.data);
};
function onMessage_callback(data){
self.recievedMessage(data);
}
}
Este articulo explica con ejemplos como funciona el binding
https://developer.mozilla.org/es/docs/Web/JavaScript/Referencia/Objetos_globales/Function/bind

Categories