Triggering updates / refreshes from outside of AngularJS - javascript

I have a web worker whose job it is to periodically poll a webservice for data, and then insert that data into an IndexedDB database.
Is there any recommended way of notifying an AngularJS module of updates to the object stores, since all the changes happen outside of Angular?

Call $scope.$apply() when your web worker finishes. You will want to grab the "root" element of where you bootstrapped your ng-app. For example:
HTML:
<div id="rootElement" ng-app="myApp">
...
</div>
JavaScript:
//Call $apply() to start an angular digest cycle where any updates you made to objects
//angular knows about will update the view.
angular.element(document.getElementById("rootElement")).scope().$apply();
Edit: Including sample onmessage subscribe.
Since your worker is only writing to the IndexedDb, you will need to also use postMessage to get that data back to your HTML (and angular). For example, in your HTML:
var worker = new Worker("pathToWorker.js");
//Start worker in some way
worker.postMessage();
//Use data from the worker to update scope.
worker.addEventListener("message", function (e) {
//Get the data from the worker
var indexData = e.data;
//Update a scope variable (scopevar)
//Note: The element you query with angular.element must have the scope variable you
//are interested in.
angular.element(document.getElementById("MainController")).scope().$apply('scopevar=' + JSON.stringify(indexData));
}, false);
From your worker:
self.addEventListener("message", function (e) {
//Do work to write to IndexedDb
//PostMessage using the indexDb Data so angular can know about it.
self.postMessage({title: "Foo", data: "Bar"});
}
Without seeing your code, I can't give a more specific example, but postMessage from the worker is definitely the way to get data from your worker back to angular.

Related

How to avoid single route in Laravel for Server Sent Events

So I am trying to use Server Sent Events in my laravel app but the problem is that in SSE, you can only specify single URL as in:
var evtSource = new EventSource("sse.php");
The problem is that I want to send events from different parts/controllers of the entire application not just single sse.php file for example. Example:
// AuthController.php
public function postLogin(Request $request) {
// login logic
// SEND SSE EVENT ABOUT NEW USER LOGIN
}
// FooController.php
public function sendMessage() {
// SEND SSE EVENT ABOUT NEW MESSAGE
}
The only idea that comes to my mind is that from these controllers, create temporary files with event text and then read those text files in sse.php file, send events and then delete files. However, this doesn't look like perfect solution. Or maybe hook sse.php file with Laravel events mechanism somehow but that seems kinda advanced.
I also don't mind creating multiple event source objects with different php files like sse.php but problem is that those controllers methods don't necessarily just send events alone, they do other work as well. For example, I cannot use postLogin action directly in SSE object because it also performs the login logic not just sending event.
Has anyone had similar problem and how to tackle with it please ?
Never used SSE but, according to their docs, two solutions come to my mind:
1.Define multiple EventSource objects and handle them differently in your js:
var loginSource = new EventSource("{!! url("/loginsse") !!}");
var messageSource = new EventSource("{!! url("/messagesse") !!}");
2. Inside your sse.php file, check for updates from the others controller:
//sse.php called function
while(1) {
$login = AuthController::postLogin($request);
if ($login) return $login;
$msg = FooController::sendMessage();
if ($msg) return $msg;
// ...
}
You will need to make sendMessage() and postLogin(Request $request) static methods.
Consider also the adoption of some libraries to use SSE with laravel: a quick google search provided me several possibilities.
You could use Laravel's events and create a listener that would combine all the different events from your app into a single stream, and then you'd have a single controller that would emit that stream of events to the client. You could generate an event anywhere in your code and it would be streamed to the client. You'd need some sort of shared FIFO buffer to allow the communication between the listener and controller, listener would write to it, and controller would read it and send SSE. The simplest solution would be to use just a plain log file for this.
Also Laravel has built-in broadcasting feature using Laravel Echo, so maybe that could help? I'm not sure if Echo is using SSE (have zero experience with both), but it does pretty much the same thing...
Updated: Let me try to add an example:
1) First we need to create an event and a listener, e.g.:
php artisan make:event SendSSE
php artisan make:listener SendSSEListener --event=SendSSE
2) Your event class is just a wrapper around the data that you wish to pass to the listener. Add to SendSSE class as many properties as you need, but lets pass just a string message for this example:
public function __construct($msg)
{
$this->message = $msg;
}
public function getMessage()
{
return $this->message;
}
3) Now you can fire this event like this:
event(new \App\Events\SendSSE('Hello there'));
4) Your listener's handle() method will receive it and then you need to push it into a FIFO buffer that you'll use to communicate with the SSE controller. This can be as simple as writing to a file, and then reading from it in your controller, or you can use a DB or linux fifo pipe or shared memory or whatever you wish. I'll leave that part to you and presume that you have a service for it:
public function handle(\App\Events\SendSSE $event)
{
// let's presume you have a helper method that will pass
// the message to the SSE Controller
FifoBufferService::write($event->getMessage());
}
5) Finally, in your SSE Controller you should read the fifo buffer and when a new message comes pass it to the client:
while(1) {
// if this read doesn't block, than you'll probably need to sleep()
if ($new_msg = FifoBufferService::read()) {
$this->sendSSE($new_msg);
}
}

Backbone and pusher - what data to send?

I am working a on backbone application at the moment, that "talks" to an API. A user can edit an organisation for example, the PATCH request will go to the API and get saved to the database, on a successful the API then "talks" to Pusher via this line,
Pusherer::trigger('organisation_'.$id, 'organisation:change', json_encode(array('organisation' => $organisation)));
Basically this telling pusher to trigger an event on the organisation_21 channel, the event that has happended is the organisation:change one, and the data to send is the organisation model.
What happens then on the Backbone side is that that i bind a method on to that channel and when the event happens that method will run, and update the view for the subscribed user.
HOWEVER, the data for my organisations has gotten quite big, the JSON object is 11.8kb, pusher won't process anything more than 10kb, is there a better way to work with backbone, my api and pusher other than sending the entire model?
On suggestion I like the idea, of doing the save, and fetching the model for new data in realtime via pusher. Would that look something like this?
organisationChanged:function(){
var self = this;
this.model.get('organisations').fetch({ //send GET To /api/organisation/id
success: function(model, response) {
self.model.get('organisations').set(response);
}
});
}
Fetch the model and set the attributes returned from the server to those of the model - so far this sounds correct to me yes? The complication comes that the model also contains a couple of collections, will set work on these, or is there a better way?
The edit is the right idea, but with a little changes.
The fetch would automatically change the model. From your code, it looks like your model is a bigger model, and organization is just a sub model. If this is the case:
var organizationmodel=this.model.get('organisations') // This would get the organization model.
organizationmodel.fetch({ //send GET To /api/organisation/id
success: function(model, response) {
// the model, and the organizationmodel both actually should point to the same object
// and they are already changed based on the server returned stuff. so no need to do a set.
// if they are not, you can just set it again.
self.model.set('organizations',model);
}
});

angular variable initialization

I have a variable in angular controller, $scope.abc.
I have Sails as backend.
$scope.abc's initial value can be determined by backend when generating the page. After the page is displayed to user, $scope.abc may or may not be changed by the user.
I can have backend to generate a complete static page and let angular query the backend for initial value of $scope.abc. I feel it is not necessary as the initial value can be determined at the page's generation and should come as part of the page.
The question is: how to initialize $scope.abc when the webpage is being generated? Is there any way to provide data to angular js file, similar to res.view("",data)?
You don't really have many options here. You can issue one more ajax request and load data. However it my be not ideal if during index page loading the data is already available. In this case you can inject it into additional script tag and access it from from there.
<script>
// Make sure data is JSON encoded.
window.appConfig = <%= data %>;
</script>
<script src="angular.js"></script>
<script src="app.js"></script>
And then I would set a constant service in the app to store data in it:
angular.module('app').constant('appConfig', {data: window.appConfig});
This way you could use it later in more Angular way, for example:
angular.module('app').controller(function($scope, appConfig) {
$scope.config = appConfig.data.userId;
});
And if you are concerned with presence of the global variable appConfig, you can remove it in app run block, when constant is already set.

Using AngularJS to process custom localStorage data

I wrote a bookmarklet that retrieves information from a page and stores it in JSON format in local storage (converting it to a string first, of course).
I would like a web app I am writing to be able to process this data, on the fly, preferably as it gets saved to the localStorage.
Right now i can change the item in LS via the console and refresh the page and the new data appears but I would like it to be live and seamless.
Any advice on how to go about this? I found several localStorage modules for angularJS and I tried them but they don't seem to allow me to retrieve from LS if the data is already there in LS.
In response to answer:
$scope.$watch(
function(){
return $window.localStorage.getItem('TestData');
},
function(newValueInStorage){
$scope.testingLS = newValueInStorage;
}
)
I tried this and I still get the data displayed by just doing a {{ testingLS }} in the view template but when I go and change the TestData key in local storage via the console it doesn't update instantly. (for now, I am just testing it without the bookmarklet with just a simple string inside TestData
There is few ways to do it
One of will be to populate correct model on scope when saving to localStorage
The other that I can think of at this moment is to setup watcher
$watch(
function(){
return localstorage object
},
function(newValueInStorage){
$scope.modelFromLS = JSON.parse(newValueInsStorage)
}
)
---edit---
as per James comment you need something that will handle the fact that data has changed in different tab and $digest process need to run for watch to be recalculated
http://plnkr.co/edit/zlS3wL65meBeA8KkV5KH?p=preview
window.addEventListener('focus', function(){
console.log('focus')
$scope.$digest()
})

Sending extra, non-model data in a save request with backbone.js?

I'm looking for a solution for dealing with an issue of state between models using backbone.js.
I have a time tracking app where a user can start/stops jobs and it will record the time the job was worked on. I have a job model which holds the job's data and whether it is currently 'on'.
Only 1 job can be worked on at a time. So if a user starts a job the currently running job must be stopped. I'm wondering what the best solution to do this is. I mean I could simply toggle each job's 'on' parameter accordingly and then call save on each but that results in 2 requests to the server each with a complete representation of each job.
Ideally it would be great if I could piggyback additional data in the save request similarly to how it's possible to send extra data in a fetch request. I only need to send the id of the currently running job and since this really is unrelated to the model it needs to be sent alongside the model, not part of it.
Is there a good way to do this? I guess I could find a way to maintain a reference to the current job server side if need be :\
when you call a save function, the first parameter is an object of the data that's going to be saved. Instead of just calling model.save(), create an object that has the model data and your extra stuff.
inside of your method that fires off the save:
...
var data = this.model.toJSON();
data.extras = { myParam : someData };
this.model.save(data, {success: function( model, response ) {
console.log('hooray it saved: ', model, response);
});
...

Categories

Resources