Formatters are helper tools to view your data differently from how they are stored in the model. Angular has built-in formatters, for example, Date
to format date-times, Currency
to format money data, and LimitTo
to limit the view to a certain number of results. The Filter
class displays items based on whether they satisfy the criteria set up in the filter. Sorting works through an orderBy
attribute in ng-repeat
. In this recipe, we will show you how to use filters to make different views on your data possible. You can follow along with the code in the project angular_formatter
.
The job listing is now preceded by an input field; when you start typing the job type, the list of only those jobs that start with these letters are shown. The checkboxes allow you to filter on company, and the type of job is shown in uppercase in the job details section, as shown in the following screenshot:
Perform the following steps to use formatters as filters:
angular_formatter.html
. Have a look at the following code:<div id="filters"> <div> <label for="type-filter">Filter jobs by type</label> <input id="type-filter" type="text" ng-model="ctrl.typeFilterString"> </div> <div> Filter jobs by company: <span ng-repeat="company in ctrl.companies"> <label> <input type="checkbox" ng-model="ctrl.companyFilterMap[company]">{{company}} </label> </span> </div> <input type="button" value="Clear Filters" ng-click="ctrl.clearFilters()"> </div>
ng-repeat = "job in ctrl.jobs"
, we now have the following:ng-repeat="job in ctrl.jobs orderBy:'type'filter:{type:ctrl.typeFilterString}companyfilter:ctrl.companyFilterMap"
We have shown here the different filter parts on consecutive lines for readability, but in HTML, they must be on one line.
In the controller code (libjob_listing.dart
), we now have two lists:
List<Job> jobs; List companies = [];
The _loadData()
option now returns jobs, and in the constructor companies is filled through as follows:
for (job in jobs) { companies.add(job.company); }
The filters need the following code:
final companyFilterMap = <String, bool>{}; String typeFilterString = ""; void clearFilters() { companyFilterMap.clear(); typeFilterString = ""; }
Furthermore, we have refactored the model code (class Job
) in its own file libmodeljob.dart
.
The company filter is a custom formatter that has to be coded. We find it in libformattercompany_filter.dart
as follows:
library company_filter; import 'package:angular/angular.dart'; @Formatter(name: 'companyfilter') class CompanyFilter { List call(JobList, filterMap) { if (JobList is Iterable && filterMap is Map) { // If there is nothing checked, treat it as "everything is checked" bool nothingChecked = filterMap.values.every((isChecked) => !isChecked); return nothingChecked ? JobList.toList() : JobList.where((i) => filterMap[i.company] == true).toList(); } return const []; } }
<div job-listing>
in angular_formatter.html
, we now format the job type as {{job.type | uppercase}}
.libformatter
, we code this uppercase formatter as follows:import 'package:angular/angular.dart'; @Formatter(name: 'uppercase') class UppercaseFormatter { call(String name) { if (name == null || name.isEmpty) return ''; return name.toUpperCase(); } }
angular_formatter.dart
script has two additional import statements and two bind statements for the formatters, as follows:import 'package:angular_formatter/formatter/company_filter.dart'; import 'package:angular_formatter/formatter/uppercase_formatter.dart'; class AppModule extends Module { AppModule() { bind(JobListingController); bind(SalaryComponent); bind(CompanyFilter); bind(UppercaseFormatter); }
In step 1, we see the two filters. The filter on type is done with an input text field that has a special attribute, ng-model="ctrl.typeFilterString"
. This tells us that the JobListingController
must have a property typeFilterString
, which is bound to this input field by ng-model
. The same goes for the checkboxes, which are bound to a controller property, companyFilterMap
:
ng-model="ctrl.companyFilterMap[company]"
Indeed, we see these properties declared in the additional controller code in step 3. Note that companyFilterMap
is a map built from the company names and the Boolean values of the checkboxes. Step 2 shows us the filtering declaration: first the jobs are ordered by type, then the type filter is applied (if any), and then the company filters (if any). The results are piped (or chained) with a |
operator from one filter to the other.
Step 4 shows the code for the company filter: it has to be a separate class, CompanyFilter
, that is annotated with @Formatter(name: 'companyfilter')
, where the name is used in the ng-repeat
attribute. This class must have a call
method, as first argument the model object to be formatted (here, JobList
), the second argument filterMap
is the filter to be applied. So the return value of call
is the filtered (formatted) job list. The filterMap
parameter is the data that comes from the checkbox inputs. Steps 5 and 6 show how to add a simple uppercase formatter. In step 7, we add our custom filters and formatters to our AppModule
.
Angular.dart
framework here makes use of the special method call
, which is explained in the Using the call method recipe in Chapter 4, Object Orientation