I have a large AngularJS application which is split into different functional modules. It is currently bundled into single JavaScript file and thus all the modules are loaded on initial page load. I want to split the bundle by functional areas and load parts of the application on demand as the user reaches some specific points (views).
I have the bundling and lazy loading sorted out with help of RequireJS and ocLazyLoad. There is last thing left to address though. Each module defines its own routes so if I won't load it at the initial load, the routes for it will be undefined until the user reaches the point that triggers loading of the module.
The issue is that the missing route can be reached directly by simply pasting its URL, in that case AngularUI Router will emit $stateNotFound event. I have a handler for it that will check the requested URL and load appropriate module (along with missing routes). The last missing piece is to, after the module and routes have been loaded, retry the route. Does anyone know how to achieve that ? There is a section in AngularUI Router's FAQ that is meant to explain that but it's not filled in.
You should probably use the "when" function of the $urlRouterProvider instead of catching a $stateNotFound event:
$urlRouterProvider.when('/myUrl', ['$match', '$state', function($match, $state) {
// do something, check for module loaded for example
// ... and then transitionTo
$state.transitionTo(state, $match);
}]);
Delay the $state.transitionTo until you want (after preloading the modules for example).
But if you really want to use the $stateNotFound you can do this:
$rootScope.$on('$stateNotFound', function(event, unfoundState, fromState, fromParams){
event.preventDefault()
// do your module load
// ... and then transitionTo
$state.transitionTo(unfoundState.to, unfoundState.toParams, unfoundState.options);
})
Related
I' am looking for a way through which I can load multiple modules under same path in Angular. For example, consider I have three modules AModule, BModule and CModule each having its own RouterModule.forChild call. Now I want to mount all these modules under say site route.
One way I have achieved this thing is by wrapping routes in RouterModule.forChild call under site route as follows:
RouterModule.forChild([
{
path: 'site',
children: [{}] // children goes here
}
])
I don't know whether this approach is correct but it is working fine. The only issue which this approach is that I have to specify canActivate in every module I want to mount under site. While this is not a problem, I was looking for a cleaner solution.
I know there is a property loadChildren which could be used to load modules lazily. But I want to load modules eagerly.
I' am using AngularCLI which splits code of module I specify in loadChildren in a separate JavaScript file which is not I want.
I' am using AngularClI v1.2.0 and Angular v4.2.5.
Any help is highly appreciated.
Thanks
I'm not entirely clear on your goal and what you are ultimately trying to achieve, but here are a few thoughts.
You can use the loadChildren and "lazy loading" to load on demand OR eagerly. If you select eagerly loaded routes, as soon as your main route is loaded and your first view is displayed, the other modules marked for eager loading are immediately loaded asynchronously.
I have an example of that here: https://github.com/DeborahK/Angular-Routing in the APM-Final folder.
I'm not clear on why you don't want module splitting. It can significantly improve the startup performance (time to display of the first page) of your application.
In addition to canActivate there is also a canActivateChild so you can put this on the parent and not have to repeat it for each route. The docs for that are here: https://angular.io/api/router/CanActivateChild
Is there any way, how to start ui-router or route-segment just after translateProvider loads its translations?
I'm using pascal prechts translate filter together with bind once {{:: }} notation. On localhost it works pretty good, but when I test it on remote server, bind once will remove watchers sooner than strings are translated.
So I was wondering if there is some way how to delay routing a little bit.
Try to check the native, built-in feature:
$urlRouterProvider.deferIntercept(defer)
Disables (or enables) deferring location change interception.
If you wish to customize the behavior of syncing the URL (for example, if you wish to defer a transition but maintain the current URL), call this method at configuration time. Then, at run time, call $urlRouter.listen() after you have configured your own $locationChangeSuccess event handler.
Check some similar issues:
AngularJS - UI-router - How to configure dynamic views
can we add dynamic states to $stateprovider with already existing states in ui-router angular.js
In one of these links, observe this plunker, where this feature is used like this:
Stop and wait in .config() phase:
.config(['$urlRouterProvider' ...,
function($urlRouterProvider, ...) {
// defer execution in config phase
$urlRouterProvider.deferIntercept();
...
Later in .run() phase turn the url hanlding on
.run(['$urlRouter' ...,
function($urlRouter...) {
...
$http
.get("modules.json")
.success(function(data) {
// do some stuff
// re-enable UI-Router url stuff
$urlRouter.sync();
$urlRouter.listen();
});
At the moment we are in the process of creating a new web application infrastructure.
We initially load a dashboard which is esentially the top bar displaying the logged in user and the set of menus along with it. Clicking on each menu would load a screen (mostly crud screens) in the main section. We areplanning to put each of the crud screens and their components (services, controllers and such) in a seperate module which will encapsulate all the screens from each other, so for example if there is 78 screens there will be 78 seperate modules for each screen. We are also using planing on using Requirejs to load these dependencies dynamically.
The problem however occurs that we need to link the menu with each of the modules for each screen. Typically in a single module based app it would be done like this.
config(function($routeProvider, $locationProvider) {
$routeProvider
.when('/Book/load', {
templateUrl: 'book.html',
controller: 'BookController'
})
.when('/Screen/load', {
templateUrl: 'chapter.html',
controller: 'ChapterController'
});
Where the BookController and ChapterController will be in the SAME module.
However in our case the BookController will be in a BookModule for the book screen and the same applies for the ChapterController. And the routes would be in the initial main module for example AppModule which loads the dashboard initially during startup then.
How would we say for example link the AppModule and the routes with each module for each screen (for example in this case BookController and ChapterController) keeping in mind that we need to load the modules dynamically when NEEDED using requirejs.
(P.S : We are essentially segmenting our application based on feature where feature in our system usually equals screen)
Also any suggestions on any other way we could best structure our app including an answer to the above problem would be very much appreciated.
Regards,
Milinda
Why do you have the route configuration in the initial main module? This creates unnecessary coupling between your modules (ie. your initial module has to know all the possible routes of every module), sort of defeating the purpose of moving your code into modules in the first place.
Each of your modules can have their own route configuration, which will take effect when the modules are loaded. (A consistent naming convention can help avoiding clashes between routes of unrelates modules)
While configuring the routeProvider, you can use lazy loading mechanisms using the resolve attribute of routes: resources referenced here will be resolved before the routeChange event happens, which enables you to wait for any promise resolution or a requireJS loading of a file. This blogpost might help in that regard.
As of this moment, there is no mechanism as far as I am aware for dynamically loading modules at runtime and then incorporating them in an AngularJS app. You can breakdown your app into 78 individual modules loaded via requirejs, but you will still need a single primary module which has all those other 78 as dependencies. It is this primary module which you will then configure all the routes.
There is work going on into a new AngularJS router which borrows from other more flexible routers (i.e. ui-router, etc) which will allow exactly the sort of dynamic loading of modules you are speaking about but as far as I know it won't be available until AngularJS 1.4.
I have a working Marionette app using modules together with RequireJS modules largely based on David Sulc's book.
When the server serves the index.html, which includes some base regions (menuRegion and mainRegion), my app.js starts the Marionette app. The app automatically loads the MenuApp module for the menu, and depending on the route, loads a certain module for the main content (i.e KanbanApp). Thus, I require all modules that define routes to be fully loaded before starting the app.
i.e
require(["apps/home/home_app", "apps/kanban/kanban_app"], function(){
Backbone.history.start();
});
At this point, the app is always using the regions defined in the index.html/app.js.
Now, I realize I need a landing page (login form, etc) to be included in the app which has a different layout. I have tried different approaches but I always get to a dead end. Which steps would you try to follow?
My main idea is that depending on the route, an "AppLayout" or "LandingLayout" is loaded. However, the fact that the routes are defined in submodules and I don't have a Main router makes this complicated. Additionally, I am unsure on where would the code for this logic go, maybe to a new extra layer between app.js and each submodule .js? I am unsure this would be the best solution because things get overcomplex. For example: Now when /route-from-the-real-app is called, a submodule route captures it and executes the controller. Until now the controller basically filled the mainRegion. But now, the controller also needs to call this extra layer to display the AppLayout and additionally, trigger an event for the MenuApp to be displayed in the menuRegion. This shouldn't be the original KanbanApp task.
This is how I am unsuccessfully trying to accomplish to do this, but any ideas working on my current code would be greatly appreciated:
I move the regions declared in the index.html/app.js into a new layout and leave it with a unique content region. LandingApp, displays its content here when "/" without problems.
I don't load MenuApp together with the app start anymore.
I created a new MainApp module, with the idea of it being the new extra layer. This module instantiates the LayoutView and adds it to the content region when an event is triggered.
I am unsure on how/when should I load both the MenuApp and the MainApp. As they have no defined routes, I don't require them with the app start, but I need them to register the event handlers.
You can view the full code (very simple) of the app before any of this changes had been applied here: https://github.com/mezod/multikanban
How would you approach this without using any advanced patterns or external libs?
If I understand the question right, you are asking for strategies involved with changing the "page" of your app?
I believe the general approach is to have one layout view instantiated in the app object which represents the container for the "page". That layout view could have a region called page. You could then switch views easily via that region. For example:
var App = Marionette.Application.extend({
changePage : function(view) {
this.rootView.page.show(view);
},
onStart : function() {
this.rootView = Marionette.LayoutView.extend({
el: 'body',
regions: {
'page' : '.page-container' // some div on the page <div class="page-container"></div>
}
});
});
});
Then to change the page (whether in a route method or somewhere else in the app) you can just go App.changePage(myNewPageView). That other page (myNewPageView) could also be a layout view which might contain a region for a sidebar, or a header, or whatever you want.
I'm using this method for lazy-loading the controllers/services required for each of my AngularJS routes.
Basically, for each route, I define the dependencies (e.g the controllers, services, etc needed), then a function is added to the resolve of the route definition for that route, that function dynamically loads the javascript files of the dependencies.
This works, however where I run into a problem is this. Suppose that I wanted to lazy-load a service called fooService for the route /foo. However, what if I also wanted to have fooService.getResults() added to the resolve for /foo? This gives an error because perhaps the fooService.js file is not loaded by the time the resolve function for fooService.getResults() is called.
Is there any solution to this?