I'm using the following angularjs project structure:
index.html
js/
-- angularjs
-- application.js
-- shared
-----SharedModule.js
-----LocalizeService.js
-----OtherSharedService.js
-- user
-----UserModule.js
-----LoginController.js
-----RegisterController.js
-----UserService.js
In other words I group files not by their type (e.g. services/controllers/directives), but by their logic purpose (e.g. user/shared/cart etc). I read this is the correct approach for large projects.
The main application.js file includes the modules like this:
angular.module('myApplication', [
'ngRoute',
'ngCookies',
'sharedModule',
'userModule',
'dahsboardModule',
])
Then, each module includes the related controllers/directives/services/whatever.
e.g. SharedModule.js
var sharedModule = angular.module('sharedModule',[]);
sharedModule.factory('Localize', ['$http', '$rootScope', '$window', LocalizeService]);
sharedModule.controller('someController',['$rootScope',SomeController]);
Then I implement the logic in each separate file.
My question is: what design pattern should I use to implement the logic of each separate service/controller?
I read this book: http://www.addyosmani.com/resources/essentialjsdesignpatterns/book/
and so far my beloved design pattern is 'Revealing module pattern' which is kinda omni-purpose design pattern. I used it many times in other projects (w/o angularjs).
But it seems I cannot use it with angularjs.
var LocalizeService = (function() {
})();
How can I pass all the stuff like $rootScope/$http to the module?
This is how it works for me now:
function LocalizeService($http,$rootScope,$window) {
var localize = (function() {
function publicFunction() {
// do smth.
}
return {
someFunction: publicFunction
}
})();
return localize;
}
But I'm not sure if it is quite correct. Could you please kindly advise?
You an use a service with a constructor instead of a factory:
var LocalizeService = (function() {
function LocalizeService($http, $rootScope, $window) {
}
LocalizeService.prototype = {
publicFunction: function() {
}
};
LocalizeService.$inject = ['$http','$rootScope','$window'];
return LocalizeService;
}());
sharedModule.service('Localize', LocalizeService);
Related
I'm trying to make an Angular Service that houses common functions.
I bundled the code within my MVC app:
bundles.Add(new ScriptBundle("~/bundles/Angular")
.IncludeDirectory("~/app", "*.js", true));
And I checked in Developer Tools if it actually brought in my Common Folder with Common.js :
I added Common to the App :
var app = angular.module('app',
[
'JobCtrl',
'JobSvc',
'WebsiteCtrl',
'WebsiteSvc',
'myClientCtrl',
'ClientSvc',
'MediaCompanyCtrl',
'MediaCompanySvc',
'PageAlertSvc',
'ui.bootstrap',
'ui.bootstrap.tpls',
'Common'
]
);
and to the Controller:
angular.module('app', ['ui.bootstrap', 'ui.bootstrap.tpls'])
.controller('JobCtrl',
[
'JobService',
'WebsiteService',
'MediaCompanyService',
'ProductService',
'$scope',
'$uibModal',
'PageAlertService',
'Common',
function (JobService, WebsiteService, MediaCompanyService,
ProductService, $scope, $uibModal,PageAlertService, Common)
This is what my Common.js file looks like:
angular.module('app')
.service('Common', function () {
this.heyThere = function ()
{
console.log('Just wanted to say hey there')
};
});
Whenever it is called within my JobCtrl I get a Error: $injector:unpr
Unknown Provider.
Could anyone see what I may be doing wrong where it won't recognize my Common.js file? When I move Common.js to the Services folder and try calling it within my controller it works, but not when it is in my Common Folder. Makes no sense!
Thanks in advance!
That is simply because you are defining your app..twice!!!!
angular.module('app', []) // this is where you re-define your app
.service('Common', function () {
this.heyThere = function ()
{
console.log('Just wanted to say hey there')
};
});
should be:
angular.module('app')
.service('Common', function () {
this.heyThere = function ()
{
console.log('Just wanted to say hey there')
};
});
the module function has 2 modes.. with 2 arguments you are setting up your app.. with a single argument you just getting a reference to an existing app (which is already defined before that)
Please be careful when you use the declaration of a module. You are basically reassigning the app module to different instances.
angular.module('app', [dependencies]) //Constructs a module with dependencies
angular.module('app').service(...) //Associates the components (service)
//with the app module.
I have the following Angular module. How can i call for example APIHost from one of my controllers?
angular.module('configuration', [])
.constant('APIHost','http://api.com')
.constant('HostUrl','http://example.com')
.constant('SolutionName', 'MySite');
Constant is nothing but one kind of provider recipe.
You need to inject constant dependency inside your controller factory function, that's it.
app.controller('testCtrl', function($scope, APIHost){
console.log(APIHost)
})
Make sure your configuration module has been added to main module as dependency
to get use of constant's provider like below
var app = angular.module('app', ['configuration', 'otherdependency']);
app.controller( ... ) //here you can have configuration constant available
Like this, just like any service or factory.
I have also include structure for industry standard (kind of) from john papa's coding guidelines.
(function() {
'use strict';
angular
.module('configuration')
.controller('ctrlXYZ', ctrlXYZ);
//Just inject as you would inject a service or factory
ctrlXYZ.$inject = ['APIHost'];
/* #ngInject */
function ctrlXYZ(APIHost) {
var vm = this;
activate();
function activate() {
//Go crazy with APIHost
console.log(APIHost);
}
}
})();
Hope the helps!
I'm trying to get into the habit of structuring my Angular projects following LIFT protocol (Locate, Identify, Flat, Try(Dry)) but I'm having some difficulty resolving dependencies from other files.
I have the following factory:
(function () {
'use strict';
angular
.module('CBPWidget', [])
.factory('apiManufacturers', apiManufacturers);
function apiManufacturers () {
function hello () {
return 'hello';
}
return {
hello: hello
};
}
})();
and the following controller:
(function () {
'use strict';
angular
.module('CBPWidget', [])
.controller('stepOneController', stepOneController);
stepOneController.$inject = ['$scope', 'apiManufacturers'];
function stepOneController ($scope, apiManufacturers) {
$scope.step = 'step1';
console.log(apiManufacturers.hello);
}
})();
and the following error is thrown:
Error: [$injector:unpr] Unknown provider: apiManufacturersProvider <- apiManufacturers <- stepOneController
My factory JS file is placed above the controller JS file in my HTML (which will be minified).
Any advice on where I'm going wrong would be greatly appreciated as I'm new to structuring projects this way.
Here you are creating CBPWidget module two times.
angular.module('CBPWidget',[]) is used for creating module and
angular.module('CBPWidget') is used for getting already created module.
so replace controller code with this :
(function () {
'use strict';
angular
.module('CBPWidget')//now you are getting CBPWidget module
.controller('stepOneController', stepOneController);
stepOneController.$inject = ['$scope', 'apiManufacturers'];
function stepOneController ($scope, apiManufacturers) {
$scope.step = 'step1';
console.log(apiManufacturers.hello);
}
})();
Your angular.module('CBPWidget', []) block code is redefining angular app, which was flushing apiManufacturers service associated with it, & it is defining controller in it. You should never do that, you should use existing module which was already defined.
Code
angular
.module('CBPWidget') //don't overide app here use existing
.controller('stepOneController', stepOneController);
From the documentation for AngularJS, you'll find that
.module('CBPWidget', [])
is different from
.module('CBPWidget')
The latter is what you need to refer to a module, the former is for defining one. In all cases except where you first define it, you should be using the latter form.
I'm wondering if it would be a good practice to avoid using the 'angular' global object within Controllers, Services, etc.
For the sake of example, let's say we want to call the function:
angular.isDefined(myVar)
How shall we reference the 'angular' object?
Options:
1 Just use it, might get some 'variable is not defined' warning from IDEs
2 Reference the 'angular' dependency the AMD way
define([
'angular'
], function (angular) {
'use strict';
return ['$log', '$filter', function ($log, $filter) {
return {
// ... code ...
angular.isDefined(myVar);
};
}];
}
);
3 Reference 'angular' the Angular way
module.factory('ang', function() { return angular; });
define([], function () {
'use strict';
return ['ang', '$log', '$filter', function (ang, $log, $filter) {
return {
// ... code ...
ang.isDefined(myVar);
};
}];
}
);
I'd go for Option 3, just wondering what would be the best way.
The fact that you're using Angular in the first place means you don't have to worry about it being undefined. From there it's really just a static analysis problem... you want to make it clear that you're referencing a global variable.
Both solutions you've presented are good. There are a couple of other options too:
Inject $window and reference $window.angular explicitly
Configure your static analysis tool so it knows angular is defined elsewhere. You can do so with the globals property in .jshintrc.
I would probably go for a combination of $window and your option 3, to prevent warnings when analysing that file:
module.factory('ang', function($window) { return $window.angular; });
I'm building an angular directive which will be used in a few different locations.
I can't always guarantee the file structure of the app the directive is used in, but I can force the user to put the directive.js and directive.html (not the real file names) in the same folder.
When the page evaluates the directive.js, it considers the templateUrl to be relative to itself. Is it possible to set the templateUrl to be relative to the directive.js file?
Or is it recommended to just include the template in the directive itself.
I'm thinking I may want to load different templates based on different circumstances, so would prefer to be able to use a relative path rather than updating the directive.js
The currently executing script file will always be the last one in the scripts array, so you can easily find its path:
// directive.js
var scripts = document.getElementsByTagName("script")
var currentScriptPath = scripts[scripts.length-1].src;
angular.module('app', [])
.directive('test', function () {
return {
templateUrl: currentScriptPath.replace('directive.js', 'directive.html')
};
});
If you're not sure what is the script name (for example if you're packing multiple scripts into one), use this:
return {
templateUrl: currentScriptPath.substring(0, currentScriptPath.lastIndexOf('/') + 1)
+ 'directive.html'
};
Note: In cases where a closure is used, your code should be outside to ensure that the currentScript is evaluated at the correct time, such as:
// directive.js
(function(currentScriptPath){
angular.module('app', [])
.directive('test', function () {
return {
templateUrl: currentScriptPath.replace('directive.js', 'directive.html')
};
});
})(
(function () {
var scripts = document.getElementsByTagName("script");
var currentScriptPath = scripts[scripts.length - 1].src;
return currentScriptPath;
})()
);
As you said you wanted to provide different templates at different times to the directives, why not allow the template itself to be passed to the directive as an attribute?
<div my-directive my-template="template"></div>
Then use something like $compile(template)(scope) inside the directive.
In addition to the answer from Alon Gubkin I'd suggest to define a constant using an Immediately-Invoked Function Expression to store the path of the script and inject it into the directive:
angular.module('app', [])
.constant('SCRIPT_URL', (function () {
var scripts = document.getElementsByTagName("script");
var scriptPath = scripts[scripts.length - 1].src;
return scriptPath.substring(0, scriptPath.lastIndexOf('/') + 1)
})())
.directive('test', function(SCRIPT_URL) {
return {
restrict : 'A',
templateUrl : SCRIPT_URL + 'directive.html'
}
});
This code is in a file called routes.js
The following did not work for me:
var scripts = document.getElementsByTagName("script")
var currentScriptPath = scripts[scripts.length-1].src;
var baseUrl = currentScriptPath.substring(0, currentScriptPath.lastIndexOf('/') + 1);
the following did:
var bu2 = document.querySelector("script[src$='routes.js']");
currentScriptPath = bu2.src;
baseUrl = currentScriptPath.substring(0, currentScriptPath.lastIndexOf('/') + 1);
My test is based on the following blog about using require to lazy load angular:
http://ify.io/lazy-loading-in-angularjs/
require.js begets a requireConfig bootstrap
requireConfig begets an angular app.js
angular app.js begets my routes.js
I had the same code being served up by a revel web framework and asp.net mvc.
In revel
document.getElementsByTagName("script")
produced a path to my require bootstrap js file and NOT my routes.js.
in ASP.NET MVC it produced a path to Visual Studio's injected Browser Link script element that is put there during debugging sessions.
this is my working routes.js code:
define([], function()
{
var scripts = document.getElementsByTagName("script");
var currentScriptPath = scripts[scripts.length-1].src;
console.log("currentScriptPath:"+currentScriptPath);
var baseUrl = currentScriptPath.substring(0, currentScriptPath.lastIndexOf('/') + 1);
console.log("baseUrl:"+baseUrl);
var bu2 = document.querySelector("script[src$='routes.js']");
currentScriptPath = bu2.src;
console.log("bu2:"+bu2);
console.log("src:"+bu2.src);
baseUrl = currentScriptPath.substring(0, currentScriptPath.lastIndexOf('/') + 1);
console.log("baseUrl:"+baseUrl);
return {
defaultRoutePath: '/',
routes: {
'/': {
templateUrl: baseUrl + 'views/home.html',
dependencies: [
'controllers/HomeViewController',
'directives/app-style'
]
},
'/about/:person': {
templateUrl: baseUrl + 'views/about.html',
dependencies: [
'controllers/AboutViewController',
'directives/app-color'
]
},
'/contact': {
templateUrl: baseUrl + 'views/contact.html',
dependencies: [
'controllers/ContactViewController',
'directives/app-color',
'directives/app-style'
]
}
}
};
});
This is my console output when running from Revel.
currentScriptPath:http://localhost:9000/public/ngApps/1/requireBootstrap.js routes.js:8
baseUrl:http://localhost:9000/public/ngApps/1/ routes.js:10
bu2:[object HTMLScriptElement] routes.js:13
src:http://localhost:9000/public/ngApps/1/routes.js routes.js:14
baseUrl:http://localhost:9000/public/ngApps/1/
Another nice thing I have done is to take advantage of the require config and put some custom configurations in it.
i.e. add
customConfig: { baseRouteUrl: '/AngularLazyBaseLine/Home/Content' }
you can then get it by using the following code from inside of routes.js
var requireConfig = requirejs.s.contexts._.config;
console.log('requireConfig.customConfig.baseRouteUrl:' + requireConfig.customConfig.baseRouteUrl);
sometimes you need to define a baseurl upfront, sometimes you need to dynamically generate it. Your choice for your situation.
Some might suggest it slightly "hacky", but I think until there is only 1 way to do it, anything is going to be hacky.
I've had a lot of luck with also doing this:
angular.module('ui.bootstrap', [])
.provider('$appUrl', function(){
this.route = function(url){
var stack = new Error('dummy').stack.match(new RegExp(/(http(s)*\:\/\/)[^\:]+/igm));
var app_path = stack[1];
app_path = app_path.slice(0, app_path.lastIndexOf('App/') + 'App/'.length);
return app_path + url;
}
this.$get = function(){
return this.route;
}
});
Then when using the code in an application after including the module in the app.
In an app config function:
.config(['$routeProvider', '$appUrlProvider', function ($routeProvider, $appUrlProvider) {
$routeProvider
.when('/path:folder_path*', {
controller: 'BrowseFolderCntrl',
templateUrl: $appUrlProvider.route('views/browse-folder.html')
});
}]);
And in an app controller (if required):
var MyCntrl = function ($scope, $appUrl) {
$scope.templateUrl = $appUrl('views/my-angular-view.html');
};
It creats a new javascript error and pulls out the stack trace. It then parses out all urls (excluding the calling line/char number).
You can then just pull out the first in the array which will be the current file where the code is running.
This is also helpful if you want to centralise the code and then pull out the second ([1]) in the array, to get the calling file location
As several users have pointed out, relevant paths are not helpful when building the static files, and I would highly recommend doing so.
There is a nifty feature in Angular called $templateCache, which more or less caches template files, and next time that angular requires one, instead of making an actual request it provides the cached version. This is a typical way to use it:
module = angular.module('myModule');
module.run(['$templateCache', function($templateCache) {
$templateCache.put('as/specified/in/templateurl/file.html',
'<div>blabla</div>');
}]);
})();
So in this way you both tackle the problem of relative urls and you gain in performance.
Of course we love the idea of having separate template html files (in contrast to react), so the above by its own is no good. Here comes the build system, which can read all template html files and construct a js such as the above.
There are several html2js modules for grunt, gulp, webpack, and this is the main idea behind them. I personally use gulp a lot, so I particularly fancy gulp-ng-html2js because it does exactly this very easily.