Angular 1 common service/module/library for multiple "apps" - angularjs

I can't describe it very well but:
I have one angular app for "one" functionality.
E.q. I have "campaigns" functionality. Add, Edit, Display all, display in another way.
For add and edit I have that same app and controller and context etc.
For rest I have different folders with app, controller, context, config, eventHanlder etc.
But all of them have simillar code in ~70%.
It is context with download and upload data via $http to ASP.NET WebAPI project.
And few methods in controller.
I want "merge" it into one but I cant don't want merge these functions into one app because they have different "view".
So I was thinking about:
Make "routing" app for these simillar apps.
one context, config, and controller same in more than half
some different options so it won't be "SoC"?
Move common from "context, config, controller" to another files/apps and inject them to concrete controller.
But I don't know how to do it.
I know I can "factory" method to app inside it config but I want these "services" be indepentent to controllers and apps. As different app in different catalog. So I can inject them via controller.
How it is possible?
Yeah I really new in Angular ;)

Question 1:
I would advise to look at the ui-router module. This module allows you to define url routes to states with views and nested states/views. Very useful for anything more than a single page app.
Question 2:
Sounds like you're talking about setting up a few angular.modules that depend on one another.
In general you would group related services, controllers, configs into a module. If another module needs to use one of those, you make that module depend on the other.
Example:
// myApp.foo module
angular.module('myApp.foo', [])
.factory('Foo', function(){
return {
data: {
foo: 'angularjs is cool!';
},
doStuff: function(){
}
};
})
.controller('FooController', function($scope, Foo){
$scope.foo = Foo.data.foo;
Foo.doStuff();
});
// myApp.bar module
angular.module('myApp.bar', ['myApp.foo']) // notice dependency on myApp.foo
.factory('Bar', function(Foo){ // you can inject Foo because of dependency
return {
data: {
bar: 'using angular modules helps us organize'
},
doStuff: {
},
doOtherStuff: {
Foo.doStuff();
}
};
})
.controller('BarController', function($scope, Bar, Foo){ // you can inject Foo into controllers too
$scope.foo = Foo.data.foo;
$scope.bar = Bar.data.bar;
$scope.doFoo = Foo.doStuff;
$scope.doBar = Bar.doStuff;
$scope.doOtherBar = Bar.doOtherStuff;
});
It is always good practice to think about and develop your modules in a way that they can be reused in different projects.
Update: (RE: question about /AppCommon, /App1, /App2, /App3)
Just make your service in: /AppCommon/my-service.srv.js*
It would look something like:
angular.module('MyCompany.Common', [])
.factory('CommonService', function($http){
return {
doStuff: function(){
$http.get('/some-url').then(function(res){
});
}
};
});
And in your /App1, /App2, /App3 projects:
Include that js file into your HTML:
<script src="/AppCommon/my-service.srv.js></script>
Inject the MyCompany.Common module into the areas of the apps where you need it.
angular.module('App1', ['MyCompany.Common']);
Inject the CommonService into the app's controllers/services where you need it.
angular.module('App1', ['MyCompany.Common'])
.controller('App1Controller', function($scope, CommonService){
});
*Note: You can name your javascript file however you want. I have found it very helpful to include .srv in files containing services, .ctrl in files containing controllers, .drv in files containing directives, .tpl in template files, etc... This goes along with the best practice of having one thing per file.

Related

AngularJS module architecture - in the middle of two approaches

I went deep into investigating of modern approaches and stuck with a problem.
One approach is to split application modules depending on purpose:
angular.module('controllers', [])
angular.module('directives', [])
angular.module('services', [])
Another approach is to split modules by feature
angular.module('page.user', [])
.controller()
.service()
I do like the second one and believe that is the most efficient one. BUT, what about reusable services? Here is a brief example I took from my head:
angular
.module('myApp') // myApp refers to the main app module
.service('userService', function($http) {
let self = this;
self.getUser = function(id) {
return $http.get('api/user/' + id)
.then(functions() {
// some important logic hoes here
})
}
})
So I have a getUser method, that contains some specific business logic.
I do not want copy the same method to every new feature-module because DRY. I want every feature, that need getUser ,method to refer userService, so in this case I would need to put the service into main app module which will bootstrap all other feature-modules as well, or I need to create a modules specially for shared services and now we at the first on purpose approach.
So the question is more like how to stick to the second approach and keep everything well organized with DRY principle. Thanks

Angular JS - Sending data from a directive to a parent controller

I have an app where I use the ngCart directive in order to store the items added to a basket. The problem is that this directive just has the functionality of sending the information about the items added by the user, but I would need also to send some information that I would get from a form.
So in order to send it in a single object, I need first to extract the data stored in the directive to my main scope, and then merge it with the data I get from the form.
For that I need to modify the ngCart.js directive. I tried to make a service, as adviced here, but I don't get to get it working. The code I added to the directive is this
.service('ngCartData', ['ngCart', function(ngCart){
return {
data:ngCart;
};
}])
, but I get an error saying Module 'ngCart' is not available!
I'm totally new to services and factories in angular, so I don't know exactly where to look to make it work. I made a plunkr with my code (I tried modifying the ngCart.js file with the code above, but the plunkr shows the directive without any modification). I just need to be able to send the data stored in the directive in the scope ngCart so that I can listen to it in the parent controller (see the checkout section in the plunkr).
Any help would be much appreciated. Thanks!
did you load the js file like this :
<script src="pathto/angular/angular.js"></script>
<script src="pathto/ngCart.js"></script> or ngCart.min.js
did you load the module in your declaration module like this ? :
var myApp = angular.module('myApp',['ngCart']);
You actually have this backward. You can't inject a directive into a service. You must inject the service into the main controller and into the directive so that you can use it as a bridge between the two. Services are singletons so if you modify the properties of a service those modifications will be available anywhere else it is asked for.
Your service will look something like this:
.service('ngCartData', [function(){
return {
data:[],
addData: function(newData){
this.data.push(newData);
},
getData: function(){
return this.data;
}
};
}])
then in your controller and directive use the ngCartData api, which would look something like this:
$scope.someData = ngCartData.getData();
$scope.someFunction = function(dataToStore){
ngCartData.addData(dataToStore);
};
You had the right idea in mind, and I'm surprised it didn't work for you.
I have edited your app in the following way (in script.js)
app.controller('myCtrl', function($scope, ngCart, myCart) {
$scope.names = [...];
...
console.log(myCart.cart);
})
.factory('myCart',function(ngCart){
return {
cart: ngCart.$cart
};
})
and it logged {shipping: 30, taxRate: null, tax: null, items: Array[2]}, which I think is what you need (I added 2 items before it logged).
Notice that adding a the service is redundant; The data is accessible whenever and wherever you need. Just inject ngCart to your controller/service/etc. and the updated data will be available to you.
Therefore, the following code is equivalent:
app.controller('myCtrl', function($scope, ngCart) {
$scope.names = [...];
...
console.log(ngCart.$cart);
});
A possible reason for the getting the error you got might be that, while editing the ngCart module, you had some sort of error (like a typo), which led to ngCart being invisible to angular.

registerModule with dependencies

I'm building an app with MEAN.JS and I'm trying to use a controller from another module. I've found that I can do this with dependency injection:
angular.module(‘MyModule’, [‘Dependency’]);
But the way modules are created in MEAN.JS is:
ApplicationConfiguration.registerModule('MyModule');
And I can't just pass a second parameter to registerModule. So, how should I do this? Do I have to use both methods? Am I doing it wrong?
Example
I want to add a new model: Campaign. Campaigns are created by admins only, but can be seen by the "campaign owner" (another User). The create campaign form should list available Users, so the admin can select the one that's going to be the "owner" of that Campaign.
The problem is, the create campaign form is controlled by CampaignsController, how can I list Users? I've used another controller (UsersController) and thats the problem, it is undefined because we are in the Campaign module.
EDIT:
The problem was grunt autorestarting the app incorrectly:
I moved the controller from one module (folder) to another, but grunt was still trying to load it from the old path, and thus failing: Controller not found. I thought the problem was dependency injection, but you only have to close grunt (Ctrl+C) and run it again. Problem solved.
Anyways, thanks for the answer #gilBirman cause it is correct: there is no need to inject anything with MEANJS.
MEAN.js makes all the modules registered via registerModule available to all other modules in your app by adding it as a dependency to the main app called mean. Here's the part of the MEAN.js source code that does this:
var applicationModuleName = 'mean';
....
// Add a new vertical module
var registerModule = function(moduleName) {
// Create angular module
angular.module(moduleName, []);
// Add the module to the AngularJS configuration file
angular.module(applicationModuleName).requires.push(moduleName);
};
So you're on the right track, however it sounds like you are trying to inject a controller. However, in angular, controllers are not injectable. You can inject services, factories, values, and constants.
First create your own module for example:
angular.module('app.controllers', []) - angular module with controllers.
then add controller to that module:
angular.module('app.controllers', [])
.controller('dashboard.admin.account.controller', ['$scope', ($scope) { .... }]);
then create global module which will bind to your markup:
angular.module('app', [
'app.controllers'
'ui.router',
'ngAnimate'
]);
then bootstrap your global module to markup:
domReady(function () {
angular.bootstrap(document, ['app']);
});
Now you can use your controller.

Dynamically loading AngularJS modules from within Templates (Views)

Background: Let's suppose for the sake of argument that you have 100,000 views (partials). Let's also suppose you have accompanying view-scoped controllers, and potentially view-scoped services and filters as well. Try to envision an aggregating application that hosts 100,000 disparate small applications.
Issue: When you have "partials" that require accompanying controllers, the typical solution is to do something like this:
$routeProvider.when('/app1', {
templateUrl: 'partials/view1.html',
controller: 'controller1'
});
The controller is typically loaded from index.html via:
<script src="js/directives/Controller1.js"></script>
The problem with this approach is that it doesn't scale. There are solutions out there for dynamically loading controllers, but they still require adding touch points in various config.
Ideal Solution: Ideally - again for very small applications whose numbers are in the 000's, the controller could be loaded dynamically, and from within the partial itself. This would alleviate the need to manage several files and several configuration touch points (not to mention network requests), and keep each partial very well contained.
It would look something like this:
In router:
$routeProvider.when('/apps/:appId', {
templateUrl: 'partials/app-frame.html',
controller: 'AppCtrl'
});
In containing html (app-frame) include the relatively disparate "mini app":
<h1>Currently hosting {{appId}}</h1><hr>
<div class="ng-include: appUrl"></div>
In partial resolved with appUrl, define controller and markup in one:
<script>
myApp.controller('controller1', ['$scope', function ($scope) {
$scope.foo = "bar";
}]);
</script>
<div ng-controller="controller1">
{{foo}}
</div>
For cases like this, where there are a lot of partials and a 1-1 mapping for controller and view, it can make sense to couple the two for development efficiencies and maintenance. It's a lot cleaner than using several files and additional configuration touch points.
The problem is, this doesn't work. It could be as simple as forcing the script to load prior to applying the directive... but not sure how to do that?
Here are some similar explanations of the problem:
https://groups.google.com/forum/#!topic/angular/H4haaMePJU0
Loading Partial Page With Angular and Compile The Controller
Igor from the AngularJS team says:
I see.. we looked into supporting script tags in jqlite, but what needs to be done to get a cross-browser support involves a lot of black magic. For this reason we decided that for now we are just going to recommend that users use jquery along with angular in this particular case. It doesn't make sense for us to rewrite one third of jquery to get this working in jqlite.
But I don't know what he means by "use jquery" ... JQuery is already loaded into the application from index.html (and prior to angularjs), but it sounds like I need to do something specifically within the partial itself.
You cannot add new controllers through module('app').controller(name, function() { .. }) after AngularJS bootstrap. In order make it work you should use $controllerProvider.register(name, function() { .. }).
You can override the original controller registering function in following way to be able to add controllers pre and pos bootstrap:
var app = angular.module('app', [
'ui.router'
]);
app.config(function($controllerProvider) {
app.controller = function (name, controller) {
$controllerProvider.register(name, controller);
};
});

How can I register an angularjs factory after the app has "started" / been loaded?

I've got the guts of a routing architecture in Angular that will dynamically download and inject Angular Controllers and Services ... the Controller part works fine, and I'm trying to download dependant services via $route's .resolve property.
Now, say if I have a factory declared in scope while the page starts up, it registers fine and Controllers that use it resolve fine, e.g:
myModule.factory('MyInjectedDep', function() {
return {};
});
....
MyController = function(MyInjectedDep)
But if I try and register that dependency at "run time" (for want of a better phrase), I get a Circular Dependency error. e.g:
$route.routes[routeItem.route] = {
resolve: {
MyInjectedDep: ['$injector', function($injector) {
// In real code I download/eval this via $http but same behavior occurs
myModule.factory('MyInjectedDep', function() {
return {};
});
}]
}
}
So when my Controller is then initiated:
MyController = function(MyInjectedDep)
I get a circular dependency error, but no dependency trace in the error message?
Error: Circular dependency:
Any ideas appreciated
The key is latching onto $provide at configuration time. If you grab $provide at configuration time and maintain a reference to it, you can use it to register your factory like:
$provide.factory.apply(null, ['MyInjectedDep', [function() {
return {};
]}]);
I have a provider/service designed to do this, adapted from some other samples on github: https://github.com/afterglowtech/angular-couchPotato .
It's primarily designed to load from AMD, but you could probably use it's registerXXX functions with $http, or at least copy the relevant portions of its code. Don't let the size of the repository fool you -- the actual provider/service is about one page of code https://github.com/afterglowtech/angular-couchPotato/blob/master/src/couchPotato.js .

Resources