Reason for removing event listener on disconnectedCallback of web component - web-component

I saw online a example of removing event listeners of a button in a web components dom in its disconnectedCallback:
class MyComponent extends HTMLElement {
constructor() {
this.attachShadow({ mode: "open" });
this.shadowRoot.innerHTML = "<button>Click</button>";
}
myEvent() {
...
}
connectedCallback() {
this.shadowRoot.querySelector("button").addEventListener("click", this.myEvent.bind(this));
}
// Removes event here:
disconnectedCallback() {
this.shadowRoot.querySelector("button").removeEventListener("click", this.myEvent.bind(this));
}
}
Is there a reason for doing this? As the button is out of dom there wouldn't be issues with it firing? Is there memory leak concerns? It is not listed in the events section of web components best practises. I could understand if was event listener on the window etc but don't understand the effects if the event is triggered by something which is not connected

You can reconnect an element any time and by doing that, you will attach twice an event;
const elem = document.createElement('my-component');
document.body.appendChild(elem);
document.body.removeChild(elem);
document.body.appendChild(elem);
if your event is doing an API call, this will result in a duplicated request

Related

Factory selector does not fire subscription with state update

Referencing this NGRX ticket, I am seeing an oddity where a code-based subscription to a factory selector does not fire with state updates, however, the async pipe does work.
To clarify, if I set up a public observable property on a component and bind the HTML to it with an async pipe, the value will update as the state updates.
component.ts
public test$: Observable<ChatInteraction>;
[...]
this.test$ = this.store
.select(getNewMessageForChat(this.chat.chatId));
component.html
test: {{(test$ | async).message}}
Then I end up with output like
test: test1
test: test2
etc etc
However, if I try to subscribe to that same observable, it fires once when the subscription is created, but never again as the state updates.
component.ts
this.store
.select(getNewMessageForChat(this.chat.chatId))
.subscribe(() => {
if (this._calculateScrollDistanceFromBottom(this.chatWindowElem.nativeElement) <= 20) {
this._scrollToBottom();
}
});
If I put a breakpoint on that if statement, it does not get hit as I update state.
Any ideas why?
Figured it out. Turned out I had an undefined object reference in my subscription, which caused the subscription to break on its first run and resulted in it no longer firing. This was difficult to catch as it turns out the subscription code has a try/crush in it unless a setting is set.
__tryOrUnsub(fn, value) {
try {
fn.call(this._context, value);
}
catch (err) {
this.unsubscribe();
if (config.useDeprecatedSynchronousErrorHandling) {
throw err;
}
else {
hostReportError(err);
}
}
}
hostReportError didn't bubble anything up so to me it looked like it just wasn't firing anymore.

How to react to javascript CustomEvent from Blazor webassembly component

I have a Canvas element which has some custom functionality written in Javascript. I'm trying to create a wrapper for this functionality in a Blazor Webassembly component.
The Javascript code generates custom events using the dispatchEvent method:
const event = new CustomEvent("uvrectadded", {
bubbles: true,
detail: { uvrect: uvr }
});
const result = this.canvas.dispatchEvent(event);
How to I listen for this event in a Blazor serverside component? I have tried the following:
<canvas id="map-canvas" #uvrectadded="OnUVRectAdded"></canvas>
#code{
private void OnUVRectAdded(EventArgs e)
{
Console.WriteLine("Blazor: ONUVRectAdded");
}
}
In the code above, the directive is not compiled, but I can't find out how to register an event directive.
Aside: I have also tried raising a change event from the Javascript component, and this does get registered by the Blazor code with an #onchange directive, however, an exception is thrown saying the system cannot deserialize the event.
In Blazor, you can register a directive using an EventHandlerAttribute like this:
[EventHandler("onuvrectadded", typeof(EventArgs))]
public static class EventHandlers
{
}
However, I am having some issues deserializing the detail property of the CustomEvent.
Im struggling too. I can see the JS event being raised (have added a JS listener to check is actually raised).
I have registered the event, and defined the C# part of it as well.
[EventHandler("oncustomjsevent", typeof(CustomJSEventArgs), enableStopPropagation: true, enablePreventDefault: true)]
public static class EventHandlers
{
// This static class doesn't need to contain any members. It's just a place where we can put
// [EventHandler] attributes to configure event types on the Razor compiler. This affects the
// compiler output as well as code completions in the editor.
}
public class CustomJSEventArgs : System.EventArgs
{
// Data for these properties will be supplied by custom JavaScript logic
public string EventData { get; set; }
}
But cant get Blazor to see the event anywhere. Have tried adding to divs, buttons, labels, etc still doesnt work., eg.
<Button #oncustomjsevent="OnLinkClick" />
private void OnLinkClick(CustomJSEventArgs eventArgs)
{
throw new InvalidCastException(eventArgs.EventData);
var x = 1;
}

Androidx OnBackPressedDispatcher - how to consume the back button press

I would like the onBackPressedDispatcher to absorb the back button press event. Sometimes but I don't see an option for it. Whats happening is that we are just today trying to upgrade to onBackPressedDispatcher in androidX but we have already overridden onBackPressd in activity. so when our onBackPressedDispatcher calls OnBackPressedCallback afterwards there is also a call to activities onBackPressed override. we don't want that. the onBackpressed should be consumed on the spot. here is what I have so far:
const val TAG = "MyTag"
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_first)
MyTester(this)
}
override fun onBackPressed() {
super.onBackPressed()
Log.v(TAG, "Activities class back button Pressed")
}
inner class MyTester(var activity: AppCompatActivity) {
init {
addBackCB()
}
fun addBackCB() {
var callback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
Log.v(TAG, "inner class MyTester back button")
}
}
activity.onBackPressedDispatcher.addCallback(activity, callback);
}
}
}
Which prints the following:
V/MyTag: inner class MyTester back button
V/MyTag: Activities class back button Pressed
If I don't call super.onBackPressed() then the dispatcher does not even work. it needs that call.
This is because the OnBackPressedDispatcher is called in ComponentActivity.onBackPressed(), if you look at the source code:
#Override
#MainThread
public void onBackPressed() {
mOnBackPressedDispatcher.onBackPressed();
}
So you'll never reach your callback if you override onBackPressed and never call the super method. I think the whole idea behind OnBackPressedDispatcher is that you shouldn't have to override Activity.onBackPressed if you want your fragments to intercept back presses. This is mentioned in one of the guides.
If you really want your activity to handle the back press, you can try something like this in your override:
if (onBackPressedDispatcher.hasEnabledCallbacks()) {
// There's an active callback; let the fragment handle it
super.onBackPressed()
} else {
// Do your activity's back press handling here
}
Be careful doing this, though. After trying it myself, you might find that hasEnabledCallbacks will return true even when you haven't added any callbacks yourself. This is because your FragmentManager has its own back press callback that's going to be enabled whenever backStackEntryCount > 0. I'm not sure if there's a quick workaround for that.

How to bind to document events with Blazor

I am writing a simple snake game with Blazor, but I can't figure out how to bind to document events. I know that it is possible to bind events on different elements such as div or input. Example: <input onkeypress="#KeyPressInDiv"/>, where the handler is public void KeyPressInDiv(UIKeyboardEventArgs ev) {...}.
I suppose that there should be some equivalent to the JavaScript method document.onkeydown = function (evt) {}. I have found two approaches for working around this problem:
Use JavaScript for binding and invoke Blazor code (taken from https://github.com/aesalazar/AsteroidsWasm):
document.onkeydown = function (evt) {
evt = evt || window.event;
DotNet.invokeMethodAsync('Test.ClientSide', 'JsKeyDown', evt.keyCode);
//Prevent all but F5 and F12
if (evt.keyCode !== 116 && evt.keyCode !== 123)
evt.preventDefault();
};
document.onkeyup = function (evt) {
evt = evt || window.event;
DotNet.invokeMethodAsync('Test.ClientSide', 'JsKeyUp', evt.keyCode);
//Prevent all but F5 and F12
if (evt.keyCode !== 116 && evt.keyCode !== 123)
evt.preventDefault();
};
... and in C# implement a static class with methods marked by [JSInvokable] and events. This works, but leads to an extreme delay on every key press.
It is possible to add an input tag and bind to its events. This works much faster than the previous approach, but it seems like a hack rather then a solution. Also, we are not able to listen for some actions, such as Up/Down Arrow.
Is there a direct way to bind to document events from Blazor?
Update 1: I created a simple project to better explain what I want to achieve: https://github.com/XelMed/BlazorSnake
There are 3 implementations of Snake:
Pure JS - this has the expected behavior
Using JS with Blazor - invoke a Blazor function from JS code with a JsInterop
Using input tag - bind to events on an input tag to control the snake
Perhaps add a event listener to the document using JsInterop and assign a anonymus function to the event which calls your C# method with the even parameters.
For example your JsInterop.js:
document.addEventListener('onkeypress', function (e) {
DotNet.invokeMethodAsync('Snake', 'OnKeyPress', serializeEvent(e))
});
with serializeEvent being as follos to avoid some quirkiness:
var serializeEvent = function (e) {
if (e) {
var o = {
altKey: e.altKey,
button: e.button,
buttons: e.buttons,
clientX: e.clientX,
clientY: e.clientY,
ctrlKey: e.ctrlKey,
metaKey: e.metaKey,
movementX: e.movementX,
movementY: e.movementY,
offsetX: e.offsetX,
offsetY: e.offsetY,
pageX: e.pageX,
pageY: e.pageY,
screenX: e.screenX,
screenY: e.screenY,
shiftKey: e.shiftKey
};
return o;
}
};
in your C# code you would have:
[JSInvokable]
public static async Task OnMouseDown(UIMouseEventArgs e){
// Do some stuff here
}
I had the same requirement as you, and I managed to wire up document events (e.g. keydown) to my Razor methods using invokeMethodAsync, but then I found that I missed out on the automatic DOM diffing and updating that Blazor provides if the Razor method changes some state which is bound to an HTML element (e.g. controls the visibility of a div element).
It seems (from my limited understanding of Blazor) that normally Blazor returns whatever state data is necessary to update the DOM, in the return data of a WebAssembly method.
But if you use invokeMethod or invokeMethodAsync from JavaScript, you have to manage returning and consuming this data yourself, but that might be problematic because your updates might conflict with Blazor's view of the DOM state.
So I came up with a hacky approach of generating a hidden button in my Razor view, e.g.:
<button id="arrow-left-button" #onclick="HandleArrowLeftPress"></button>
And then on the JavaScript side, I wired up a document event which finds that button by Id and calls .click() on it:
document.getElementById('arrow-left-button').click();
I know this seems truly awful, but it worked for what I was doing, which was using the arrow keys to move an absolutely positioned element on screen.
If anyone knows a cleaner way (like how to force an update of a Razor view by its name from the JavaScript side, in the handler callback), please let me know.
I encountered similar situation and use the following approach as a workaround. You add event listener to a div tag, then set focus to that div such that your key presses can be captured.
<div tabindex="0" #ref="container" #onkeydown="onKeyDown" id="container">
//your snake game markup
</div>
#code {
ElementReference container;
protected override async Task OnAfterRenderAsync(bool firstRender) {
await container.FocusAsync();
}
private void onKeyPress(KeyboardEventArgs obj) { //your code here. }
}
You may need to remove the focus border of the div tag:
#container:focus-visible {
outline: none;
}
#container:focus {
outline: none;
}
To extend a bit on the answer by Blightbuster
For me at least the returned serialized event object would not cast to its .NET counterpart (KeyboardEventArgs in my case). I had to stringify it first
var serializeEvent = function (e) {
if (e) {
var o = {
//assign properties here
//we can´t just stringify e since it has circular references
};
return JSON.stringify(o);
}
};
And then deserialize in the .NET method called by the JSRuntime
using System.Text.Json;
T deserialized = JsonSerializer.Deserialize<T>(
s,
new JsonSerializerOptions() {
PropertyNameCaseInsensitive = true
});
Also, if you are lazy like me and don´t want to look up the properties you need from the event, you can look up the property names in .NET before calling your js method
var propertyNames = typeof(T)
.GetProperties()
.Where(x => x.CanWrite)
.Select(x => $"{x.Name[..1].ToLower()}{x.Name[1..]}")
.ToArray();
Then pass those to js and build your event object
let o = {};
propertyNames.forEach(propertyName => {
o[propertyName] = e[propertyName];
});
var serialized = JSON.stringify(o);
Finally I did not want to make a static method with the [JSInvokable] attribute, so I just made a little wrapper.
class JSInvokableWrapper<T>
{
Func<T, Task> Func { get; }
public JSInvokableWrapper(
Func<T, Task> func)
{
Func = func;
}
[JSInvokable]
public async Task Invoke(T argument)
{
await Func.Invoke(argument);
}
}
Use that to wrap whatever func you want js to execute and turn the wrapper into a DotNetObjectReference (Maybe in some extension method for the IJSRuntime)
var dotNetObjectReference = DotNetObjectReference
.Create(new JSInvokableWrapper<T>(func));
And call its Invoke method from js
dotNetObjectReference.invokeMethodAsync('Invoke', param);
You can bind the event directly to a C# method, just using the event tag that you need (onkeyup/onmousemove ....) .
#page "/"
<div>
<input type="text" onkeyup=#KeyUp />
</div>
#functions {
void KeyUp(UIKeyboardEventArgs e)
{
Console.WriteLine(e.Key);
}
protected override Task OnInitAsync()
{
return Task.CompletedTask;
}
}

Listen to custom event in flex

I created a custom event with a custom property in actionscript. I can dispatch a custom event (in Main.mxml) with a custom property but I'm not able to listen for this event anywhere else in my application. I'm trying to listen to this event in a custom component.
When I debug I can confirm that the event gets dispatched correctly with the right value.
Snippet of Main.mxml:
var userid:String = result.charAt(4);
//dispatch a custom event in which we store the userid
var event_userid:MyEvent = new MyEvent(MyEvent.COMPLETE,userid);
dispatchEvent(event_userid);
CustomEvent class:
package ownASClass
{
{
//http://stackoverflow.com/questions/1729470/attaching-a-property-to-an-event-in-flex-as3
import flash.events.Event;
public class MyEvent extends Event
{
public static const COMPLETE:String = "MyEventComplete";
public var myCustomProperty:*;
public function MyEvent(type:String, prop:*) :void
{
super(type,true);
myCustomProperty = prop;
}
//override clone() so your event bubbles correctly
override public function clone() :Event {
return new MyEvent(this.type, this.myCustomProperty)
}
}
}
}
I listen to this event in my custom component:
//init gets called as: initialize="init();
protected function init():void
{
//add evt listener for custom event to get userid
this.parentApplication.addEventListener(MyEvent.COMPLETE,getUserid);
//my custom component is part of a viewstack defined in main.mxml
}
protected function getUserid(event:Event):void
{
//do something (but this never gets called)
}
protected function init():void
{
//add evt listener for custom event to get userid
this.parentApplication.addEventListener(MyEvent.COMPLETE,getUserid);
//my custom component is part of a viewstack defined in main.mxml
}
Please try to change above line as follows -
this.addEventListener(MyEvent.COMPLETE,getUserid);
Events typically 'bubble up' the chain from child windows to parent to application(main.mxml).
http://livedocs.adobe.com/flex/3/html/help.html?content=events_08.html
If you are trying to use an event to signal some sub-object you'll need to target it there and have a registered event listener.
Used a workaround for this, realized I didn't really need to dispatch a custom event.
Declared my userid in main application and called it in my component with:
public var userid:String = FlexGlobals.topLevelApplication.userid;

Resources