Conditionally include service in angular controller [duplicate] - javascript

I have defined a service like this :
angular.module('myApp').service('myService', [
'$rootScope',
...
...
I want my service to be instantiated only for new user (i.e. when user.createdAt > today).
So is there a way to conditionally inject my service or at least destroy my service without any side effect if the user is an old one.

You can use the $injector service to get inject-ibles dynamically if you need to:
app.controller('DemoCtrl', function($injector) {
this.clicky = function() {
myFact = $injector.get('myFactory');
myFact();
};
});
app.factory('myFactory', function() {
return function() {
alert('foobar!');
};
});
Here's a full running demo: http://jsbin.com/bemakemaja/1/edit
And the $injector docs: https://docs.angularjs.org/api/auto/service/$injector
As a general guideline though I'd recommend not designing your services such that simply injecting them has side effects.

use factory instead of service, so that you can have some checking logic in your service
angular.module('myApp').factory('myService', function () {
if (newUser) {
return { // your service implementation here
}
}
return undefined;
};

So is there a way to conditionally inject my service or at least destroy my service without any side effect if the user is an old one.
Best to capture this logic inside the service:
class Service{
static $inject = ['$rootScope'];
constructor($rootScope){
if (user.createdAt > today){ /*something*/ }
else {/*other thing*/}
}
}
NOTE: services are singletons so conditional injection doesn't see like a desirable solution.
Also to learn about $inject : https://www.youtube.com/watch?v=Yis8m3BdnEM

Related

How to change Angular factory to resolve before injection

I have an existing application that uses a MapProvider like so:
mapModule.factory('MapProvider', ['$injector', function($injector) {
return $injector.get('GoogleMapsService');
}]);
This MapProvider is used extensively across the application and is injected into various other controllers and services (rightly or wrongly).
I now need to add a BaiduMapsService, which I have been able to get working as a test with:
mapModule.factory('MapProvider', ['$injector', function($injector) {
if(true) {
return $injector.get('GoogleMapsService');
} else {
return $injector.get('BaiduMapsService');
}
}]);
And flipping the if value accordingly. (Both of these services are using a TypeScript interface, so have the same methods). Now, I need to add a $http call to the API, which will return which map to use, based on the provided data. How can I make my factory asynchronous, without having to change all my MapProvider.someCallHere() calls to MapProvider.then(m => m.someCallHere()).
Ideally, when MapProvider is injected across my application, it will be able to resolve using the async data (only once), and then inject the necessary service afterwards.
Alternatively, is there a way to defer / delay loading Angular at all, until I make an API call and set some global data somewhere?
Thanks.
You can postpone the application bootstrap (also, don't use ng-app, do it manually) until you get data from server. I've answered this before on this question but each case has its own specific details.
I usually see a config value being declared on the app before the application gets bootstraped, this is very useful for multi-tenant apps. So that this preference values can be used in the whole app as an injected provider.
For example:
var app = angular.module('app', []);
// retrieve the $http provider
var ngInjector = angular.injector(["ng"]);
var $http = ngInjector.get("$http");
// load config function. Returns a promise.
function loadConfig(){
return $http.get("/config.json").then(function(response) {
// declare the configuration value on your app
app.constant("Config", response.data);
}, function(err) {
console.error("Error loading the application config.", err)
});
}
// Call loadConfig then bootstrap the app
loadConfig().then(function () {
angular.element(document).ready(function() {
angular.bootstrap(document, ["app"]);
});
});
Finally from your factory, you can use the Config constant to retrieve the preferred map.
mapModule.factory('MapProvider', ['$injector', 'Config', function($injector, Config) {
if(Config.preferedMap == 'GoogleMap') {
return $injector.get('GoogleMapsService');
} else {
return $injector.get('BaiduMapsService');
}
}]);
Only way I can think is to hold initialize whole angular (and modules) until you got your "config" (and set is as global variable).

AngularJS controllers, design pattern for a DRY code

I have created a full example for the purpose of describing this issue. My actual application is even bigger than the presented demo and there are more services and directives operated by every controller. This leads to even more code repetition. I tried to put some code comments for clarifications,
PLUNKER: http://plnkr.co/edit/781Phn?p=preview
Repetitive part:
routerApp.controller('page1Ctrl', function(pageFactory) {
var vm = this;
// page dependent
vm.name = 'theOne';
vm.service = 'oneService';
vm.seriesLabels = ['One1', 'Two1', 'Three1'];
// these variables are declared in all pages
// directive variables,
vm.date = {
date: new Date(),
dateOptions: {
formatYear: 'yy',
startingDay: 1
},
format: 'dd-MMMM-yyyy',
opened: false
};
vm.open = function($event) {
vm.date.opened = true;
};
// dataservice
vm.data = []; // the structure can be different but still similar enough
vm.update = function() {
vm.data = pageFactory.get(vm.service);
}
//default call
vm.update();
})
Basically I moved all the logic I could to factories and directives. But now in every controller that uses certain directive I need, for example, a field that keeps the value that directive is modifying. And it's settings. Later I need similar field to keep the data that comes from dataservice, and the call itself (method) is the same as well.
This leads to a lot of repetition.
Graphically I see the current example to look like this:
While I believe the proper design should look more like this:
I tried to find some solution here, but none seem to be confirmed. What I have found:
AngularJS DRY controller structure, suggesting I pass the $scope or vm and decorate it with extra methods and fields. But many sources say it is dirty solution.
What's the recommended way to extend AngularJS controllers? using angular.extend, but this have problems when using controller as syntax.
And then I have found also the answer (in the link above):
You don't extend controllers. If they perform the same basic functions then those functions need to be moved to a service. That service can be injected into your controllers.
And even when I did there is still a lot of repetition. Or is it the way it just has to be? Like John Papa sais (http://www.johnpapa.net/angular-app-structuring-guidelines/):
Try to stay DRY (Don't Repeat Yourself) or T-DRY
Did you face a similar issue? What are the options?
From a over all design perspective I don't see much of a difference between decorating a controller and extending a controller. In the end these are both a form of mixins and not inheritance. So it really comes down to what you are most comfortable working with. One of the big design decisions comes down to not just how to pass in functionality to just all of the controllers, but how to also pass in functionality to say 2 out of the 3 controllers also.
Factory Decorator
One way to do this, as you mention, is to pass your $scope or vm into a factory, that decorates your controller with extra methods and fields. I don't see this as a dirty solution, but I can understand why some people would want to separate factories from their $scope in order to separate concerns of their code. If you need to add in additional functionality to the 2 out of 3 scenario, you can pass in additional factories. I made a plunker example of this.
dataservice.js
routerApp.factory('pageFactory', function() {
return {
setup: setup
}
function setup(vm, name, service, seriesLabels) {
// page dependent
vm.name = name;
vm.service = service;
vm.seriesLabels = seriesLabels;
// these variables are declared in all pages
// directive variables,
vm.date = {
date: moment().startOf('month').valueOf(),
dateOptions: {
formatYear: 'yy',
startingDay: 1
},
format: 'dd-MMMM-yyyy',
opened: false
};
vm.open = function($event) {
vm.date.opened = true;
};
// dataservice
vm.data = []; // the structure can be different but still similar enough
vm.update = function() {
vm.data = get(vm.service);
}
//default call
vm.update();
}
});
page1.js
routerApp.controller('page1Ctrl', function(pageFactory) {
var vm = this;
pageFactory.setup(vm, 'theOne', 'oneService', ['One1', 'Two1', 'Three1']);
})
Extending controller
Another solution you mention is extending a controller. This is doable by creating a super controller that you mix in to the controller in use. If you need to add additional functionality to a specific controller, you can just mix in other super controllers with specific functionality. Here is a plunker example.
ParentPage
routerApp.controller('parentPageCtrl', function(vm, pageFactory) {
setup()
function setup() {
// these variables are declared in all pages
// directive variables,
vm.date = {
date: moment().startOf('month').valueOf(),
dateOptions: {
formatYear: 'yy',
startingDay: 1
},
format: 'dd-MMMM-yyyy',
opened: false
};
vm.open = function($event) {
vm.date.opened = true;
};
// dataservice
vm.data = []; // the structure can be different but still similar enough
vm.update = function() {
vm.data = pageFactory.get(vm.service);
}
//default call
vm.update();
}
})
page1.js
routerApp.controller('page1Ctrl', function($controller) {
var vm = this;
// page dependent
vm.name = 'theOne';
vm.service = 'oneService';
vm.seriesLabels = ['One1', 'Two1', 'Three1'];
angular.extend(this, $controller('parentPageCtrl', {vm: vm}));
})
Nested States UI-Router
Since you are using ui-router, you can also achieve similar results by nesting states. One caveat to this is that the $scope is not passed from parent to child controller. So instead you have to add the duplicate code in the $rootScope. I use this when there are functions I want to pass through out the whole program, such as a function to test if we are on a mobile phone, that is not dependent on any controllers. Here is a plunker example.
You can reduce a lot of your boilerplate by using a directive. I've created a simple one to replace all of your controllers. You just pass in the page-specific data through properties, and they will get bound to your scope.
routerApp.directive('pageDir', function() {
return {
restrict: 'E',
scope: {},
controller: function(pageFactory) {
vm = this;
vm.date = {
date: moment().startOf('month').valueOf(),
dateOptions: {
formatYear: 'yy',
startingDay: 1
},
format: 'dd-MMMM-yyyy',
opened: false
};
vm.open = function($event) {
vm.date.opened = true;
};
// dataservice
vm.data = []; // the structure can be different but still similar enough
vm.update = function() {
vm.data = pageFactory.get(vm.service);
};
vm.update();
},
controllerAs: 'vm',
bindToController: {
name: '#',
service: '#',
seriesLabels: '='
},
templateUrl: 'page.html',
replace: true
}
});
As you can see it's not much different than your controllers. The difference is that to use them, you'll use the directive in your route's template property to initialize it. Like so:
.state('state1', {
url: '/state1',
template: '<page-dir ' +
'name="theOne" ' +
'service="oneService" ' +
'series-labels="[\'One1\', \'Two1\', \'Three1\']"' +
'></page-dir>'
})
And that's pretty much it. I forked your Plunk to demonstrate.
http://plnkr.co/edit/NEqXeD?p=preview
EDIT: Forgot to add that you can also style the directive as you wish. Forgot to add that to the Plunk when I was removing redundant code.
I can't respond in comment but here what i will do :
I will have A ConfigFactory holding a map of page dependent variables :
{
theOne:{
name: 'theOne',
service: 'oneService',
seriesLabels: ['One1', 'Two1', 'Three1']
},
...
}
Then i will have a LogicFactory with a newInstance() method to get a proper object each time i need it.
The logicFactory will get all the data / method shared betwwen controllers.
To this LogicFactory, i will give the view-specific data. and the view will have to bind to this Factory.
And to retrieve the view-specific data i will pass the key of my configuration map in the router.
so let say the router give you #current=theOne, i will do in the controller :
var specificData = ServiceConfig.get($location.search().current);
this.logic = LogicFactory.newInstance(specificData);
Hope it help
I retouch your example, here is the result : http://plnkr.co/edit/ORzbSka8YXZUV6JNtexk?p=preview
Edit: Just to say this way, you can load the specific configuration from a remote server serving you the specific-view data
I faced completely the same issues as you described. I'm a very big supporter of keeping things DRY. When I started using Angular there was no prescribed or recommended way to do this, so I just refactored my code as I went along. As with many things I dont think their is a right or wrong way to do these things, so use whichever method you feel comfortable with. So below is what I ended up using and it has served me well.
In my applications I generally have three types of pages:
List Page - Table list of specific resource. You can
search/filter/sort your data.
Form Page - Create or Edit resource.
Display Page - Detailed view-only display page of resource/data.
I've found there are typically a lot of repetitive code in (1) and (2), and I'm not referring to features that should be extracted to a service. So to address that I'm using the following inheritance hierarchy:
List Pages
BaseListController
loadNotification()
search()
advancedSearch()
etc....
ResourceListController
any resource specific stuff
Form Pages
BaseFormController
setServerErrors()
clearServerErrors()
stuff like warn user is navigating away from this page before saving the form, and any other general features.
AbstractFormController
save()
processUpdateSuccess()
processCreateSuccess()
processServerErrors()
set any other shared options
ResourceFormController
any resource specific stuff
To enable this you need some conventions in place. I typically only have a single view template per resource for Form Pages. Using the router resolve functionality I pass in a variable to indicate if the form is being used for either Create or Edit purposes, and I publish this onto my vm. This can then be used inside your AbstractFormController to either call save or update on your data service.
To implement the controller inheritance I use Angulars $injector.invoke function passing in this as the instance. Since $injector.invoke is part of Angulars DI infrastructure, it works great as it will handle any dependencies that the base controller classes need, and I can supply any specific instance variables as I like.
Here is a small snippet of how it all is implemented:
Common.BaseFormController = function (dependencies....) {
var self = this;
this.setServerErrors = function () {
};
/* .... */
};
Common.BaseFormController['$inject'] = [dependencies....];
Common.AbstractFormController = function ($injector, other dependencies....) {
$scope.vm = {};
var vm = $scope.vm;
$injector.invoke(Common.BaseFormController, this, { $scope: $scope, $log: $log, $window: $window, alertService: alertService, any other variables.... });
/* ...... */
}
Common.AbstractFormController['$inject'] = ['$injector', other dependencies....];
CustomerFormController = function ($injector, other dependencies....) {
$injector.invoke(Common.AbstractFormController, this, {
$scope: $scope,
$log: $log,
$window: $window,
/* other services and local variable to be injected .... */
});
var vm = $scope.vm;
/* resource specific controller stuff */
}
CustomerFormController['$inject'] = ['$injector', other dependencies....];
To take things a step further, I found massive reductions in repetitive code through my data access service implementation. For the data layer convention is king. I've found that if you keep a common convention on your server API you can go a very long way with a base factory/repository/class or whatever you want to call it. The way I achieve this in AngularJs is to use a AngularJs factory that returns a base repository class, i.e. the factory returns a javascript class function with prototype definitions and not an object instance, I call it abstractRepository. Then for each resource I create a concrete repository for that specific resource that prototypically inherits from abstractRepository, so I inherit all the shared/base features from abstractRepository and define any resource specific features to the concrete repository.
I think an example will be clearer. Lets assume your server API uses the following URL convention (I'm not a REST purest, so we'll leave the convention up to whatever you want to implement):
GET -> /{resource}?listQueryString // Return resource list
GET -> /{resource}/{id} // Return single resource
GET -> /{resource}/{id}/{resource}view // Return display representation of resource
PUT -> /{resource}/{id} // Update existing resource
POST -> /{resource}/ // Create new resource
etc.
I personally use Restangular so the following example is based on it, but you should be able to easily adapt this to $http or $resource or whatever library you are using.
AbstractRepository
app.factory('abstractRepository', [function () {
function abstractRepository(restangular, route) {
this.restangular = restangular;
this.route = route;
}
abstractRepository.prototype = {
getList: function (params) {
return this.restangular.all(this.route).getList(params);
},
get: function (id) {
return this.restangular.one(this.route, id).get();
},
getView: function (id) {
return this.restangular.one(this.route, id).one(this.route + 'view').get();
},
update: function (updatedResource) {
return updatedResource.put();
},
create: function (newResource) {
return this.restangular.all(this.route).post(newResource);
}
// etc.
};
abstractRepository.extend = function (repository) {
repository.prototype = Object.create(abstractRepository.prototype);
repository.prototype.constructor = repository;
};
return abstractRepository;
}]);
Concrete repository, let's use customer as an example:
app.factory('customerRepository', ['Restangular', 'abstractRepository', function (restangular, abstractRepository) {
function customerRepository() {
abstractRepository.call(this, restangular, 'customers');
}
abstractRepository.extend(customerRepository);
return new customerRepository();
}]);
So now we have common methods for data services, which can easily be consumed in the Form and List controller base classes.
To summarize the previous answers:
Decorating controllers: as you said, this is a dirty solution; Imagine having different factories decorating the same controller, it will be very difficult (especially for other developers) to prevent collision of properties, and equally difficult to trace which factory added which properties. It's actually like having multiple inheritance in OOP, something that most modern languages prevent by design for the same reasons.
Using a directive: this can be a great solution if all your controllers are going to have the same html views, but other than that you will have to include fairly complex logic in your views which can be difficult to debug.
The approach I propose is using composition (instead of inheritance with decorators). Separate all the repetitive logic in factories, and leave only the creation of the factories in the controller.
routerApp.controller('page1Ctrl', function (Page, DateConfig, DataService) {
var vm = this;
// page dependent
vm.page = new Page('theOne', 'oneService', ['One1', 'Two1', 'Three1']);
// these variables are declared in all pages
// directive variables,
vm.date = new DateConfig()
// dataservice
vm.dataService = new DataService(vm.page.service);
//default call
vm.dataService.update();
})
.factory('Page', function () {
//constructor function
var Page = function (name, service, seriesLabels) {
this.name = name;
this.service = service;
this.seriesLabels = seriesLabels;
};
return Page;
})
.factory('DateConfig', function () {
//constructor function
var DateConfig = function () {
this.date = new Date();
this.dateOptions = {
formatYear: 'yy',
startingDay: 1
};
this.format = 'dd-MMMM-yyyy';
this.opened = false;
this.open = function ($event) {
this.opened = true;
};
};
return DateConfig;
})
This code is not tested, but I just want to give an idea. The key here is to separate the code in the factories, and add them as properties in the controller. This way the implementation is not repeated (DRY), and everything is obvious in the controller code.
You can make your controller even smaller by wrapping all the factories in a larger factory (facade), but this may make them more tightly coupled.

Is it good practise to use angular.element("[ng-controller="someCtrl"]").scope()

Is it good practise to use angular.element("ng-controller="someCtrl"]").scope() instead of using factory to handle data flow between controllers using dependency injection. The problem here is I want to call a function of another controller, so there are two ways either I put it in a factory and reuse it among controllers or use above syntax to call the function directly.
If you need to call a function from other controller, it should be a SERVICE/Factory. This way, you will share code between controllers, and you will code with good practices.
As they say in angularjs docs
Angular services are substitutable objects that are wired together
using dependency injection (DI). You can use services to organize and
share code across your app.
Then, you just need to create a service or a factory
//The service
angular.module('myApp')
.service('serviceName', function ($http, $scope, socket) {
//This functions will be available in your controller
return {
list: function () {
$http.get(listUrl).success(function (lista) {
$scope.centerList = lista;
socket.syncUpdates('center', $scope.centerList);
});
}
};
});
//The controller
angular.module('myApp').controller('myCtrl', function ($scope, centerService) {
$scope.listCenters = function () {
centerService.list();
};
});
Just to clarify, and to add some comprehensive ideas about services and factories:
https://www.youtube.com/watch?v=J6qr6Wx3VPs
AngularJS: Service vs provider vs factory
https://www.airpair.com/angularjs/posts/top-10-mistakes-angularjs-developers-make
It is never good practice to access the DOM from a controller. So if wrapping the method in a factory/service is an option, I'd say that's the way to go.

Angular JS - How to safely retain global value "extracted" using a service

I need an object to be globally accessible all throughout my Angular application, and I've gladly put this object in a value service.
Unfortunately, this object is computed by another service, and I've not been able to inject myService into same value service.
ATM, I've acheived my goal with something like this:
global service code
app.service('myService', function() {
this.foo = function() {
// some code that returns an object
return computedObject
}
})
app.run(function ($rootScope, myService) {
$rootScope.global = {dbObject: myService.foo()}
})
And which I can use in any controller that pleases me by simply injecting $rootScope in it
However, I don't like the need of injecting the whole $rootScope wherever I need that damn object, and I trust is not much safe (or efficient?) either, since the team specifies
Of course, global state sucks and you should use $rootScope sparingly, like you would (hopefully) use with global variables in any language. In particular, don't use it for code, only data. If you're tempted to put a function on $rootScope, it's almost always better to put it in a service that can be injected where it's needed, and more easily tested.
Conversely, don't create a service whose only purpose in life is to store and return bits of data.
Do you, perchance, happens to know any way I can inject a service into a service value?
Or maybe any other Angular best practice which I could exploit?
I forgot one very important notice
The computation of the object could be quite computational intense, so I absolutely don't want it to be recomputed everytime I move from page to page, or anything else really.
Ideally Using $rootScope for storing a few globally required values is totally ok. But if you are still adamant on using a module, I suggest you use a provider.
Make your 'myService' a provider and that will let you configure the variables in the service.
More info here
AngularJS: Service vs provider vs factory
You could use $broadcast to send the value from the value service to myService.
You would still need to inject $rootscope into each of the services, but from then on you could just inject myService around the rest of the app.
Reference here.
I need an object to be globally accessible all throughout my Angular application
I would use service. Since Service is singleton you can register the service to all your controllers and share any data over service.
Unfortunately, this object is computed by another service, and I've not been able to inject myService into same value service.
Just create one main service (Parent) and child service that will inherit the parent. (like abstract service in Java world).
Application.factory('AbstractObject', [function () {
var AbstractObject = Class.extend({
virtualMethod: function() {
alert("Hello world");
},
abstractMethod: function() { // You may omit abstract definitions, but they make your interface more readable
throw new Error("Pure abstract call");
}
});
return AbstractObject; // You return class definition instead of it's instance
}]);
Application.factory('DerivedObject', ['AbstractObject', function (AbstractObject) {
var DerivedObject = AbstractObject.extend({
virtualMethod: function() { // Shows two alerts: `Hey!` and `Hello world`
alert("Hey!");
this._super();
},
abstractMethod: function() {
alert("Now I'm not abstract");
}
});
return DerivedObject;
}]);
Now, we can add some logic into AbstractObject and continue use DerivedObject
Here is example Plunker

AngularJS pass data from controller to another controller

What I have done.
I retrieve a list of videos from youtube api with json in a controllerA with specific directive. The json contain the list of video and the video itself details.
What I want to do.
When click on a video, I want the video's details display in another ng-view with other controllerB with using the json data I request before.
So my question is
How to pass the data from controllerA to controllerB
Note - $http service is use in controllerA
This is one of the common doubts when starting with AngularJS. By your requirement, I believe your best option is to create a service that retrieves the movie list and then use this service in both controllerA and controllerB.
module.factory('youtube', function() {
var movieListCache;
function getMovies(ignoreCache) {
if (ignoreCache || !movieListCache) {
movieListCache = $http...;
}
return movieListCache;
}
return {
get: getMovies
};
});
Then you just inject this service in both controllers.
module.controller('controllerA', ['youtube', function(youtube) {
youtube.get().then(function doSomethingAfterRetrievingTheMovies() {
});
}]);
module.controller('controllerB', ['youtube', function(youtube) {
youtube.get().then(function doAnotherThingAfterRetrievingTheMovies() {
});
}]);
If you need controllerA to manipulate the info before you use it in B then you could create more methods in the service. Something like this:
module.factory('youtube', function($q) {
var movieListCache,
deferred = $q.defer();
function getMovies(ignoreCache) {
if (ignoreCache || !movieListCache) {
movieListCache = $http...;
}
return movieListCache;
}
function getChangedMovies() {
return deferred.promise;
}
function setChangedMovies(movies) {
deferred.resolve(movies);
}
return {
get: getMovies,
getChanged: getChangedMovies,
setChanged: setChangedMovies
};
});
If you don't know what $q is, take a look at the docs. This is mandatory to handle async operations.
Anyway, there are some other ways of accomplishing this task too:
You could save the videos at $rootScope
If the controllers are father and son the you could use require to retrieve each other controller
IMHO, #1 is a generic solution; I'd use it only if there is no other option. And #2 is useful if you have an intrinsic need to communicate between these controllers, such as configuring or letting one know about the other's existence. There is a example here.
What you want to do is to share stateful singleton information; therefore, a service is the way to go.

Categories

Resources