|
@@ -384,7 +384,7 @@ Modify `beer.html` to
|
384
|
384
|
</ion-content>
|
385
|
385
|
```
|
386
|
386
|
|
387
|
|
-Modify `beer.ts` to be
|
|
387
|
+Modify `beer.ts` to import `BeerService` and add as a provider. Call the `getGoodBeers()` method in the `ionViewDidLoad()` lifecycle method.
|
388
|
388
|
|
389
|
389
|
```typescript
|
390
|
390
|
import { Component } from '@angular/core';
|
|
@@ -431,7 +431,6 @@ export class TabsPage {
|
431
|
431
|
tab4Root: any = AboutPage;
|
432
|
432
|
|
433
|
433
|
constructor() {
|
434
|
|
-
|
435
|
434
|
}
|
436
|
435
|
}
|
437
|
436
|
```
|
|
@@ -505,7 +504,6 @@ export class BeerPage {
|
505
|
504
|
}
|
506
|
505
|
})
|
507
|
506
|
}
|
508
|
|
- ...
|
509
|
507
|
}
|
510
|
508
|
```
|
511
|
509
|
|
|
@@ -526,6 +524,239 @@ If everything works as expected, you should see a page similar to the one below
|
526
|
524
|
<img src="./static/good-beers-ui.png" width="600" alt="Good Beers UI">
|
527
|
525
|
</p>
|
528
|
526
|
|
|
527
|
+### Add a Modal for Editing
|
|
528
|
+
|
|
529
|
+Change the header in `beer.html` to have a button that opens a modal to add a new beer.
|
|
530
|
+
|
|
531
|
+```html
|
|
532
|
+<ion-header>
|
|
533
|
+ <ion-navbar>
|
|
534
|
+ <ion-title>Good Beers</ion-title>
|
|
535
|
+ <ion-buttons end>
|
|
536
|
+ <button ion-button icon-only (click)="openModal()" color="primary">
|
|
537
|
+ <ion-icon name="add-circle"></ion-icon>
|
|
538
|
+ <ion-icon name="beer"></ion-icon>
|
|
539
|
+ </button>
|
|
540
|
+ </ion-buttons>
|
|
541
|
+ </ion-navbar>
|
|
542
|
+```
|
|
543
|
+
|
|
544
|
+In this same file, change `<ion-item>` to have a click handler for opening the modal for the current item.
|
|
545
|
+
|
|
546
|
+```html
|
|
547
|
+<ion-item (click)="openModal({id: beer.id})">
|
|
548
|
+```
|
|
549
|
+
|
|
550
|
+Add `ModalController` as a dependency in `BeerPage` and add an `openModal()` method.
|
|
551
|
+
|
|
552
|
+```typescript
|
|
553
|
+export class BeerPage {
|
|
554
|
+ private beers: Array<any>;
|
|
555
|
+
|
|
556
|
+ constructor(public beerService: BeerService, public giphyService: GiphyService,
|
|
557
|
+ public modalCtrl: ModalController) {
|
|
558
|
+ }
|
|
559
|
+
|
|
560
|
+ // ionViewDidLoad method
|
|
561
|
+
|
|
562
|
+ openModal(beerId) {
|
|
563
|
+ let modal = this.modalCtrl.create(BeerModalPage, beerId);
|
|
564
|
+ modal.present();
|
|
565
|
+ // refresh data after modal dismissed
|
|
566
|
+ modal.onDidDismiss(() => this.ionViewDidLoad())
|
|
567
|
+ }
|
|
568
|
+}
|
|
569
|
+```
|
|
570
|
+
|
|
571
|
+This won't compile because `BeerModalPage` doesn't exist. Create `beer-modal.ts` in the same directory. This page will retrieve the beer from the `beerId` that's passed in. It will render the name, allow it to be edited, and show the Giphy image found for the name.
|
|
572
|
+
|
|
573
|
+```typescript
|
|
574
|
+import { BeerService } from '../../providers/beer-service';
|
|
575
|
+import { Component, ViewChild } from '@angular/core';
|
|
576
|
+import { GiphyService } from '../../providers/giphy-service';
|
|
577
|
+import { NavParams, ViewController, ToastController, NavController } from 'ionic-angular';
|
|
578
|
+import { NgForm } from '@angular/forms';
|
|
579
|
+
|
|
580
|
+@Component({
|
|
581
|
+ templateUrl: './beer-modal.html'
|
|
582
|
+})
|
|
583
|
+export class BeerModalPage {
|
|
584
|
+ @ViewChild('name') name;
|
|
585
|
+ beer: any = {};
|
|
586
|
+ error: any;
|
|
587
|
+
|
|
588
|
+ constructor(public beerService: BeerService,
|
|
589
|
+ public giphyService: GiphyService,
|
|
590
|
+ public params: NavParams,
|
|
591
|
+ public viewCtrl: ViewController,
|
|
592
|
+ public toastCtrl: ToastController,
|
|
593
|
+ public navCtrl: NavController) {
|
|
594
|
+ if (this.params.data.id) {
|
|
595
|
+ this.beerService.get(this.params.get('id')).subscribe(beer => {
|
|
596
|
+ this.beer = beer;
|
|
597
|
+ this.beer.href = beer._links.self.href;
|
|
598
|
+ this.giphyService.get(beer.name).subscribe(url => beer.giphyUrl = url);
|
|
599
|
+ });
|
|
600
|
+ }
|
|
601
|
+ }
|
|
602
|
+
|
|
603
|
+ dismiss() {
|
|
604
|
+ this.viewCtrl.dismiss();
|
|
605
|
+ }
|
|
606
|
+
|
|
607
|
+ save(form: NgForm) {
|
|
608
|
+ let update: boolean = form['href'];
|
|
609
|
+ this.beerService.save(form).subscribe(result => {
|
|
610
|
+ let toast = this.toastCtrl.create({
|
|
611
|
+ message: 'Beer "' + form.name + '" ' + ((update) ? 'updated' : 'added') + '.',
|
|
612
|
+ duration: 2000
|
|
613
|
+ });
|
|
614
|
+ toast.present();
|
|
615
|
+ this.dismiss();
|
|
616
|
+ }, error => this.error = error)
|
|
617
|
+ }
|
|
618
|
+
|
|
619
|
+ ionViewDidLoad() {
|
|
620
|
+ setTimeout(() => {
|
|
621
|
+ this.name.setFocus();
|
|
622
|
+ },150);
|
|
623
|
+ }
|
|
624
|
+}
|
|
625
|
+```
|
|
626
|
+
|
|
627
|
+Create `beer-modal.html` as a template for this page.
|
|
628
|
+
|
|
629
|
+```html
|
|
630
|
+<ion-header>
|
|
631
|
+ <ion-toolbar>
|
|
632
|
+ <ion-title>
|
|
633
|
+ {{beer ? 'Beer Details' : 'Add Beer'}}
|
|
634
|
+ </ion-title>
|
|
635
|
+ <ion-buttons start>
|
|
636
|
+ <button ion-button (click)="dismiss()">
|
|
637
|
+ <span ion-text color="primary" showWhen="ios,core">Cancel</span>
|
|
638
|
+ <ion-icon name="md-close" showWhen="android,windows"></ion-icon>
|
|
639
|
+ </button>
|
|
640
|
+ </ion-buttons>
|
|
641
|
+ </ion-toolbar>
|
|
642
|
+</ion-header>
|
|
643
|
+<ion-content padding>
|
|
644
|
+ <form #beerForm="ngForm" (ngSubmit)="save(beerForm.value)">
|
|
645
|
+ <input type="hidden" name="href" [(ngModel)]="beer.href">
|
|
646
|
+ <ion-row>
|
|
647
|
+ <ion-col>
|
|
648
|
+ <ion-list inset>
|
|
649
|
+ <ion-item>
|
|
650
|
+ <ion-input placeholder="Beer Name" name="name" type="text"
|
|
651
|
+ required [(ngModel)]="beer.name" #name></ion-input>
|
|
652
|
+ </ion-item>
|
|
653
|
+ </ion-list>
|
|
654
|
+ </ion-col>
|
|
655
|
+ </ion-row>
|
|
656
|
+ <ion-row>
|
|
657
|
+ <ion-col *ngIf="beer" text-center>
|
|
658
|
+ <img src="{{beer.giphyUrl}}">
|
|
659
|
+ </ion-col>
|
|
660
|
+ </ion-row>
|
|
661
|
+ <ion-row>
|
|
662
|
+ <ion-col>
|
|
663
|
+ <div *ngIf="error" class="alert alert-danger">{{error}}</div>
|
|
664
|
+ <button ion-button color="primary" full type="submit"
|
|
665
|
+ [disabled]="!beerForm.form.valid">Save</button>
|
|
666
|
+ </ion-col>
|
|
667
|
+ </ion-row>
|
|
668
|
+ </form>
|
|
669
|
+</ion-content>
|
|
670
|
+```
|
|
671
|
+
|
|
672
|
+Add `BeerModalPage` to the `declarations` and `entryComponent` lists in `app.module.ts`.
|
|
673
|
+
|
|
674
|
+You'll also need to modify `beer-service.ts` to have `get()` and `save()` methods.
|
|
675
|
+
|
|
676
|
+```typescript
|
|
677
|
+get(id: string) {
|
|
678
|
+ return this.http.get(this.BEER_API + '/' + id)
|
|
679
|
+ .map((response: Response) => response.json());
|
|
680
|
+}
|
|
681
|
+
|
|
682
|
+save(beer: any): Observable<any> {
|
|
683
|
+ let result: Observable<Response>;
|
|
684
|
+ if (beer['href']) {
|
|
685
|
+ result = this.http.put(beer.href, beer);
|
|
686
|
+ } else {
|
|
687
|
+ result = this.http.post(this.BEER_API, beer)
|
|
688
|
+ }
|
|
689
|
+ return result.map((response: Response) => response.json())
|
|
690
|
+ .catch(error => Observable.throw(error));
|
|
691
|
+}
|
|
692
|
+```
|
|
693
|
+
|
|
694
|
+### Add Swipe to Delete
|
|
695
|
+
|
|
696
|
+To add swipe-to-delete functionality on the list of beers, open `beer.html` and make it so `<ion-item-sliding>` wraps `<ion-item>` and contains the `*ngFor`. Add a delete button using `<ion-item-options>`.
|
|
697
|
+
|
|
698
|
+```html
|
|
699
|
+<ion-content padding>
|
|
700
|
+ <ion-list>
|
|
701
|
+ <ion-item-sliding *ngFor="let beer of beers" >
|
|
702
|
+ <ion-item (click)="openModal({id: beer.id})">
|
|
703
|
+ <ion-avatar item-left>
|
|
704
|
+ <img src="{{beer.giphyUrl}}">
|
|
705
|
+ </ion-avatar>
|
|
706
|
+ <h2>{{beer.name}}</h2>
|
|
707
|
+ </ion-item>
|
|
708
|
+ <ion-item-options>
|
|
709
|
+ <button ion-button color="danger" (click)="remove(beer)"><ion-icon name="trash"></ion-icon> Delete</button>
|
|
710
|
+ </ion-item-options>
|
|
711
|
+ </ion-item-sliding>
|
|
712
|
+ </ion-list>
|
|
713
|
+</ion-content>
|
|
714
|
+```
|
|
715
|
+
|
|
716
|
+Add a `remove()` method to `beer.ts`.
|
|
717
|
+
|
|
718
|
+```typescript
|
|
719
|
+remove(beer) {
|
|
720
|
+ this.beerService.remove(beer.id).subscribe(response => {
|
|
721
|
+ for (let i = 0; i < this.beers.length; i++) {
|
|
722
|
+ if (this.beers[i] === beer) {
|
|
723
|
+ this.beers.splice(i, 1);
|
|
724
|
+ let toast = this.toastCtrl.create({
|
|
725
|
+ message: 'Beer "' + beer.name + '" deleted.',
|
|
726
|
+ duration: 2000,
|
|
727
|
+ position: 'top'
|
|
728
|
+ });
|
|
729
|
+ toast.present();
|
|
730
|
+ }
|
|
731
|
+ }
|
|
732
|
+ });
|
|
733
|
+}
|
|
734
|
+```
|
|
735
|
+
|
|
736
|
+Add `toastCtrl` as a dependency in the constructor so everything compiles.
|
|
737
|
+
|
|
738
|
+```typescript
|
|
739
|
+constructor(public beerService: BeerService, public giphyService: GiphyService,
|
|
740
|
+ public modalCtrl: ModalController, public toastCtrl: ToastController) {
|
|
741
|
+}
|
|
742
|
+```
|
|
743
|
+
|
|
744
|
+You'll also need to modify `beer-service.ts` to have a `remove()` method.
|
|
745
|
+
|
|
746
|
+```typescript
|
|
747
|
+remove(id: string) {
|
|
748
|
+ return this.http.delete(this.BEER_API + '/' + id)
|
|
749
|
+ .map((response: Response) => response.json());
|
|
750
|
+}
|
|
751
|
+```
|
|
752
|
+
|
|
753
|
+After making these additions, you should be able to add, edit and delete beers.
|
|
754
|
+
|
|
755
|
+<p align="center">
|
|
756
|
+<img src="./static/beer-modal.png" width="350">
|
|
757
|
+<img src="./static/beer-delete.png" width="350">
|
|
758
|
+</div>
|
|
759
|
+
|
529
|
760
|
## PWAs with Ionic
|
530
|
761
|
|
531
|
762
|
Ionic 2 ships with support for creating progressive web apps (PWAs). If you’d like to learn more about what PWAs are, see [Navigating the World of Progressive Web Apps with Ionic 2](http://blog.ionic.io/navigating-the-world-of-progressive-web-apps-with-ionic-2/).
|