More Components and Custom Validators Check code at: https://github.com/wghglory/angular2-fundamental
We can vote for sessions whatever user likes.
Voter function 1) events/event-detail/session-list.component.html add this:
1 2 3 4 5 <div class ="col-md-1" > <div *ngIf ="auth.isAuthenticated()" > <upvote (vote )="toggleVote(session)" [count ]="session.voters.length" [voted ]="userHasVoted(session)" > </upvote > </div > </div >
2) events/event-detail/session-list.component.ts
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 53 54 55 56 57 58 59 60 61 62 import { Component, Input, OnChanges } from '@angular/core' import { ISession } from '../shared/index' + import { AuthService } from '../../user/auth.service' + import { VoterService } from './voter.service' @Component({ selector: 'session-list' , templateUrl: 'app/events/event-detail/session-list.component.html' }) export class SessionListComponent implements OnChanges { @Input() sessions: ISession[] @Input() filterBy: string visibleSessions: ISession[] = []; @Input() sortBy: string + constructor (private auth: AuthService, private voterService: VoterService) { } ngOnChanges() { if (this .sessions) { this .filterSessions(this .filterBy); this .sortBy === 'name' ? this .visibleSessions.sort(sortByNameAsc) : this .visibleSessions.sort(sortByVotesDesc) } } filterSessions(filter) { if (filter === 'all' ) { this .visibleSessions = this .sessions.slice(0 ); } else { this .visibleSessions = this .sessions.filter(s => { return s.level.toLocaleLowerCase() == filter; }) } } + toggleVote(session: ISession) { + if (this .userHasVoted(session)) { + this .voterService.deleteVoter(session, this .auth.currentUser.userName); + } else { + this .voterService.addVoter(session, this .auth.currentUser.userName); + } + if (this .sortBy === 'votes' ) { + this .visibleSessions.sort(sortByVotesDesc); + } + } + userHasVoted(session: ISession) { + return this .voterService.userHasVoted(session, this .auth.currentUser.userName); + } } function sortByNameAsc (s1: ISession, s2: ISession ) { if (s1.name > s2.name) return 1 ; else if (s1.name === s2.name) return 0 ; else return -1 ; } function sortByVotesDesc (s1: ISession, s2: ISession ) { return (s2.voters.length - s1.voters.length); }
3) add events/event-detail/upvote.component.ts, register in barrel and app.module.ts
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 import { Component, EventEmitter, Input, Output } from '@angular/core' @Component({ selector: 'upvote' , template: ` <div class="votingWidgetContainer pointable" (click)="onClick()"> <div class="well votingWidget"> <div class="votingButton"> <i *ngIf="voted" class="glyphicon glyphicon-heart"></i> <i *ngIf="!voted" class="glyphicon glyphicon-heart-empty"></i> </div> <div class="badge badge-inverse votingCount"> <div>{{count}}</div> </div> </div> </div>` , styleUrls: ['/app/events/event-detail/upvote.component.css' ] }) export class UpvoteComponent { @Input() count: number; @Input() voted: boolean; @Output() vote = new EventEmitter(); onClick() { this .vote.emit({}); } }
4) voter.service.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import { Injectable } from '@angular/core' import { ISession } from '../shared/event.model' @Injectable() export class VoterService { deleteVoter(session: ISession, voterName : string) { session.voters = session.voters.filter(v => v !== voterName); } addVoter(session: ISession, voterName : string) { session.voters.push(voterName); } userHasVoted(session: ISession, voterName : string) { return session.voters.some(v => v === voterName); } }
Update feature: voted red color, unvoted white color Using @Input Setters
upvote.component.ts:
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 import { Component, EventEmitter, Input, Output } from '@angular/core' @Component({ selector: 'upvote' , template: ` <div class="votingWidgetContainer pointable" (click)="onClick()"> <div class="well votingWidget"> <div class="votingButton"> + <i [style.color]="iconColor" class="glyphicon glyphicon-heart"></i> - <!-- - <i *ngIf="voted" class="glyphicon glyphicon-heart"></i> - <i *ngIf="!voted" class="glyphicon glyphicon-heart-empty"></i> - --> </div> <div class="badge badge-inverse votingCount"> <div>{{count}}</div> </div> </div> </div>` , styleUrls: ['/app/events/event-detail/upvote.component.css' ] }) export class UpvoteComponent { @Input() count: number; + iconColor: string; + @Input() set voted(val) { + this .iconColor = val ? 'red' : 'white' ; + } @Output() vote = new EventEmitter(); onClick() { this .vote.emit({}); } }
Creating a Complex Custom Validator when user creates a new event, we want to make sure all 3 location fields or onlineUrl are typed.
location-validator.directive.ts, register it in barrel and app.module.ts
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 import { Directive } from '@angular/core' import { Validator, FormGroup, NG_VALIDATORS } from '@angular/forms' @Directive({ selector: '[validateLocation]' , providers: [{ provide : NG_VALIDATORS, useExisting : LocationValidator, multi : true }] }) export class LocationValidator implements Validator { validate(formGroup: FormGroup): { [key: string]: any } { let addressControl = formGroup.controls['address' ]; let cityControl = formGroup.controls['city' ]; let countryControl = formGroup.controls['country' ]; let onlineUrlControl = (<FormGroup > formGroup.root).controls['onlineUrl']; console.log(addressControl && addressControl.value) if ((addressControl && addressControl.value && cityControl && cityControl.value && countryControl && countryControl.value) || (onlineUrlControl && onlineUrlControl.value)) { return null; } else { return { validateLocation: false } } } }
create-event.component.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <form #newEventForm ="ngForm" (ngSubmit )="saveEvent(newEventForm.value)" autocomplete ="off" novalidate > + <div ngModelGroup ="location" validateLocation #locationGroup ="ngModelGroup" > <div class ="form-group" > <label for ="address" > Event Location:</label > + <em *ngIf ="locationGroup?.invalid && locationGroup?.touched" > You must enter either flll location or online url</em > <input [(ngModel )]="address" name ="address" id ="address" type ="text" class ="form-control" placeholder ="Address of event..." /> </div > <div class ="row" > <div class ="col-md-6" > <input [(ngModel )]="city" name ="city" id ="city" type ="text" class ="form-control" placeholder ="City..." /> </div > <div class ="col-md-6" > <input [(ngModel )]="country" name ="country" id ="country" type ="text" class ="form-control" placeholder ="Country..." /> </div > </div > </div > <div class ="form-group" > <label for ="onlineUrl" > Online Url:</label > + <input (change )="locationGroup.control.controls.address.updateValueAndValidity()" [(ngModel )]="onlineUrl" name ="onlineUrl" id ="onlineUrl" type ="text" class ="form-control" placeholder ="Online Url..." /> </div > </form >