Angular 2 - single service provider across multiple browser windows - javascript

I am trying to supply an alert once a task is complete - the user may be in any of multiple pages at the time. The alert should display to all pages.
I am using a service implementing BehaviorSubject
The provider for which is in my app.component.ts page - single instance
In my app.component.html I have the two components, one the alert, the other that fires the alert.
<alert></alert>
<submit-service></submit-service>
The service emits to the alert component which renders the alert.
This works fine, but only ever on the page that submits the service (not to any other page) - submission function is also in the alert component.
submit-service utilises
public emit: BehaviorSubject<model> = new BehaviorSubject(new model());
Once the event is completed it then fires off this.emit.next(_model);
In the alert component I subscribe to the event
ngOnInit(): void {
this.service.emit.subscribe(data=> {
this.fireAlert(data);
}
});
}
so I suppose the main question is, how do I have a single service subscribed across multiple instances, across multiple pages?
EDIT 1
Apologies for the ambiguity, by page I mean separate browser window or tab i.e. window.open

Just in case others are having this same issue - there is in fact an answer.
Using global storage events allows the traversing of information across all browser tabs/windows.
In the service instead of using BehaviourSubject, the service updates or 'emits' the data to a local storage item, event listener utilising a HostListener() decorator can then listen for these udpates - which listens across all windows and tabs.
Example:
#HostListener('window:storage', ['$event'])
onStorageChange(ev: StorageEvent) {
console.log(ev.key);
console.log(ev.newValue);
}
See here for further information: Storage events

So there's a couple things at play here. The first is the service that let's your application know that it's time to display the alert. It sounds like you already have that, but for simplicity sake I would make sure you are declaring that in a forRoot() context. I won't go into a crazy amount of detail regarding this topic, but essentially you need to make sure that your service is running in the root context. If you start lazy loading modules, and then subscribing to your service from within the lazy loaded module, it will create it's own Dependency Injection context and you'll start pounding your head against the table wondering why your service isn't updating. (been there :)
The next thing to look at is where you want to render your alert. You'll likely want to use the ComponentFactoryResolver to render your alert in the highest level component you can think of that makes sense. Basically (if I understand your need correctly), you need this to be within the same component, or higher as all of the pages you want to have the alert rendered to. For example I am working on an application that has a dashboard where we have a ComponentFactoryResolver that renders any and all modals we might need throughout the application. This allows us to call modals from anywhere within the dashboard using, like you, a behavior subject that activates the modals. Here's a great article on using the ComponentFactoryResolver.
Update 1
So after realizing that "page" was actually a new browser window this method won't necessarily work. Using BehaviorSubjects will only update within the application context, so opening a new window creates a new application context, i.e. killing the BehaviorSubject of being a viable candidate to make this work. You'll need to have a service that is not instance specific. Web sockets as you mentioned would be a good alternative.
It is worth noting though that if it's possible to refactor the code to open modals instead of new windows, you could maintain the integrity of your Dependency Injection tree, and then use BehaviorSubjects to achieve this. Otherwise you'll need something outside of the application that is maintaining state.

Related

Transfer alerts or notifications to next route with Angular 5

I am building a CRUD (Create, Read, Update, Delete) form with Angular 5 and want to navigate to the route showing an overview of all already added elements including the new one.
this.myElement.create({
//... Do some API magic
}).subscribe(() =>
this.router.navigate(['/overview']);
);
After updating the database the user gets router.navigate'd to the overview. There should be displayed a message (alert, notification, toast or what ever) whether the change was successfull or not.
Possible aproaches:
Do I have to implement an own service to transfer informations like this one?
Is there a built-in solution in Angular?
Should I use something like an event handler or oberserver watching for any CRUD methods?
You need to be careful where you implement this alert. If it's a service, you might do
.subscribe(() =>
this.toast.show('Task is finished');
this.router.navigate(['/overview']);
);
Since you are switching routes, the toast component has to be in the parent templates of <router> or up.
If the service is designed targeting the body tag, ex. using position: fixed. Then you are safe no matter what.
I don't think the entire page will get refreshed if that's your concern.
Just to answer your question
Do I have to implement an own service to transfer informations like this one?
YES, not necessarily you, but someone.
Is there a built-in solution in Angular? Maybe, because you can just put an element that positioned to the screen as a simple alert. Angular doesn't do anything specific for this task.
Should I use something like an event handler or oberserver watching for any CRUD methods? No need, you want to finish this alert before moving to others. However if you want others to know an alert has been fired, then YES, you want that service to publish an observerable.

What is the best place to put a JavaScript listening function in Angular component?

TL;DR:
I am rendering a BioDigital HumanAPI anatomical model in my Angular 5 app within an iFrame. I instantiate API object using:
this.human = new HumanAPI(iFrameSrc);
There's an API function human.on(...) that can be used to register click events from within iFrame (like picking objects from the model, etc.). I need this function to be able to listen to the events at all times. I do the object instantiation and put this function within ngOnInit() and it works, but when I change the source of iFrame to render a different model, this function stops working. Where should I put this listening function so that its logic is available at all times?
Longer version:
I am developing an Angular app using BioDigital HumanAPI. The basic idea here is that HumanAPI provides several anatomical models which can be rendered in a web-app using an iFrame (an example here). The src of this iFrame is a link, something like:
https://human.biodigital.com/widget?m=congestive_heart_failure
Since I want the user of my Angular app to be able to view several of such models, I have a list of these URLs, and based on user's selection, I update the src of iFrame, using a function updateFrameSrc which has following code:
iframeSrc: SafeUrl;
this.iframeSrc = this.sanitizer.bypassSecurityTrustResourceUrl(newUrl);
Finally (the question is coming, please stay with me), in order to manipulate and register different click events and user interactions with the model within the iFrame itself, we make a HumanAPI object like this:
this.human = new HumanAPI(iFrameID);
This lets us use API event listener functions like human.on('scene.picked') to register and save click events (like shown in the example I referenced above). All of this is working fine.
The problem is that since I initialize the human object in the ngOnInit() function and also put the human.on('scene.picked') function there, I cannot register the click events after the iFrame source is changed. As I understand it, ngOnInit() is only called once when the component is first initialized, so may be the listening logic of human.on is not available after updating the iFrame source? I have tried placing the logic in different life-cycle hooks but its doesn't work.
My current work-around is to re-call the ngOnInit() function after updating the iFrame source, and it works that way, but I believe this is against the standard life-cycle management practices.
My questions are:
It is acceptable to re-call the ngOnInit() function from within the component logic?
If not, where should I place a JavaScript API function that listens to click events from an iFrame at all times, even after the source of that iFrame has been changed?
As suggested in an earlier comment, you can just move the code in ngOnInit() to a separate function and call that function from both ngOnInit() as well as your update function.
Don't forget to re-initialize the human object of HumanAPI in that function as well, when updating the iFrame source.
Re-calling ngOnInit() should be avoided as it circumvents the acceptable functionality of lifecycle hooks, as mentioned by #iHazCode.
If you are looking for near real time you will want this to occur in the NgOnChanges life-cycle hook. Be advised this is expensive.
If slightly less "near real time" is acceptable, I would advise wiring up a rapid delay subject Observable.Interval(500) (also, but slightly less expensive) at the time of Component initialization NgOnInit.
Please DO NOT circumvent the hooks by re-calling ngOnInit.
If you have additional questions let me know.

Update the DOM based on click event using service

I'm trying to update the DOM based on a click event, by using a service.
Specifically, I'm trying to use bootstrap's alerts to alert the user when they click on a particular button on the page. I'd like to be able to call notify.createNotification("Saved successfully") for instance, to tell the user that they have saved successfully.
I'm using ui-router and have managed to abstract the notification to a (isolate scope) directive and the accompanying template. The notification currently shows, updated, at the head of my application (all other views inherit this view) upon $scope.createNotification() (from within the click event). This works because the child views inherit the $scope property. Clearly though this pollution and abuse of the inheritance of the $scope is not ideal, hence I'd like to move it all into a service.
I've got as far as trying to use a factory to update the notification object, which contains state information for the alert, i.e. display:true/false, text etc. The trouble with the factory is it just returns where it's called from, I need to be able to update the parent.
...I feel I've done 9/10ths of the work on this, but that last 1/10th is really puzzling me.
Any help would be greatly appreciated.
Note: Guess I'm also looking for a 'best practices' here too, I mean $scope pollution works, but it's far from ideal. Thanks
This could be solved using pub/sub approach.
Create NotificationService that is used to send notifications. For callers it would look like NotificationService.alert({text: '..', ...})
Create <notification-area> component that subscribes to NotificationSerivce and displays notifications sent from anywhere.
NotificationService itself should implement pub/sub interface. Use any implementation of EventEmitter (like this one) or even angular.element to provide on(), off(), trigger() methods.

(another) request for clarification about the meaning of "singleton" in Angular

I'm learning angular, experimenting with different ways of using services/factories, and am trying to wrap my head around the unique sense in which they are "singletons".
I have an API service that exposes domain models and wraps functionality for retrieving them from my REST server. This service can be easily comprehended as a singleton in the classic sense: I want a single instance to be shared across my application, with a state that can be observed by many different controllers, enabling those controllers to "synchronize" with each other through the conduit of the service: The controllers stay in sync not by being aware of (and thus coupled with) each other directly, but by being aware of this common service. (side question: is this a correct characterization of the role of a service?)
This is a use-case where a singleton service is clearly and unambiguously appropriate. But:
One of the domain objects that gets returned by the API service is called thread, which is essentially a wrapper around a linked-list of points. In addition to the list of points, a thread has a currentPoint variable and a next() function, which pops the next value from the list and makes it the currentPoint.
My UI visualizes a thread simultaneously in two different ways - in two separate elements with their own controllers and directives. Each of these elements contains a button that, when clicked, calls nextPoint() and thereby changes the state of the thread. When this button is pushed in one element, the state of the thread needs to be updated in both elements - so, here again we have a situation where a service seems ideal.
But, at any given time, there can be an arbitrary number of threads being displayed, each of which should be independent and unaware of each other - which conflicts with (what I understand to be) the "classic" sense of "singleton".
Is there a word for this sort of thing in angular?
I've experimented/looked into this enough to know that it's certainly possible to create these "non-singleton singletons" using factorys, but surely there must be a better term for them than "non-singleton singletons" - I cringed both times I just typed it.
Second, what is the best way of implementing one? Can the strategy illustrated below (which I found in an angular github issue here) be regarded as a best practice?
myApp.factory('myServiceProvider',function(){
var serviceProvider=function(){};
serviceProvider.prototype.foo=function(){};
var service = {
getInstance:function(){ return new serviceProvider(); }
}
return service;
});
//Singleton Services
myApp.factory('myService',['myServiceProvider',function(myServiceProvider){
return myServiceProvider.getInstance();
}]);
//Where non-singletons are required
function myController(myServiceProvider){
var instance_a=myServiceProvider.getInstance();
var instance_b=myServiceProvider.getInstance();
};
//Where singleton service is required
function myOtherController(myService){
myService.foo();
};
An angular singleton is not exactly the classic. A singleton in angular is basically one instance of an object that holds a single state. That state can be data bindable via a services allowing for value sharing. This means that you can have a service property that is bound across all of its uses an app; one change will be reflected everywhere. The singleton persists through the life of the app. If the app is refreshed the singleton will loose value until it is set again by the app via a user interaction or a storage retrieval.
I like the pattern you are using. I also tend to use it. Here is an example of a web storage service I recently made similar to your service, https://github.com/breck421/WebStorage/blob/master/src/js/WebStorage.js.
I hope this helps and feel free to continue this dialog :)
Thanks,
Jordan

Durandal Event in widgets

So I have yet another DurandalJS question.
So I have a few widgets that are pretty much self contained. They render or hide themselves depending on whether the current user is logged in or not e.g. I have a widget that displays the current users name, and another one that displays some setting for the current user. The 'current user is a value stored in local storage so everyone basically knows to get it from there and do their bit.
I have a security module which triggers an event on itself when a user is logged in and when a user is logged out.
All my widgets including shell require this security module and they all handle the event.
Now I know the event is working because shell's event handler gets called but the widgets never see the event even thought they are displayed in shell.
However, if I do a hard refresh of the page (whether the user is logged in or out), all the widgets render properly so I know the widgets know what to do.
Am I doing this wrong? If yes how best do I go about it.
Thanks
The problem was that I was returning singletons from my widgets. I have no idea what the difference was but I never got the events when I returned singletons. Durandal expects widgets to be constructor functions so that it can instantiate multiple widgets of the same kind see here
viewmodel.js is a function exported module that will serve as a location for all your widget's code. It will be bound to view.html by the widget infrastructure via the composition module.

Categories