angularjs-Working with Filters & ngRoute
更新日期:
What Are AngularJS Filters
AngularJS filters are used to process data and format values to present to the user.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | <div ng-controller="FilterCtrl as ctrl"> <div> Amount as a number: {\{ctrl.amount | number}} </div> <div> Total Cost as a currency: {\{ctrl.totalCost | currency}} </div> <div> Total Cost in INR: {\{ctrl.totalCost | currency:'INR '}} </div> <div> Shouting the name: {\{ctrl.name | uppercase}} </div> <div> Whispering the name: {\{ctrl.name | lowercase}} </div> <div> Start Time: {\{ctrl.startTime | date:'medium'}} </div> </div> <script type="text/javascript"> angular.module('filtersApp', []) .controller('FilterCtrl', [function() { this.amount = 1024; this.totalCost = 4906; this.name = 'Shyam Seshadri'; this.startTime = new Date().getTime(); }]); </script> |
The filter will take the value of the expression (a string, number, or array) and convert it into some other form.
{\{ctrl.name | lowercase | limitTo: 5}}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | <ul ng-controller="FilterCtrl as ctrl"> <li> Amount - {\{ctrl.amount}} </li> <li> Amount - Default Currency: {\{ctrl.amount | currency}} </li> <li> <!-- Using the English pound sign --> Amount - INR Currency: {\{ctrl.amount | currency:'£ '}} </li> <li> Amount - Number: {\{ctrl.amount | number}} </li> <li> Amount - No. with 4 decimals: {\{ctrl.amount | number:4}} </li> <li> Name with no filters: {\{ctrl.name}} </li> <li> Name - lowercase filter: {\{ctrl.name | lowercase}} </li> <li> Name - uppercase filter: {\{ctrl.name | uppercase}} </li> <li> <!-- printed as a string {"test": "value", "num": 123} --> The JSON Filter: {\{ctrl.obj | json}} </li> <li> Timestamp: {\{ctrl.startTime}} </li> <li> Default Date filter: {\{ctrl.startTime | date}} </li> <li> Medium Date filter: {\{ctrl.startTime | date:'medium'}} </li> <li> Custom Date filter: {\{ctrl.startTime | date:'M/dd, yyyy'}} </li> </ul> <script type="text/javascript"> angular.module('filtersApp', []) .controller('FilterCtrl', [function() { this.amount = 1024; this.name = 'Shyam Seshadri'; this.obj = {test: 'value', num: 123}; this.startTime = new Date().getTime(); }]); </script> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | <div ng-controller="FilterCtrl as ctrl"> <button ng-click="ctrl.currentFilter = 'string'"> Filter with String </button> <button ng-click="ctrl.currentFilter = 'object'"> Filter with Object </button> <button ng-click="ctrl.currentFilter = 'function'"> Filter Text </button> <input type="text" ng-model="ctrl.filterOptions['string']"> Show Done Only <input type="checkbox" ng-model="ctrl.filterOptions['object'].done"> <ul> <li ng-repeat="note in ctrl.notes | filter:ctrl.filterOptions[ctrl.currentFilter] | orderBy:ctrl.sortOrder | limitTo:5"> {\{note.label}} - - </li> </ul> </div> <script type="text/javascript"> angular.module('filtersApp', []) .controller('FilterCtrl', [function() { this.notes = [ {label: 'FC Todo', type: 'chore', done: false}, {label: 'FT Todo', type: 'task', done: false}, {label: 'FF Todo', type: 'fun', done: true}, {label: 'SC Todo', type: 'chore', done: false}, {label: 'ST Todo', type: 'task', done: true}, {label: 'SF Todo', type: 'fun', done: true}, {label: 'TC Todo', type: 'chore', done: false}, {label: 'TT Todo', type: 'task', done: false}, {label: 'TF Todo', type: 'fun', done: false} ]; this.sortOrder = ['+type', '-label']; this.filterOptions = { "string": '', "object": {done: false, label: 'C'}, "function": function(note) { return note.type === 'task' && note.done === false; } }; this.currentFilter = 'string'; }]); </script> |
string
, AngularJS will look for the string in the keys of each object of the array, and if it is found, the element is included.object
, AngularJS takes each key of the object and makes sure that its value is present in the corresponding key of each object of the array. For example, an object expression like{size: "M"}
would check each item of the array and ensure that the objects have a key called size and that they contain the letter “M”function
, return true or false
Using Filters in Controllers and Services
AngularJS allows us to use the filters wherever we want or need through the power of Dependency Injection.
Any filter, whether built-in or our own, can be injected into any service or controller by affixing the word “Filter” at the end of the name of the filter, and asking it to be injected.
1 2 3 4 5 6 | // if we need the currency filter in our controller angular.module('myModule', []) .controller('MyCtrl', ['currencyFilter', function(currencyFilter) { self.filteredArray = filterFilter(self.notes, 'ch'); }]); |
Creating AngularJS Filters
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | <div ng-controller="FilterCtrl as ctrl"> <div> Start Time (Timestamp): {\{ctrl.startTime}} </div> <div> Start Time (DateTime): {\{ctrl.startTime | date:'medium'}} </div> <div> Start Time (Our filter): {\{ctrl.startTime | timeAgo}} </div> <div> someTimeAgo : {\{ctrl.someTimeAgo | date:'short'}} </div> <div> someTimeAgo (Our filter): {\{ctrl.someTimeAgo | timeAgo}} </div> </div> <script type="text/javascript"> angular.module('filtersApp', []) .controller('FilterCtrl', [function() { this.startTime = new Date().getTime(); this.someTimeAgo = new Date().getTime() - (1000 * 60 * 60 * 4); }]) .filter('timeAgo', [function() { var ONE_MINUTE = 1000 * 60; var ONE_HOUR = ONE_MINUTE * 60; var ONE_DAY = ONE_HOUR * 24; var ONE_MONTH = ONE_DAY * 30; return function(ts) { var currentTime = new Date().getTime(); var diff = currentTime - ts; if (diff < ONE_MINUTE) { return 'seconds ago'; } else if (diff < ONE_HOUR) { return 'minutes ago'; } else if (diff < ONE_DAY) { return 'hours ago'; } else if (diff < ONE_MONTH) { return 'days ago'; } else { return 'months ago'; } }; }]); //return function(ts, arg1, arg2, arg3) { //{\{ctrl.startTime | timeAgo:arg1:arg2:arg3}} </script> |
- View filters are executed every digest cycle
- Filters should be blazingly fast
- Prefer filters in services and controllers for optimization
Unit Testing Filters
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | // File: chapter9/timeAgoFilter.js angular.module('filtersApp', []) .filter('timeAgo', [function() { var ONE_MINUTE = 1000 * 60; var ONE_HOUR = ONE_MINUTE * 60; var ONE_DAY = ONE_HOUR * 24; var ONE_MONTH = ONE_DAY * 30; return function(ts, optShowSecondsMessage) { if (optShowSecondsMessage !== false) { optShowSecondsMessage = true; } var currentTime = new Date().getTime(); var diff = currentTime - ts; if (diff < ONE_MINUTE && optShowSecondsMessage) { return 'seconds ago'; } else if (diff < ONE_HOUR) { return 'minutes ago'; } else if (diff < ONE_DAY) { return 'hours ago'; } else if (diff < ONE_MONTH) { return 'days ago'; } else { return 'months ago'; } }; }]); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | // File: chapter9/timeAgoFilterSpec.js describe('timeAgo Filter', function() { beforeEach(module('filtersApp')); var filter; beforeEach(inject(function(timeAgoFilter) { filter = timeAgoFilter; })); it('should respond based on timestamp', function() { // The presence of new Date().getTime() makes it slightly // hard to unit test deterministicly. // Ideally, we would inject a dateProvider into the timeAgo // filter, but we are trying to keep it simple here. // So we will assume that our tests are fast enough to // execute in mere milliseconds. var currentTime = new Date().getTime(); currentTime -= 10000; expect(filter(currentTime)).toEqual('seconds ago'); var fewMinutesAgo = currentTime - 1000 * 60; expect(filter(fewMinutesAgo)).toEqual('minutes ago'); var fewHoursAgo = currentTime - 1000 * 60 * 68; expect(filter(fewHoursAgo)).toEqual('hours ago'); var fewDaysAgo = currentTime - 1000 * 60 * 60 * 26; expect(filter(fewDaysAgo)).toEqual('days ago'); var fewMonthsAgo = currentTime - 1000 * 60 * 60 * 24 * 32; expect(filter(fewMonthsAgo)).toEqual('months ago'); }); }); |
Routing Using ngRoute
AngularJS provides us with an optional module called ngRoute, which can be used to do routing in an AngularJS application.
We mark in our HTML where we want the routing to take
effect with the ng-view
directive.
It takes care of the browser history, so you can actually use back and forward buttons in your browser to navigate within the application.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <script type="text/javascript" src="/path/to/angular-route.min.js"></script> <ul> <li><a href="#/">Default Route</a></li> <li><a href="#/second">Second Route</a></li> <li><a href="#/asdasdasd">Nonexistent Route</a></li> </ul> <div ng-view></div> <script type="text/javascript"> angular.module('routingApp', ['ngRoute']) .config(['$routeProvider', function($routeProvider) { $routeProvider.when('/', { template: '<h5>This is the default route</h5>' }) .when('/second', { template: '<h5>This is the second route</h5>' }) .otherwise({redirectTo: '/'}); }]); </script> |
Routing Options
The AngularJS route definition allows us to
define more complex templates.
The $routeProvider.when
function takes a URL or
URL regular expression as the first argument, and the
route configuration object as the second.
1 2 3 4 5 6 7 | $routeProvider.when(url, { template: string, templateUrl: string, controller: string, function or array, controllerAs: string, resolve: object<key, function> }); |
- url,
/list
,/recipe/:recipeId
- template, AngularJS directly inserts this template HTML into the ng-view directive
tempateUrl
1 2 3
$routeProvider.when('/test', { templateUrl: 'views/test.html', });
controller, if we have not directly defined the controller in the HTML using the ng-controller directive, e we pass the con‐ troller function directly to the controller key.
1 2 3 4 5 6
$routeProvider.when('/test', { template: '<h1>Test Route</h1>', controller: ['$window', function($window) { $window.alert('Test route has been loaded!'); }] });
controllerAs
1 2 3 4 5 6 7 8 9
$routeProvider.when('/test', { template: '<h1>Test Route</h1>', controller: 'MyCtrl as ctrl' }); $routeProvider.when('/test', { template: '<h1>Test Route</h1>', controller: 'MyCtrl', controllerAs: 'ctrl' });
redirectTo
1 2 3 4 5 6
$routeProvider.when('/new', { template: '<h1>New Route</h1>' }); $routeProvider.when('/old', { redirectTo: '/new' });
resolve, At a conceptual level, resolves are a way of executing and finishing asynchronous tasks before a particular route is loaded. This is a great way to check if the user is logged in and has authorization and permissions, and even preload some data before a controller and route are loaded into the view.
Using Resolves for Pre-Route Checks
When we define a resolve, we can define a set of asynchronous tasks to execute before the route is loaded. A resolve is a set of keys and functions. Each function can return a value or a promise.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | angular.module('resolveApp', ['ngRoute']) .value('Constant', {MAGIC_NUMBER: 42}) .config(['$routeProvider', function($routeProvider) { $routeProvider.when('/', { template: '<h1>Main Page, no resolves</h1>' }) .when('/protected', { template: '<h2>Protected Page</h2>', resolve: { immediate: ['Constant', function(Constant) { return Constant.MAGIC_NUMBER * 4; }], async: ['$http', function($http) { return $http.get('/api/hasAccess'); }] }, controller: ['$log', 'immediate', 'async', function($log, immediate, async) { $log.log('Immediate is ', immediate); $log.log('Server returned for async', async); }] }); }]); |
- If the resolve function returns a value, AngularJS immediately finishes executing and treats it as a successful resolve.
- If the resolve function returns a promise, AngularJS waits for the promise to return and treats the resolve as successful if the promise is successful. If the promise is rejected, the resolve is treated as a failure.
- If there are multiple re solve keys that make asynchronous calls, AngularJS executes all of them in parallel and waits for all of them to finish executing before loading the page.
- If any of the resolves encounter an error or any of the promises returned are rejected (is a failure), AngularJS doesn’t load the route.
Using the $routeParams Service
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | angular.module('resolveApp', ['ngRoute']) .config(['$routeProvider', function($routeProvider) { $routeProvider.when('/', { template: '<h1>Main Page</h1>' }).when('/detail/:detId', { template: '<h2>Loaded ' + ' and query String is </h2>', controller: ['$routeParams', function($routeParams) { this.detailId = $routeParams.detId; this.qStr = $routeParams.q; }], controllerAs: 'myCtrl' }); }]); // /detail/123?q=MySearchParam |
For every AngularJS application that uses ngRoute, there can be one and only
one ng-view
directive for that application.
A Full AngularJS Routing Example
- A landing page shows a list of teams. Anybody can access this page.
- A login page allows users to log in to the application. Anybody can access this page.
- Details pages for teams are access-controlled. Only logged-in users can access the details page. This is true whether the user logged in right before accessing, or logged in and then closed the window and came back at any later point.
TODO
HTML5 Mode
A URL like http://www.myawesomeapp.com/#/first/
page would look
like http://www.myawesomeapp.com/first/page
with HTML5 mode enabled.
To support search engine crawling, it is expected
that the SPA will use hashbang URLs instead of
pure hash URLs (#!
instead of #
).
1 2 3 4 5 6 7 8 9 10 | angular.module('myHtml5App', ['ngRoute']) .config(['$locationProvider', '$routeProvider', function($locationProvider, $routeProvider) { $locationProvider.html5Mode(true); //Optional $locationProvider.hashPrefix('!'); // Route configuration here as normal // Route for /first/page // Route for /second/page }]); |
This is to tell the browser where, in relation to the URL, the static resources are served from, so that if the application requests an image or CSS file with a relative path, it doesn’t take it from the current URL necessarily.
All relative paths would be resolved relative to /app and not to some other URL.
1 2 3 4 5 | <html> <head> <base href="/app" /> </head> </html> |
ui-router
But what if we had more complex requirements and wanted to change different parts
of our UI differently depending on the URL? use ng-show
or ng-hide
or ng-switch
ui-router
is state-oriented, and by default does
not modify URLs. We need to specify the URL for
each state individually.
We should consider using ui-router if our project needs or has the following requirements:
- We need different parts of the page to react differently to URL changes or user interactions.
- We have multiple different (nested) sections of the page that are conditionally shown for various actions and events.
- We don’t need the URL to change while the user navigates throughout our application.
- The entire UI layout needs to change completely across different pages.