Opinionated Yeoman generator for creating Angular applications which lazy load components as needed at runtime.
- Getting started
- Structure
- What's included
- Gulp tasks
- Preparing for production
- Troubleshooting
- License
To get started you'll need to install the Yeoman, JSPM, Gulp and Protractor CLI tools globally.
$: npm install -g yo jspm gulp protractor
Then, of course, you need to install the Angular Lazy generator itself.
$: npm install -g generator-angular-lazy
Now you can start using the generators described in the structure section.
Angular Lazy follows a component based approach. Everything is a component, even the application itself. Components should be self-contained and should be easily reusable for multiple projects. Of course there will be cases where a component is very specific to a project and might not be reusable. But always have the goal of reusability in focus.
Each component has a index.js
file which is the main access point of it. Components should not directly reference resources from each other, except using index.js
. Within there the component should expose / export all of it's public API. Other common files across all components are the *-spec.js
and *-test.js
files. Spec-files contain the unit tests and test-files the end-to-end tests. For larger components tests can also be split across multiple files. The Karma test runner will scan the project for all *-spec.js
files and Protractor will load all *-test.js
files to run the end-to-end tests.
If you choose to activate i18n
while generating the application, each component will have a i18n
folder which contains it's translations.
$: yo angular-lazy [--skip-install] [--root=]
Option | Description | Default |
---|---|---|
--help, -h | Print the generator's options and usage | |
--skip-cache | Do not remember prompt answers | false |
--skip-install | Do not automatically install dependencies | false |
--root | Use a subfolder as root directory for the project instead of the current working directory |
+src
| +components
| | +application
| | | +config
| | | | constants.json
| | | | default-locale.js
| | | | error-handling.js
| | | | routing.js
| | | | states.json
| | | i18n
| | | stylesheets
| | | application.html
| | | application-controller.js
| | | application-route.js
| | | application-spec.js
| | | application-test
| | | index.js
| index.js
As the filename suggests, this is the place where you define application wide constants. Those can be imported where necessary and are also available as injectable values within the application.
In here, the default locale is configured. This file will only be present if you choose to activate i18n
while generating the application structure.
By default, this file only contains a small code piece which logs state transition errors. UI Router swallows transition error by default and we're left on our own to figure out what happened. This file can be extend with additional error handling functionality as needed, e.g. network errors.
This is where we define our lazy loaded routes. For the Future States feature from UI Router Extras work properly, we need to tell it what routes exist and where they can be loaded from.
[
{
"name": "app",
"url": "/",
"type": "load",
"src": "components/application/index"
},
{
"name": "home",
"url": "home",
"type": "load",
"prefetch": [
"components/login-form/index",
"angular-ui-bootstrap"
]
"src": "components/home-state/index"
}
]
The name and url properties must match those we use in the $stateProvider.state(...)
call. If a route is not yet loaded, UI Router Extras will catch the $stateNotFound
event and look it up in the list of states defined in states.json
. If it finds a match it will load the specific component and then resume the state transition. The type property is a helper to distinguish state types. Abstract states must be defined here too, like the app
state in the example above. There is only one value load
by default. It is used by the future state provider in the angular-lazy package to know which states should be handled by the default loader. If we have states which need to be handled specially, we can introduce new types and loaders. In most cases the default loader will be sufficient. The src
property tells the loader where to load the state component from. This is always relative to the baseURL
configured within SystemJS. And finally, there is an optional prefetch
property where we can define components which should be fetched right after the state has been loaded. This allows us to load parts of an application which are highly likely to be accessed by the user. By prefetching them we can reduce eventual wait times.
This file contains the configuration for the state factory which lazy loads our code based on the definitions in states.json
.
This is the template for our basic layout, common for all states. By default it only contains a ui-view
element.
This file contains our application controller which is accessible for all components. It's mainly used for handling data which is needed throughout the whole application, e.g. information about the currently logged in user. Be careful to not overload it. Functionality like loading the actual user data should always be within a service.
This file contains the application state. This is only an abstract state and each other state within our application should be a direct or indirect descendant of it. This enables us to load application wide data before any of the actual states get loaded.
$: yo angular-lazy:state name [--prefix=] [--url=] [--target=]
Option | Description | Default |
---|---|---|
--help, -h | Print the generator's options and usage | |
--prefix | Write component files to a subdirectory | |
--url | Url relative to the parent state | Same as state name |
--target | Name of the target ui-view within the parent state |
+src
| +components
| | +[name]-state
| | | i18n
| | | [name]-route.js
| | | [name]-state.html
| | | [name]-state.scss
| | | [name]-state-controller.js
| | | [name]-state-spec.js
| | | [name]-state-test.js
| | | index.js
When running the state component generator it will automatically add the new state to states.json
within the application component.
This file contains the state definition for UI Router. If you change the URL or the state name at some point in time don't forget to also update it in states.json
. Otherwise the state will not be loaded properly when lazy loaded.
This file contains the controller for the newly generated state.
$: yo angular-lazy:directive name [--prefix=]
Option | Description | Default |
---|---|---|
--help, -h | Print the generator's options and usage | |
--prefix | Write component files to a subdirectory |
+src
| +components
| | +[name]-directive
| | | i18n
| | | [name]-directive-controller.js
| | | [name]-directive-spec.js
| | | [name]-directive-test.js
| | | [name]-directive.js
| | | index.js
Since the component provider introduced in 1.5 is restricted to elements this generator was introduced for the case we want to create a custom attribute. Attributes don't have templates nor should they influence the styling of the element they're applied on. Thus, no stylesheet or HTML template will be generated.
$: yo angular-lazy:component name [--prefix=]
Option | Description | Default |
---|---|---|
--help, -h | Print the generator's options and usage | |
--prefix | Write component files to a subdirectory |
+src
| +components
| | +[name]
| | | i18n
| | | [name]-component-controller.js
| | | [name]-component-spec.js
| | | [name]-component-test.js
| | | [name]-component.html
| | | [name]-component.js
| | | [name]-component.scss
| | | index.js
This will generate a Angular component using the component provider introduced in 1.5.
These are the main tools and libraries the project stack relies on.
We're using the recently, in ECMAScript 2015, standardized module system. SystemJS builds up on these APIs to make it easier for us to modularize out code properly and to load those modules as they're needed. Since most browsers don't implement the module system natively SystemJS uses the ES2015 Module Loader Polyfill under the hood to close the gap.
Since the ES2015 module loader system is farly new most of the existing JavaScript libraries didn't have the chance yet to migrate to the new syntax. AMD and CommonJS are still the most used systems. SystemJS implements adapters for those module systems so that we're not blocked when it comes to use popular libraries like AngularJS or Lodash which do not yet use the new import / export syntax.
NPM is a great package manager but it was initially designed to be used on the server side. There is no straight forward way to load NPM packages in the browser at runtime. JSPM eases that process and also overwrites some package.json properties for certain packages where necessary, e.g. the main file.
If you're here then you should know what Angular is.
Angular's integrated router has very limited capabilities, e.g. it doesn't support nested views. UI Router gives you much more flexibility and has become the de-facto standard router for Angular applications.
UI Router Extras adds even more functionality to the router on top of UI Router. Most important, Future States which enable us to describe, in an abstract way, what states our application has and where the code for those resides, without actually loading the JavaScript code itself. It is then lazy loaded at runtime when the uset accesses the state for the first time.
By default Angular requires us to load all application code upfront before it boots the application. That works well for smaller applications. For large scale applications this introduces long loading times an impacts the user experience negatively. ocLazyLoad allows us to add modules to Angular applications at runtime.
The Angular Lazy package glues UI Router Extras and ocLazyLoad together, so that we can easily lazy load our states. It also provides a component loader which makes it possible to load additional components at any time in the code.
You will realise, that you end up with a lot of small files when you use the angular-lazy generator. To reduce the number of network requests required to load a component we want to bundle those files together where possible.
If you choose to activate i18n
while generating the application, the project will include Angular Translate to handle translations. Angular has no support for i18n and l10n, so we need to include this package.
Karma is a test runner created by the Angular team, specifically to ease the testing of Angular applications. It is only a test runner and not a test framework. To actually write our tests we're going to use Jasmine.
Jasmine is the actual test framework we're using to write our tests. It's integrated into Karma through the karma-jasmine
package.
Protractor's main focus is to ease the end-to-end testing of Angular applications. Under the hood it uses Selenium WebDriver which is an established tool for automated browser testing. Like with Karma, we can also use Jasmine to write our tests which protractor should run.
Writing stylesheets in plain CSS for large applications is a pain in the ass. That's why Angular Lazy comes with SASS preconfigured as CSS preprocessor. Under the hood it uses node-sass which itself uses libsass, a C implementation of SASS. We're not using the Ruby SASS implementation because it's much slower than libsass and it would require us to install Ruby next to Node.
Not all ES2015 features are yet supported across major browsers. Babel allows us to take advantage of all new language features by transpiling then into equivalent ES5 code.
Angular Lazy uses Gulp for task automation and comes preconfigured with all essential tasks to get started.
Each Gulp task sits in its own file. This makes it easier to navigate to the source of an individual task and it's clearer which task depends on which libraries. Thus, it also makes the tasks better maintainable.
$: gulp
Alias for build ➔ watch ➔ serve
$: gulp build
Alias for copy-static ➔ compile-source ➔ compile-stylesheets
$: gulp serve
Starts a connect based server on port 8088
which can be used during application development. Will perform a build before starting the server.
$: gulp watch
Starts a file system watcher which rebuilds our code as we change it.
$: gulp test
Starts the Karma server and run all unit tests (*-spec.js). The configuration for the test runner can be found in config/karma.js
. Will perform a build before running the tests.
$: gulp test-e2e
Starts a connect server on port 8089
and runs all e2e tests (*-test.js) against that server. Will perform a build and an update of the necessary web drivers before running the tests.
$: gulp compile-source
Transpiles our JavaScript source code from ES2015 to ES5 using Babel.
$: gulp compile-stylesheets
Compiles our SASS stylesheets and uses Autoprefixer to automatically add vendor prefixes for the most common browsers.
$: gulp bundle
Runs Angular Lazy Bundler and optimizes the loading process of our application in production. It bundles every component and 3rd-party package of our application into one file. This reduces the number of HTTP requests to load the individual parts. You can configure it further to combine multiple components which should be loaded together.
$: gulp htmlhint
Runs a code quality analysis for all HTML templates using HTMLHint.
$: gulp eslint
Runs a code quality analysis for all JavaScript code using ESLint.
Before our appllication goes into production we want to run the bundle
Gulp task to reduce the amount of network requests needed to load everything that's needed to show the first screen to the user. The bundle
task combines files into logical bundles so that resources which always must be loaded together sit in one file. Also, it updates SystemJS' configuration so that the loader knows it should load the bundled resources instead of the individual files. You don't need to change anything in your code to take advantage of the optimized loading process.
Before running the bundle
task we want to commit everything and revert the changes made by the bundler after the application is deployed. Otherwise SystemJS would also load the bundled resources in development. We would then have to run the bundle
task everytime we change some thing whie developing. This would slow down the development since bundling takes quite some time. For more information on bundling see the Angular Lazy Bundler and SystemJS documentations.
A lot of packages don't declare their dependencies properly. For example, UI Router doesn't declare a dependency to Angular in it's package.json. Same with Bootstrap, which doesn't have a dependency to jQuery. This leads to errors when loading such libraries as their dependencies don't get loaded. If you encounter such issues search for special distribution build of the package on it's GitHub page, e.g. github.com/angular/bower-angular-animate for angular-animate. In that case try installing the package from there.
Another possibility is to amend the missing information in config/system.js
as already done by JSPM when it finds dependency declarations in package.json or in the JSPM registry itself.
ocLazyLoad's FAQ mentions some Angular modules which cannot be lazy loaded. If we want to use one of those, e.g. angular-animate
, we need to import them in the application component and make them a dependency of it. And then do the same in the component where they are effectively required.
The Safari Selenium Driver cannot be installed automatically. We need to install it manually before we can run any Protractor tests against it. Safari 9.0.1 has a bug where it's only possible to install the plugin in safe mode. Hold the shift key while booting to start in safe mode.
Licensed under BSD 3-Clause.