Mistakes Made
This week has been a slow week, through-out development time we constantly focused on reducing any kind of feature creep. The mistake I made this week was focusing too much on the small features leading to specific features taking much longer than expected. Luckily the server, search page and import are 100% complete allowing me to focus on the view page next week.
Ui-router Negates ngRoute
Single Page Web Applications (SPA) contain no page reloading, instead, they download the whole application. To route users to different views (web pages), the Angular ngRoute module can be used. NgRoute functions by linking controllers to views by watching the URL using $location.url(). Using $routeProvider specific routes can be defined, the example below routes ‘/import’ URL to the importController and import.html template.
angular.module('importApp',['ngRoute']) .config(function($routeProvider)){ $routeProvider .when('/import/:importId',{ templateUrl: 'import.html', controller: 'importController', resolve: { //get data } }) };
You will notice ‘:importId’ appends the import URL the colons tells us that ‘importId is a path parameter, within the importController the URL parameters can be accessed using $routeParams. Additionally, the example uses a resolve property which can be used to request data before instantiating the controller. If the resolve property object contains a promise the controller will only be instantiated once resolving all promises if a rejection happens the $routeChangeError event fires. The $routeChangeError can be hooked upon to redirect a user to a different view.
NgRoute is very powerful tool that can easily support small-scale applications but lacks required features for more complex ones. An alternative to ngRoute is ui-router, ui-router is a 3rd-party module that contains all ngRoute features as well as much needed extra functionality. Ui-router provides multiple named views, one template can contain multiple views, nested views which can inherit from one another. Furthermore, ui-router provides a superior way to access state parameters. The example below shows 2 views within the same template.
<!--index.html--> <body> <divui-view='importDetails'></div> <divui-view='importSelection'></div> </body>
angular.module('importApp',['ui-router']) .config(function($stateProvider)){ $stateProvider .state('import',{ views: { 'importDetails': { templateUrl: 'importDetails.html', controller: 'importDetailsController' }, 'importSelection': { templateUrl: 'importSelection.html', controller: 'importSelectionController' } } }) };
Each ‘div’ with the ui-view attribute within the html describes each view, the view names must match with the keys within the views object.
Furthermore, ui-router provides lazy loading allowing the application to download resources on-demand rather than startup time. Using lazy loading can drastically reduce the startup time of an application only by downloading the necessary data when needed. Ui-router will download the needed data for a state before entering by waiting for the function promise to resolve. Below the example shows how to configure the import state to lazy load, which loads import.js before going to the ‘import’ state.
angular.module('importApp',['ui-router']) .config(function($stateProvider)){ varimportState={ name: 'import', url: '/import/:importId', templateUrl: 'import.html', controller: 'importController', lazyLoad: function(){ returnSystem.import('import') } } $stateProvider.state(importState); }) };
State Page Stored in URL
When first meeting with the client one requirement the client requested was the ability to save searches and share views, each feature would take a large amount of time and may provide limited functionality. Instead of having two different features, save searches and share views, storing the pages state within the URL provides a cleaner solution which would take less development time.
Storing the pages state within the URL, is common practice with Single Page Web Applications (SPA), allowing the user to intuitively share the pages URL knowing the URL with direct back to the exact state of the page.
Storing the page state in the URL is a cyclic process, each variable that needs to be watched within the page is stored within the URL as a query parameter, when one of the watched variables changes the corresponding URL parameter changes, triggering the pages UI to update.
A common mistake when storing the page state in the URL is to update the URL and UI independently. Updating the URL and UI independently is considered bad practice as it breaks the cycle thus the URL and UI could de-sync causing unwanted side effects.
To maintain the page state within the URL I used ui-router, using $stateParams and $sate services. The first part I developed was to extract the parameters from the URL using $stateParams. The $stateParams is a service to provide controllers or services with URL parameters which are declared when configuring the application. Within the $sateParams object each key corresponds with each URL parameter with the value the value of the parameter. Within the application, the URL parameters are extracted if a parameter has changed or when the state is initially loaded. The example below demonstrates how to extract the importId URL parameter using the $stateParams service.
app.controller('importController',['$stateParams', function($stateParams){ varurlParams=function(){ return$stateParams.importId; } }])
Once the application could extract URL parameters the second step was to update the parameters dynamically. When researching how to update URL parameters the UI-router official documentation said to use the $state service. The $state service exposes multiple methods to control the current state, for example, $state.get gets the registered StateDeclaration object (used to define a state). To update the URL parameters the .go method can be used passing the absolute state name, state object or relative state path as well as a map of parameters which will populate $stateParams. To update the current state a ‘.’ can be passed as the state name to the .go method.
app.controller('importController',['$state', function($state){ functionstateReload(params){ $state.go('.',params); } }])
The issue with using this method is the application state reloads each time the .go method is called, which doesn’t provide a seamless user experience. The application page reloads because the .go method calls the .transitionTo method which transitions to a new state. To fix the reloading issue the dynamic flag can be used. When declaring parameters for a specific state within the application configuration the dynamic flag can be set to true. Setting the dynamic flag to true will not cause the state to enter/exit when the parameters value changes, when set to false the state will enter/exit when a parameter changes
angular.module('importApp',['ui-router']) .config(function($stateProvider)){ varimportState={ name: 'import', url: '/import/:importId', params: { importId: { dynamic: true } }, templateUrl: 'import.html', controller: 'importController', } $stateProvider.state(importState); }) };
A comment mistake when integrating the page state within the URL is having too many watchers. The $watch method registers a listener callback which is fire whenever the passed expression changes. It might be tempting to attach a listener for each variable when a variable change the .go method is called updating the URL parameters but this considered bad practice. Having too many watchers can impact the application performance drastically, once the number of watchers exceeds around 2,000 an application performance will start to have noticeable performance issues. Instead a function should be implemented and is specifically called when URL parameters need to be updated.
Conclusion
Overall this issues last week have impacted this weeks development before this week started I still had to finish the incomplete features from last week. I was hoping to fully complete the Single Page Web Application (SPA) this week but finishing the Import page took longer than expected. Next week the whole system should be complete leaving me with 1 week of extra development time.