How is modularity mitigated in AngularJS? - angularjs

I've been playing with the seed app for AngularJS and I noticed that most dependencies (controllers, directive, filters, services) for the app are loaded up front. I was wondering how to modularize an Angular app into smaller bytes, where dependencies aren't loaded unless required.
For example, if I had a large application that had a cart, add/edit shipping address, search results, product details, product lists, etc... A user on a shopping site may never encounter any of these views, but it looks like (from the seed app) that the code for these all views are loaded in at startup.
How is modularity mitigated in AngularJS?

This question about modularity is being asked quite often here on SO and the google group. I'm not part of core team but my understanding is the following one:
You can easily load partials (HTML/templates fragments) on demand by including them (ngInclude) or referencing them in directives / routes. So at least you don't need to download all the partials up-front (although you might want to do so, see the other question here: Is there a way to make AngularJS load partials in the beginning and not at when needed?)
When it comes to JavaScript (controller, directives, filters etc. - basically everything that is defined in AngularJs modules) I believe that there is no, as of today, support for on-demand load of modules in AngularJS. This issue closed by the core team is an evidence of this: https://github.com/angular/angular.js/issues/1382
Lack of the on-demand load of AngularJS modules might sound like a big limitation, but:
when it comes to performance one can't be sure till things are measured; so I would suggest simply measuring if this is a real problem for you
usually code written with AngularJS is really small, I mean, really small. This small code base minified and gzipped might result in a really small artifact to download
Now, since this question is coming back so often I'm sure that the AngularJS team is aware of this. In fact I saw some experimental commits recently ( https://github.com/mhevery/angular.js/commit/1d674d5bfc47d18dc4a14ee0feffe4d1f77ea23b#L0R396 ) suggesting that the support might be in progress (or at least there are some experiments with it).

I've been playing lately with require modules and angular and I've implemented lazy loading of partials and controllers.
It can be easily done without any modifications to Angular sources (version 1.0.2).
Repository: https://github.com/matys84pl/angularjs-requirejs-lazy-controllers .
There is also an implementation that uses yepnope (https://github.com/cmelion/angular-yepnope) made by Charles Fulnecky.

All we need is put this code in our application config, as that:
application.config [
"$provide", "$compileProvider", "$controllerProvider", "$routeProvider"
, ($provide, $compileProvider, $controllerProvider, $routeProvider) ->
application.controller = $controllerProvider.register
application.provider = $provide.provider
application.service = $provide.service
application.factory = $provide.factory
application.constant = $provide.constant
application.value = $provide.value
application.directive = -> $compileProvider.directive.apply application, arguments
_when = $routeProvider.when
$routeProvider.when = (path, route) ->
loaded = off
route.resolve = new Object unless route.resolve
route.resolve[route.controller] = [
"$q",
($q) ->
return loaded if loaded
defer = $q.defer()
require [
route.controllerUrl
], (requiredController) ->
defer.resolve()
loaded = on
defer.promise
]
_when.call $routeProvider, path, route
For use add require our components in modules where we need ( provider, constant, directive etc ). Like that:
define [
"application"
"services/someService"
], (
application
) ->
application.controller "chartController", [
"$scope", "chart", "someService"
, ($scope, chart, someService) ->
$scope.title = chart.data.title
$scope.message = chart.data.message
]
someService.coffee file:
define [
"application"
], (
application
) ->
application.service "someService", ->
#name = "Vlad"
And add to controllerUrl our path to controller for routing:
application.config [
"$routeProvider"
, ($routeProvider) ->
$routeProvider
.when "/table",
templateUrl: "views/table.html"
controller: "tableController"
controllerUrl: "controllers/tableController"
resolve:
table: ($http) ->
$http
type: "GET"
url: "app/data/table.json"
]
tableController.coffee file:
define [
"application"
"services/someService"
], (
application
) ->
application.controller "tableController", [
"$scope", "table"
, ($scope, table) ->
$scope.title = table.data.title
$scope.users = table.data.users
]
And all components have "lazy" load in place where our need.

Related

Add custom headers to all resources in all modules

In my Angular JS site, I have many modules & many resources (From where I consume Rest API)
I want to add a custom header to all outgoing requests in each & every module.
For eg : Here are 2 modules : common & ABC
//---File 1 common.js
angular.module("common",[])
.config(['$httpProvider',
function($httpProvider)
{
$httpProvider.defaults.headers.common['x-access-token'] =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJOYW1lIjoiQWJkdWwiLCJpYXQiOjE0NjUwMzkwMzgsImV4cCI6MTQ2NTEyNTQzOH0.6BMBuEl2dbL736qUqNYXG29UBn_HRyCyWEmMXSG3euE';
}
])
.service("commonApi",['$resource',
function($resource)
{
this.getBankList = function()
{
return $resource('api/emi/banklist:quoteId', { },{}).query();
}
}]);
//---File 2 abc.js
angular.module("abc",[])
.config(['$httpProvider',
function($httpProvider)
{
$httpProvider.defaults.headers.common['x-access-token'] =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJOYW1lIjoiQWJkdWwiLCJpYXQiOjE0NjUwMzkwMzgsImV4cCI6MTQ2NTEyNTQzOH0.6BMBuEl2dbL736qUqNYXG29UBn_HRyCyWEmMXSG3euE';
}
])
.factory('emiModel', ['$resource',
function($resource) {
return $resource('api/emi/QuoteList:quoteId', { }, {
update: { method: 'PUT' }
});
}])
In the above code, I had to add .config to each module & add the header there.
It is quite time consuming to add it in each module & violates DRY principle.
Is there any simple way by which I can add this configuration to all modules in my app without repeating the code ?
For Carity : I used factory & service just to show that i might be using any thing but I still want the header to be passed.
In the above code, I had to add .config to each module & add the
header there.
It is quite time consuming to add it in each module & violates DRY
principle.
This isn't true. Once the module is loaded, Angular doesn't make a difference between them.
config block affects each and every module in the app that has common module loaded. I.e. all of $http calls will be affected with config in this setup:
angular.module("app",["abc", "common"])...
angular.module("abc",[])...
Though it is recommended to load common module in each submodule that depends on config, too. So they don't break in the case when they are loaded apart from app (e.g. in specs).

How to best organize translation strings in angular-translate?

I am using angular-translate on a rather large Angular project. I am breaking the project into multiple modules to make it more manageable, but I am unable to break up my translation strings per module.
For example, I have modules A and B, where B is a submodule of A. There are strings that pertain to the HTML covered by module A, which are placed in '/json/localization/A/en.json'. Likewise, there are strings pertaining to B that I place in '/json/localization/B/en.json'. First I load B's en.json in module B using angular-translate's $translationProvider. Then I load module A's en.json, also using $translationProvider. The problem is that loading A's strings overrides B's strings and they are lost.
Using angular-translate, is there a way to load strings per module, without overriding, or does the parent module have to load all of the strings from a single en.json?
Here is an example (in coffeescript) of how I am loading the translation strings:
my_module.config(['$translateProvider', ($translateProvider) ->
$translateProvider.useStaticFilesLoader
prefix: '/json/localization/A/'
suffix: '.json'
$translateProvider.preferredLanguage 'en'
])
angular-translate supports async loading of partial language files. All partials are merged into one dictionary per language.
The official documentation can be found here: http://angular-translate.github.io/docs/#/guide/12_asynchronous-loading
It supports applying a template for url templates that point to the modularised language files:
$translateProvider.useLoader('$translatePartialLoader', {
urlTemplate: '/i18n/{part}/{lang}.json'
});
From within your controllers, you can add language modules and refresh the data bindings like this:
angular.module('contact')
.controller('ContactCtrl',
function ($scope, $translatePartialLoader, $translate) {
$translatePartialLoader.addPart('contact');
$translate.refresh();
});
Of course, loading the partials can also be covered in a route's resolve phase
Alternatively, you can also look into building your own custom loader function. http://angular-translate.github.io/docs/#/guide/13_custom-loaders
This provides all the flexibility you need to combine required language modules in one shot. E.g. you could do something like this:
app.factory('customLoader', function ($http, $q) {
// return loaderFn
return function (options) {
var deferred = $q.defer();
var data = {
'TEXT': 'Fooooo'
};
$http.get('nls/moduleA/en.json').success(function(moduleA){
angular.extend(data, moduleA);
$http.get('nls/moduleB/en.json').success(function(moduleB){
angular.extend(data, moduleB);
deferred.resolve(data);
});
});
return deferred.promise;
};
});

AngularJS best practice REST / CRUD

What is the best practice of doing CRUD operations via REST with AngularJS?
Specially what is the Angular-Way here. By this I mean the way using the least code and the most default angular settings to achive this.
I know $resource and it's default operations. Where I'm not sure is how to implement/name the endpoints and which controllers to use.
For this example I would like to implement a simple user-management system which creates / updates /deletes / lists users. Since I'm implementing the Server-Endpoints by myself I'm completely free in doing it in the most angular friendly way.
What I like as answer is something like:
Server-Endpoints:
GET /service/users -> array of users
GET /service/user/new -> return an empty user with default values which has no id
POST /service/user/new -> store a new user and create an id. return the saved user.
POST /service/user/:ID -> save an existing user. Return the saved user
DELETE /service/user/:ID -> delete an existing user
Angular-Services:
.factory( 'User', [ '$resource', function( $resource ){
return $resource( '/service/user/:userId', { userId: '#id' } )
[...]
}])
Routing:
.when( '/users', {
templateUrl: BASE + 'partials/user-list.html',
controller: 'UserListCtrl' } )
.when( '/user/new', {
templateUrl: BASE + 'partials/user-edit.html',
controller: 'UserNewCtrl' } )
.when( '/user/:userId', {
templateUrl: BASE + 'partials/user-edit.html',
controller: 'UserEditCtrl' } )
...
Controllers:
UserListCtrl:
$scope.data = User.get(...)
UserNewCtrl:
$scope.user = User.get( { userId: "new" } )
...
Note that I'm not interessted in opinion what is the best (tm) way to do this but I'd like to know what is the Angular intended way (which I think should produce the least code because it can use the most default).
EDIT:
I'm looking for the whole picture. What I would love would be an answer like e.g.: "You can do this using online 3 Endpoints [...], 2 routes [...] and 2 controllers [...] if you do it this way using that defaults ..."
There is no Angular prescribed way for what you are asking. It's up to you to determine the implementation detail.
Typically I only use two controllers and templates per resource:
ListController
FormController
The Form controller is used for both Edit and Create operations. Use the resolve option in your route definitions to pass in either User.get() or User.new() and a flag indicating if this is an edit or create operation. This flag can then be used inside your FormController to decide which save method to call. Here's a simple example:
.when( '/users', {
templateUrl: BASE + 'partials/user-list.html',
controller: 'UserListCtrl' } )
.when( '/user/new', {
templateUrl: BASE + 'partials/user-form.html',
resolve: {
data: ['User', function(User) { return User.new(); }],
operation: 'create'
}
controller: 'UserFormCtrl' } )
.when( '/user/:userId', {
templateUrl: BASE + 'partials/user-form.html',
resolve: {
data: ['User', '$route', function(User, $route) { return User.get($route.current.params.userId); }],
operation: 'edit'
}
controller: 'UserFormCtrl' } )
And your form controller:
app.controller('UserFormCtrl', ['$scope', 'data', 'operation', function($scope, data, operation){
$scope.data = data;
$scope.save = function() {
if (operation === 'edit') {
// Do you edit save stuff
} else {
// Do you create save stuff
}
}
}]);
You can go a step further and create a base list and form controller to move stuff like error handling, server-side validation notifications etc. In fact for the majority of CRUD operations you can even move the save logic to this base controller.
My research into a similar quest has lead me to this project "angular-schema-form" https://github.com/Textalk/angular-schema-form.
For this approach...
You make a JSON-Schema that describes your data. Then augment it with another little JSON-struct that describes a "form" (ie. view specific info that does not belong in the data schema) and it makes a UI (form) for you.
One cool advantage is that the Schema is also useful in validating the data (client and server side), so that is a bonus.
You have to figure out what events should fire off GET/POST/... back to your API. but that would be your preference, eg. Hit the API for every key stroke OR the classic [Submit] button POST back style OR something in between with a timed Auto Save.
To keep this idea going, I think that it is possible to use StrongLoop to make a quick API, which (again) uses your data's schema (augmented with some storage details) to define the API.
no <3 uses of that schema, see [http://json-schema.org/] which is central to this approach.
(read: no less than three :)
You maybe mixing things up. CRUD operations at API level are done using $resource and these may or may not map to UI.
So using $resouce if you define resource as
var r = $resource('/users/:id',null, {'update': { method:'PUT' }});
r.query() //does GET on /users and gets all users
r.get({id:1}) // does GET on /users/1 and gets a specific user
r.save(userObject) // does a POST to /users to save the user
r.update({ id:1 }, userObject) // Not defined by default but does PUT to /users/1 with user object.
As you see the API is resource full but is in no way linked to any UI view.
For view you can use the convention you have defined, but nothing specific is provided by Angular.
I think what you are looking for can be found in http://www.synthjs.com/
Easily create new RESTful API resources by just creating folders and
naming functions a certain way.
Return data or promises from these
functions and they'll be rendered to the client as JSON.
Throw an
error, and it'll be logged. If running in dev mode, the error will
also be returned to the client.
Preload angular model data on page load (saving an extra
roundtrip).
Preload html view on page load (saving another extra
roundtrip!)
A simplified project structure
where front-end code (angular code, html, css, bower packages, etc)
is in the 'front' folder and back-end code (node code and node
packages) are in the 'back' folder.
A command-line tool for
installing third party packages, using npm + bower, that auto-updates
manifest files.
Auto compilation of assets on request for dev, and
pre-compilation for prod (including minification and ngmin).
Auto-restarts the server when changes are detected.
Support for
various back-end and front-end templates to help get a new project
going quickly.

how to cache angularjs partials?

What's the simplest/modern way of caching partials in angularjs production?
Currently the code looks like:
$routeProvider.when('/error', {templateUrl: 'partials/error.html', controller: 'ErrorCtrl'});
Where the templateUrl is obviously an http path to a separate file. On mobile the loading time for that file is noticeable and I'd love to just cache everything.
The main part of the answer is the $templateCache. An extract:
var myApp = angular.module('myApp', []);
myApp.run(function($templateCache) {
$templateCache.put('templateId.html', 'This is the content of the template');
});
Any of the html templates, can be moved to the $templateCache, and the rest of our application will act as expected (no other changes required)
local-storage as a cache
In case, that we would like to keep the template on the client, we can use the local storage as well. This angular-local-storage extension would simplify lot of stuff.
So, let's adjust the run() to
observe the local-storage, if we do not already have the template on the client
issue the request to load the latest, if needed...
put it into the caches (local-storage and $templateCache)
The adjusted code
.run([ 'localStorageService','$templateCache','$http',
function myAppConfig(localStorageService , $templateCache , $http) {
// The clearAll() should be called if we need clear the local storage
// the best is by checking the previously stored value, e.g. version constant
// localStorageService.clearAll();
var templateKey = "TheRealPathToTheTemplate.tpl.html";
// is it already loaded?
var template = localStorageService.get(templateKey);
// load the template and cache it
if (!template) {
$http.get(templateKey)
.then(function(response) {
// template loaded from the server
template = response.data;
localStorageService.add(templateKey, template);
$templateCache.put(templateKey, template);
});
} else {
// inject the template
$templateCache.put(templateKey, template);
}
}])
So, this way, we do profit from the local-storage. It is filled with the "template" from the server, kept there... and therefore not loaded next time.
NOTE: Very important is also to inject some version key/value and check it. If the Local storage is out-dated... all templates must be reloaded.

syncing the views of two angular apps

I have two angular applications in one page, and I need them to communicate. Specifically, I want one application to use a service of another application.
I am able to get the service of the other application using Injector.get(service), but when I change the data using the service in one application, it does not reflect in the view of the other, even though both are supposed to show the same data. You can see a simplified version of the problem in jsFiddle.
To save you the click, this is the relevant script:
//myAppLeft - an angular app with controller and service
var myAppLeft = angular.module('myAppLeft', []);
myAppLeft.factory('Service1',function(){
var serviceInstance = {};
serviceInstance.data = ['a','b','c','d','e'];
serviceInstance.remove = function(){
serviceInstance.data.pop();
console.log(serviceInstance.data);
};
return serviceInstance;
} );
myAppLeft.controller('Ctrl1', ['$scope', 'Service1', function($scope, service1) {
$scope.data = service1.data;
$scope.changeData =function(){
service1.remove();
}
}]);
var leftAppInjector = angular.bootstrap($("#leftPanel"), ['myAppLeft']);
//myAppRight = an angular app with controller which uses a service from myAppLeft
var myAppRight = angular.module('myAppRight', []);
myAppRight.controller('Ctrl2', ['$scope', function($scope) {
$scope.data = leftAppInjector.get('Service1').data;
$scope.changeData =function(){
leftAppInjector.get('Service1').remove();
}
}]);
var rightAppInjector = angular.bootstrap($("#rightPanel"), ['myAppRight']);
I'd be happy to know why my code does not work as expected, and would be even happier to know if and how such thing can work.
I understand that if instead of two angular-apps I would have used one angular-app with two modules this would have worked just as I wanted, but unfortunately I cannot adopt this approach because my application consists of a pure-js core with extensions, each extension can be written in a different library/platform and I want my extensions to be angular ones.
Thanks,
Nurit.
Angular apps are separate entities, even if you use the same service in both. the second app just initializes its own version off it.
What you want can be done using localStorage, and the storage events.
Ping me if you need additional help on this!

Resources