Decorating a service provider in Angular - angularjs

I'm working on a library that extends some of the functionality in ui.router for building out sets of application states. Currently I am managing this by defining a new provider in my library (let's call it enhancedState) in the form:
myApp.provider('enhancedState', EnhancedState);
EnhancedState.$inject = ['$stateProvider'];
function EnhancedState ($stateProvider) {
this.$stateProvider = $stateProvider;
}
EnhancedState.prototype = {
/* fun new methods that wrap $stateProvider */
$get: ['$state', function ($state) {
/* maybe wrap some $state functionality too */
return $state;
}
};
I can then use my enhancedStateProvider in my application config to do fun new state definitions following the extended capabilities of my library.
I would prefer, however, to directly decorate the $stateProvider class. I am aware of Angular's $provider.decorate() utility, but as far as I can tell, it can only be used to decorate the generated service instances, not the provider.
Has anyone successfully used it to decorate providers, or is aware of a similar mechanism to do so in Angular 1.x?
Thanks!

You need AngularJS decorators (find decorator method). Check this plunker example. Based on this so post.
Example:
app.config(function($provide) {
$provide.decorator('$state', function($delegate) {
$delegate.customMethod = function(a) {
console.log(a);
};
return $delegate;
});
});

Related

Use $componentController in production?

I am building a dialog service. A dialog can have a controller, very similar to $mdDialog, like this:
myDialogService.show({
templateUrl: `<div ng-click="$ctrl.log()">Hello dialog</div>`,
controller: function() {
this.log = function() {
console.log("logged from myDialogController");
}
}
});
which works great. I invoke the controller that way:
locals.$scope = scope;
const invokeController = $controller(options.controller, locals, true);
const controller = invokeController();
if (options.controllerAs) {
scope[options.controllerAs] = controller;
} else {
const controllerAs = "$ctrl";
scope[controllerAs] = controller;
}
In angular-mock is the $componentController service, which can invoke component controllers. With my code, I only can invoke registered controllers, or given controller functions. This is not very helpful, as I only have components registered, not single controllers.
My question
Is it possible/recommended to use the $componentController in production? Or is there any AngularJS build in variant I have overseen?
$componentController belongs to ngMock module because it is useful for testing but is considered a hack in production. Since ngMock is big enough and isn't supposed to be available in production, it should be pasted in order to become available.
The proper way to solve this is to either have registered controllers that are reused as component controllers or import/export controller functions/classes with JS modules.
Since MdDialogController belongs to third-party module, isn't registered or exported and is small, it can be just pasted.

Use angular compileProvider outside config block

I'm trying to create directives on the fly, actually I achived that, but seams pretty hacky.
This was my first approach:
function create(myDir) {
angular.module("app").directive(myDir.name, function() {
return {
template:myDir.template
};
});
}
It didn't work because you can't register directives after application started.
based on this post: http://weblogs.thinktecture.com/pawel/2014/07/angularjs-dynamic-directives.html
I found out that I could use compileProvider to do the work, but since compileProvider isn't available outside config block, you need to put it out, so I did:
var provider = {};
angular.module("app",[]);
angular.module('app')
.config(function ($compileProvider) {
//It feels hacky to me too.
angular.copy($compileProvider, provider);
});
....
function create(myDir) {
provider.directive.apply(null, [myDir.name, function () {
return { template: myDir.template } }]);
render(myDir); //This render a new instance of my new directive
}
Surprisingly it worked. But I can't feel like being hacking the compileProvider, because I'm using it not in the way it was suppose to be, I would really like to know if is it possible to use the compileProvider properly after the application has started.
There is a list of dependencies that can be injected to config blocks (these are built-in $provide, $injector and all service providers) and a list of dependencies that can be injected to everywhere else (service instances and good old $injector). As you can see all that constant does is adding the dependency to both lists.
A common recipe for using providers outside config is
app.config(function ($provide, $compileProvider) {
$provide.constant('$compileProvider', $compileProvider);
});

Injecting and using a service into my provider then using the provider in the config block

I am trying to inject a custom factory from one module into a custom provider from another module. What I really want to do is to use the custom factory inside the config block but I can't, so I'm trying to configure a provider that uses the custom factory which will then get injected into config.
I have been trying to inject my custom factory into the provider but I can't seem to get it right. I don't know if it's syntax or maybe my approach is wrong. My questions are:
1.) is this even possible?
2.) is my syntax correct?
Here is the factory:
.factory('myFactory', myFactory);
myFactory.$inject = ['$q', '$http', 'Story'];
function myFactory($q, $http) {
return {
getSomething: getSomething,
}
function getSomething() {
}
}
Here is the provider:
.provider('myProvider', function() {
return {
$get: function(myFactory) {
function getStuff() {
return myFactory.getSomething().then(function(data){
return data;
})
}
return {
stuff: getStuff
}
}
}
})
The error that I am getting is this:
Cannot read property 'getSomething' of undefined
Is this the correct use of a provider? I feel like I may be missing something. Thanks!
During the configuration phase, you can't access services:
During application bootstrap, before Angular goes off creating all
services, it configures and instantiates all providers. We call this
the configuration phase of the application life-cycle. During this
phase, services aren't accessible because they haven't been created
yet.
https://docs.angularjs.org/guide/providers

making service available in ngRoute

This code is from the angularjs TodoMVC. You can see resolve object of the routeConfig that it fetches the todos from localStorage (if localStorage is used). The localStorage code is created in an service in another file todoStorage.js. My question is, how is that service (or how is that code in general) available in this config for the fetch of the todo records? Don't I have to make available the service somehow for the code below to use it?
angular.module('todomvc', ['ngRoute'])
.config(function ($routeProvider) {
'use strict';
var routeConfig = {
controller: 'TodoCtrl',
templateUrl: 'todomvc-index.html',
resolve: {
store: function (todoStorage) {
// Get the correct module (API or localStorage).
return todoStorage.then(function (module) {
module.get(); // Fetch the todo records in the background.
return module;
});
}
}
};
The resolve property is a hash/dictionary of key value pairs.
The key is the eventual name of the value that will be available for injection. The value can be an injectible function that Angular will automatically pass dependencies into.
So when you see this code:
store: function (todoStorage) { ... }
Angular will automatically infer the 'todoStorage' service from the parameter name and then inject it automatically. And of course it supports array notation as well:
store: ['todoStorage', function (todoStorage) { ... }]
In Angular pretty much anything can be dependency injected.

AngularJS application architecture

I am relatively new to Angular but I am quite an experienced developer. So far I have made quite some progress in building my application to work with a CMS. I am a bit lost however on what the 'correct' approach would be to handle data in my model.
This is best described with an example:
Because I am hooking up my angular frontend with a CMS, the routing (pages) exist only in the CMS context. This means that the routing should be dynamic as well. I have managed to get the dynamic routes thing to work, but when I try to do things the right way (actually getting data from a server) I run into some issues...
app.config(function($provide, $routeProvider) {
$provide.factory("$routeProvider", function() {
return $routeProvider;
});
});
// Load the dynamic routes from the API...
app.run(function($routeProvider, $http, $scope, logger, siteRoutes) {
$routeProvider.when('/', { templateUrl: '__views/', controller: 'ContentPageController' });
$routeProvider.otherwise({redirectTo: '/'});
});
In other words, I inject a service into my app.run method (siteRoutes) and this one should connect to the API.
So my siteRoutes is a service:
cmsModule.service('siteRoutes', function siteRouteFactory(apiConnection, logger)
// SNIP
And in this service I inject my generic apiConnection service:
cmsModule.factory('apiConnection', ['$q', '$http', '$timeout', 'logger', function apiConnectionService($q, $http, $timeout, logger)
What I want is this:
I would like the siteRoutes service to load the data once and not execute the connection every time. I did this in the following way:
bla.service('example', function() {
var service = {
get: function(apiStuff) { // DO API CONNECT WITH .THEN HERE },
data: {}
}
service.get();
return service;
}
I would like one entry point towards the Api that handles all the $q stuff (my factory) I assumed I need to handle all the .then() stuff in my siteRoutes object, which is what I did.
Now, what happens in my app.run method is that I don't get the siteRoutes object with any data. So I recon I need to do a .then there as well?
But that made me question the entire design of putting all logic in a separate factory for the connection, because I basically like my app to just use the data and have my library deal with the async stuff (if you get what I am saying)...
Hope this is clear.
TL;DR -> How to make your services / factories handle async stuff without making your 'app' deal with it?
The templateUrl property can also be a function that takes the url parametes as input.
In the example below all routes will load a template with same name.
Eg. domain.com/#/blabla.html will load the view blabla.html from the server.
myApp.config(['$routeProvider', function ($routeProvider) {
$routeProvider
.when('/:templateName',
{
templateUrl: function (params) {
return params.templateName + ".html";
}
}
)
.otherwise({ redirectTo: '/main' });
}]);

Resources