Developing an AngularJS app with dynamic set of modules
I have an application with a complex layout where the user could put (drag/drop) widgets (by choosing from a predefined set of 100+ widgets) where every widget is a custom implementation that displays a set of data (fetched using REST call) in a specific way. I've read tons of blog posts, stackoverflow questions and the official AngularJS docs but I cannot figure out how should I design my application to handle there requirements. Looking at demo apps, there is a single module (ng-app) and when constructing it in the .js file the dependent modules are declared as its dependencies, however i have a big set of widgets and somehow it's not advisable to describe them all there. I need suggession for the following questions:
- How should I design my app and widgets - should i have a separate AngularJS module or each widget should be a directive to the main module?
- If I design my widget as directives, is there a way to define dependency within a directive. I.e. to say that my directive uses ng-calender in its implementation?
- If I design each widget as a separate module, is there a way to dynamically add the widget module as a dependency to the main module?
- How should I design the controllers - one controller per widget probably?
- How should i separate the state (scope) if i have multiple widgets from the same type in the view?
- Are there bestpractices for designing reusable widgets with AngularJS?
EDIT
Useful references:
- ocLazyLoad - great lazy loading lib for AngularJS
- Seed project - modules + lazy loading on route change (ES6, systemjs, ocLazyLoad)
- Lazy loading in AngularJS
- Dynamically Loading Controllers and Views with AngularJS and RequireJS
- Loading AngularJS Components With RequireJS After Application Bootstrap
- Demo project about lazy loading of AngularJS resources on GitHub
- Load On Demand project
- Inject module dynamically only if required
- Another lazy loading in Angular article
- Code Organization in Large AngularJS and JavaScript Applications
Solution 1:
These are just general advices.
How should I design my app and widgets - should i have a separate AngularJS module or each widget should be a directive to the main module?
You're talking hundres of widgets, it seems natural to split them into several modules. Some widgets might have more in common than other widgets. Some might be very general and fit in other projects, others are more specific.
If I design my widget as directives, is there a way to define dependency within a directive. I.e. to say that my directive uses ng-calender in its implementation?
Dependencies to other modules are done on a module level, but there is no problem if module A
depends on module B
and both A
and B
depends on module C
. Directives are a natural choice for creating widgets in Angular. If a directive depends on another directive you either define them in the same module, or create the dependency on a modular level.
If I design each widget as a separate module, is there a way to dynamically add the widget module as a dependency to the main module?
I'm not sure why you would want to do this, and I'm not sure how to do it. Directives and services are not initialized before they get used in Angular. If you have a huge library of directives (widgets) and know that you'll probably use some of them, but not all of them - but you don't know which ones will get used when the application gets initialized you can actually "lazy load" your directives after your module has been loaded. I've created an example here
The benefit is that you can get your application to load fast even if you have lots of code, because you don't have to load the scripts before you need them. The disadvantage is that there can be a considerably delay the first time a new directive is loaded.
How should I design the controllers - one controller per widget probably?
A widget will probably need its own controller. Controllers should generally be small, if they get big you can consider if there's any functionality that would fit better in a service.
How should i separate the state (scope) if i have multiple widgets from the same type in the view?
Widgets that need scope variables should without doubt have their own isolated scopes (scope:{ ... }
in the directive configuration).
Are there bestpractices for designing reusable widgets with AngularJS?
Isolate the scope, keep the dependencies to a necessary minimum.See Misko's video about best practices in Angular
Brian Ford has also written an article about writing a huge application in Angular
Solution 2:
This question is also very important to me. The AngularJS homepage has a few examples on it (you could call them widgets) so I went though their source code to try and see how they separated their widgets.
First, they never declare an "ng-app" attribute. They use
function bootstrap() {
if (window.prettyPrint && window.$ && $.fn.popover && angular.bootstrap &&
hasModule('ngLocal.sk') && hasModule('ngLocal.us') && hasModule('homepage') && hasModule('ngResource')) {
$(function(){
angular.bootstrap(document, ['homepage', 'ngLocal.us']);
});
}
}
to make sure everything is loaded correctly. Neat idea, but it's odd that they push the ng-app attribute on you so much then don't even use it themselves. Anyways here is the homepage module they load with the app - http://angularjs.org/js/homepage.js
In there is a directive called appRun
.directive('appRun', function(fetchCode, $templateCache, $browser) {
return {
terminal: true,
link: function(scope, element, attrs) {
var modules = [];
modules.push(function($provide, $locationProvider) {
$provide.value('$templateCache', {
get: function(key) {
var value = $templateCache.get(key);
if (value) {
value = value.replace(/\#\//mg, '/');
}
return value;
}
});
$provide.value('$anchorScroll', angular.noop);
$provide.value('$browser', $browser);
$locationProvider.html5Mode(true);
$locationProvider.hashPrefix('!');
});
if (attrs.module) {
modules.push(attrs.module);
}
element.html(fetchCode(attrs.appRun));
element.bind('click', function(event) {
if (event.target.attributes.getNamedItem('ng-click')) {
event.preventDefault();
}
});
angular.bootstrap(element, modules);
}
};
})
I'll use the ToDo list as an example. For the html, they have
<div app-run="todo.html" class="well"></div>
and then at the bottom of the page they have
<script type="text/ng-template" id="todo.html">
<h2>Todo</h2>
<div ng-controller="TodoCtrl">
<span>{{remaining()}} of {{todos.length}} remaining</span>
[ <a href="" ng-click="archive()">archive</a> ]
<ul class="unstyled">
<li ng-repeat="todo in todos">
<input type="checkbox" ng-model="todo.done">
<span class="done-{{todo.done}}">{{todo.text}}</span>
</li>
</ul>
<form ng-submit="addTodo()">
<input type="text" ng-model="todoText" size="30"
placeholder="add new todo here">
<input class="btn-primary" type="submit" value="add">
</form>
</div>
</script>
They also have
<style type="text/css" id="todo.css"> //style stuff here </style>
<script id="todo.js"> //controller stuff here </script>
The code is used, but the id attributes on those scripts aren't important for running the app. That's just for the source code display to the left of the app.
Basically, they have a directive called appRun which uses a function fetchCode
.factory('fetchCode', function(indent) {
return function get(id, spaces) {
return indent(angular.element(document.getElementById(id)).html(), spaces);
}
})
to fetch the code. Then they use angular.bootstrap() to create a new application. They can also load modules though app-run. The JavaScript Project example is initialized like
<div app-run="project.html" module="project" class="well"></div>
Hopefully this helps. I'm still not sure what's the "best" technique, but it appears as though the AngularJS homepage simply uses a totally separate angular application (ng-app) for each example/widget. I think I'm going to do the same, except change the fetchCode function to get stuff with AJAX.