Angular Modular File Structure - javascript

This post was based on this.
My intention is to separate components on a file basis. For example, I want a specific controller to have it's own file (Same goes with services, filters and directives). Of course, files will be group together based on the module they will fall into. Here's an overview of what I currently have:
Directory
User/
User/UserModule.js
User/UserDirective.js
User/UserService.js
User/UserFilter.js
User/UserController.js
UserModules.js
UserModule = angular.module('UserModule', []);
UserModule.controller('userCtrl', ['$scope', 'UserService', UserCtrl])
.factory('userService', function() {
return new UserService();
})
.filter('userFilter', UserFilter)
.directive('userDirective', UserDirective);
UserController.js
UserCtrl = function($scope, UserService) {
// ...
};
UserDirective.js
UserDirective = function() {
return {
// ...
}
};
UserService.js
UserService = function() {
// ...
};
UserFilter.js
UserFilter = function() {
return function() {
// ...
}
};
Then I'll just push the user module to the app module.
app.requires.push('UserModule');
My concern lies on the registration of the concepts (Such as controllers, services...) to the module. I was wondering if this is the best way to go and if it's correct. Also possible issues on the parameters and the external js file.
Consider this part:
.controller('userCtrl', ['$scope', 'UserService', UserCtrl])
The UserCtrl above refers to a function defined in a separate file. Will I be able to pass the $scope and UserService dependency as parameters to the UserCtrl?
UserCtrl = function($scope, UserService) { // Pass parameters (UserController.js)
What's the correct way of doing this in terms of Services, Filters and Directives?
Finally, how can I improve the code?
I'm also using Meteor btw.

You do not need to declare global variables UserModule, UserDirective, UserService. You just need to declare/register them as below. Angular will take care of injecting dependencies.
UserModule.js
angular.module('UserModule', []);
UserDirective.js
angular.module('UserModule').directive('userDirective', function() {
return {
// ...
}
});
UserService.js
angular.module('UserModule').service('UserService', function() {
});
UserController.js
angular.module('UserModule').controller('UserController', ['$scope', 'UserService', function($scope, UserService){
}])

Related

Angular list/detail view

I'm fairly new to Angular and I'm wondering how to go about creating a list/detail view using Angular routes as what I currently have doesn't seem to be working.
The app has a list of 'projects' and when you click on a project you see a detailed view of that selected project, standard stuff. I've got this working using ng-switch but ideally I want to use seperate routes for the list/detail views. I've read that for this I'm going to need to use a factory method but I'm having difficulty passing the selected data between the routes. Here's what I have:
app.factory('Project', [ function ($rootScope) {
var _selectedProject = {};
_selectedProject.project = {};
return _selectedProject;
}]);
app.controller('GalleryController', ['$scope', function ($scope, _selectedProject) {
$scope.sharedProject = _selectedProject || {};
$scope.selectProject = function (proj) {
_selectedProject.project = proj;
};
$scope.$watch('sharedProject', function (proj) {
$scope.chosenProject = proj;
})
}]);
I'm actually getting cannot set property 'property' of undefined which is inside $scope.selectedProject.
A nice solution for this is ui-router.
ui-router allows the nesting of states which correspond to controllers, urls and html templates.
In your example I would do the following:
Install ui-router (described in the link above)
Apply a configuration as follows:
myApp.config(function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('project', {
url: "/project",
templateUrl: "partials/project/list.html",
controller: project_list_controller
})
.state('project.details', {
url: "/details",
templateUrl: "partials/project/details.html",
controller: project_detail_controller
})
}
Split your current controller into a project list controller and a project details controller.
Finally I would use a service to store your selectedProject as its a singleton, see the correct useage and differences between services and factories in this helpful blog post
Hope this helps.
You named your factory Project but are using _selectedProject as the injection to controller. You also didn't include it in the injection array
Controller should look more like:
app.controller('GalleryController', ['$scope','Project', function ($scope, Project) {
$scope.sharedProject = Project || {};
$scope.selectProject = function (proj) {
Project.project = proj;
};
$scope.$watch('sharedProject', function (proj) {
$scope.chosenProject = proj;
});
}]);

Best way to override module values/constants in angularJS

I have written a module in angularJS that encapsulates all the backend communications. For greater flexibility I have the api prefix as a constant value on the module (could be value since I am not using it in the config phase).
so something like
angular.module('myapp.data').constant('apiPrefix', '/api/data');
Now I want to use this module from two different applications. One uses /api1/data and the other one /api2/data and I would like to change this during the config phase of the application.
I know how to do that with a provider, but having a provider to hold a value seems like an overkill to me. Can I modify used modules constants or values from the application config phase?
something like:
angular.module("data", [])
.value('apiPrefix', '/api/data')
.factory('display', function(apiPrefix){
return {
pref: function(){
console.log(apiPrefix);
return apiPrefix;
}
}
});
angular.module("myApp",['data'])
.config(['apiPrefix', function(prefix){
prefix = 'https:/api/data';
}])
.controller("Example", function($scope, display) {
$scope.prefix = display.pref;
});
to override the module values, you can redefine the angular value in later modules. I believe it should not be done module config time.
angular.module("data", [])
.value('apiPrefix', '/api/data')
.factory('Display', function(apiPrefix){
return {
pref: function(){
return apiPrefix;
}
}
});
angular.module('myapp', ['data'])
.value('apiPrefix', '/api2/data')
.controller('MainCtrl', function($scope, Display) {
$scope.name = Display.pref();
});
see the plunker here:
http://plnkr.co/edit/k806WE
same thing is applicable for angular constants too.
Our module
angular.module("data", [])
.constant('apiPrefix', '/api/data');
We can override constant fully, like value.
angular.module('myapp', ['data'])
.constant('apiPrefix', '/api2/data');
also we can override fully in config
angular.module('myapp', ['data'])
.config(function ($provide) {
$provide.constant('apiPrefix', '/api2/data');
});
also we can override fully or partially (if object) in run
angular.module('myapp', ['data'])
.run(function (apiPrefix) {
apiPrefix = '/api2/data';
});
But if we want to override constant with object partially in config (not in run), we can do something like this
angular.module("module1", [])
.constant('myConfig', {
param1: 'value1' ,
param2: 'value2'
});
angular.module('myapp', ['data'])
.config(function ($provide, myConfig) {
$provide.constant(
'myConfig',
angular.extend(myConfig, {param2: 'value2_1'});
);
});
Angular modules, controllers, etc. can be contained within functions, if-statements, etc. They do not have to be at the top level. So, you could include this in your code:
if (environmentOne()) {
module.value('apiPrefix','api1/data');
} else {
module.value('apiPrefix','api2/data');
}
Hope that helps!

Angular CRUD for more than one model object in one application

I have an Angular application that has three different business objects which require CRUD operations to be implemented on all of them independently. Let's call them a,b,c. I have equivalent aCtrl,bCtrl,cCtrl and aSvc,bSvc,cSvc to manage these CRUD operations.
So in this example, aCtrl manages CRUD for 'a'. aSvc helps persist the data to the backend with an ajax call.
I also have an allCtrl and allSvc which pulls all the objects a,b,c together from the backend in one json object when the application loads for the first time (instead of pulling them separately, I designed the backend to push one unified json object with embedded a,b,c in it.
The 'all' object
{
a:{},
b:{},
c:{}
}
so I'm stuck in structuring the app in a straightforward meaningful way. When the app loads for the first time, I'll have 'all' object pulled in by the allSvc from the backend. This has all the data needed to perform CRUD (of course I have to keep it in sync with the backend). And when the app loads for the first time, I want to list the objects of type 'a' and then give the user options to edit/delete/add. Similarly, I have drop down navigation to do take the user to other pages that do exactly the same for object 'b', 'c'.
Here are some snippets of what I did so far and how I fail miserably :)
index.html
<html lang="en" ng-app="myApp">
...
...
<div class="" ng-view></div>
js
var myApp = angular.module('myApp', ['ngRoute','restangular']);
myApp.controller('aCtrl', function($scope, aSvc)
myApp.controller('bCtrl', function($scope, bSvc)
myApp.controller('cCtrl', function($scope, cSvc)
myApp.controller('allCtrl', function($scope, allSvc)
routes
myApp.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/a/list', {templateUrl: 'partials/a/list.html',controller: 'aCtrl'});
$routeProvider.when('/a/add', {templateUrl: 'partials/a/add.html', controller: 'aCtrl'});
$routeProvider.when('/a/edit/:id', {templateUrl: 'partials/a/edit.html', controller: 'aCtrl'});
$routeProvider.when('/a/delete/:id', {templateUrl: 'partials/a/list.html', controller: 'aCtrl'});
.... similary I have routes for b & c to perform crud
$routeProvider.otherwise({redirectTo: '/a/list'});
}]);
aCtrl
myApp.controller('aCtrl', function($scope, aSvc,allSvc) {
$scope.allCollection= allService.getAll();
$scope.aCollection = allCollection.a;
I'm unable to meaningfully setup routeParams in this design to perform edit/delete properly. I also have to account for the userId (the user who logs in for whom the CRUD operations need to be performed). How do I better manage the routes? should I use something like /a/edit/userId/aId for editing 'a' and /a/delete/userId/aId to delete 'a' as an example?
Please help me polish this turd.
I have separated the service/update/request calls in a controller and the project can be found at the below path. Let me know if anyone thinks it can be improved further. However, to test the services, I have used strongloop and the description can be found at the repository itself.
Angular - Create, Update, Delete
The sample would look like this:
'use strict';
function MainController($scope, $state, $rootScope, $window, $stateParams, HttpService) {
$scope.init = function () {
HttpService.load("http://0.0.0.0:3000/api/custdetails")
.then(function (response) {
if (response) {
console.log(JSON.stringify(response.data));
$scope.showAllData = response.data;
}
}, function (error) {
console.log("Error occurred");
});
};
$scope.addMoreData = function () {
var data = {
cust_name: $scope.custNameModel,
phone_number: $scope.phoneNumberModel,
id: $scope.idModel
};
HttpService.update('http://0.0.0.0:3000/api/custdetails?', data);
$scope.init();
};
$scope.updateData = function () {
var data = {
cust_name: $scope.custNameModel,
phone_number: $scope.phoneNumberModel,
id: $scope.idModel
};
HttpService.patch('http://0.0.0.0:3000/api/custdetails?', data);
$scope.init();
};
$scope.deleteData = function () {
console.log("ID defined is: " + $scope.idModel);
HttpService.delete("http://0.0.0.0:3000/api/custdetails", $scope.idModel);
$scope.init();
};
}

Unit testing AngularJS lazy-loaded controller

I am using this strategy to lazy-load stuff with RequireJS in my AngularJS app:
define([
'src/services/dependency_resolver', // resolves promise when dependencies are `require`d
'json!modules.json'
], function (dependencyResolver, modules) {
var app = angular.module('myApp', [ 'ngRoute' ]);
app.config(function ($controllerProvider, $routeProvider) {
app.lazy = {
controller: $controllerProvider.register
// <...> other providers
};
angular.forEach(modules, function (moduleConfig) {
angular.forEach(moduleConfig.routes, function (route) {
$routeProvider.when(route.path, {
templateUrl: route.templateUrl,
controller: route.controller,
resolve: dependencyResolver(moduleConfig.dependencies)
});
});
});
});
return app;
});
But I'm not sure what is the correct way test a lazy-loaded controller. It is registered like this:
define(['src/app'], function (app) {
app.lazy.controller('MainCtrl', function () {
//
});
});
And this is my current spec:
describe('`MainCtrl` controller', function () {
var Ctrl,
$scope;
beforeEach(angular.mock.module('myApp'));
beforeEach(function (done) {
require(['module/main'], done);
});
beforeEach(function () {
angular.mock.inject(function ($rootScope, $controller) {
$scope = $rootScope.$new();
Ctrl = $controller('MainCtrl', {
$scope: $scope
});
});
});
it('should ...', function () {
console.log(Ctrl);
});
});
With this spec, an error occurs when controller is being registered, because app.lazy is undefined.
So the question is how to test such controllers?
Cheers!
I was experiencing a similar problem when writing my unit test using the "lazy" property to register my controller. The problem with this approach is that when in the context of a unit test, the module config block will not be executed and as a result, app.lazy will resolve to undefined.
To solve your problem, instead of using provider registration methods to set your properties of app.lazy, the provider registration method should be used to override their counterparts on the module. In other words, your config block should now become:
`app.config(function ($controllerProvider, $routeProvider) {
app.controller = $controllerProvider.register
// <...> other providers
.......
}`
Instead of register your controller using (app.lazy):
`define(['src/app'], function (app) {
app.lazy.controller('MainCtrl', function () {
//
});
});`
you can just define like this:
`define(['src/app'], function (app) {
app.controller('MainCtrl', function () {
//
});
});`
And this should work! Hopefully this can help, and please let me know if this works out or not.
First of all, thank you for the reference you provided - the article is really interesting.
Author of the article is using AngularJs providers to implement his strategy. The thing is, that AngularJs doesn't have providers for 'specs'. So my opinion is that you should omit this strategy in your unit tests.
On this basis, I think, that you should add AMD to your spec file. Define your controller as a dependency in your spec. After this, you may just require all your specs somewhere in main-spec.js and launch your testing framework.

Argument 'fn' is not a function got string

I have a part in my angular application on which I've binded a controller,
since then I got the Argument 'fn' is not a function Error, can anyone look at my code and explain why I got that Error?
I would be very gratefull :)
html-markup:
<section class="col-lg-12" data-ng-controller="MessageController">
<fieldset>
<legend>{{ 'MESSAGES' | translate }}</legend>
</fieldset>
<div class="margin-left-15">
<ul class="list-style-button">
<li data-ng-repeat="message in MSG">{{ message }}</li>
</ul>
</div>
</section>
controller:
(function() {
'use strict';
var controllers = angular.module('portal.controllers');
controllers.controller('MessageController', ['$scope', 'MessageService', '$rootScope', function MessageController($scope, MessageService, $rootScope) {
$rootScope.MSG = MessageService.getMessages();
$rootScope.$watch('MSG', function(newValue) {
$scope.MSG = newValue;
});
}]);
}());
Service:
(function() {
'use strict';
var messageServices = angular.module('portal.services');
messageServices.factory('MessageService', ['MessageData', 'localStorageService', 'UserService'], function(MessageData, localStorageService, UserService) {
return new MessageService(MessageData, localStorageService, UserService);
});
function MessageService(MessageData, localStorageService, UserService) {
this.messageData = MessageData;
this.localStorageService = localStorageService;
this.userService = UserService;
}
MessageService.prototype.getMessages = function() {
var locale = this.userService.getUserinfoLocale();
var messages = this.localStorageService.get(Constants.key_messages + locale);
if (messages !== null && messages !== undefined) {
return JSON.parse(messages);
} else {
return this.messageData.query({
locale: locale
}, $.proxy(function(data, locale) {
this.save(Constants.key_messages + locale, JSON.stringify(data));
}, this));
}
};
MessageService.prototype.save = function(key, value) {
this.localStorageService.add(key, value);
};
}());
data:
(function() {
'use strict';
var data = angular.module('portal.data');
data.factory('MessageData', function($resource) {
return $resource(Constants.url_messages, {}, {
query: {
method: 'GET',
params: {
locale: 'locale'
},
isArray: true
}
});
});
}());
order of js files in html head:
<script src="js/lib/jquery-1.10.js"></script>
<script src="js/lib/angular.js"></script>
<script src="js/lib/angular-resource.js"></script>
<script src="js/lib/angular-translate.js"></script>
<script src="js/lib/angular-localstorage.js"></script>
<script src="js/lib/jquery-cookies.js"></script>
<script src="js/lib/bootstrap.js"></script>
<script src="js/portal.js"></script>
The problem was in using the 'wrong' syntax to create the service
instead of using:
messageServices.factory('MessageService',
['MessageData','localStorageService', 'UserService'],
function(MessageData, localStorageService, UserService){
return new MessageService(MessageData, localStorageService, UserService);
}
);
I had to use:
messageServices.factory('MessageService',
['MessageData','localStorageService', 'UserService',
function(MessageData, localStorageService, UserService){
return new MessageService(MessageData, localStorageService, UserService);
}
]);
I closed the array with parameters to soon, and since I'm still learning I didn't see it directly, anyhow I hope I can help others who stumble upon this.
Today I got the same kind of error doing that silly mistake:
(function(){
angular
.module('mymodule')
.factory('myFactory', 'myFactory'); // <-- silly mistake
myFactory.$inject = ['myDeps'];
function myFactory(myDeps){
...
}
}());
instead of that:
(function(){
angular
.module('mymodule')
.factory('myFactory', myFactory); // <-- right way to write it
myFactory.$inject = ['myDeps'];
function myFactory(myDeps){
...
}
}());
In fact the string "myFactory" was brought into the injector who was waiting for a function and not a string.
That explained the [ng:areq] error.
The above answers helped me considerably in correcting the same issue I had in my application that arose from a different cause.
At built time, my client app is being concatenated and minified, so I'm writing my Angular specifically to avoid related issues. I define my config as follows
config.$inject = [];
function config() {
// config stuff
}
(I define a function, $inject it as a module and declare what it is).
And then I tried to register the config just as I registered other modules in my app (controllers, directives, etc..).
angular.module("app").config('config', config); // this is bad!
// for example, this is right
angular.module("app").factory('mainService', mainService);
This is wrong, and gave me the aforementioned error. So I changed to
angular.module("app").config(config);
And it worked.
I guess the angular devs intended config to have a singular instance and by so having Angular not accept a name when config is registered.
I had the same issue and In my case the problem was with angular-cookies.js file. It was in folder with other angularjs scripts and when I have used gulp to minify my js files the error occured.
Simple solution was just to place the angular-cookies.js file to another folder, outside the selected folder to minify js files.
My case
let app: any = angular.module("ngCartosServiceWorker"),
requires: any[] = [
"$log",
"$q",
"$rootScope",
"$window",
"ngCartosServiceWorker.registration",
PushNotification
];
app.service("ngCartosServiceWorker.PushNotification");
I forgot to add requires Array as parameters to service like this
app.service("ngCartosServiceWorker.PushNotification", requires);

Categories