AngularJS: Custom Providers, undefined functions? - angularjs

I have a provider like so:
angular.module('myApp').provider('test', function(){
this.$get = function($http){
return {
test2: function(){
}
}
};
});
I then use the provider in my app.config:
app.config(function($urlRouterProvider, $stateProvider, testProvider){
testProvider.test2();
$urlRouterProvider.otherwise('/home');
$stateProvider.state('home', {url: '/home', templateUrl: 'template/guide.html'})
.state('cost', {url:'/cost', templateUrl:'template/cost.html'})
});
Im trying to use a provider to get all my pages from a database and return them to the stateProvider... (Which I cant do in app.config because I cant inject the $http service)
This is the error I'm getting:
Failed to instantiate module myApp due to: TypeError: undefined is
not a function

You are getting the error, because yu are trying to call test2() on the provider (TestProvider).
test2() is not a method of the provider, but of the provided service (and thus is only accessible at "runtime", not during config).
If I understand correctly what you are trying to do, here is a possible approach:
Create a DelayedRoutes service (using provider()), which has a method for fetching the routes from the server and then registering them with the routing module (be it ngRoute or ui.router or whatever).
This configRoutes() methods needs to be invoked at runtime (so it has access to $http etc).
It also needs access to the routing module's provider (e.g. $routeProvider, $stateProvider etc), which are only available during the configuration time.
Your provider will have a method (setRouteProvider()), which get's executed during config and stores the $whateverProvider for later use (i.e. when the configRoutes() gets executed at runtime).
Here is a sample implementation (using ngRoute for simplicity). You can modify it according to your needs:
app.provider('DelayedRoutes', function () {
var routeProvider;
// This will be called during `config`
this.setRouteProvider = function (rp) {
routeProvider = rp;
};
// This will return the actual service
this.$get = function ($http, $route) {
// This will be called at runtime, to fetch the routes
// from the server and configure client-side routing
function configRoutes() {
return $http.get('/gimme/ma/routes').then(
function onResponse(response) {
var routes = response.data.routes;
angular.forEach(routes, function (config, path) {
if (path === 'otherwise') {
routeProvider.otherwise(config);
} else {
routeProvider.when(path, config);
}
});
$route.reload();
},
function onError(error) {
// Handle the error...
}
);
}
return {
configRoutes: configRoutes
};
};
});
// During `config`, store `$routeProvider` for later use
app.config(function ($routeProvider, DelayedRoutesProvider) {
DelayedRoutesProvider.setRouteProvider($routeProvider);
});
// At runtime, config the routes
app.run(function (DelayedRoutes) {
DelayedRoutes.configRoutes();
});
See, also, this short demo.

Related

Unit Testing AngularJS 'config' Written in TypeScript using Jasmine

I'm currently trying to Unit Test the config of a new AngularJS component. We are using ui-router to handle the routing in our application. We have been able to successfully test it for all our previous components, but the code for all of them was written in plain Javascript. Now that we switched to TypeScript we are having some issues.
This is the TypeScript code where we make the configuration of the module:
'use strict';
// #ngInject
class StatetiworkpaperConfig {
constructor(private $stateProvider: ng.ui.IStateProvider) {
this.config();
}
private config() {
this.$stateProvider
.state('oit.stateticolumnar.stateticolumnarworkpaper', {
url: '/stateticolumnarworkpaper',
params: { tabToLoad: null, groupTabId: null, jurisdiction: null, showOnlyItemsWithValues: false, showOnlyEditableItems: false},
template: '<stateticolumnarworkpaper-component active-tab-code="$ctrl.activeTabCode"></stateticolumnarworkpaper-component>',
component: 'stateticolumnarworkpaperComponent',
resolve: {
onLoad: this.resolves
}
});
}
//#ngInject
private resolves($q, $stateParams, ColumnarWorkpaperModel, ChooseTasksModel, localStorageService) {
// Some not important code
}
}
angular
.module('oit.components.batch.batchprocess.stateticolumnar.stateticolumnarworkpaper')
.config(["$stateProvider", ($stateProvider) => {
return new StatetiworkpaperConfig($stateProvider);
}]);
This is the Spec file, which is written in Javascript:
describe('oit.components.batch.batchprocess.stateticolumnar.stateticolumnarworkpaper', function () {
beforeEach(module('oit.components.batch.batchprocess.stateticolumnar.stateticolumnarworkpaper'));
beforeEach(module('oit'));
var state = 'oit.stateticolumnar.stateticolumnarworkpaper';
it('has a route', inject(function ($state) {
var route = $state.get(state);
expect(route.url).toBe('/stateticolumnarworkpaper');
}));
});
My issue is when executing the line var route = $state.get(state), as the route variable is always null. I could verify that the config() method is being executed, but I'm simply out of ideas as to why route is always null on my test.
Just for reference, this is the configuration of another component, but using Javascript
'use strict';
angular
.module('oit.components.binders.binder.dom_tas.taxaccountingsystem.stateworkpapers.stateworkpapersreview')
.config(stateworkpapersreviewConfig);
function stateworkpapersreviewConfig($stateProvider) {
$stateProvider
.state('oit.binder.taxaccountingsystem.stateworkpapersreview', {
url: '/stateworkpapersreview?reviewType&binderId&year&jurisdiction&chartId&withBalance',
templateUrl: 'components/binders/binder/dom_tas/taxaccountingsystem/stateworkpapers/stateworkpapersreview/stateworkpapersreview.tpl.html',
controller: 'StateworkpapersreviewController',
controllerAs: 'stateworkpapersreviewCtrl',
resolve: {
onLoad: resolves
}
});
function resolves($q, $stateParams, StateTiBinderJurisdictionsModel, WorkpaperModel, localStorageService, StateTiFiltersModel) {
// Some not important code
}
}
As you can see the code is basically the same, but still, I can successfully test this component's config in the way I described, but when I try with the one written in TypeScript I get the error I mentioned.
PD: I'm aware of several similar posts (like this one), but none of them deal with TypeScript, which is my issue.
There is huge difference between the TS snippet and the JS one.
I’m not sure why you are using a class to elite a function? .config suppose to get a function.
You can write the same code as in JS just with .ts suffix, it is a valid TS code.
Then you just can import that config function, pass it all the injectables and test it.

Angular Services within Modules

Having difficulty injecting a service into another service. I want a service hierarchy, where I can pass/request logic to/from the parent service, for sake of brevity and encapsulation.
So for example, I have a userService, and I want the userService to manage the user's toDoList. So I created a toDoService, and I want controllers to add a toDo for the user by passing the request to the userService, which relays to the toDoService. Here's what I have:
// app.js
angular.module('myApp', [
// other dependencies...
'myApp.myServices'
]);
// services/toDoService.js
angular.module('myApp.myServices', [])
.factory('toDoService', function($http) {
getStuff = function(userId) {
// returns $http call
};
addStuff = function(userId, data) {
// returns $http call
};
});
// services/userService.js
angular.module('myApp.myServices', [])
.factory('userService',
['$http', 'toDoService', function(
$http, toDoService) {
addToDo = function(data) {
toDoService.addStuff(user.uid, data)
.then(function(success) {
// apply bindings
})
.catch(function(error) {
// display error
});
};
getToDos = function(data) {
toDoService.getStuff(user.uid)
.then(function(success) {
// apply bindings
})
.catch(function(error) {
// display error
});
};
}]);
The controller works with the userService, and code from toDoService worked when it was originally in userService. But when I create the toDoService and move that code there and encapsulate it, angular complains about toDoService.
Error: [$injector:unpr] Unknown provider: toDoServiceProvider <- toDoService <- userService
I've checked script references, and all scripts are properly included. e.g. <script src='/[..]/toDoService.js' /> etc...
So I'm wondering is it even possible to inject a service into another service within the same module? Is it an issue with my naming convention?
angular.module('myApp.myServices', [])
This line in userService.js defines the module myApp.services, overwriting the one that has previously been defined in toDoService.js.
Define the module only once (in a separate file). Get a reference to this previously defined module using
angular.module('myApp.myServices')
i.e. without the empty array as second argument.

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.

AngularFire/Firebase Simple Login: how to setup login resolver on controller?

I'm using AngularJS with UI Router along with Firebase/AngularFire.
I was able to implement Firebase's Simple Login with Facebook, Twitter, and Google+ within a controller based on their example in the docs; however, as I think about the scalability of my code I would like to abstract this login logic out of my controller and into a factory or service so that it is able to resolve properly on my controller in UI Router. The resolving is really important because I obviously don't want to show logged-in content to a logged-out user.
I've seen a number of implementations that sort of address this issue, but nothing seems to completely solve what I'm looking to achieve for the three social logins mentioned above. How could I further expand upon this with the code I currently have or rewrite a more suitable solution?
// Auth Service
// auth.js
APP.service('authService', ['$rootScope', 'endpoints',
function($rootScope, endpoints) {
var ref = new Firebase(endpoints.firebaseReference);
this.auth = new FirebaseSimpleLogin(ref, function(error, user) {
if (user) {
$rootScope.$emit("login", user);
} else if (error) {
$rootScope.$emit("loginError", error);
} else {
$rootScope.$emit("logout");
}
});
}
]);
The Firebase + Angular guide has examples using simple login with routers under the authentication section
ngRoute:
app.config(["$routeProvider", function($routeProvider) {
$routeProvider.when("/home", {
controller: "HomeCtrl",
templateUrl: "views/home.html",
resolve: {
// controller will not be invoked until getCurrentUser resolves
"currentUser": ["simpleLogin", function(simpleLogin) {
// simpleLogin refers to our $firebaseSimpleLogin wrapper in the example above
// since $getCurrentUser returns a promise resolved when auth is initialized,
// we can simple return that here to ensure the controller waits for auth before
// loading
return simpleLogin.$getCurrentUser();
}]
}
})
}]);
app.controller("HomeCtrl", ["currentUser", function(currentUser) {
// currentUser (provided by resolve) will contain the
// authenticated user or null if not logged in
}]);
ui-router:
app.factory("User", ["$FirebaseObject", "$firebase", function($FirebaseObject, $firebase) {
// create a new factory based on $FirebaseObject
var UserFactory = $FirebaseObject.$extendFactory({
// these methods exist on the prototype, so we can access the data using `this`
getFullName: function() {
return this.firstName + " " + this.lastName;
}
});
return function(userId) {
var ref = new Firebase("https://<your firebase>.firebaseio.com/users/").child(userId);
// override the factory used by $firebase
var sync = $firebase(ref, {objectFactory: UserFactory});
return sync.$asObject(); // this will be an instance of UserFactory
}
}]);

Wire up AngularJS controller to express controller

I'm getting started with node/express/Angular by using the MEAN stack at mean.io.
I don't understand how the Angular controller calls the express controller to fetch data.
What I have is public/js/controllers/index.js:
angular.module('mean.system').controller('IndexController', ['$scope', 'Global', 'Tabs',
function ($scope, Global, Tabs) {
$scope.global = Global;
Tabs.query(function(tabs) {
$scope.tabs = tabs;
});
}]);
But I'm confused what exactly 'Tabs' is. I know that somehow, magically, eventually this method is called - I think this is the Express controller?
app/controllers/tabs.js:
exports.all = function(req, res) {
Tab.find().sort('artist').select("-content").populate('user').exec(function(err, tabs) {
if (err) {
res.render('error', {
status: 500
});
} else {
res.jsonp(tabs);
}
});
};
But I don't understand how it gets called. What I want to do is call a different method in app/controllers/tabs.js instead - namely, this:
exports.newest = function(req, res) {
Tab.find().sort('-created').limit(10).select("-content").exec(function(err, tabs) {
...
But I don't understand how to "wire up" the AngularJS controller with the express controller.
i.e. what do I have to do so that I can do something like this in my controller:
angular.module('mean.system').controller('IndexController', ['$scope', 'Global', 'Tabs',
function ($scope, Global, Tabs) {
$scope.global = Global;
Tabs.newest(function(tabs) { // this won't work
$scope.tabs = tabs;
});
}]);
In MEAN, The Articles service is an angular service that returns a $resource object you can usually find in the public/js/services folder.
$resource is a wrapper around $http AJAX service that comes with angularjs, it enables you to connect with RESTful endpoints if your REST service is built in a specific manner.
The connection to the the node.js controller happens using the routes.js object found in the config folder that binds routes to specific module methods.
further reading:
http://docs.angularjs.org/api/ngResource.$resource
http://expressjs.com/api.html#app.VERB

Resources