Pub/Sub design pattern angularjs service - javascript

I've been trying to use the answer here posted by Mark Rajcok
angular JS - communicate between non-dependend services
I am having trouble understanding his answer. Specially this part:
angular.forEach(event1ServiceHandlers, function(handler) {
handler(some_data);
});
Is the event1ServiceHandlers array populated with functions (here called handler) that is triggered in this forEach loop?
I think it would be much easier to understand with a good example how a publish/subscribe is set up.
I have two services who need to communicate but I want to avoid $rootScope.$broadcast so from what I have read a pub/sub service is the best approach. One of my services need to execute a function on my other service, but that service already has my first service as a dependency so I cannot do the same both ways because of circular dependency.
My question: So assume you have two angularjs services (factory), how does service 1 execute a function on service 2 if service 2 already has service 1 as a dependency. Not using $broadcast and $on

Is the event1ServiceHandlers array populated with functions (here called handler) that is triggered in this forEach loop?
Yes
how does service 1 execute a function on service 2 if service 2 already has service 1 as a dependency
Create service 3, NotificationService as before:
.factory('NotificationService', [function() {
var event1ServiceHandlers = [];
return {
// publish
event1Happened: function(some_data) {
angular.forEach(event1ServiceHandlers, function(handler) {
handler(some_data);
});
},
// subscribe
onEvent1: function(handler) {
event1ServiceHandlers.push(handler);
}
};
}])
Have service 2 register a callback function with the NotificationService:
.factory('Service2', ['NotificationService',
function(NotificationService) {
// event1 handler
var doSomething = function(someData) {
console.log('S2', someData);
// do something here
}
// subscribe to event1
NotificationService.onEvent1(doSomething);
return {
// define public API for Service2 here
}
}])
Whenever service 1 wants function doSomething() on service 2 to execute, it can publish the event1Happened event:
.factory('Service1', ['NotificationService',
function(NotificationService) {
var someData = ...;
return {
// define public API for Service1 here
callService2Method: function() {
// publish event
NotificationService.event1Happened(someData);
}
}
}])

In his example, NotificationService is a new service that any of the existing services would depend on. He provided an implementation of Service1 but Service2 would essentially be the same...both depend on NotificationService and neither know about each other.
Service1 and Service2 each subscribe to events by calling NotificationService.onEvent1(event1Happened); and trigger events by calling NotificationService.event1Happened(my_data);.

Related

Angular: circular dependency of specific case

Some time ago, I started to refactor my code of the main project, decoupling the business logic from controllers to services, according to guidelines. Everything went well, until I faced the problem of circular dependency (CD). I read some resources about this problem:
Question 1 on Stack Overflow
Question 2 on Stack Overflow
Miško Hevery blog
Unfortunately, for me it is not clear now, how can I solve the problem of CD for my project. Therefore, I prepared a small demo, which represents the core features of my project:
Link to Plunker
Link to GitHub
Short description of components:
gridCtrl Does not contain any business logic, only triggers dataload and when the data is ready, show grid.
gridMainService This is the main service, which contains object gridOptions. This object is a core object of grid, contains some api, initialized by framework, row data, columns headers and so on. From this service, I was planning to control all related grid stuff. Function loadGridOptions is triggerd by gridCtrl and waits until the row data and column definitions are loaded by corresponding services. Then, it initializes with this data gridOptions object.
gridConfigService This is simple service, which used to load column definitions. No problem here.
gridDataService This service used to load row data with function loadRowData. Another feature of this service: it simulates the live updates, coming from server ($interval function). Problem 1 here!
gridSettingsService This service is used to apply some settings to the grid(for example, how the columns should be sorted). Problem 2 here!
Problem 1:
I have a circular dependency between gridMainService and gridDataService. First gridMainService use gridDataService to load row data with function:
self.loadRowData = function () {
// implementation here
}
But then, gridDataService receives the updates from server and has to insert some rows into grid. So, it has to use gridMainService:
$interval(function () {
var rowToAdd = {
make: "VW " + index,
model: "Golf " + index,
price: 10000 * index
};
var newItems = [rowToAdd];
// here I need to get access to the gridMainService
var gridMainService = $injector.get('gridMainService');
gridMainService.gridOptions.api.addItems(newItems);
index++;
}, 500, 10);
So, here I faced first CD.
Problem 2:
I have a circular dependency between gridMainService and gridSettingsService. First, gridMainService triggering, when the grid is initially loaded and then it sets up default settings by the following function:
self.onGridReady = function () {
$log.info("Grid is ready, apply default settings");
gridSettingsService.applyDefaults();
};
But, to make some changes, gridSettingsService need an access to the gridMainService and its object gridOptions in order to apply settings:
self.applyDefaults = function() {
var sort = [
{colId: 'make', sort: 'asc'}
];
var gridMainService = $injector.get('gridMainService');
gridMainService.gridOptions.api.setSortModel(sort);
};
Question:
How can I solve these CD cases in proper way? Because, Miško Hevery Blog was quite short and good, but I have not managed to apply his strategy to my case.
And currently, I don't like an approach of manual injection a lot, because I will have to use it a lot and the code looks a little bit fuzzy.
Please note:
I prepared only a demo of my big project. You can probably advice to put all code in gridDataService and ready. But, I already has a 500 LOC in this service, if I merge all services, it will be a ~1300 LOC nightmare.
In these kind of problems there are many solutions, which depend by the way you are thinking. I prefer thinking each service (or class) has some goal and needs some other service to complete its goal, but its goal is one, clear and small. Let’s see your code by this view.
Problem 1:
GridData: Here lives the data of grid. MainService comes here to get the data that needs, so we inject this to mainService and we use the loadRowData function to get the rowData data as you do, but in $interval you inject mainService inside gridData but gridData doesn’t need mainService to end its goal (get items from server).
I solve this problem using an observer design pattern, (using $rootScope). That means that I get notified when the data are arrived and mainService come and get them.
grid-data.service.js :
angular.module("gridApp").service("gridDataService",
["$injector", "$interval", "$timeout", "$rootScope",
function ($injector, $interval, $timeout, $rootScope) {
[…]
$interval(function () {
[..]
self.newItems = [rowToAdd];
// delete this code
// var gridMainService = $injector.get('gridMainService');
// gridMainService.gridOptions.api.addItems(newItems);
// notify the mainService that new data has come!
$rootScope.$broadcast('newGridItemAvailable');
grid-main.service.js:
angular.module("gridApp").service("gridMainService",
["$log", "$q", "gridConfigService", "gridDataService", '$rootScope',
function ($log, $q, gridConfigService, gridDataService, $rootScope) {
[..]
// new GridData data arrive go to GridData to get it!
$rootScope.$on('newGridItemAvailable', function(){
self.gridOptions.api.addItems(gridDataService.getNewItems());
})
[..]
When a real server is used, the most common solution is to use the promises (not observer pattern), like the loadRowData.
Problem 2:
gridSettingsService: This service change the settings of mainService so it needs mainService but mainService doesn’t care about gridSettings, when someone wants to change or learn mainService internal state (data, form) must communicate with mainService interface.
So, I delete grid Settings injection from gridMainService and only give an interface to put a callback function for when Grid is Ready.
grid-main.service.js:
angular.module("gridApp").service("gridMainService",
["$log", "$q", "gridConfigService", "gridDataService", '$rootScope',
function ($log, $q, gridConfigService, gridDataService, $rootScope) {
[…]
// You want a function to run onGridReady, put it here!
self.loadGridOptions = function (onGridReady) {
[..]
self.gridOptions = {
columnDefs: gridConfigService.columnDefs,
rowData: gridDataService.rowData,
enableSorting: true,
onGridReady: onGridReady // put callback here
};
return self.gridOptions;
});
[..]// I delete the onGridReady function , no place for outsiders
// If you want to change my state do it with the my interface
Ag-grid-controller.js:
gridMainService.loadGridOptions(gridSettingsService.applyDefaults).then(function () {
vm.gridOptions = gridMainService.gridOptions;
vm.showGrid = true;
});
here the full code: https://plnkr.co/edit/VRVANCXiyY8FjSfKzPna?p=preview
You can introduce a separate service that exposes specific calls, such as adding items to the grid. Both services will have a dependency to this api service, which allows for the data service to drop its dependency on the main service. This separate service will require your main service to register a callback that should be used when you want to add an item. The data service in turn will be able to make use of this callback.
angular.module("gridApp").service("gridApiService", function () {
var self = this;
var addItemCallbacks = [];
self.insertAddItemCallback = function (callback) {
addItemCallbacks.push(callback);
};
self.execAddItemCallback = function (item) {
addItemCallbacks.forEach(function(callback) {
callback(item);
});
};
});
In the above example, there are two functions made available from the service. The insert function will allow for you to register a callback from your main function, that can be used at a later time. The exec function will allow for your data service to make use of the stored callback, passing in the new item.
First of all, I agree with the other comments that the decoupling seems to be a bit extreme; nonetheless, I'm sure you know more than us how much is needed for you project.
I believe an acceptable solution is to use the pub/sub (observer pattern), in which the gridSettingsService and gridDataService services do not directly update the gridMainService, but instead raise a notification that gridMainService can hook into and use to update itself.
So using the plunker that you provided, the changes I would make would be:
Inject the $rootScope service into gridSettingsService, gridDataService and gridMainService.
From gridSettingsService and gridDataService, stop manually injecting the gridMainServiceand instead just $broadcast the notification with the associated data:
```
// in gridDataService
$rootScope.$broadcast('GridDataItemsReady', newItems);
// in gridSettingsService
$rootScope.$broadcast('SortModelChanged', sort);
```
In gridMainService, listed to the notifications and update using the passed in data:
```
// in gridMainService
$rootScope.$on('GridDataItemsReady', function(event, newItem) {
self.gridOptions.api.addItems(newItem);
});
$rootScope.$on('SortModelChanged', function(event, newSort) {
self.gridOptions.api.setSortModel(newSort);
});
```
And here is the updated plunker: https://plnkr.co/edit/pl8NBxU5gdqU8SupnMgy

Application with multiple API data services

I am trying to design an Angular application that has components which register a callback function and a 'data request object' with an Angular service. This service basically keeps track of all the data request objects and which callback functions they refer to. Then, it performs long polling to make asynchronous calls to a RESTful API. When all the data comes in, the service decides which components need which pieces of data and calls into them with the results of the API call.
The problem I am having trouble wrapping my head around is that each component may need the data 'transformed' into a JSON object that prescribes to a specific format. For example, a chart component may require the data result to look one way, but a table component that may required the data result to look another way.
To make things even more complicated, I want to design my data service in such a way that the component can register for data from multiple different RESTful APIs.
Since I'm fairly new to Angular, I wanted to get help on some best practices for accomplishing this type of application. What I am thinking of doing is having a 'primary' data service that my components register with. The registration function will take as arguments a callback function and a data request object that will be in a format like so:
{
"service": "apiService",
"request": {
...
}
}
Then there will be a separate Angular service for each RESTful API. These sub-services will handle what to do with the data request object and correspond to the 'service' field above. The primary data service will then queue up requests to the sub-services on a long polling cycle. So the primary data service will look something like (NOTE: I am using ES6):
class DataService {
constructor($q, $interval, $injector) {
this.$q = $q;
this.$interval = $interval;
this.$injector = $injector;
this.subscriptions = [];
this.callbacks = [];
this.poll();
}
poll() {
let reqs = [];
angular.forEach(this.subscriptions, (sub, i) => {
let service = this.$injector.get(sub.service);
let deferred = this.$q.defer();
reqs.push(deferred);
service.get(sub.request).then((result) => {
this.callbacks[i](result);
deferred.resolve(result);
}, (result) => {
deferred.reject(result);
});
});
this.$q.all(reqs).then(() => {
this.$interval(poll, this.pollInterval || 10000);
});
}
register(request, callback) {
this.subscriptions.push(request);
this.callbacks.push(callback);
}
}
angular.module('DataService', []).service('DataService', DataService);
The piece that I am having trouble figuring out how to implement is the 'data transform' piece. As far as I can tell, there's really only two places I can see where this data transform can take place:
Inside the component
Inside the individual API services
The first way doesn't seem like a viable option to me as it breaks the common practice that components should be somewhat 'dumb.' The component should not handle transforming the data that the RESTful API returns: it should just use the data as-is.
But the second way also poses another problem, which is that each RESTful API service would have to have transform functions for every component type that I've created. This somehow doesn't seem 'clean' to me.
Is there another way I can design my application to achieve this goal? Any insight would be appreciated.
Just a suggestion: use angular's built-in event system.
//Constructor
constructor($q, $interval, $injector, $rootScope) {
this.$q = $q;
this.$interval = $interval;
this.$injector = $injector;
this.$rootScope = $rootScope;
this.listeners = [];
//No idea how its written in Angular2 but cleanup the event listeners when this is destroyed
//Example in 1.5.x:
//$scope.$on('$destroy', () => angular.forEach(this.listeners, l => l());
this.poll();
}
//The new register method
register(ev, callback) {
//When cleaning up - iterate over listeners and invoke each destroy function - see constructor
this.listeners.push(this.$rootScope.$on(ev, callback));
}
//The new poll method
poll() {
let reqs = [];
angular.forEach(this.subscriptions, (sub, i) => {
let service = this.$injector.get(sub.service);
let deferred = service.get(sub.request).then((response) => {
//responseToEventsMapper should map and parse response to list of events to fire.
//Lets say the response is an authentication response for login attempt,
//then the triggered events will be 'Auth:stateChange' and 'Auth:login' with
//response.user as data. configure responseToEventsMapper upfront.
let events = this.responseToEventsMapper(response);
angular.forEach(events, event => this.$rootScope.$emit(event.type, event.data));
});
reqs.push(deferred);
});
this.$q.all(reqs).then(() => {
this.$interval(poll, this.pollInterval || 10000);
});
}

Can we access $provide inside a decorator in Angular JS?

Recently I came across a quiz and the question is
Decorators use
Select one:
a. Both
b. $delegate
c. None
d. $provide
I choose b.$delegate and the quiz says it is wrong and the quiz says the correct answer is a.Both.
So I was wondering if this is true, I thought that decorators are inside provider i.e. they are a service which is invoked by provider and they can use $delegate like in this example
app.config(function ($provide) {
$provide.decorator('movieTitle', function ($delegate) {
return $delegate + ' - starring Keanu Reeves';
});
});
also, in the decorator documentation it states that
This function will be invoked when the service needs to be instantiated and should return the decorated service instance. The function is called using the injector.invoke method and is therefore fully injectable. Local injection arguments:
$delegate - The original service instance, which can be monkey patched, configured, decorated or delegated to.
so, am I missing something or is the quiz wrong, or am I wrong, can someone please help me understand this.
Yes the right answer is both. As an example this is a piece of code where is set a decorator for the $log service using a custom service logEnchance to add custom functionality. In this case logEnchance make posts to a external log service.
angular.module('angularApp').config(configureLogger);
// The decorator allows us to inject custom behaviors
function configureLogger($provide) {
// registers a value/object that can be accessed by providers and services
$provide.constant('logDecorator', logDecorator);
// registers a decorator function
// $provide.decorator intercept $log service letting us add custom functionality
$provide.decorator('$log', logDecorator);
// inject dependencies into logDecorator function
logDecorator.$inject = ['$delegate', 'logEnchance'];
function logDecorator($delegate, logEnchance) {
// logEnchance is the service who modify the $log service
logEnchance( $delegate );
return $delegate;
}
}

Angular provider configuration reset when service injected into somewhere else

Not an easy one to describe this, but basically I have a service which is set up as a provider so I can configure it. It has an array of APIs used in my project which is initially empty. Various config blocks can add an API object to the array and this seems to be working. I console.log the output each time and the array is growing.
I then inject my service into something else (in this case an $http interceptor function) and use a service method to return the array, but each time I get an empty array.
I thought the way this worked is that all config blocks ran first and that the $http call which is being intercepted is happening way after that, so the array should be full of APIs by the time it's intercepted.
Anyway, here's some code
angular.module('authModule').provider('authService', function(){
var _apis = [];
return({
addApi: addApi,
$get: instantiateAuth
});
function addApi(newApi){
_apis.push(newApi);
console.log("API added", _apis);
}
function instantiateAuth() {
return({
getApis: function(){
console.log("Getting APIs", _apis);
return _apis;
}
});
}
})
.config(function($httpProvider){
$httpProvider.interceptors.push(function($log, $rootScope, $q) {
return {
request: function(config) {
var injector = angular.injector(['ng', 'authModule']);
var authService = injector.get('authService');
console.log("apis", authService.getApis());
}
};
});
});
And an example config block
angular.module('myModule').config(function ($provide, authServiceProvider) {
authServiceProvider.addApi({
url: 'https://apiurl.com',
other: 'stuff'
});
authServiceProvider.addApi({
url: 'https://apiurl2.com',
other: 'stuff'
});
});
So, each time the appApi method is called in a config block (twice here), this line outputs the array console.log("API added", _apis); and it correctly outputs 1 item after the first call and two items after the second call.
When this code - authService.getApis() - fires the first time an HTTP call is intercepted, it logs an empty array to the console.
Any help would really be appreciated.
EDIT:
The problem seems to be this line
var injector = angular.injector(['ng', 'authModule']);
My provider seems to be reset/recreated each time this happens, so maybe I'm misunderstanding how to use the injector. I was originally just injecting my authService the normal way in the function parameters but I was getting a circular dependency (my auth service needs to open a modal window, but angular-ui modals rely on the http service and my http calls are being intercepted to check with my auth service that the user is authenticated :( )
Yes, angular.injector(['ng', 'authModule']) essentially creates a new injector instance (an application instance, in layman's terms):
angular.injector(['authModule']) !== `angular.injector(['authModule'])
ng module is loaded by default, it doesn't have to be specified explicitly. And singleton services are singletons only within the same injector instance:
injector.get('authService') === injector.get('authService');
But
angular.injector(['authModule']).get('authService') !== `angular.injector(['authModule']).get('authService')
To reuse current injector instance (which is a desirable behaviour in almost every situation) $injector service should be used:
$httpProvider.interceptors.push(function($log, $rootScope, $q, $injector) {
return {
request: function(config) {
var authService = $injector.get('authService');
}
};
});
$injector.get is known and straightforward solution to get around circular dependencies.

Why are Factories more flexible than Services in AngularJS?

According to this source,
Why do the two styles exist if they can accomplish the same thing?
Factories offer slightly more flexibility than services because they
can return functions which can then be new'd. This follows the factory
pattern from object oriented programming. A factory can be an object
for creating other objects.
I'm having trouble connecting this piece of information to the concept of constructors in Javascript and object / prototype. Can someone help me connect the dot? Thanks.
Service VS Factory
A service is a constructor function that can be called with 'new' where as a factory returns an object that you may run some code before that is why it is more flexible.
To see a detailed example please check out this question. matys84pl talks about it in detail.
Factory
These are practically singletons. So for instance you can hold data across calls to it.
factory('myFactory', function () {
var internalList = [];
return {
add: function (value) { internalList.push(value); },
get: function () { return internalList; }
}
});
As you could imagine, if controller1 pushed Hi into the list and controller2 called get() immediately after, it would recieve [ 'Hi' ].
Service
From the angular docs
JavaScript developers often use custom types to write object-oriented code.
service('UnicornLauncher', function (apiToken) {
this.launchedCount = 0;
this.launch = function() {
// Make a request to the remote API and include the apiToken
...
this.launchedCount++;
}
});
Provider
For some bonus reading, I'll tell you about a provider. A provider wraps a factory. Why? Because there are two phases in an angular application, the configure phase and the run phase. Services, factories and values AREN'T available during the run phase.
So, say we need to configure a service before it is injected into the controllers. We need to use a provider.
The angular docs have a nice example:
provider('unicornLauncher', function UnicornLauncherProvider() {
var useTinfoilShielding = false;
this.useTinfoilShielding = function(value) {
useTinfoilShielding = !!value;
};
this.$get = ["apiToken", function unicornLauncherFactory(apiToken) {
// let's assume that the UnicornLauncher constructor was also changed to
// accept and use the useTinfoilShielding argument
return new UnicornLauncher(apiToken, useTinfoilShielding);
}];
});
so this.$get is basically the second argument of the factory('...', ....
So if we use this in the configure phase we do this:
config(["unicornLauncherProvider", function(unicornLauncherProvider) {
unicornLauncherProvider.useTinfoilShielding(true);
}]);
Notice how we ask for unicornLauncherProvider, we add that Provider suffix. Yet when we named our provider we didn't supply it. That is because angular automatically adds the suffix for you. If you want to access the service this provider creates, ask for the unicornLauncher.
I had an 'Eureka effect' when i realized that service & factory are only syntactic sugar for provider (https://docs.angularjs.org/api/auto/service/$provide#provider)
The only difference between these two is the way Angular processes the passed value before providing it with the injector.
When I get the object from the injector, I usually can't tell if it has been created using a service or a factory (or any of the other $provide methods)
So the Question is "How do I want to implement this?" and not "How do I want to use this?".

Categories

Resources