Create Directives and Advanced Component Check code at: https://github.com/wghglory/angular2-fundamental
Implementing the Session Search Todo: implement logic for search session textbox, click search, modal window pops up showing sessions
nav.component.html
1 2 3 4 5 6 + <form (ngSubmit )="searchSessions(searchTerm)" id ="searchForm" class ="navbar-form navbar-right" > <div class ="form-group" > + <input [(ngModel )]="searchTerm" name ="searchTerm" type ="text" class ="form-control" placeholder ="Search Sessions" > </div > <button class ="btn btn-default" > Search</button > </form >
event.service.ts add searchSessions function
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 searchSessions(searchTerm: string) { var term = searchTerm.toLocaleLowerCase(); var result: ISession[] = []; EVENTS.forEach(e => { var matchingSessions = e.sessions.filter(s => { return s.name.toLocaleLowerCase().indexOf(term) > -1 ; }); matchingSessions = matchingSessions.map((session: any ) => { session.eventId = e.id; return session; }); result.push(...matchingSessions); }); var emitter = new EventEmitter(true ); setTimeout(() => { emitter.emit(result); }, 100 ); return emitter; }
nav.component.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import {ISession} from '../events/shared/event.model' import {EventService} from '../events/shared/event.service' export class NavBarComponent {+ constructor (private authService: AuthService, private eventService: EventService) { } + searchTerm: string = "" ; + foundSessions: ISession[]; + searchSessions(searchTerm) { + this .eventService.searchSessions(searchTerm) + .subscribe(sessions => { + this .foundSessions = sessions; + console .log(this .foundSessions) + }); + } }
Now if you search “pipe”, there should be one matching session in your console.
Adding jQuery create common/jQuery.service.ts and create common index barrel.
1 2 3 import { OpaqueToken } from '@angular/core' export let JQ_TOKEN = new OpaqueToken('jQuery' );
register in app.module.ts
1 2 3 4 5 6 7 8 9 10 11 import { TOASTR_TOKEN, Toastr, JQ_TOKEN, CollapsibleWellComponent } from './common/index' declare let toastr: Toastr; declare let jQuery: Object ; providers: [ { provide : JQ_TOKEN, useValue : jQuery }, ],
Creating a Modal Component
1) create common/simpleModal.component.ts. Add it to barrel and register in 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 29 30 import {Component, Input} from '@angular/core' @Component({ selector: 'simple-modal' , template: ` <div id="simple-modal" class="modal fade" tabindex="-1"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal"> <span>×</span> </button> <h4 class="modal-title">{{title}}</h4> </div> <div class="modal-body"> <ng-content></ng-content> </div> </div> </div> </div> ` , styles: [ ` .modal-body {height:250px;overflow-y:scroll;} ` ] }) export class SimpleModal { @Input() title: string }
2) nav.component.html add below at bottom
1 2 3 4 5 6 7 8 9 10 11 12 13 <form (ngSubmit )="searchSessions(searchTerm)" id ="searchForm" class ="navbar-form navbar-right" > <div class ="form-group" > <input [(ngModel )]="searchTerm" name ="searchTerm" type ="text" class ="form-control" placeholder ="Search Sessions" > </div > + <button class ="btn btn-default" modal-trigger > Search</button > </form > <simple-modal title ="Matching Sessions" > <div class ="list-group" > <a class ="list-group-item" *ngFor ="let session of foundSessions" [routerLink ]="['/events', session.eventId]" > {{session.name}}</a > </div > </simple-modal >
Creating Directives - The Trigger Directive I don’t want bind a click event on the search button and write the function with something like $(‘#id’).modal() in nav.component.ts because this will tightly bind modal openup functions with nav.component. Modal feature should be independent on the nav.component. We want to hide the concrete implements, a good way is directive!
create common/modalTrigger.directive.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import { Directive, OnInit, Inject, ElementRef } from '@angular/core' import {JQ_TOKEN} from './jQuery.service' @Directive({ selector: '[modal-trigger]' }) export class ModalTriggerDirective implements OnInit { private el: HTMLElement; constructor (ref: ElementRef, @Inject(JQ_TOKEN) private $: any) { this .el = ref.nativeElement; } ngOnInit() { this .el.addEventListener('click' , e => { this .$('#simple-modal' ).modal({}) }); } }
Add it to barrel and register Directive in app.module.ts’s declarations
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import { TOASTR_TOKEN, Toastr, JQ_TOKEN, CollapsibleWellComponent, + SimpleModal, ModalTriggerDirective } from './common/index' declare let toastr: Toastr; declare let jQuery: Object ; @NgModule({ declarations: [ CollapsibleWellComponent, + SimpleModal, ModalTriggerDirective, DurationPipe ]})
Binding an ID issue: we may have many modal windows for different purpose, so we need to pass id dynamically
current common/modalTrigger.directive.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import { Directive, OnInit, Inject, ElementRef } from '@angular/core' import {JQ_TOKEN} from './jQuery.service' @Directive({ selector: '[modal-trigger]' }) export class ModalTriggerDirective implements OnInit { private el: HTMLElement; constructor (ref: ElementRef, @Inject(JQ_TOKEN) private $: any) { this .el = ref.nativeElement; } ngOnInit() { this .el.addEventListener('click' , e => { this .$('#simple-modal' ).modal({}) }); } }
So we add elementId="searchResult"
in nav.component. Note modal-trigger value and elementId value are same! So I can tell which button triggers which modal.
1 2 3 4 5 6 7 8 + <button class ="btn btn-default" modal-trigger ="searchResult" > Search</button > + <simple-modal title ="Matching Sessions" elementId ="searchResult" > <div class ="list-group" > <a class ="list-group-item" *ngFor ="let session of foundSessions" [routerLink ]="['/events', session.eventId]" > {{session.name}}</a > </div > </simple-modal >
SimpleModal.component.ts, we pass the elementId:
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 import {Component, Input} from '@angular/core' @Component({ selector: 'simple-modal' , template: ` + <div id="{{elementId}}" class="modal fade" tabindex="-1"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal"> <span>×</span> </button> <h4 class="modal-title">{{title}}</h4> </div> <div class="modal-body"> <ng-content></ng-content> </div> </div> </div> </div> ` , styles: [ ` .modal-body {height:250px;overflow-y:scroll;} ` ] }) export class SimpleModal { @Input() title: string; + @Input() elementId: string; }
ModalTriggerDirective
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import { Directive, OnInit, Inject, ElementRef, Input } from '@angular/core' import {JQ_TOKEN} from './jQuery.service' @Directive({ selector: '[modal-trigger]' }) export class ModalTriggerDirective implements OnInit { private el: HTMLElement; + @Input('modal-trigger' ) modalId: string; constructor (ref: ElementRef, @Inject(JQ_TOKEN) private $: any) { this .el = ref.nativeElement; } ngOnInit() { this .el.addEventListener('click' , e => { + this .$('#${this.modalId}' ).modal({}); }); } }
Routing to the Same Component Current event-detail page has a bug: when clicking any item in modal window, the url does change, but page doesn’t navigate to the right url…
event-detail.Component.ts
snapshot.params is good for the first time to load the page. We need use observable
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 + import {ActivatedRoute, Params} from '@angular/router' @Component({ templateUrl: '/app/events/event-detail/event-detail.component.html' , styles: [` .container{padding:0 20px;} .event-img{height:100px;} a {cursor:pointer;} ` ]}) export class EventDetailComponent implements OnInit { constructor (private eventService: EventService, private route: ActivatedRoute) { } event: IEvent addMode: boolean = false filterBy: string = 'all' ; sortBy: string = 'votes' ; ngOnInit() { - - + + this .route.params.forEach((params: Params ) => { + this .event = this .eventService.getEvent(+params['id' ]); + this .addMode = false ; + this .filterBy = 'all' ; + this .sortBy = 'votes' ; + }); } }
Using the @ViewChild Decorator We want to close the modal when clicking any item
simpleModal.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 + import {Component, Input, ViewChild, ElementRef, Inject} from '@angular/core' + import {JQ_TOKEN} from './jQuery.service' @Component({ selector: 'simple-modal' , template: ` + <div id="{{elementId}}" #modalcontainer class="modal fade" tabindex="-1"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal"> <span>×</span> </button> <h4 class="modal-title">{{title}}</h4> </div> <div class="modal-body" (click)="closeModal()"> <ng-content></ng-content> </div> </div> </div> </div> ` , styles: [ ` .modal-body {height:250px;overflow-y:scroll;} ` ] }) export class SimpleModal { @Input() title: string; @Input() elementId: string; + @ViewChild('modalcontainer' ) containerEl: ElementRef; + constructor ( @Inject(JQ_TOKEN) private $: any) { } + closeModal() { + this .$(this .containerEl.nativeElement).modal('hide' ); + } }
Creating Settings on Components For some modal windows, we may want to close modal when clicking body while others don’t. So we create settings for this purpose.
nav.component.html add closeOnBodyClick="true"
1 2 3 4 5 + <simple-modal closeOnBodyClick ="true" title ="Matching Sessions" elementId ="searchResult" > <div class ="list-group" > <a class ="list-group-item" *ngFor ="let session of foundSessions" [routerLink ]="['/events', session.eventId]" > {{session.name}}</a > </div > </simple-modal >
SimpleModal.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 import {Component, Input, ViewChild, ElementRef, Inject} from '@angular/core' import {JQ_TOKEN} from './jQuery.service' @Component({ selector: 'simple-modal' , template: ` <div id="{{elementId}}" #modalcontainer class="modal fade" tabindex="-1"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal"> <span>×</span> </button> <h4 class="modal-title">{{title}}</h4> </div> <div class="modal-body" (click)="closeModal()"> <ng-content></ng-content> </div> </div> </div> </div> ` , styles: [ ` .modal-body {height:250px;overflow-y:scroll;} ` ] }) export class SimpleModal { @Input() title: string; @Input() elementId: string; @ViewChild('modalcontainer' ) containerEl: ElementRef; + @Input() closeOnBodyClick: string; constructor ( @Inject(JQ_TOKEN) private $: any) { } closeModal() { + if (this .closeOnBodyClick.toLocaleLowerCase() === 'true' ) { this .$(this .containerEl.nativeElement).modal('hide' ); + } } }