Rebinding a page in AngularDart when the data is loaded via SignalR - dart

I've got an AngularDart application that is working, but I feel like I'm doing it wrong...
The application shows a list of events as they are received. The events get to the app via SignalR, but I don't think that's really relevant - the issue is the way I'm having to update the component's state in order to see the changes in the state get displayed on the page.
Here is a cut down version of my component:
#Component( selector: 'liveEvents', templateUrl: 'live_events.html', useShadowDom: false )
class LiveEvents implements ScopeAware {
VmTurnZone _vmTurnZone;
EventReceiver _eventReceiver;
LiveEvents( this._vmTurnZone, this._eventReceiver );
List<Event> events = new List<Event>();
void _onEventReceived(Event event) {
//TODO: This just does not seem right...
_vmTurnZone.run(() => events.add(event));
}
void set scope(Scope scope) {
var _events = _eventReceiver.subscribeToAllEvents( "localhost", _onEventReceived );
}
}
The EventReceiver class is responsible for connecting to a SignalR server, receiving messages from that server and converting them into Event objects, and then calling whatever function was specified in the subscribeToAllEvents (in this case, _onEventReceived)
The angular template html is very simple:
<ul>
<li ng-repeat="event in events">Event: {{event.Id}}</li>
</ul>
This all works fine, but the bit I don't like is having to inject a VmTurnZone object into the component, and to have to update the events property from within a call to run on that VmTurnZone. It just seems a bit overly complicated to me. But if I DON'T do it in the VmTurnZone.run method, when the events property gets updated, the view is not updated to reflect that change. Only from within VmTurnZone.run does it work.
Is there a simpler way to do what I'm after here? I've looked at adding a watch to the scope, but that didn't seem to work (and looks to me like it should be used for changes that happen the other way around - ie, the view updates the scope, and you want to do something when that happens).
Can any AngularDart experts let me know if there's a better way to do what I'm after?

https://api.dartlang.org/apidocs/channels/stable/dartdoc-viewer/dart:async.Stream
You could add your events to stream and then listen for these and signalr will see these changes, for example:
#Component( selector: 'liveEvents', templateUrl: 'live_events.html', useShadowDom: false )
class LiveEvents implements ScopeAware {
EventReceiver _eventReceiver;
LiveEvents( this._eventReceiver );
List<Event> events = new List<Event>();
void set scope(Scope scope) {
subscribe().listen((event){
events.add(event);
});
}
Stream<Event> subscribe(){
var streamController = new StreamController<Event>();
_eventReceiver.subscribeToAllEvents( "localhost", (event){
streamController.add(event);
});
return streamController.stream;
}
}

As far as I know this is necessary when the source of the change is outside of the default Angular zone (for example some JavaScript library) for Angular to recognize model changes.

Related

Import googlemaps via importmaps in Rails 7

What is the way to go to load GoogleMaps via importmaps in Rails7?
I've seen folks import it via the script-tag like this:
<script src="https://maps.googleapis.com/maps/api/js?key=<%= Rails.application.credentials[:p10_google_maps_js_api_key] %>&callback=initMap" async defer data-turbolinks-eval="false"></script>
but I would like to take advantage of the new importmap feature of Rails 7. Unfortunately I don't understand how i could trigger the initMap callback without using it as a script.
So there is a Stimulus component that you could use stimulus-places-autocomplete. However, you could easily implement this yourself and save you the trouble of pulling in a dependency.
At the end of the day, the google callback needs to be fired off. The way most have gotten around this is by creating an event and attaching that to the window. You then add a data-action to your views controller div that will look for this even and fire a callback of its own. That callback being an initializer within your Stimulus controller itself.
##########places_controller.rb##########
import { Controller } from "#hotwired/stimulus";
export default class extends Controller {
static targets = ["street", "city", "state", "zip"];
connect() {
// Can also initialize this with a CustomEvent
const event = new Event('google-maps-callback', {
bubbles: true,
cancelable: true,
})
window.dispatchEvent(event);
}
// additional actions
}
##########index.html.erb##########
<div
data-controller="places"
data-action="google-maps-callback#window->places#initMap"
>
<%# view code here %>
</div>
This still is not a solution that I love. However, its the workaround that we have for now.

Trigger AngularComponent-constructor on startup with PreLoadingStrategy

The goal: trigger a component residing in a module, so my subscription in the ctor of the component is activated.
I'm using PreloadAllModules as a preloadingStrategy. But it's not happening.
I need to subscribe to some events in de constructor of my FriendsComponent.
the setup is like this:
FriendsComponent is shown in the template of the SocialComponent, which is part of the SocialModule.
social.component.html
<div>
<friends-component></friends-component>
</div>
the SharedModule declares the FriendsComponent.
AppModule imports SharedModule,
RouterModule for AppModule is like this:
{
path: 'social',
component: SocialModule,
children: [
{
path: 'friends',
component: FriendsComponent
}
]
},
I think the problem is because the FriendsComponent is not part of a router-outlet?
Can it be done without a router-outlet?
If a module would be pre- or eager loaded, would it automatically trigger the constructors (and the subscription)?
Is the issue with my preloading strategy?
I have tried adding: data:{preload:true} to the paths declared in routermodule.
Everything works fine, when the user activates the SocialModule (for instance by clicking on a button with a routerLink to social/friends), but I want it activated on startup (just not shown on any html)
I'm working with Angular Ivy, but think I'm still missing the points. Any help is appreciated
You need to handle your initial subscriptions in a service and have the component subscribe to that service. You won't need to touch the routes. It what services are for.
You subscribe to the value you need in your FriendService and have FriendComponent subscribe to your FriendService.

Listening to JS CustomEvent in Dart

I know that my question is not new, but all solutions I've found here and in the Internet are not working :( Or, I'm doing something completely wrong.
I need to create a communication between Dart and JS, and I would love to use events, as the idea seems to be neat and simple.
So, I tried this tutorial: https://dart.academy/easy-dart-js-interopt/
My JS code:
var customEvent = new CustomEvent("fromJavascriptToDart");
window.onload = document.dispatchEvent(customEvent);
document.addEventListener("fromJavascriptToDart", test, false);
function test() {
console.log("Listening");
}
The event is dispatched, as I see Listening in console log.
But when it comes to dart, nothing is working.
I've tried the following methods and everything failed:
document.on['fromJavascriptToDart'].listen((CustomEvent event) {
print("HEY! I'M LISTENING!");
});
window.on["fromJavascriptToDart"].listen((e) => print( "HEY! I'M LISTENING!"));
window.on['foo'].add((e) => print(e.detail)); //This is not working, as there is no add method in Stream Event anymore
#Listen('fromJavascriptToDart')
void eventTest(){
print("HEY! I'M LISTENING!");
}
Any help is mostly appreciated.
DartPad example
document.on['fromJavascriptToDart'].listen((CustomEvent event) {
print("HEY! I'M LISTENING!");
});
Works fine.
#Listen() is Polymer specific
add doesn't exist (listen as show above should be used)
the event doesn't seem to reach window, but I'm sure this will behave the same in pure JS.

How to remove "on-click" from a custom Polymer component

I have a custom button component done in Polymer Dart:
<div id="buttonDiv">
<my-button id="traceButton"
mode="icon" faicon="fa-comment-o"
toolTip="Print a simple comment"
disabled="false" on-click="{{ traceSomething }}">
</my-button>
</div>
I'm trying to copy/paste this button somewhere else. So a user defines it somwhere, and I basically move it by way of getting $['buttonDiv'].children then inserting it somewhere else. The problem is that {{ traceSomething }} is now irrelevant since it's not part of the new parent. I get errors saying that the parent object, which is another polymer component doesn't have an instance getter "traceSomething".
My question is, is there a way to remove "traceSomething" before I insert it somwhere else? I tried removing the "onClick" event listeners, but the buttons still wants to call that function upon click. Also, I've tried adding a preventDefault, etc, like in: In Dart, if I listen to a click event with two listeners, how do I know which happens first?
But, no luck.
I'm not sure what you mean by copy/past. Do you clone the element, or do you just append it to some other elements children.
Anyway, I don't think you can remove the event listener if it was added declaratively. If you add it imperatively it is easy to remove and readd later.
import 'dart:async';
...
StreamSubscription subsc;
#override
attached() {
super.attached();
subscr = onClick.listen((e) => (this.parentNode as ShadowRoot).host.traceSomething(e));
}
#override
detached() {
super.detached();
if(subscr != null) {
subscr.cancel();
}
}
See also https://stackoverflow.com/a/22168745/217408 about accessing the parent of a Polymer element (for Dart Polymer <= 0.16.x)

Event when an Component is added to the dom

Say I have a AngularDart component that adds a div and an iframe to that div as it's template.
I have the element passed for the outer component in the components constructor
#Component(
selector: "input-html",
templateUrl: "packages/myproject/components/inputs/html.html",
useShadowDom: false
)
class HtmlComponent implements ShadowRootAware {
HtmlComponent(NgModel ngModel, Element element):super(ngModel, element){
}
}
I have shadowdom turned off because I'm using Bootstrap for styling and want the elements easily accessible for the bootstrap css.
My template code is along the lines of
<div>
<iframe id="my-iframe"></iframe>
</div>
It's more complicated than that, there's a bunch of buttons etc, as I'm porting a javascript html editor to angulardart.
My problem is, I need to get the iframe element, but whenever I query element.querySelector("#my-iframe") or even window.document.querySelector("#my-iframe") the object is null. I believe this is because the template hasn't been added to the DOM yet.
I need the iframe object because I need to set the iframe content for the HTML editor to work. There's a few other areas of my project that I wanted to get the template dom objects but couldn't either.
I've tried onShadowRoot, which worked in AngularDart 0.14 but no longer works in 1.0. I've tried ScopeAware and querying for the iframe when the scope is set, but that didn't work (ScopeAware fires before shadowroot event).
I have a hack that's messy that works, by using ng-show="init()" and in that init method I have
bool _initDone = false;
bool init() {
if(_initDone == false) {
iframe = element.querySelector("#my-iframe")
_initDone = true;
}
return true;
}
Which works, but it's messy and I don't like that solution and obviously isn't the correct way to do it.
Anyone know how I can achieve this in AngularDart 1.0?
I think onShadowRoot is the right place for the code to query the element. If it really doesn't work wrap it in a Future to add it as a task at the end of the event queue to delay it a bit more.
onShadowRoot() {
new Future(() {
querySelector(...);
});
}

Resources