In this Lesson, we will cover the following recipes:
uppercase
and lowercase
filtersnumber
and currency
filtersdate
filterjson
filterIn this Lesson, you will learn how to effectively utilize AngularJS filters and services in your applications. Service types are essential tools required for code reuse, abstraction, and resource consumption in your application. Filters, however, are frequently glazed over in introductory courses as they are not considered integral to learning the framework basics. This is a pity as filters let you afford the ability to abstract and compartmentalize large chunks of application functionality cleanly.
All AngularJS filters perform the same class of operations on the data they are passed, but it is easier to think about filters in the context of a pseudo-dichotomy in which there are two kinds: data filters and search filters.
At a very high level, AngularJS data filters are merely tools that modulate JavaScript objects cleanly in the template. On the other half of the spectrum, search filters have the ability to select elements of an enumerable collection that match some of the criteria you have defined. They should be thought of as black box modifiers in your template—well-defined layers of indirection that keep your scopes free of messy data-parsing functions. They both enable your HTML code to be more declarative, and your code to be DRY.
Service types can be thought of as injectable singleton classes to be used throughout your application in order to house the utility functionality and maintain states. The AngularJS service types can appear as values, constants, factories, services, or providers.
Although filters and services are used very differently, a cunning developer can use them both as powerful tools for code abstraction.
Two of the most basic built-in filters are uppercase
and lowercase
filters, and they can be used in the following fashion.
Suppose that you define the following controller in your application:
(app.js) angular.module('myApp', []) .controller('Ctrl', function ($scope) { $scope.data = { text: 'The QUICK brown Fox JUMPS over The LAZY dog', nums: '0123456789', specialChars: '!@#$%^&*()', whitespace: ' ' }; });
You will then be able to use the filters in the template by passing them via the pipe operator, as follows:
(index.html) <div ng-app="myApp"> <div ng-controller="Ctrl"> <p>{{ data.text | uppercase }}</p> <p>{{ data.nums | uppercase }}</p> <p>{{ data.specialChars | uppercase }}</p> <p>_{{ data.whitespace | uppercase }}_</p> </div> </div>
The output rendered will be as follows:
THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG 0123456789 !@#$%^&*() _ _
Similarly, the lowercase
filter can be used with predictable results:
(index.html) <div ng-app="myApp"> <div ng-controller="Ctrl"> <p>{{ data.text | lowercase }}</p> <p>{{ data.nums | lowercase }}</p> <p>{{ data.specialChars | lowercase }}</p> <p>_{{ data.whitespace | lowercase }}_</p> </div> </div>
The output rendered will be as follows:
the quick brown fox jumps over the lazy dog 0123456789 !@#$%^&*() _ _
JSFiddle: http://jsfiddle.net/msfrisbie/vcuvxrom/
The uppercase
and lowercase
filters are essentially simple AngularJS wrappers used for native string methods toUpperCase()
and toLowerCase()
available in JavaScript. These filters ignore number characters, special characters, and whitespace when performing appropriate substitutions.
As these filters are merely wrappers for native JavaScript methods, you almost certainly won't ever have a need to use them anywhere outside the template. Their primary utility is in their ability to be invoked in the template and their ability to chain themselves alongside other filters that might require them. For example, if you had created a search filter that only matched identical string matches in its results, you might want to pass a search string through a lowercase
filter before passing it through the search comparator.
AngularJS has some built-in filters that are less simple, such as number
and currency
; they can be used to format numbers into normalized strings. They also accept optional arguments that can further customize how the filters work.
Suppose that you define the following controller in your application:
(app.js) angular.module('myApp', []) .controller('Ctrl', function ($scope) { $scope.data = { bignum: 1000000, num: 1.0, smallnum: 0.9999, tinynum: 0.0000001 }; });
You can apply the number
filter in your template, as follows:
(index.html) <div ng-app="myApp"> <div ng-controller="Ctrl"> <p>{{ data.bignum | number }}</p> <p>{{ data.num | number }}</p> <p>{{ data.smallnum | number }}</p> <p>{{ data.tinynum | number }}</p> </div> </div>
The output rendered will be as follows:
1,000,000 1 1.000 1e-7
This outcome might seem a bit arbitrary, but it demonstrates the next facet of filters examined here, which are arguments. Filters can take arguments to further customize the output. The number
filter takes a fractionSize
argument, which defines how many decimal places it will round to, defaulting to 3. This is shown in the following code:
(index.html) <div ng-app="myApp"> <div ng-controller="Ctrl"> <!—- data | number : fractionSize(optional) --> <p>{{ data.smallnum | number : 4 }}</p> <p>{{ data.tinynum | number: 7 }}</p> <p>{{ 012345.6789 | number : 2 }}</p> </div> </div>
The output rendered will be as follows:
0.9999 0.0000001 12,345.68
The currency
filter is another AngularJS filter that takes an optional argument, symbol
:
(index.html) <div ng-app="myApp"> <div ng-controller="Ctrl"> <!—- data | currency : symbol(optional) --> <p>{{ 1234.56 | currency }}</p> <p>{{ 0.02 | currency }}</p> <p>{{ 45682.78 | currency : "€" }}</p> </div> </div>
The output rendered will be as follows:
$1,234.56 $0.02 €45,682.78
JSFiddle: http://jsfiddle.net/msfrisbie/Lcb33vnz/
JavaScript has a single format in which it stores numbers as 64-bit double precision floating point numbers. These AngularJS filters exist to neatly format this raw number format by examining the values passed to it and by deciding how to appropriately format it as a string. The number
filter handles rounding, truncation, and compression in negative exponents. It optionally accepts the fractionSize
argument, in order to allow you to customize the filter to your needs, something that greatly increases the utility of filters. The currency
filter handles rounding and appending of the designated currency symbol. It optionally accepts the symbol
argument, which will insert the provided symbol in front of the formatted number.
Both of these filters inherently utilize the $locale
service, which acts as a fallback for default arguments (for example, providing a $
character for the currency
filter in regions that use dollar, ordering of dates, and more). This service exists as a part of AngularJS's mission to act as a region agnostic framework.
The date
filter is an extremely robust and customizable filter that can handle many different kinds of raw date strings and convert them into human readable versions. This is useful in situations when you want to let your server defer datetime processing to the client and just be able to pass it a Unix timestamp or an ISO date.
Suppose, you have your controller set up in the following fashion:
(app.js) angular.module('myApp', []) .controller('Ctrl', function ($scope) { $scope.data = { unix: 1394787566535, iso: '2014-03-14T08:59:26Z', date: new Date(2014, 2, 14, 1, 59, 26, 535) }; });
All the date formats can be used seamlessly with the date
filter inside the template, as follows:
(index.html) <div ng-app="myApp"> <div ng-controller="Ctrl"> <p>{{ data.unix | date }}</p> <p>{{ data.iso | date }}</p> <p>{{ data.date | date }}</p> </div> </div>
The output rendered will be as follows:
Mar 14, 2014 Mar 14, 2014 Mar 14, 2014
The date
filter is heavily customizable, giving you the ability to generate a date and time representation using any piece of the datetime passed to it:
(index.html) <div ng-app="myApp"> <div ng-controller="Ctrl"> <!—- AngularJS matches the expression components to datetime components, then stringifies as specified --> <p>{{ data.unix | date : "EEEE 'at' H:mma" }}</p> <p>{{ data.iso | date : "longDate" }}</p> <p>{{ data.date | date : "M/d H:m:s.sss" }}</p> </div> </div>
This code uses various pieces of the date
filter syntax to pull out elements from the datetime generated inside the filter, and assemble them together in the output string, the template for which is provided in the optional format argument. The output rendered will be as follows:
Friday at 1:59AM March 14, 2014 3/14 1:59:26.535
JSFiddle: http://jsfiddle.net/msfrisbie/mvdqfv5z/
The date
filter wraps a robust set of complex regular expressions inside the framework, which exists to parse the string passed to it into a normalized JavaScript date
object. This date
object is then broken apart and molded into the desired string format specified by the filter's argument syntax.
The AngularJS documentation at https://docs.angularjs.org/api/ng/filter/date provides the details of all the possible input and output formats required for date filters.
The date
filter provides you with two levels of indirection: normalized conversion from various datetime formats and normalized conversion into almost any human readable format. Note that in the absence of a provided time zone, the time zone assumed is the local time zone, which in this example is Pacific Daylight Time (UTC - 7), which is accommodated through the $locale
service.
AngularJS provides you with a JSON conversion tool, the json
filter, to serialize JavaScript objects into prettified JSON code. This filter isn't so much in use for production applications as it is used for real-time inspection of your scope objects.
Suppose your controller is set up as follows with a prefilled user
data object:
(app.js) angular.module('myApp', []) .controller('Ctrl', function ($scope) { $scope.user = { id: 123, name: { first: 'Jake', last: 'Hsu' }, username: 'papatango', friendIds: [5, 13, 3, 1, 2, 8, 21], // properties prefixed with $$ will be excluded $$no_show: 'Hide me!' }; });
Your user
object can be serialized in the template, as follows:
(index.html) <div ng-app="myApp"> <div ng-controller="Ctrl"> <pre>{{ user | json }}</pre> </div> </div>
The output will be rendered in HTML, as follows:
{ "id": 123, "name": { "first": "Jake", "last": "Hsu" }, "username": "papatango", "friendIds": [ 5, 13, 3, 1, 2, 8, 21 ] }
JSFiddle: http://jsfiddle.net/msfrisbie/yk0zxc9b/
The json
filter simply wraps the JSON.stringify()
method in JavaScript in order to provide you with an easy way to spit out formatted objects for inspection. When the filtered object is fed into a <pre>
tag, the JSON string will be properly indented in the rendered template. Properties prefixed with $$
will be skipped by the serializer as this notation is used internally in AngularJS as a private identifier.
Filters are built to perform template data processing, so their utilization outside the template will be infrequent. Nonetheless, AngularJS provides you with the ability to use filter functions via an injection of $filter
.
Suppose that you have an application, as follows:
(app.js) angular.module('myApp', []) .controller('Ctrl', function ($scope) { $scope.val = 1234.56789; });
In the view templates, the argument order is scrambled with the following format:
data | filter : optionalArgument
For this example, it would take the form in the template as follows:
<p>{{ val | number : 4 }}</p>
This will give the following result:
1,234.5679
In this example, it's cleanest to apply the filter in the view template, as the purpose of formatting the number is merely for readability. If, however, the number
filter is needed to be used in a controller, $filter
can be injected and used as follows:
(app.js)
angular.module('myApp', [])
.controller('Ctrl', function ($scope, $filter) {
$scope.val = 1234.56789;
$scope.filteredVal = $filter('number')($scope.val, 4);
});
With this, the values of $scope.val
and $scope.filteredVal
will be identical.
JSFiddle: http://jsfiddle.net/msfrisbie/9bzu85uu/
Although the syntax is very different compared to what is found in a template, using a dependency injected filter is functionally the same as applying it in the view template. The same filter method is invoked for both formats and both generate the same output.
Although there are no cardinal sins committed by injecting $filter
and using your filters that way, the syntax is awkward and verbose. Filters aren't really designed for that sort of use anyway. AngularJS is meant for building declarative templates, and that is exactly what data filters provide when used in templates—lightweight and flexible modulation functions for cleaning and organizing your data.
One of the primary use cases for using filters outside the template is when you are building a custom filter that uses one or more existing filters inside it. For example, you might want to use the currency
filter inside a custom filter, which decides whether to use a $
or a ¢
prefix based on whether or not the amount is greater or less than $1.00.
Search filters serve to evaluate individual elements in an enumerable object and return whether or not they belong in the resultant set. The returned value from the filter will also be an enumerable set with none, some, or all of the original values that were removed. AngularJS provides a rich suite of ways to filter an enumerable object.
Search filters return a subset of an enumerable object, so prepare a controller as follows, with a simple array of strings:
(app.js) angular.module('myApp', []) .controller('Ctrl', function ($scope) { $scope.users = [ 'Albert Pai', 'Jake Hsu', 'Jack Hanford', 'Scott Robinson', 'Diwank Singh' ]; });
The default search filter is used in the template in the same fashion as a data filter, but invoked with the pipe operator. It takes a mandatory argument, that is, the object that the filter will compare against.
The easiest way to test a search filter is by tying an input field to a model and using that model as the search filter argument, as follows:
(index.html) <div ng-app="myApp"> <div ng-controller="Ctrl"> <input type="text" ng-model="search.val" /> </div> </div>
This model can then be applied in a search filter on an enumerable data object. The filter is most commonly applied inside an ng-repeat
expression:
(index.html) <div ng-app="myApp"> <div ng-controller="Ctrl"> <input type="text" ng-model="search.val" /> <p ng-repeat="user in users | filter : search.val"> {{ user }} </p> </div> </div>
Entering ja
will return the following output:
Jake Hsu Jack Hanford
Entering s
will return the following output:
Jake Hsu Scott Robinson Diwank Singh
Entering a
will return the following output:
Albert Pai Jake Hsu Jack Hanford Diwank Singh
JSFiddle: http://jsfiddle.net/msfrisbie/h1dbover/
With this setup, the string in the search.val
model will be matched (case insensitive) against each element in the enumerable object and will only return the matches for the repeater to iterate through. This transformation occurs before the object is passed to the repeater, so the filter combined with AngularJS data binding results in a very impressive real-time, in-browser filtering system with minimal overhead.
As AngularJS search filters simply reduce the modulation functions that return a subset of the object that is passed to it, it is possible to chain multiple filters together.
When filtering enumerable objects, AngularJS provides two built-in enumeration filters that are commonly used in conjunction with the search filters: limitTo
and orderBy
.
Suppose that your application contains a controller as follows with a simple array of objects containing a name
string property:
(app.js) angular.module('myApp', []) .controller('Ctrl', function ($scope) { $scope.users = [ {name: 'Albert Pai'}, {name: 'Jake Hsu'}, {name: 'Jack Hanford'}, {name: 'Scott Robinson'}, {name: 'Diwank Singh'} ]; });
In addition, suppose that the application template is set up as follows:
(index.html) <div ng-app="myApp"> <div ng-controller="Ctrl"> <input type="text" ng-model="search.val" /> <!—- simple repeater filtering against search.val --> <p ng-repeat="user in users | filter : search.val"> {{ user.name }} </p> </div> </div>
You can chain another filter following your first with an identical syntax by merely adding another pipe operator and the filter name with arguments. Here, you can see the setup to apply the limitTo
filter to the matching results:
(index.html) <p ng-repeat="user in users | filter : search.val | limitTo: 2"> {{ user.name }} </p>
Searching for h
will result in the following output:
Jake Hsu Jack Hanford
You can chain another filter, orderBy
, which will sort the array, as follows:
(index.html) <p ng-repeat="user in users | filter : search.val | orderBy: 'name' | limitTo : 2"> {{ user.name }} </p>
Searching for h
will result in the following output:
Diwank Singh Jack Hanford
JSFiddle: http://jsfiddle.net/msfrisbie/ht3hfLrt/
AngularJS search filters are functions that return a Boolean, representing whether or not the particular element of the enumerable object belongs to the resultant set. For the array of string primitives in the preceding code, the filter performs a simple case-insensitive substring match operation against the provided matching string taken from the model bound to the <input>
tag.
The subsequent chained filters orderBy
and limitTo
also take an enumerable object as an argument and perform an additional operation on it. In the preceding example, the filter first reduces the string array to a subset string array, which is first passed to the orderBy
filter. This filter sorts the subset string array by the expression provided, which here is alphabetical order, as the argument is a string. This sorted array is then passed to the limitTo
filter which truncates the sorted substring subset string array to the number of characters specified in the argument. This final array is then fed into the repeater in the template for rendering.
It's worth mentioning that chained AngularJS filters are not necessarily commutative; the order in which filters are chained matters, as they are evaluated sequentially. In the last example, reversing the order of the chained filters (limitTo
followed by orderBy
) will truncate the subset string array and then sort only the truncated results. The proper way to think about this is to compare them to nested functions—similar to how foo(bar(x))
is obviously not the same as bar(foo(x))
, and x | foo | bar
is not the same as x | bar | foo
.
At some point, the provided AngularJS data filters will not be enough to fill your needs, and you will need to create your own data filters. For example, assume that in an application that you are building, you have a region of the page that is limited in physical dimensions, but contains an arbitrary amount of text. You would like to truncate that text to a length which is guaranteed to fit in the limited space. A custom filter, as you might imagine, is perfect for this task.
The filter you wish to build accepts a string argument and returns another string. For now, the filter will truncate the string to 100 characters and append an ellipsis at the point of truncation:
(app.js) angular.module('myApp', []) .filter('simpletruncate', function () { // the text parameter return function (text) { var truncated = text.slice(0, 100); if (text.length > 100) { truncated += '...'; } return truncated; }; });
This will be used in the template, as follows:
(index.html) <div ng-app="myApp"> <div ng-controller="Ctrl"> <p>{{ myText | simpletruncate }}</p> </div> </div>
This filter works well, but it feels a bit brittle. Instead of just defaulting to 100 characters and an ellipsis, the filter should also accept parameters that allow undefined input and optional definition of how many characters to truncate to and what the stop character(s) should be. It would be even better if the filter only cut off the text at a set of whitespace characters if possible:
(app.js) angular.module('myApp', []) .filter('regextruncate',function() { return function(text,limit,stoptext) { var regex = /s/; if (!angular.isDefined(limit)) { limit = 100; } if (!angular.isDefined(stoptext)) { stoptext = '...'; } limit = Math.min(limit,text.length); for(var i=0;i<limit;i++) { if(regex.exec(text[limit-i]) && !regex.exec(text[(limit-i)-1])) { limit = limit-i; break; } } var truncated = text.slice(0, limit); if (text.length>limit) { truncated += stoptext; } return truncated; }; });
This will be used in the template as follows:
(index.html) <div ng-app="myApp"> <div ng-controller="Ctrl"> <p>{{ myText | regextruncate : 150 : '???' }}</p> </div> </div>
JSFiddle: http://jsfiddle.net/msfrisbie/a4ez926f/
The final version of the filter uses a simple whitespace-detecting regular expression to find the first point in the string that it can truncate. After setting the default values of limit
and stoptext
, the data filter iterates backwards through the relevant string values, watching for the first point at which it sees a non whitespace character followed by a whitespace character. This is the point at which it sets the truncation, and the string is broken apart, and then the relevant segment is returned with the appended stoptext
statement.
These filter examples don't modify the model in any way, they are merely context-free data wrappers that package your model data neatly into a format that your template can easily digest. Each model change causes the filter to be invoked in order to keep the data in the template up-to-date, so the filter processing must be lightweight as it is assumed that the filter will be frequently invoked.
A rich suite of data filters in your application will allow a cleaner decoupling of the presentation layer and model. The demonstration in this recipe was limited to the string primitive, but there is no reason you could not extend your filter logic to encompass and handle complex data objects in your application's models.
The entire purpose of filters is to improve readability and reusability, so if the construction and application of a custom filter enables you to do that, you are encouraged to do so.
AngularJS search filters work exceedingly well out of the box, but you will quickly develop the desire to introduce some customization of how the filter actually relates the search object to the enumerable collection. This collection is frequently composed of complex data objects; a simple string comparison will not suffice, especially when you want to modify the rules by which matches are governed.
Searching against data objects is simply a matter of building the search object in the same mould as the enumerable collection objects.
Suppose, for example, your controller looks as follows:
(app.js) angular.module('myApp', []) .controller('Ctrl', function($scope) { $scope.users = [ { firstName: 'John', lastName: 'Stockton' }, { firstName: 'Michael', lastName: 'Jordan' } ]; });
When searching against this collection, in the case where the search filter is passed a string primitive, it will perform a wildcard search, as follows:
(index.html) <div ng-app="myApp"> <div ng-controller="Ctrl"> <input ng-model="search" /> <p ng-repeat="user in users | filter:search"> {{ user.firstName}} {{ user.lastName }} </p> </div> </div>
JSFiddle: http://jsfiddle.net/msfrisbie/ghsa3nym/
With this, if you were to enter jo
in the input field, both John Stockton
and Michael Jordan
will be returned. When asked to compare a string primitive to an object, AngularJS has no choice but to compare the string to every field it can, and any objects that match are declared to be a part of the match-positive resultant set.
If instead you only want to compare against specific attributes of the enumerable collection, you can set the search object to have correlating attributes that should be matched against the collection attributes, as shown here:
(index.html)
<div ng-app="myApp">
<div ng-controller="Ctrl">
<input ng-model="search.firstName" />
<p ng-repeat="user in users | filter:search">
{{ user.firstName}} {{ user.lastName }}
</p>
</div>
</div>
JSFiddle: http://jsfiddle.net/msfrisbie/72qucbhp/
Now, if you were to enter jo
in the input field, only John Stockton
will be returned.
If you want to search only for exact matches, vanilla wildcard filtering becomes problematic as the default comparator uses the search object to match against substrings in the collection object. Instead, you might want a way to specify exactly what constitutes a match between the reference object and enumerable collection.
Suppose that your controller contains the following data object:
(app.js) angular.module('myApp', []) .controller('Ctrl', function($scope) { $scope.users = [ { firstName: 'John', lastName: 'Stockton', number: '12' }, { firstName: 'Michael', lastName: 'Jordan', number: '23' }, { firstName: 'Allen', lastName: 'Iverson', number: '3' } ]; });
Instead of using just a single search box, the application will use two search fields, one for the name and one for the number. Having a wildcard search for the first name and last name is more useful, but searching for wildcard numbers is not useful in this situation.
The search fields are constructed as follows:
(index.html) <div ng-app="myApp"> <div ng-controller="Ctrl"> <input ng-model="search.$" /> <input ng-model="search.number" /> <p ng-repeat="user in users | filter:search"> {{ user.firstName}} {{ user.lastName }} </p> </div> </div>
The first input field appears with $
; this is done merely to assign the wildcard search to the entire search object so that it does not interfere with other assigned search attributes. The second input field specifies that the application should only search against the collection's number
attribute.
As expected, testing this code reveals that the number
search field is performing a wildcard search, which is not desirable. To specify exact matches when searching, the filter takes an optional comparator argument that mandates how matches will be ascertained. A true
value passed will enable exact matches:
(index.html) <div ng-app="myApp"> <div ng-controller="Ctrl"> <input ng-model="search.$" required /> <input ng-model="search.number" required /> <p ng-repeat="user in users | filter:search:true"> {{ user.firstName}} {{ user.lastName }} </p> </div> </div>
With this setup, both inputs will create an AND filter to select data from the array with one or multiple criteria. The required
statement will cause the model bound to it to reset to undefined
, when the input is an empty string.
JSFiddle: http://jsfiddle.net/msfrisbie/on394so2/
The comparator
argument will be resolved to a function in all cases. When passing in true
, AngularJS will treat it as an alias for the following code:
function(actual, expected) { return angular.equals(expected, actual); }
This will function as a strict comparison of the element in the enumerable collection and the reference object.
More generally, you can also pass in your own comparator function, which will return true
or false
based on whether or not actual
matches expected
. This will take the following form:
function(actual, expected) { // logic to determine if actual // should count as a match for expected }
The functions from the comparator argument are the ones used to determine whether each piece of the enumerable collection belongs in the resultant subset.
The provided search filters can serve your application's purposes only to a point. Eventually, you will need to construct a complete solution in order to filter an enumerable collection.
Suppose that your controller contains the following data object:
(app.js) angular.module('myApp', []) .controller('Ctrl', function($scope) { $scope.users = [ { firstName: 'John', lastName: 'Stockton', number: '12' }, { firstName: 'Michael', lastName: 'Jordan', number: '23' }, { firstName: 'Allen', lastName: 'Iverson', number: '3' } ]; });
Suppose you wanted to create an OR filter for the name and number values. The brute force way to do this is to create an entirely new filter in order to replace the AngularJS filter. The filter takes an enumerable object and returns a subset of the object. Adding the following will do exactly that:
(app.js) .filter('userSearch', function () { return function (users, search) { var matches = []; angular.forEach(users, function (user) { if (!angular.isDefined(users) || !angular.isDefined(search)) { return false; } // initialize match conditions var nameMatch = false, numberMatch = false; if (angular.isDefined(search.name) && search.name.length > 0) { // substring of first or last name will match if (angular.isDefined(user.firstName)) { nameMatch = nameMatch || user.firstName.indexOf(search.name) > -1; } if (angular.isDefined(user.lastName)) { nameMatch = nameMatch || user.lastName.indexOf(search.name) > -1; } } if (angular.isDefined(user.number) && angular.isDefined(search.number)) { // only match if number is exact match numberMatch = user.number === search.number; } // either match should populate the results with user if (nameMatch || numberMatch) { matches.push(user); } }); // this is the array that will be fed to the repeater return matches; }; });
This would then be used as follows:
(index.html) <div ng-app="myApp"> <div ng-controller="Ctrl"> <input ng-model="search.name" required /> <input ng-model="search.number" required /> <p ng-repeat="user in users | userSearch : search"> {{ user.firstName }} {{ user.lastName }} </p> </div> </div>
JSFiddle: http://jsfiddle.net/msfrisbie/k4umoj3p/
Since this filter is built from scratch, it's constructed to handle all the edge cases of missing attributes and objects in the parameters. The filter performs substring lookups on the first and last name attributes and exact matches on number attributes. Once this is done, it performs the actual OR operation on the two results. However, having entirely rebuilt the search filter, it must return the entire collection subset.
Rebuilding the filtering mechanism from top to bottom, as shown in this recipe, only makes sense if you need to significantly diverge from the existing filtering mechanism functionality.
Instead of reinventing the wheel, you can create a search filter expression that evaluates to true
or false
for each iteration in the enumerable collection.
The simplest way to do this is to define a function on your scope, as follows:
(app.js) angular.module('myApp', []) .controller('Ctrl', function ($scope) { $scope.users = [ ... ]; $scope.usermatch = function (user) { if (!angular.isDefined(user) || !angular.isDefined($scope.search)) { return false; } var nameMatch = false, numberMatch = false; if (angular.isDefined($scope.search.name) && $scope.search.name.length > 0) { if (angular.isDefined(user.firstName)) { nameMatch = nameMatch || user.firstName.indexOf($scope.search.name) > -1; } if (angular.isDefined(user.lastName)) { nameMatch = nameMatch || user.lastName.indexOf($scope.search.name) > -1; } } if (angular.isDefined(user.number) && angular.isDefined($scope.search.number)) { numberMatch = user.number === $scope.search.number; } return nameMatch || numberMatch; }; });
Now, this can be passed to the built-in filter as follows:
(index.html) <div ng-app="myApp"> <div ng-controller="Ctrl"> <input ng-model="search.name" required /> <input ng-model="search.number" required /> <p ng-repeat="user in users | filter:usermatch"> {{ user.firstName }} {{ user.lastName }} </p> </div> </div>
JSFiddle: http://jsfiddle.net/msfrisbie/76874ygr/
In the Name search box, typing Jo
now returns Michael Jordan
and John Stockton
and in the Number search box, typing 3
only returns Allen Iverson
. Searching for both Mi
and 3
will return Michael Jordan
and Allen Iverson
, as the filter constructed here is an OR filter. If you want to change it to an AND filter, you can simply change the return line to the following:
return nameMatch && numberMatch;
All of these search filter techniques can be framed through a perspective that pays attention to what you are filtering. Search filters merely apply the question: "Does this fit my definition of a match?", over and over again. AngularJS's data binding causes this question to be asked to each member of the enumerable collection each time the object changes in content or population. The preceding recipes merely define how this question gets asked.
Filters are merely applied JavaScript functions and the mechanisms by which they can be configured are flexible. Rarely in production applications will the built-in search filter infrastructure be sufficient, so it is advantageous to instead be able to mould exactly how the filter interprets a match.
Furthermore, as you begin to examine performance limitations, you will begin to consider ways to optimize repeaters and filters. If kept lightweight, filters are inexpensive and can be run hundreds of times in rapid succession without consequence. As complexity and data magnitude scale, filters can allow you to maintain a performant and responsive application.
AngularJS service types, at their core, are singleton containers used for unified resource access across your application. Sometimes, the resource access will just be a single JS object. For this, AngularJS offers service values and service constants.
Service values and service constants both act in a very similar way, but with one important difference.
The service value is the simplest of all service types. The value
service acts as a key-value pair and can be injected and used as follows:
(app.js) angular.module('myApp', []) .controller('Ctrl', function($scope, MyValue) { $scope.data = MyValue; $scope.update = function() { MyValue.name = 'Brandon Marshall'; }; }) .value('MyValue', { name: 'Tim Tebow', number: 15 });
An example of template use is as follows:
(index.html) <div ng-app="myApp"> <div ng-controller="Ctrl"> <button ng-click="update()">Update</button> {{ data.name }} #{{ data.number }} </div> </div>
JSFiddle: http://jsfiddle.net/msfrisbie/hs7uL1y0/
You'll notice that AngularJS has no problem with you updating the service value. Since it is a singleton, any part of your application that injects the value
service and reads/writes to it will be accessing the same data. Service values act like service factories (discussed in the Using service factories recipe) and cannot be injected into the providers or the config()
phase of your application.
Like service values, service constants also act as singleton key-value pairs. The important difference is that service constants act like service providers and can be injected into the config()
phase and service providers. They can be used as follows:
(app.js) angular.module('myApp', []) .config(function(MyConstant) { // can't inject $log into config() console.log(MyConstant); }) .controller('Ctrl', function($scope, MyConstant) { $scope.data = MyConstant; $scope.update = function() { MyConstant.name = 'Brandon Marshall'; }; }) .constant('MyConstant', { name: 'Tim Tebow', number: 15 });
The template remains unchanged from the service value example.
JSFiddle: http://jsfiddle.net/msfrisbie/whaea0y1/
A service factory is the simplest general purpose service type that allows you to use the singleton nature of AngularJS services with encapsulation.
The service factory's return value is what will be injected when the factory is listed as a dependency. A common and useful pattern is to define private data and functions outside this object, and define an API to them through a returned object. This is shown in the following code:
(app.js) angular.module('myApp', []) .controller('Ctrl', function($scope, MyFactory) { $scope.data = MyFactory.getPlayer(); $scope.update = MyFactory.swapPlayer; }) .factory('MyFactory', function() { // private variables and functions var player = { name: 'Peyton Manning', number: 18 }, swap = function() { player.name = 'A.J. Green'; }; // public API return { getPlayer: function() { return player; }, swapPlayer: function() { swap(); } }; });
Since the service factory values are now bound to $scope
, they can be used in the template normally, as follows:
(index.html) <div ng-app="myApp"> <div ng-controller="Ctrl"> <button ng-click="update()">Update</button> {{ data.name }} #{{ data.number }} </div> </div>
JSFiddle: http://jsfiddle.net/msfrisbie/5gydkrjw/
This example might feel a bit contrived, but it demonstrates the basic usage pattern that can be used with service factories for great effect. As with all service types, this is a singleton, so any modifications done by a component of the application will be reflected anywhere the factory is injected.
Services act in much the same way as service factories. Private data and methods can be defined and an API can be implemented on the service object through it.
A service is consumed in the same way as a factory. It differs in that the object to be injected is the controller itself. It can be used in the following way:
(app.js) angular.module('myApp', []) .controller('Ctrl', function($scope, MyService) { $scope.data = MyService.getPlayer(); $scope.update = MyService.swapPlayer; }) .service('MyService', function() { var player = { name: 'Philip Rivers', number: 17 }, swap = function() { player.name = 'Alshon Jeffery'; }; this.getPlayer = function() { return player; }; this.swapPlayer = function() { swap(); }; });
When bound to $scope
, the service interface is indistinguishable from a factory. This is shown here:
(index.html) <div ng-app="myApp"> <div ng-controller="Ctrl"> <button ng-click="update()">Update</button> {{ data.name }} #{{ data.number }} </div> </div>
JSFiddle: http://jsfiddle.net/msfrisbie/5wn16dyk/
Services invoke a constructor with the new
operator, and the instantiated service object is the delivered injectable. Like a factory, it still exists as a singleton and the instantiation is deferred until the service is actually injected.
Service providers are the parent service type used for factories and services. They are the most configurable and extensible of the service types, and allow you to inspect and modify other service types during the application's initialization.
Service providers take a function parameter that returns an object that has a $get
method. This method is what AngularJS will use to produce the injected value after the application has been initialized. The object wrapping the $get
method is what will be supplied if the service provider is injected into the config
phase. This can be implemented as follows:
(app.js) angular.module('myApp', []) .config(function(PlayerProvider) { // appending 'Provider' to the injectable // is an Angular config() provider convention PlayerProvider.configSwapPlayer(); console.log(PlayerProvider.configGetPlayer()); }) .controller('Ctrl', function($scope, Player) { $scope.data = Player.getPlayer(); $scope.update = Player.swapPlayer; }) .provider('Player', function() { var player = { name: 'Aaron Rodgers', number: 12 }, swap = function() { player.name = 'Tom Brady'; }; return { configSwapPlayer: function() { player.name = 'Andrew Luck'; }, configGetPlayer: function() { return player; }, $get: function() { return { getPlayer: function() { return player; }, swapPlayer: function() { swap(); } }; } }; });
When used this way, the provider appears to the controller as a normal service type, as follows:
(app.js) .controller('Ctrl', function($scope, Player) { $scope.data = Player.getPlayer(); $scope.update = Player.swapPlayer; }) (index.html) <div ng-app="myApp"> <div ng-controller="Ctrl"> <button ng-click="update()">Update</button> {{ data.name }} #{{ data.number }} </div> </div>
JSFiddle: http://jsfiddle.net/msfrisbie/49wjk54L/
Providers is the only service type that can be passed into a config
function. Injecting a provider into the config
function gives access to the wrapper object, and injecting a provider into an initialized application component will give you access to the return value of the $get
method. This is useful when you need to configure aspects of a service type before it is used throughout the application.
Providers can only be injected as their configured services in an initialized application. Similarly, types like service factories and services cannot be injected in a provider, as they will not yet exist during the config
phase.
An often overlooked aspect of AngularJS services is their ability to decorate service types in the initialization logic. This allows you to add or modify how factories or services will behave in the config
phase before they are injected in the application.
In the config
phase, the $provide
service offers a decorator method that allows you to inject a service and modify its definition before it is formally instantiated. This is shown here:
(app.js) angular.module('myApp', []) .config(function($provide) { $provide.decorator('Player', function($delegate) { // $delegate is the Player service instance $delegate.setPlayer('Eli Manning'); return $delegate; }); }) .controller('Ctrl', function($scope, Player) { $scope.data = Player.getPlayer(); $scope.update = Player.swapPlayer; }) .factory('Player', function() { var player = { number: 10 }, swap = function() { player.name = 'DeSean Jackson'; }; return { setPlayer: function(newName) { player.name = newName; }, getPlayer: function() { return player; }, swapPlayer: function() { swap(); } }; });
As you have merely modified a regular factory, it can be used in the template normally, as follows:
(index.html) <div ng-app="myApp"> <div ng-controller="Ctrl"> <button ng-click="update()">Update</button> {{ data.name }} #{{ data.number }} </div> </div>
JSFiddle: http://jsfiddle.net/msfrisbie/cd3286rt/
35.171.45.182