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
Related
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.
I am building an app in angular, which consumes different APIs and Gives options for the user to select it will be recorded and sent back to the server.
I have designed it as follows.
All the common logic in Main Controller and all other options in different controllers as the child of main controller.
Main Controller retrieve all the data that are required to run the app.
which is consumed by all other child controllers.
To make sure data is loaded I am using promise attached to scope. So all the child controller will know data loaded.
I have moved data updation part of all child controllers to main controller
because all the updates happen in one object.
Child Controller emit/broadcast to communicate between child and main. So when update happens child will emit an event with data which will be captured by Main and it will do the update.
MainController {
$scope.loaded = DataService.get();
$scope.userOptions = {};
$scope.$on('update',function(){
updateUserOptions();
})
}
ChildController {
$scope.loaded.then(function(){
//all logic of child controller
}
$scope.onselect = function(){
$scope.$emit('update',data);
}
}
Questions
Is it a good practice to use events between controllers ?
is it good to use promise attached to scope for child controllers ?
Will it improve my code if I start using services ?
I will try to answer your question based on my own experience. Recently I've built a single page application and I've refactored its architecture.
Here are my answers:
Is it a good practice to use events between controllers? IMHO, it is the most flexible way to share information between all controllers even if they have isolated scope (using $broadcast or $emit for example). This is called the Observer design pattern. However, you can use services instead of events to share data between them. If you are going to use $rootScope, be careful as all the $scopes inherit from $rootScope.
is it good to use promise attached to scope for child controllers ? Firstly, you have to learn about how scope inheritance works. You have to take care to avoid property shadow in JS. Secondly, I would move out all the logic from scope.loaded in ChildController to a service such as ChildService. Keeping the business logic (such as request, etc) in Services instead of Controllers, will ensure it can be re-used.
Segregation of business logic is good design principle.
Will it improve my code if I start using services ? I answered this question above.
In addition, in order to build a good architecture I've read this angular style guide written by John Papa.
I recommend the following changes:
To make sure data is loaded I am using promise attached to scope. So all the child controller will know data loaded.. Instead I would emit a custom 'loaded' event in the MainController using $scope.$emit('loaded'). After that, in the ChildController I would use $scope.$on('loaded', function(){}) to handle the event.
I would move the updateUserOptions function to a service and inject the it into just the controllers that need it.
I hope that helps!
Is it a good practice to use events between controllers ? Not as the main form of data sharing, but you can use it to notify about system events, such as data ready.
Is it good to use promise attached to scope for child controllers ? Don't use scope inheritance, it causes lots of annoying problems.
Will it improve my code if I start using services ? Yep.
This is what I would do in your place:
dataService - this service is responsible for all data coming in / going out. Whenever a request for data is made (no matter which controller asked for the data), the service caches the data (save the promise is good enough). All further requests get the cached data unless they specify they want a fresh data. Whenever data is updated (1st time or refresh), the service broadcasts a 'dataReady' event via $rootScope to which the main controller and other subscribers can listen.
The service is also responsible for data updates, and when the data is updated you can also broadcast an event via the $rootScope.
When the event is activated, all subscribers query the service, and get the data they need.
Controllers - avoid controllers, use directives with isolated scope, and pass the data between them using attributes. In this way you can be sure that each directive gets what it needs, and not everything. The directives can communicate using attributes, services, broadcast / emit or require their parents / siblings if they work closely together.
Is it a good practice to use events between controllers ?
No it's not, it will be deprecated by Angular JS 2.0. It also often leads to unmanagable tangle of events which are hard to understand and debug. Use services to share data between controllers. (Inject same service into multiple controllers, service then holds data, controllers bind to that data and are automatically synchronized) I wrote a blog post explaining this use case.
Is it good to use promise attached to scope for child controllers ?
No it's not. Use promises and resolve data in services. Don't use $scope at all but use controllerAs syntax instead. $scope was deprecated also in Angular JS 1.X because it's usage leads to many different problems with scope inheritance.
Will it improve my code if I start using services ?
YES! Use services for all logic and data manipulation. Use controllers only for UI interaction and delegate everything to services. Also use ui-router for managing state of your application.
I'm not going to answer your questions directly as I have some other comments as well. I think the approach you mentioned is not the best way to build angular applications.
All the common logic in Main Controller and all other options in different controllers as the child of main controller.
It's against all angular style guides to place common logic in controllers. Controllers should only be used for the logic related to the view (data binding, validation, ...). Because the code inside a controller is not reusable, the less code you have in a controller the better. The more logic you have in services, the more scalable your application becomes.
Fix: I suggest you create a service that retrieves data from the server, and inject this service in controllers as you need. Notice also this way offers better dependency management as you can keep track of which controllers need which services exactly.
Nested controllers should be avoided when possible, because angular keeps track of all the active scopes and re-evaluates them in every $apply() loop.
Fix: same as #1, use services instead of the main controller.
To make sure data is loaded I am using promise attached to scope. So all the child controller will know data loaded.
Using a promise for data retrieval is a good practice. But, again, keeping it in a service is much cleaner than main controller.
I have moved data updation part of all child controllers to main controller because all the updates happen in one object.
Child Controller emit/broadcast to communicate between child and main. So when update happens child will emit an event with data which will be captured by Main and it will do the update.
Fix: use a service with an update function instead of events. Events are harder to debug and track. And you need to unregister event handlers on destroying a controller. If you can use a function/promise instead of events, then it's usually a better idea.
Is it a good practice to use events between controllers ?
A problem with your current set-up is that you're implicitly relying on the hierarchy of your controllers (the fact that one is the child of the other) - because you emit the event, only scopes higher up on the hierarchy can catch it. Besides being an implicit connection (that a developer has to remember), this also limits he extendability of this feature.
On the other hand, if you injected a shared service into all the controllers that need it, the connection between the controllers would become explicit and documented, and their scopes' position in the hierarchy independent. This will make your architecture easier to maintain, with the added benefit of also being easier to test, for one.
You can still implement an observer pattern with a service.
is it good to use promise attached to scope for child controllers ?
The issue of polluting scopes pointed out in other answers is valid. This is one of the reasons why it's better to limit the number of objects you attach to your scope, and to use objects as bundles of variables on your scope instead of attaching all the variables to the scope directly.
(For an explanation of these reasons, see discussions about "always having a . in your bindings".)
(Of course, don't do this blindly just to reduce the number of variables, try to find semantic connections between variables that might be bundled together sensefully.)
Will it improve my code if I start using services ?
I think the above answers already outline the answer for this: yes. There are other benefits too, but this format is not best for too long answers, so I won't list anything else now.
All in all, these above pointers are not big issues with your code currently, but if you're looking for the best architecture, I think you can do better.
Answers:
No, it will be deprecated soon.
$scope is deprecated already.
Services is a great choice. Services allow us to share data and behaviour across other objects like controllers.
I'm new to Flux/React and I'm having a hard time understanding some of the fundamental architecture decisions:
I know all stores are meant to be singletons, but are they all created at app start, or can the lifetime of a store be smaller, specific to the user's actions?
Can I have multiple instances of the same store type, each initialized with a different context?
Unfortunately, all the examples I've seen seem too simplistic to answer these questions. Let's start with Facebook's chat app example. There are multiple threads each with messages. MessageStore holds all the messages for the entire app and a method called getAllForThread(id) returns a filtered subset of messages. When a message comes into ANY thread, it emits a change notification that causes the MessageSection react component to re-fetch data (regardless of which thread the user is viewing). This obviously doesn't scale. What if we had 10,000 threads each with lots of message activity? Here's how I decided to solve the issue:
Each MessageStore is initialized with a thread id.
Create a singleton MessageStoreFactory that creates and manages MessageStores.
When the user clicks on a thread, instead of the React component subscribing to a global MessageStore, it asks the MessageStoreFactory for the MessageStore for that specific thread.
If the factory already has a MessageStore for that thread, it returns it. Otherwise it creates one, kicks off an async task to fetch the initial data for it, and returns it.
When the React component is torn down (let's say the user navigates away from it), it notifies the Factory that it's all done with the Store. Using reference counting or some other cache logic would allow the Factory to prune unused stores.
How far off base am I with this approach? Is there a simpler approach that still scales?
It seems easier to make smarter data fetching according to the thread which user is viewing. Could I see this facebook's example at some blog-post or presentation?
I am fairly new to Angular and trying to build an Angular application.
I have a lot of data that needs to be used by multiple controllers throughout the app. As I understand it, that is the perfect situation to use a service.
I am planning on storing this kind of data in services. For example I plan on having a users service which all controllers that need user data will inject.
I would like the users service to hold the master list of users and any controller that needs users to just use the one instance of service list.
I am having trouble envisioning the pattern though. I mean:
1) What is the standard way of having the service refresh its data from the server? I realize that I could just go and request the entire list of users every 10 seconds from the server but that seems kind of heavy weight...
2) Ideally I would like to be passing around only a single instance of each user. This way if it gets updated in the service, it is sure to be updated in all of the controllers. I guess the other option is to have the service broadcast an event every time it updates a user? or to use watchers?
3) What is the pattern by which the controllers interact with the service and filters? Do the controllers just request data from the service and filter it in the controller? The other option is to have the service do the filtering. If so how do I communicate the kind of filtering I need done to the service?
I think that by using some kind of solid pattern I can take care of alot of these issues (and more that I am sure will arise). Just looking for advice on some common patterns people employ when using singleton services.
Thanks in advance.
Answer to point 1. A service is just a singleton. How you store and refresh data into it has nothing to do with its nature. Not sure why you want all user data inside a service (unless you are building a user management app), but you could use several techniques like polling (eg. using $timeout ask for new users and append them to the existing ones) or push (eg. socket.io/signalR which will push you the payload of new users when available). This can be done both inside the service itself or by a controller that will add/remove data to the service (eg. a refresh users button in the UI)
Answer to point 2. You can bind/use the reference of the data inside the service directly into your controllers using a getter so that changes to the data are shown instantly (given that are two way binded, if not use events).
Answer to point 3. You can apply filters inside the controllers or in the view it self (not recommended). You can also have a function in the service where you pass the filter or filter params and get the filtered copy of the users collection back (since you will be using the users collection directly in many controllers at once you shouldn't modify that, unless that's desired). If you are reusing the same filters again and again across the controllers you can have a function for each filter that returns the filtered collection with a "hardcoded" filter. You can even have helper function in the service to help you assemble complex filters or have multiple copies of the collection already filtered cached(if you find you are using the same filter again and again)
How would you implement something like this in Angular:
I have a multi-page user interaction that shares state across pages/controllers. Page A launches a multi-page process across pages B, C, and D. I need to share state across pages B-C-D but as soon as the user goes back to page A (either at completion, or abandoning halfway through) the shared B-C-D state should go away.
I could put the shared state in a service and that would take care of sharing across pages. But then it's a global singleton for the whole application. Is there a way to ensure the service is disposed if the transaction is abandoned or completed?
In the server-side, Java EE world this was called "conversation scope" - I'm wondering what the equivalent might be in Knockout or Angular.
Or there a better way to approach the design? Should I use nested controllers?
This is a pretty broad question and is going to certainly elicit some opinion based responses. That said, one of the things you can rely on is that services (and factories) are singletons. What that means is if you need to share data between controllers, or across the life of a SPA you can create a service to hold and share that data. That service and its data will exist as long as the page isn't re-loaded.
The basic pattern for a shared data factory is like this is:
// define the shared data service
app.factory("SharedData", function () {
return {
// just one data object for the purposes of the example
sharedDataObject: {}
};
});
Once you have defined the service you can define a controller that uses the service something like:
app.controller("FirstController", function ($scope, SharedData) {
$scope.localData = SharedData.sharedDataObject;
});
Because we have defined the shared data as an object and not as a value - binding to "localData" will allow us to set values that can then be retrieved for as long as the page isn't changed. I put together an example that shows sharing data between two controllers here. This pattern works just as well if the controllers were on separate views.
Again - I'm not sure if this is the recommended pattern but it is one I have seen around and it works pretty well. Best of luck!
NOTE - This answer is referenced in your question - so it probably isn't exactly what you are looking for. That said, I am posting for others that may need a simple method of saving state on an SPA.
If you put the shared state into a service, just add a reset() function to it that gets called when they go to the "A" step or when they complete the process.
And I think you could do it with a nested controller too. You'd probably still need to add a reset() function for handling all the cases.