This was the code of that controller in which we had already used the techniques specified in the two previous posts (extending Angular resources with behavior and moving logic to simple JavaScript widgets):
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"use strict"; | |
app.controller('TracksIndexPageController', [ | |
'$scope', | |
'Categories', | |
'accumulatedStatistics', | |
'categoryListWidgetFactory', | |
'dateRangeWidgetFactory', | |
'Pagination', | |
'activitiesWidgetFactory', | |
function ($scope, Categories, accumulatedStatistics, categoryListWidgetFactory, dateRangeWidgetFactory, Pagination, activitiesWidgetFactory) { | |
var updatePageInfo = function () { | |
$scope.activities.filterBy($scope.categoryList.getSelectedCategory()); | |
$scope.updateActivitiesRelatedData(); | |
$scope.pagination = new Pagination($scope.activities.displayedOnes); | |
}, | |
loadActivities = function () { | |
$scope.activities.load( | |
$scope.dateRange, | |
updatePageInfo | |
); | |
}; | |
$scope.selectCategory = function (category) { | |
if ($scope.categoryList.noActivitiesMatching(category)) { | |
return; | |
} | |
$scope.categoryList.selectCategory(category); | |
updatePageInfo(); | |
}; | |
$scope.loadMonthActivities = function () { | |
$scope.dateRange.useMonthPeriod(); | |
loadActivities(); | |
}; | |
$scope.loadWeekActivities = function () { | |
$scope.dateRange.useWeekPeriod(); | |
loadActivities(); | |
}; | |
$scope.loadYearActivities = function () { | |
$scope.dateRange.useYearPeriod(); | |
loadActivities(); | |
}; | |
$scope.loadCurrentDateRangeActivities = function () { | |
$scope.dateRange.today(); | |
loadActivities(); | |
}; | |
$scope.loadNextDateRangeActivities = function () { | |
$scope.dateRange.forwards(1); | |
loadActivities(); | |
}; | |
$scope.loadPreviousDateRangeActivities = function () { | |
$scope.dateRange.backwards(1); | |
loadActivities(); | |
}; | |
$scope.updateStatistics = function () { | |
$scope.displayedStatistics = accumulatedStatistics.getGroupedStatisticsToDisplay( | |
$scope.activities.displayedOnes | |
); | |
}; | |
$scope.updateActivitiesRelatedData = function () { | |
$scope.updateStatistics(); | |
$scope.categoryList.updateNonEmptyCategories( | |
$scope.activities.distinctCategories() | |
); | |
}; | |
$scope.userHasStatistics = function () { | |
return accumulatedStatistics.exists(); | |
}; | |
$scope.shouldShowActivities = function () { | |
return !resolved() || $scope.activities.thereAreSomeToDisplay(); | |
function resolved() { | |
return _.isUndefined($scope.activities.displayedOnes.$resolved) || | |
$scope.activities.displayedOnes.$resolved; | |
} | |
}; | |
$scope.initialize = function () { | |
$scope.Categories = Categories; | |
$scope.categories = Categories.descriptions; | |
$scope.activities = activitiesWidgetFactory.create(); | |
$scope.displayedStatistics = []; | |
$scope.categoryList = categoryListWidgetFactory.create(Categories.allCategories); | |
$scope.dateRange = dateRangeWidgetFactory.create(); | |
$scope.loadCurrentDateRangeActivities(); | |
}; | |
$scope.initialize(); | |
} | |
]); |
A way of getting rid of this code was using events.
We started by publishing a 'DateRange:Changed' event using the Angular's $rootScope.$broadcast from the updateRange function inside the dateRange widget and then added a listener for that event in the controller which loaded new activities every time that event was published. We also added some new convenience helpers to the dateRange widget in order to make it easier to use from the partial.
This was the resulting code of TracksIndexPageController after these changes:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"use strict"; | |
app.controller('TracksIndexPageController', [ | |
'$scope', | |
'Categories', | |
'accumulatedStatistics', | |
'categoryListWidgetFactory', | |
'dateRangeWidgetFactory', | |
'Pagination', | |
'activitiesWidgetFactory', | |
function ($scope, Categories, accumulatedStatistics, categoryListWidgetFactory, dateRangeWidgetFactory, Pagination, activitiesWidgetFactory) { | |
var updatePageInfo = function () { | |
$scope.activities.filterBy($scope.categoryList.getSelectedCategory()); | |
$scope.updateActivitiesRelatedData(); | |
$scope.pagination = new Pagination($scope.activities.displayedOnes); | |
}; | |
$scope.selectCategory = function (category) { | |
if ($scope.categoryList.noActivitiesMatching(category)) { | |
return; | |
} | |
$scope.categoryList.selectCategory(category); | |
updatePageInfo(); | |
}; | |
$scope.$on('DateRange:changed', function () { | |
$scope.activities.load($scope.dateRange, updatePageInfo); | |
}); | |
$scope.updateStatistics = function () { | |
$scope.displayedStatistics = accumulatedStatistics.getGroupedStatisticsToDisplay( | |
$scope.activities.displayedOnes | |
); | |
}; | |
$scope.updateActivitiesRelatedData = function () { | |
$scope.updateStatistics(); | |
$scope.categoryList.updateNonEmptyCategories( | |
$scope.activities.distinctCategories() | |
); | |
}; | |
$scope.userHasStatistics = function () { | |
return accumulatedStatistics.exists(); | |
}; | |
$scope.shouldShowActivities = function () { | |
return !resolved() || $scope.activities.thereAreSomeToDisplay(); | |
function resolved() { | |
return _.isUndefined($scope.activities.displayedOnes.$resolved) || | |
$scope.activities.displayedOnes.$resolved; | |
} | |
}; | |
$scope.initialize = function () { | |
$scope.Categories = Categories; | |
$scope.categories = Categories.descriptions; | |
$scope.activities = activitiesWidgetFactory.create(); | |
$scope.displayedStatistics = []; | |
$scope.categoryList = categoryListWidgetFactory.create(Categories.allCategories); | |
$scope.dateRange = dateRangeWidgetFactory.create(); | |
$scope.dateRange.today(); | |
}; | |
$scope.initialize(); | |
} | |
]); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$scope.$on('DateRange:changed', function () { | |
$scope.activities.load($scope.dateRange, updatePageInfo); | |
}); |
Next, we did the same for the categoryList widget getting this new version of TracksIndexPageController:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"use strict"; | |
app.controller('TracksIndexPageController', [ | |
'$scope', | |
'Categories', | |
'accumulatedStatistics', | |
'categoryListWidgetFactory', | |
'dateRangeWidgetFactory', | |
'Pagination', | |
'activitiesWidgetFactory', | |
function ($scope, Categories, accumulatedStatistics, categoryListWidgetFactory, dateRangeWidgetFactory, Pagination, activitiesWidgetFactory) { | |
var updatePageInfo = function () { | |
$scope.activities.filterBy($scope.categoryList.getSelectedCategory()); | |
$scope.updateActivitiesRelatedData(); | |
$scope.pagination = new Pagination($scope.activities.displayedOnes); | |
}, | |
updateStatistics = function () { | |
$scope.displayedStatistics = accumulatedStatistics.getGroupedStatisticsToDisplay( | |
$scope.activities.displayedOnes | |
); | |
}; | |
$scope.$on('DateRange:Changed', | |
function () { | |
$scope.activities.load($scope.dateRange, updatePageInfo); | |
} | |
); | |
$scope.$on('CategoryList:NewCategorySelected', | |
updatePageInfo | |
); | |
$scope.updateActivitiesRelatedData = function () { | |
updateStatistics(); | |
$scope.categoryList.updateNonEmptyCategories( | |
$scope.activities.distinctCategories() | |
); | |
}; | |
$scope.userHasStatistics = function () { | |
return accumulatedStatistics.exists(); | |
}; | |
$scope.shouldShowActivities = function () { | |
return !resolved() || $scope.activities.thereAreSomeToDisplay(); | |
function resolved() { | |
return _.isUndefined($scope.activities.displayedOnes.$resolved) || | |
$scope.activities.displayedOnes.$resolved; | |
} | |
}; | |
$scope.initialize = function () { | |
$scope.Categories = Categories; | |
$scope.categories = Categories.descriptions; | |
$scope.activities = activitiesWidgetFactory.create(); | |
$scope.displayedStatistics = []; | |
$scope.categoryList = categoryListWidgetFactory.create( | |
Categories.allCategories, | |
$scope.activities.distinctCategories() | |
); | |
$scope.dateRange = dateRangeWidgetFactory.create(); | |
$scope.dateRange.today(); | |
}; | |
$scope.initialize(); | |
} | |
]); |
The duplication that we highlighted in the first version of the controller had been removed but there were still some things we didn't like in this code.
- We were using strings to identify the events in different parts of the code (the widgets and the controller). This was a case of the magic numbers smell.
- Now our widgets weren't simple JavaScript objects anymore because they were using Angular's $rootScope.$broadcast inside. This made their testing a bit more difficult.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Events used in the application | |
//-------------------------------------------------- | |
app.value("applicationEvents", { | |
CategoryList_NewSelectedCategory: 'CategoryList:NewCategorySelected', | |
UserProfile_Updated: 'UserProfile:Updated', | |
Map_created: 'OpenLayersMap:created', | |
DateRange_Changed:'DateRange:Changed' | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Wrapper on event publishing functionality | |
//-------------------------------------------------- | |
app.service('eventPublisher', [ | |
'$rootScope', | |
'applicationEvents', | |
function ($rootScope, applicationEvents) { | |
return { | |
publish: function (eventType, payLoad) { | |
$rootScope.$broadcast(eventType, payLoad); | |
}, | |
events: applicationEvents | |
}; | |
} | |
]); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//.. some code before | |
//.. | |
describe("Updating date range publishes an event", function () { | |
var dateRangeWidget; | |
beforeEach( | |
inject(function (DateRangePeriods) { | |
dateRangeWidget = dateRangeWidgetFactory.create(DateRangePeriods.YEAR); | |
spyOn(eventPublisher, "publish"); | |
}) | |
); | |
it("publishes a DateRange:Changed event when the date range is updated to " + | |
"the period containing the start date", function () { | |
dateRangeWidget.containingCurrentStartDate(); | |
expect(eventPublisher.publish.calls.first().args) | |
.toEqual([applicationEvents.DateRange_Changed, {}]); | |
}); | |
it("publishes a DateRange:Changed event when the date range is updated to " + | |
"the period after the start date", function () { | |
dateRangeWidget.forwards(1); | |
expect(eventPublisher.publish.calls.first().args) | |
.toEqual([applicationEvents.DateRange_Changed, {}]); | |
}); | |
it("publishes a DateRange:Changed event when the date range is updated to " + | |
"the period after the start date", function () { | |
dateRangeWidget.backwards(1); | |
expect(eventPublisher.publish.calls.first().args) | |
.toEqual([applicationEvents.DateRange_Changed, {}]); | |
}); | |
}); | |
//.. | |
//.. some code after |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"use strict"; | |
app.controller('TracksIndexPageController', [ | |
'$scope', | |
'Categories', | |
'accumulatedStatistics', | |
'categoryListWidgetFactory', | |
'dateRangeWidgetFactory', | |
'Pagination', | |
'activitiesWidgetFactory', | |
'applicationEvents', | |
function ($scope, Categories, accumulatedStatistics, categoryListWidgetFactory, dateRangeWidgetFactory, Pagination, activitiesWidgetFactory, applicationEvents) { | |
var updatePageInfo = function () { | |
$scope.activities.filterBy($scope.categoryList.getSelectedCategory()); | |
$scope.updateActivitiesRelatedData(); | |
$scope.pagination = new Pagination($scope.activities.displayedOnes); | |
}, | |
updateStatistics = function () { | |
$scope.displayedStatistics = accumulatedStatistics.getGroupedStatisticsToDisplay( | |
$scope.activities.displayedOnes | |
); | |
}; | |
$scope.$on( | |
applicationEvents.DateRange_Changed, | |
function () { | |
$scope.activities.load($scope.dateRange, updatePageInfo); | |
} | |
); | |
$scope.$on( | |
applicationEvents.CategoryList_NewSelectedCategory, | |
updatePageInfo | |
); | |
$scope.updateActivitiesRelatedData = function () { | |
updateStatistics(); | |
$scope.categoryList.updateNonEmptyCategories( | |
$scope.activities.distinctCategories() | |
); | |
}; | |
$scope.userHasStatistics = function () { | |
return accumulatedStatistics.exists(); | |
}; | |
$scope.shouldShowActivities = function () { | |
return !resolved() || $scope.activities.thereAreSomeToDisplay(); | |
function resolved() { | |
return _.isUndefined($scope.activities.displayedOnes.$resolved) || | |
$scope.activities.displayedOnes.$resolved; | |
} | |
}; | |
$scope.initialize = function () { | |
$scope.Categories = Categories; | |
$scope.categories = Categories.descriptions; | |
$scope.activities = activitiesWidgetFactory.create(); | |
$scope.displayedStatistics = []; | |
$scope.categoryList = categoryListWidgetFactory.create( | |
Categories.allCategories, | |
$scope.activities.distinctCategories() | |
); | |
$scope.dateRange = dateRangeWidgetFactory.create(); | |
$scope.dateRange.today(); | |
}; | |
$scope.initialize(); | |
} | |
]); |