CHAPTER 15
In this chapter, we are going to use the service we created in the previous chapter to display the standings on the website.
The standings component is responsible for showing the standings on a website. A club might use a large video monitor to let the players see the website between games.
Code Listing 124: App.standings.ts
// Import various modules we might need, // module name and what file/library to find them import { Component } from '@angular/core'; import { ViewEncapsulation } from '@angular/core'; // Our interfaces import { Team } from './interfaces/Teams'; import { Ranking } from './interfaces/rankings'; import { Schedule } from './interfaces/schedule'; import { SoccerService} from './services/soccerService'; |
We need to import some modules from Angular, as well as a few of our own interfaces and services.
Code Listing 125: App.component.ts Continued
// Component metadata, defining the template code @Component({ templateUrl: './app/views/Standings.html', // HTML template name encapsulation: ViewEncapsulation.Native, // Use Shadow DOM // Set styles for template styles: [` h3 {text-align:center;color:navy;font-size:x-large;margin:0px;} table { width:92%;margin:1em auto;font-size:large; font-family:"Comic Sans MS", cursive, sans-serif; } th { text-decoration:underline;} ` ], providers: [SoccerService ] }) |
Note that in the templateUrl property, the views folder is relative to the app folder. Be sure to adjust your path if you choose a different folder structure.
I generally place my templates in a folder called Views under the main app folder. This is strictly a style choice—you can place them anywhere and reference their locations properly. However, I prefer keeping views, interfaces, and app code in their own folders.
Code Listing 126: Views/standings.html
<h3>{{LeagueName}} </h3> <br /> <table > <thead> <tr> <th style="width:35%;"></th> <th style="width:13%;text-align:right;">Games</th> <th style="width:13%;text-align:right;">Points</th> <th style="width:18%;text-align:right;">Goals for</th> <th style="width:21%;text-align:right;">Against</th> </tr> </thead> <tbody> <tr *ngFor="let currentRow of Standings"> <td>{{ currentRow.TeamName }}</td> <td style="text-align:right;">{{ currentRow.GamesPlayed }}</td> <td style="text-align:right;"> {{ currentRow.Wins*3 + currentRow.Ties }}</td> <td style="text-align:right;">{{ currentRow.GoalsFor }}</td> <td style="text-align:right;">{{ currentRow.GoalsAgainst }}</td> </tr> </tbody> </table> |
The template code is composed mostly of HTML and interpolation variables, such as the LeagueName at the top of the page. We also use the *ngFor directive to loop through the Standings collection in the component. Most of the information is straight from the properties, although we use the interpolated expression currentRow.Wins* 3 + currentRow.Ties to compute the points column. In general, you should let the component perform such calculations, but I wanted to provide a small example of how you can do calculations within the template as well.
Our class code is going to declare an empty Ranking array, then ask the service for the schedule data. Once the schedule data is returned, a public method will walk through the schedule and compute the rankings, updating the Ranking array.
Code Listing 127
export class AppStandings { // public properties (default is public) public LeagueName: string; public UsingAsync: boolean = false; public MySchedule: Schedule[]; public Standings: Ranking[]; |
The constructor code:
Code Listing 128
public constructor(private _soccerService: SoccerService ) { this.LeagueName = "Over 30 men's league"; this.getSchedule(); this.ComputeRankings(); } |
The reason ComputeRankings is public is that we might want to wrap the call in a timer function, in case the club is running a tournament and wants standings updates in real time.
This code gets the schedule data from the service and places it in the MySchedule array.
Code Listing 129
private getSchedule() { if (this.UsingAsync) { let xx = this._soccerService.getScheduleAsnyc(); xx.then((Schedules:iSchedule[])=> this.MySchedule =Schedules ); } else { this.MySchedule = this._soccerService.getSchedule(); } } |
Once the schedule data is available, we can call ComputeRankings to update the Rankings collection (which is what the template displays).
ComputeRankings is an example of business logic in a component. Rather than have the service determine the rankings (a function only needed by the component), we will rely on the service to provide the schedule data, and let the component determine the ranking.
In general, soccer rankings award three points for a win and one point for a tie. There are often tie-breaker rules, such as goals scored, head-to-head, etc. So our component needs to read the schedule and sum up the wins, ties, goals for, and goals against. This data is placed into the ranking collection, which is then sorted to display the Standings page.
Code Listing 130
public ComputeRankings() { var curDate: Date = new Date(); var TeamAt: number; this.Standings = []; // Empty the array this.MySchedule.forEach(element => { // If game has already been played if (element.PlayingDate < curDate && element.HomeScore>=0) { TeamAt = this.FindTeam(element.HomeTeam); if (TeamAt<0) { this.Standings.push( { TeamName: element.HomeTeam, GamesPlayed:0,Wins:0,Ties:0, GoalsFor:0,GoalsAgainst:0 } ) TeamAt = this.Standings.length-1; } this.UpdCurrentRow(element,TeamAt,"H"); TeamAt = this.FindTeam(element.AwayTeam); if (TeamAt<0) { this.Standings.push( { TeamName: element.AwayTeam, GamesPlayed:0,Wins:0,Ties:0, GoalsFor:0,GoalsAgainst:0 } ) TeamAt = this.Standings.length-1; } this.UpdCurrentRow(element,TeamAt,"A"); } }); |
The code reads through the schedule, and for any game in the past that has already been played (a HomeScore of -1 means not yet played), it adds the statistics about the game to the Standings collection for the matching team (using the UpdCurrentRow private method).
Once the collection is built, we use the TypeScript code to sort the collection, based on total points, and then GoalsFor.
Code Listing 131
// Sort standings this.Standings.sort((left, right): number => { if (left.Wins*3+left.Ties<right.Wins*3+right.Ties) return 1; if (left.Wins*3+left.Ties>right.Wins*3+right.Ties) return -1; // Else, then are tied, typically we'd add addition logic to break Ties if (left.GoalsFor<right.GoalsFor) return 1; if (left.GoalsFor>right.GoalsFor) return -1; // Finally, return zero if still tied. return 0; }) }; |
The sort function compares two objects (so we can access the properties) and returns 1 if the right side is higher, -1 if the left side is higher, and 0 for a tie.
Note: We could add additional tie-break rules if needed, particularly early in a soccer season when only a few games have been played.
There are a couple private routines used to support the Standings methods. The UpdCurrentRow updates the Standings collection based on the outcome of the selected game from the schedule.
Code Listing 132
private UpdCurrentRow(element:Schedule,TeamAt:number,HomeAway:string) { this.Standings[TeamAt].GamesPlayed ++; if (HomeAway=="H") { this.Standings[TeamAt].GoalsFor += element.HomeScore; this.Standings[TeamAt].GoalsAgainst += element.AwayScore; // Win if (element.HomeScore>element.AwayScore) { this.Standings[TeamAt].Wins++; } } if (HomeAway=="A") { this.Standings[TeamAt].GoalsFor += element.AwayScore; this.Standings[TeamAt].GoalsAgainst += element.HomeScore; if (element.AwayScore>element.HomeScore) { this.Standings[TeamAt].Wins++; } } if (element.HomeScore==element.AwayScore) { this.Standings[TeamAt].Ties++; } } |
The FindTeam method searches the Standings collection for the team by name.
Code Listing 133
// Find the team or -1 private FindTeam(TeamName:string) : number { var FoundAt: number = -1; for (var _x=0;_x < this.Standings.length;_x++) { if (this.Standings[_x].TeamName==TeamName) { return _x; } } return FoundAt; } |
When our component is completed and run, the following screen display appears.

Figure 17: Standings Page
This chapter illustrates how to combine a service (to provide schedule data) with a component to compute and display rankings.