Angular and requirejs, provider with injectors - javascript

I had a working version of my app that I a trying to now split up into requirejs modules. I am having some trouble getting a provider that has some injectors to work. It seems when I brought it into require js, it does not want to inject my provider properly. I could use some help, this one has me stumped.
Here's what I mean -
The provider -
define('test/my-provider',['angular',
'test/my-factory'
],
function myProvider(angular, MYfactory) {
var metadata = {
componentName: 'myProvider',
moduleName: 'test.myProvider'
};
var $moduleObjectProvider;
$moduleObjectProvider.$inject = [];
$moduleObjectProvider = function() {
var trackConstructor = {};
this.moduleConstructor = function(name, cb) {
//constructor
};
var $get;
this.$get = $get;
$get.$inject = ['URLfactory', '$log', '$location'];
$get = function(URLfactory, $log, $location) {
return {
function1: function(var1) {
},
function2: function(var1, var2) {
},
function3: function() {
}
};
};
};
//metadata.componentName ?
angular.module(metadata.moduleName, []).provider(metadata.componentName, $moduleObjectProvider);
return metadata;
}
);
The issue I am having is with the injectors -
TypeError: Cannot set property '$inject' of undefined
which points to the line containing
$moduleObjectProvider.$inject = [];
One thing worth mentioning is I changed the format of the top bit (JSlinter was yelling at me). When it was working previous to turning it into requirejs format, the top bit looked like this :
$moduleObjectProvider.$inject = [];
function $moduleObjectProvider() {
It seems to be hitting everything fine, the injector is having a problem here (I think). I have hit a wall here - would be great if someone could shed some light on the situation. Thanks!

Because your do var myFunction = function(){} your function is not 'hoisted' to the top. So it is undefined when setting the $inject array. Change it to function myFunction(){} and you should be fine.

Related

Properly using an Angular MVC setup

Feel free to clear up any misunderstandings you see here; I'm not a front end guy.
Now, I've read that much of the logic shouldn't exist in the controller, and that it should be put somewhere else. But most places I look that show code don't specify which file it belongs in. On this project that I've inherited I have 4 files that deal with the main functionality:
A controller - autoDeploy.js
A service - autoDeploy.service.js
A module - autoDeploy.module.js
Directives - directives.js
directives.js just contains the templates that I want to inject into the DOM upon the click of a button, the directives will be referenced later.
autoDeploy.module.js does all of the module.config and $stateProvider routing stuff; I don't touch it beyond my initial modification to point to the new page I'm making so it can be properly routed to.
autoDeploy.service.js: In the examples I've seen, the .factory()'s (or .service()) last parameter usually opens up as a function, and all of the functionality in the file happens inside there. Mine isn't like that, it's an iife with the factory being a standalone command followed by what looks like a constructor. Here's what I have:
(function () { //start iife
'use strict';
angular.module('gms.autoDeploy')
.factory('AutoDeployService', ["$http", "$q", "$log", "$cookies", "APP_CONFIGS", "SweetAlert", "$timeout", "GridSettingsService", "APP_USER", AutoDeployService]);
function AutoDeployService($http, $q, $log, $cookies, APP_CONFIGS, $timeout, SweetAlert, GridSettingsService, APP_USER) {
//return service object at bottom of function
function returnOne() {
return "one";
}
var service = {
returnOne: returnOne
};
return service;
} //end AutoDeployService
}()); //end iife
Why did the original developer...
Use as an iife?
Return the service variable after making it a function mapping?
Put all of the functionality in the constructor like function?
My guess to the above 2nd and 3rd answers is that it is the constructor for the service and the controller can some how know it's a usable object because we pass it in as a parameter to the controller as you can see on the top line of the code below. I also don't know much about the parameters for the "constructor", but I can look those up later.
Now for the controller. Unlike the service above, the controller declaration does open up as a function in the parameters of .controller() as I've seen other places. Here it is, simplified (methods with similar functionality are cut out):
angular.module("gms.autoDeploy").controller('AutoDeployController', ['$scope', '$compile', 'AutoDeployService',
function ($scope, $compile, AutoDeployService) {
var vm = this;
init();
function init() {
vm.isInstantiated = true;
vm.data = {
"parameter": []
};
}
// calling a function from the service to show that we can pass
// data from controller to service
vm.change = function () {
vm.message = AutoDeployService.returnOne("not one");
};
//function assigned to button click on the DOM allowing
// the directive to inject the template where the <tib-copy> tag is
vm.addCopy = function (ev, attrs) {
var copy = angular.element(document.createElement('copy'));
var el = $compile(copy)(vm);
angular.element(document.getElementsByTagName("tib-copy")).append(copy);
vm.insertHere = el;
};
// This method extracts data from the following fields dynamically added by the click of a button:
// - TIBCO Server(s)
// - TIBCO Domain(s)
// - TIBCO Application(s)
vm.getTibPromotionData = function () {
// Add all TIBCO servers
var servers = document.getElementsByName("tibServer");
var tibSvrList = [];
for (var i = 0; i < servers.length; i++) {
tibSvrList.push(servers[i].value);
}
// Add all TIBCO domains
var domains = document.getElementsByName("tibDomain");
var tibDomainList = [];
for (i = 0; i < domains.length; i++) {
tibDomainList.push(domains[i].value);
}
// Add all applications
var tibApps = document.getElementsByName("tibApp");
var tibAppList = [];
for (i = 0; i < tibApps.length; i++) {
tibAppList.push(tibApps[i].value);
}
// Add the processed data to the final JSON
vm.data.parameter.push({
"name": "TIBCO_Promotion",
"value": JSON.stringify("[{\"server\":[" + tibSvrList + "]},{\"domain\":[" + tibDomainList + "]},{\"application\":[" + tibAppList + "]}]")
});
};
}
]);
Please, let me know if you see anything in the controller that should belong in the autoDeploy.service.js file. Furthermore, if anyone has experience with this file naming convention, I'd love to hear an explanation as to why there are files named *.service.js and *.module.js, and if the *.service.js file has anything to do with the concept of services and factories, or if it's intended to be conceptual, as if it's just a reference to being the back end services component.

How to DRY up $scope-manipulating code across controllers in angularJS

Pardon if this question is a total blow-off... Just getting warmed-up into the world angularJS.
I have these two controllers: seekerController and wizardController...
Inside the wizardController, I have a chat Scope object, and I have implemented a bunch of functions that are manipulating this chat Scope object.
Going back to the other controller now, ( seekerController ), I discover that I need to have basically a direct replica of this chat Scope object and all the other functions manipulating it as I have inside wizardController
The obvious way is just to copy all these into my other controller, and my work is done under a minute, but then I'll have a lot of repeated stuffs everywhere...
So: I'm looking for a way where I can have this(the code) in a single place, but still be able to have access to this chat Scope object from both controllers, as well as all the other functions working seamlessly.
Update - add code samples:
//seekerController
angular.module('cg.seeker', [])
.controller('SeekerController', ['$scope', 'seekerService', 'timeService', 'chatService', '$stateParams', 'toastr',
function ($scope, seekerService, timeService, chatService, $stateParams, toastr) {
...
// THE CHAT BUSINESS
$scope.chat = { close: true };
chatService.unreadCount(function(count){
$scope.chat.unreadCount = count;
$scope.$apply();
});
chatService.listDialogs( function (dialogList) {
$scope.chat.dialogList = dialogList.items;
$scope.$apply();
} );
$scope.endChat = function () {
$scope.chat.close = true;
}
$scope.chatBox = function (dialogId, occupants_ids) {
$scope.chat.opponentId = getOpponentId(occupants_ids);
chatService.getMessages( dialogId, function (messageList) {
$scope.chat.messages = messageList.items;
$scope.chat.close = false;
$scope.$apply();
});
}
var getOpponentId = function (opponentId) {
if(typeof(opponentId) != 'object') {
return opponentId;
} else {
return opponentId.filter(function(x) { return x != $scope.seeker.chat_user.chat_id_string; })[0];
}
}
$scope.sendMsg = function (opponentId) {
var msg = {
type: 'chat',
body: $scope.chat.msg,
extension: {
save_to_history: 1,
}
};
chatService.sendMsg(opponentId, msg);
$scope.chat.msg = '';
}
...
I now have an exact replica of the above code in a second controller WizardController. Exactly same, with no changes... and even a third controller have some of these, though not all.
The next level of abstraction to angularjs controllers are
Factory
Service
Provider
You could use a service called maybe chatService which could contain the common code. You can inject the service into any controller which needs the common functionality and invoke the methods on the Service.
Do note that you could use any of the above three options even though I have mentioned just Service in the above statement.
EDIT 1:
You could move the common parts of the code from Controller to Service.
For example:- You could move the construction of msg object from controller to chatService. You controller would be simply -
$scope.sendMsg = function (opponentId) {
chatService.sendMsg(opponentId);
$scope.chat.msg = '';
}
And your chatService would be doing the hard-work.
$chatService.sendMsg = function (opponentId) {
var msg = {
type: 'chat',
body: $scope.chat.msg,
extension: {
save_to_history: 1,
}
};
sendMsg(opponentId, msg);
}
After simplifying the Controllers you could revisit to see if you could use only one controller instead of 3 as they seem to be doing similar function.

Angular service inheritance using Object.create()

I'm trying to use inheritance in angular services, as explained here:
http://blog.mgechev.com/2013/12/18/inheritance-services-controllers-in-angularjs/, I want to use the "Inject the parent" method.
However, it doesn't seem to work, and I can't see why.
var myApp = angular.module('myApp',[]);
angular.module('myApp').controller('MyCtrl', MyCtrl);
angular.module('myApp').factory('BaseModel', BaseModel);
angular.module('myApp').factory('ThreadModel', ThreadModel);
angular.module('myApp').factory('PostModel', PostModel);
function MyCtrl($scope, ThreadModel, PostModel) {
$scope.tableNameForThreads = ThreadModel.getTableName();
$scope.tableNameForPosts = PostModel.getTableName();
}
function BaseModel() {
var tableName = "";
var service = {
init: init,
getTableName: getTableName
};
return service;
function getTableName() {
return tableName;
}
function init(theTableName) {
tableName = theTableName;
}
}
function ThreadModel(BaseModel) {
var service = Object.create(BaseModel);
service.init("threads");
return service;
}
function PostModel(BaseModel) {
var service = Object.create(BaseModel);
service.init("posts");
return service;
}
The result is that ThreadModel.getTableName() returns "posts" in stead of "threads".
I tried both Object.create(...) and angular.copy(BaseModel, this), but both don't seem to make a deep copy.
JSFIDDLE: http://jsfiddle.net/dirkpostma/Lvc0u55v/3989/
What am I doing wrong here?
The problem is that with this set up using Object.create you produce services with the tableName variable stored in the same common closure (BaseModel function). To put it simply, init method modifies the same local tableName variable.
You could fix it like this:
function BaseModel() {
var service = {
init: init,
getTableName: getTableName
};
return service;
function getTableName() {
return this._tableName;
}
function init(theTableName) {
this._tableName = theTableName;
}
}
Note, that getTableName and init methods now work with instance property this._tableName which is not shared between TableModel and PostModel instances.
Demo: http://jsfiddle.net/Lvc0u55v/3991/
#dfsq has already well explained and given a simple solution. I put here what I am thinking about this issue.
In your code Object.create(BaseModel) creates a new object whose prototype is a returned value of BaseModel function. In those children models init method modifies tableName within the local scope of BaseModel function. If you replace tableName with this.tableName, that will work as you expected: both init and getTableName methods will actually modify/call tableName property of service variable within ThreadModel or PostModel functions. But it looks complicated.
In your case I would like suggest the following service inheritance solution, which would be clearer. There is an other post that can be interesting.
var myApp = angular.module('myApp', []);
angular.module('myApp').controller('MyCtrl', MyCtrl);
angular.module('myApp').service('BaseModel', BaseModel);
angular.module('myApp').service('ThreadModel', ['BaseModel', ThreadModel]);
angular.module('myApp').service('PostModel', ['BaseModel', PostModel]);
function MyCtrl($scope, ThreadModel, PostModel) {
$scope.tableNameForThreads = ThreadModel.getTableName();
$scope.tableNameForPosts = PostModel.getTableName();
}
function BaseModel() {
this.tableName = "";
this.getTableName = function() {
return this.tableName;
}
this.init = function(theTableName) {
this.tableName = theTableName;
}
}
function ThreadModel(BaseModel) {
angular.extend(ThreadModel.prototype, BaseModel);
this.tableName = "threads";
}
function PostModel(BaseModel) {
angular.extend(PostModel.prototype, BaseModel);
this.tableName = "posts";
}
JSFiddle: http://jsfiddle.net/Lvc0u55v/3993/

Inject dependencies in "run" method of the module in Angularjs

I trying to understand how do I work with Angularjs. It looks like nice framework, but I stuck with a little problem with DI...
How I can inject dependecies in "run" method of the module? I mean I able to do it, but it works only if I have service/factory/value with as same name as "run" parameter name.
I build a simple application do illustrate what I mean:
var CONFIGURATION = "Configuration"; //I would like to have App.Configuration
var LOG_SERVICE = "LogService"; //I would like to have App.Services.LogService
var LOGIN_CONTROLLER = "LoginController";
var App = {};
App.Services = {};
App.Controllers = {};
App = angular.extend(App, angular.module("App", [])
.run(function ($rootScope, $location, Configuration, LogService) {
//How to force LogService to be the logger in params?
//not var = logger = LogService :)
LogService.log("app run");
}));
//App.$inject = [CONFIGURATION, LOG_SERVICE]; /* NOT WORKS */
App.Services.LogService = function (config) {
this.log = function (message) {
config.hasConsole ? console.log(message) : alert(message);
};
};
App.Services.LogService.$inject = [CONFIGURATION];
App.service(LOG_SERVICE, App.Services.LogService);
App.Controllers.LoginController = function (config, logger) {
logger.log("Controller constructed");
}
//The line below, required only because of problem described
App.Controllers.LoginController.$inject = [CONFIGURATION, LOG_SERVICE];
App.factory(CONFIGURATION, function () { return { hasConsole: console && console.log }; });
Why I need it may you ask :) But in my mind, first off all to have meaningful namespaces to organize the code. It will also minimize name collision and in the last, when minifing the JS, the things breaks down, since it renamed to more shorten names.
I think that the reason
App.$inject = [CONFIGURATION, LOG_SERVICE];
doesn't work, is because you have 2 other parameters $rootScope & $location that you need to inject in the $inject. So it needs to be:
App.$inject = ["$rootScope", "$location", CONFIGURATION, LOG_SERVICE];
Another way you can inject your service is to use this version:
app.run(["$rootScope", "$location", CONFIGURATION, LOG_SERVICE,
function ($rootScope, $location, Configuration, LogService) {
}] );

Angularjs - what I'm doing wrong with injections?

In my previous question I got an answer hot to inject dependencies and while I tested it, everything worked well. Then I refactored the code and wanted to start the app implementation, but the injections stopped to work :(
http://jsbin.com/alemik/1/edit
In addittion to jsbin, here is the source:
var ABCS = angular.module("ABCS", []);
ABCS.Configuration = angular.module('ABCS.Configuration', []);
ABCS.Controllers = angular.module('ABCS.Controllers', []);
ABCS.Modules = angular.module("ABCS.Modules", []);
ABCS.Services = angular.module("ABCS.Services", []);
ABCS.run(["$rootScope", "$location", "ABCS.Services.LogService",
function ($rootScope, $location, logger) {
logger.log("app start");
}]);
ABCS.Configuration.factory("Environment", function () {
return {
logOutputElementId: "output",
hasConsole: console && console.log
}
});
//Log service
ABCS.Services.LogService = function (config) {
this.log = function (message) {
if (typeof (config.Environment.logOutputElementId) === "string") {
document.getElementById(config.Environment.logOutputElementId).innerHTML += message + "<br/>";
}
else if (config.Environment.hasConsole) {
console.log(message);
}
else {
alert(message);
}
};
};
ABCS.Services.LogService.$inject = ["ABCS.Configuration"];
ABCS.Services.factory("ABCS.Services.LogService", ABCS.Services.LogService);
What I miss? Why ABCS.Services.LogService can not be injected in current structure.
Thanks
When you've got several modules, you need to make sure that you declare your modules' dependencies. This tells the dependency injector to look in those modules when looking for providers.
var ABCS = angular.module("ABCS", [
'ABCS.Services', 'ABCS.Configuration', 'ABCS.Controllers',
'ABCS.Modules', 'ABCS.Services'
]);
I had to make a few more adjustments to get it working:
The dependency injector won't inject an entire module, and so ABCS.Services.LogService.$inject = ["ABCS.Configuration"]; wasn't working. I've changed that to ABCS.Services.LogService.$inject = ["ABCS.Configuration.Environment"]; and adjusted the associated factory in order to fit your naming conventions.
factory accepts a function, but won't call it as a constructor, and thus using this in your definition of LogService won't act as you are expecting it to. I've changed your factory function to define a constructor function, which is then instantiated and returned.
See a working version here: http://jsbin.com/alemik/2/edit
In my opinion , that's the angularJS way of doing things ( though i kept all the DI definitions ):
angular.module("ABCS", ['ABCS.Services', 'ABCS.Configuration', 'ABCS.Controllers', 'ABCS.Modules', 'ABCS.Services']);
angular.module('ABCS.Configuration', []);
angular.module('ABCS.Controllers', []);
angular.module("ABCS.Modules", []);
angular.module("ABCS.Services", []);
angular.module("ABCS").run(["$rootScope", "$location", "ABCS.Services.LogService",
function ($rootScope, $location, logger) {
logger.log("app start");
}]);
angular.module('ABCS.Configuration').factory("ABCS.Configuration.Environment", function () {
return {
logOutputElementId: "output",
hasConsole: console && console.log
};
});
angular.module("ABCS.Services").factory("ABCS.Services.LogService",["ABCS.Configuration.Environment",function (environment) {
function LogService() {
this.log = function (message) {
if (typeof (environment.logOutputElementId) === "string") {
document.getElementById(environment.logOutputElementId).innerHTML += message + "<br/>";
}
else if (environment.hasConsole) {
console.log(message);
}
else {
alert(message);
}
}
}
return new LogService();
}]);
angularJS allows to reopen a module definition in multiple files , and javascript namespacing is totally unecessary unless the object is not tight to the angularJS application.
Mixing javascript namespacing and DI namespacing makes code more error prone ,not more maintanable.

Categories