Browse Source

Add Ionic App with Spring Boot API

Matt Raible 7 years ago
parent
commit
0a0f154bf1
100 changed files with 464 additions and 3571 deletions
  1. 2
    7
      README.md
  2. 246
    324
      TUTORIAL.md
  3. 0
    40
      client/config.xml
  4. 0
    29
      client/src/app/app.component.ts
  5. 0
    56
      client/src/app/app.module.ts
  6. BIN
      client/src/assets/imgs/logo.png
  7. BIN
      client/src/assets/imgs/stormpath-sdks.jpg
  8. 0
    18
      client/src/pages/about/about.html
  9. 0
    20
      client/src/pages/home/home.ts
  10. 0
    2971
      client/yarn.lock
  11. 3
    9
      deploy.sh
  12. 0
    0
      ionic-beer/.editorconfig
  13. 0
    0
      ionic-beer/.gitignore
  14. 39
    0
      ionic-beer/config.xml
  15. 0
    0
      ionic-beer/ionic.config.json
  16. 18
    18
      ionic-beer/package.json
  17. BIN
      ionic-beer/resources/android/icon/drawable-hdpi-icon.png
  18. BIN
      ionic-beer/resources/android/icon/drawable-ldpi-icon.png
  19. BIN
      ionic-beer/resources/android/icon/drawable-mdpi-icon.png
  20. BIN
      ionic-beer/resources/android/icon/drawable-xhdpi-icon.png
  21. BIN
      ionic-beer/resources/android/icon/drawable-xxhdpi-icon.png
  22. BIN
      ionic-beer/resources/android/icon/drawable-xxxhdpi-icon.png
  23. 0
    0
      ionic-beer/resources/android/splash/drawable-land-hdpi-screen.png
  24. 0
    0
      ionic-beer/resources/android/splash/drawable-land-ldpi-screen.png
  25. 0
    0
      ionic-beer/resources/android/splash/drawable-land-mdpi-screen.png
  26. 0
    0
      ionic-beer/resources/android/splash/drawable-land-xhdpi-screen.png
  27. 0
    0
      ionic-beer/resources/android/splash/drawable-land-xxhdpi-screen.png
  28. 0
    0
      ionic-beer/resources/android/splash/drawable-land-xxxhdpi-screen.png
  29. 0
    0
      ionic-beer/resources/android/splash/drawable-port-hdpi-screen.png
  30. 0
    0
      ionic-beer/resources/android/splash/drawable-port-ldpi-screen.png
  31. 0
    0
      ionic-beer/resources/android/splash/drawable-port-mdpi-screen.png
  32. 0
    0
      ionic-beer/resources/android/splash/drawable-port-xhdpi-screen.png
  33. 0
    0
      ionic-beer/resources/android/splash/drawable-port-xxhdpi-screen.png
  34. 0
    0
      ionic-beer/resources/android/splash/drawable-port-xxxhdpi-screen.png
  35. 0
    0
      ionic-beer/resources/icon.png
  36. BIN
      ionic-beer/resources/ios/icon/icon-40.png
  37. BIN
      ionic-beer/resources/ios/icon/icon-40@2x.png
  38. BIN
      ionic-beer/resources/ios/icon/icon-40@3x.png
  39. BIN
      ionic-beer/resources/ios/icon/icon-50.png
  40. BIN
      ionic-beer/resources/ios/icon/icon-50@2x.png
  41. BIN
      ionic-beer/resources/ios/icon/icon-60.png
  42. BIN
      ionic-beer/resources/ios/icon/icon-60@2x.png
  43. BIN
      ionic-beer/resources/ios/icon/icon-60@3x.png
  44. BIN
      ionic-beer/resources/ios/icon/icon-72.png
  45. BIN
      ionic-beer/resources/ios/icon/icon-72@2x.png
  46. BIN
      ionic-beer/resources/ios/icon/icon-76.png
  47. BIN
      ionic-beer/resources/ios/icon/icon-76@2x.png
  48. BIN
      ionic-beer/resources/ios/icon/icon-83.5@2x.png
  49. BIN
      ionic-beer/resources/ios/icon/icon-small.png
  50. BIN
      ionic-beer/resources/ios/icon/icon-small@2x.png
  51. BIN
      ionic-beer/resources/ios/icon/icon-small@3x.png
  52. BIN
      ionic-beer/resources/ios/icon/icon.png
  53. BIN
      ionic-beer/resources/ios/icon/icon@2x.png
  54. 0
    0
      ionic-beer/resources/ios/splash/Default-568h@2x~iphone.png
  55. 0
    0
      ionic-beer/resources/ios/splash/Default-667h.png
  56. 0
    0
      ionic-beer/resources/ios/splash/Default-736h.png
  57. 0
    0
      ionic-beer/resources/ios/splash/Default-Landscape-736h.png
  58. 0
    0
      ionic-beer/resources/ios/splash/Default-Landscape@2x~ipad.png
  59. 0
    0
      ionic-beer/resources/ios/splash/Default-Landscape~ipad.png
  60. 0
    0
      ionic-beer/resources/ios/splash/Default-Portrait@2x~ipad.png
  61. 0
    0
      ionic-beer/resources/ios/splash/Default-Portrait~ipad.png
  62. 0
    0
      ionic-beer/resources/ios/splash/Default@2x~iphone.png
  63. 0
    0
      ionic-beer/resources/ios/splash/Default~iphone.png
  64. 0
    0
      ionic-beer/resources/splash.png
  65. 22
    0
      ionic-beer/src/app/app.component.ts
  66. 0
    0
      ionic-beer/src/app/app.html
  67. 46
    0
      ionic-beer/src/app/app.module.ts
  68. 0
    18
      ionic-beer/src/app/app.scss
  69. 0
    0
      ionic-beer/src/app/main.ts
  70. BIN
      ionic-beer/src/assets/icon/favicon.ico
  71. BIN
      ionic-beer/src/assets/imgs/logo.png
  72. 0
    0
      ionic-beer/src/declarations.d.ts
  73. 3
    3
      ionic-beer/src/index.html
  74. 2
    2
      ionic-beer/src/manifest.json
  75. 11
    0
      ionic-beer/src/pages/about/about.html
  76. 0
    0
      ionic-beer/src/pages/about/about.scss
  77. 0
    1
      ionic-beer/src/pages/about/about.ts
  78. 0
    0
      ionic-beer/src/pages/beer/beer-modal.html
  79. 0
    0
      ionic-beer/src/pages/beer/beer-modal.ts
  80. 1
    1
      ionic-beer/src/pages/beer/beer.html
  81. 25
    0
      ionic-beer/src/pages/beer/beer.module.ts
  82. 0
    0
      ionic-beer/src/pages/beer/beer.scss
  83. 7
    6
      ionic-beer/src/pages/beer/beer.ts
  84. 1
    5
      ionic-beer/src/pages/contact/contact.html
  85. 0
    0
      ionic-beer/src/pages/contact/contact.scss
  86. 0
    1
      ionic-beer/src/pages/contact/contact.ts
  87. 0
    8
      ionic-beer/src/pages/home/home.html
  88. 0
    0
      ionic-beer/src/pages/home/home.scss
  89. 14
    0
      ionic-beer/src/pages/home/home.ts
  90. 0
    0
      ionic-beer/src/pages/tabs/tabs.html
  91. 2
    6
      ionic-beer/src/pages/tabs/tabs.ts
  92. 8
    12
      ionic-beer/src/providers/beer-service.ts
  93. 1
    0
      ionic-beer/src/providers/giphy-service.ts
  94. 0
    0
      ionic-beer/src/service-worker.js
  95. 1
    1
      ionic-beer/src/theme/variables.scss
  96. 0
    0
      ionic-beer/tsconfig.json
  97. 0
    0
      ionic-beer/tslint.json
  98. 1
    1
      server/.mvn/wrapper/maven-wrapper.properties
  99. 11
    14
      server/mvnw
  100. 0
    0
      server/mvnw.cmd

+ 2
- 7
README.md View File

1
-# Stormpath is Joining Okta
2
-We are incredibly excited to announce that [Stormpath is joining forces with Okta](https://stormpath.com/blog/stormpaths-new-path?utm_source=github&utm_medium=readme&utm-campaign=okta-announcement). Please visit [the Migration FAQs](https://stormpath.com/oktaplusstormpath?utm_source=github&utm_medium=readme&utm-campaign=okta-announcement) for a detailed look at what this means for Stormpath users.
3
-
4
-We're available to answer all questions at [support@stormpath.com](mailto:support@stormpath.com).
5
-
6
-# Spring Boot, Ionic, and Stormpath 🍻
1
+# Spring Boot + Ionic 🍻
7
 
2
 
8
 This project is an example application for a typical [Ionic](https://ionicframework.com/) app with a [Spring Boot](https://projects.spring.io/spring-boot/) backend.
3
 This project is an example application for a typical [Ionic](https://ionicframework.com/) app with a [Spring Boot](https://projects.spring.io/spring-boot/) backend.
9
 
4
 
10
 You can read about how this application was created in [this tutorial](./TUTORIAL.md). Feel free to copy any code in this project for your own use in accordance with the [MIT license](LICENSE).
5
 You can read about how this application was created in [this tutorial](./TUTORIAL.md). Feel free to copy any code in this project for your own use in accordance with the [MIT license](LICENSE).
11
 
6
 
12
-**Prerequisites**: Java 8, Node.js, Maven, a [Stormpath Account](https://api.stormpath.com/register), and an `apiKey.properties` file in `~/stormpath/`.
7
+**Prerequisites**: Java 8, Node.js, and Maven.
13
 
8
 
14
 To run the Spring Boot backend, cd into `server` and run `mvn spring-boot:run`.
9
 To run the Spring Boot backend, cd into `server` and run `mvn spring-boot:run`.
15
 
10
 

+ 246
- 324
TUTORIAL.md View File

1
-# Spring Boot, Ionic, and Stormpath
1
+# Tutorial: Develop a Mobile App with Ionic and Spring Boot
2
 
2
 
3
-This tutorial shows how to build a secure Spring Boot API with Stormpath. It also shows how to build an Ionic app that securely connects to this API and can be deployed to a mobile device.
3
+Ionic 3.0 was [recently released](http://blog.ionic.io/ionic-3-0-has-arrived/), with support for 
4
+Angular 4, TypeScript 2.2, and lazy loading. Ionic, often called Ionic framework, is an open source
5
+(MIT-licensed) project that simplifies building native and progressive web apps. When developing an Ionic app, you'll use Angular and have access to native APIs via [Ionic Native](https://ionicframework.com/docs/native/) and [Apache Cordova](https://cordova.apache.org/). This means you can develop slick-looking UIs using the technologies you know and love: HTML, CSS, and JavaScript/TypeScript.
4
 
6
 
5
-**Prerequisites**: Java 8, Node.js, Maven, a [Stormpath Account](https://api.stormpath.com/register), and an `apiKey.properties` file in `~/stormpath/`.
7
+This tutorial shows how to build a Spring Boot API, how to build an Ionic app, and how to deploy it to a mobile device.
6
 
8
 
7
-## Spring Boot API
8
-
9
-Create your Spring Boot API project using [start.spring.io](https://start.spring.io).
10
-
11
-```
12
-http https://start.spring.io/starter.zip \
13
-dependencies==data-jpa,data-rest,h2,web,devtools,security,stormpath -d
14
-```
15
-
16
-Run the application with `./mvnw spring-boot:run`.
17
-
18
-Create a `Beer` entity class in `src/main/java/com/example/beer`.
9
+**Prerequisites**: [Java 8](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) and [Node.js](https://nodejs.org) installed.
19
 
10
 
20
-```java
21
-package com.example.beer;
22
-
23
-import javax.persistence.Entity;
24
-import javax.persistence.GeneratedValue;
25
-import javax.persistence.Id;
26
-
27
-@Entity
28
-public class Beer {
29
-
30
-    @Id
31
-    @GeneratedValue
32
-    private Long id;
33
-    private String name;
11
+## Create a Project
34
 
12
 
35
-    public Beer() {
36
-    }
37
-
38
-    public Beer(String name) {
39
-        this.name = name;
40
-    }
13
+To begin, create a directory on your hard drive called `spring-boot-ionic-example`. During this tutorial, you will create `server` and `ionic-beer` directories to hold the server and client applications, respectively.  
41
 
14
 
42
-    public Long getId() {
43
-        return id;
44
-    }
45
-
46
-    public void setId(Long id) {
47
-        this.id = id;
48
-    }
49
-
50
-    public String getName() {
51
-        return name;
52
-    }
15
+## Spring Boot API
53
 
16
 
54
-    public void setName(String name) {
55
-        this.name = name;
56
-    }
17
+I recently wrote about how to build a Spring Boot API in a [QuickStart Guide to Spring Boot with Angular]. Rather than covering that again, you can clone the existing project and copy the `server` directory into `spring-boot-ionic-example`.
57
 
18
 
58
-    @Override
59
-    public String toString() {
60
-        return "Beer{" +
61
-                "id=" + id +
62
-                ", name='" + name + '\'' +
63
-                '}';
64
-    }
65
-}
19
+```bash
20
+git clone https://github.com/oktadeveloper/spring-boot-angular-example.git
21
+cp -r spring-boot-angular-example/server ~/spring-boot-ionic-example/.
66
 ```
22
 ```
67
 
23
 
68
-Create a JPA Repository to manage the `Beer` entity.
69
-
70
-```java
71
-package com.example.beer;
72
-
73
-import org.springframework.data.jpa.repository.JpaRepository;
74
-import org.springframework.data.rest.core.annotation.RepositoryRestResource;
24
+This project contains a `beers` API that allows you to <abbr title="Create, Read, Update, and Delete">CRUD</abbr> a list of beer names. It also contains a `/good-beers` endpoint that filters out less-than-great beers.
75
 
25
 
76
-@RepositoryRestResource
77
-interface BeerRepository extends JpaRepository<Beer, Long> {
78
-}
79
-```
80
-
81
-Create a CommandLineRunner to populate the database.
26
+The default list of beers is created by a `BeerCommandLineRunner` class:
82
 
27
 
83
 ```java
28
 ```java
84
-package com.example.beer;
85
-
86
-import org.springframework.boot.CommandLineRunner;
87
-import org.springframework.stereotype.Component;
88
-
89
-import java.util.stream.Stream;
90
-
91
 @Component
29
 @Component
92
 class BeerCommandLineRunner implements CommandLineRunner {
30
 class BeerCommandLineRunner implements CommandLineRunner {
93
     private final BeerRepository repository;
31
     private final BeerRepository repository;
98
 
36
 
99
     @Override
37
     @Override
100
     public void run(String... strings) throws Exception {
38
     public void run(String... strings) throws Exception {
101
-        // top 5 beers from https://www.beeradvocate.com/lists/top/
102
-        Stream.of("Good Morning", "Kentucky Brunch Brand Stout", "ManBearPig", "King Julius",
103
-                "Very Hazy", "Budweiser", "Coors Light", "PBR").forEach(name ->
39
+        // Top beers from https://www.beeradvocate.com/lists/top/
40
+        Stream.of("Kentucky Brunch Brand Stout", "Good Morning", "Very Hazy", "King Julius",
41
+                "Budweiser", "Coors Light", "PBR").forEach(name ->
104
                 repository.save(new Beer(name))
42
                 repository.save(new Beer(name))
105
         );
43
         );
106
-        System.out.println(repository.findAll());
44
+        repository.findAll().forEach(System.out::println);
107
     }
45
     }
108
 }
46
 }
109
 ```
47
 ```
110
 
48
 
111
-Create a `BeerController` for your REST API. Add some business logic that results in a `/good-beers` endpoint.
49
+The `BeerRepository` interface is decorated with `@RepositoryRestResource` to expose CRUD endpoints for the `Beer` entity.
112
 
50
 
113
 ```java
51
 ```java
114
-package com.example.beer;
115
-
116
-import org.springframework.web.bind.annotation.GetMapping;
117
-import org.springframework.web.bind.annotation.RestController;
52
+@RepositoryRestResource
53
+interface BeerRepository extends JpaRepository<Beer, Long> {}
54
+```
118
 
55
 
119
-import java.util.Collection;
120
-import java.util.HashMap;
121
-import java.util.Map;
122
-import java.util.stream.Collectors;
56
+The last piece of the API is the `BeerController` that exposes `/good-beers` and specifies cross-origin resource sharing (CORS) settings.
123
 
57
 
58
+```java
124
 @RestController
59
 @RestController
125
 public class BeerController {
60
 public class BeerController {
126
     private BeerRepository repository;
61
     private BeerRepository repository;
130
     }
65
     }
131
 
66
 
132
     @GetMapping("/good-beers")
67
     @GetMapping("/good-beers")
68
+    @CrossOrigin(origins = "http://localhost:4200")
133
     public Collection<Map<String, String>> goodBeers() {
69
     public Collection<Map<String, String>> goodBeers() {
134
 
70
 
135
         return repository.findAll().stream()
71
         return repository.findAll().stream()
148
                 !beer.getName().equals("PBR");
84
                 !beer.getName().equals("PBR");
149
     }
85
     }
150
 }
86
 }
151
-
152
 ```
87
 ```
153
 
88
 
154
-Access the API using `http localhost:8080/good-beers --auth <user>:<password>`.
89
+You should be able to start the `server` application by running it in your favorite IDE or from the command line using `mvn spring-boot:run`. If you don't have Maven installed, you can use the Maven wrapper that's included in the project (`./mvnw spring-boot:run` on *nix, `\mvnw spring-boot:run` on Windows).
155
 
90
 
156
-## Create Ionic App
91
+After the app has started, navigate to <http://localhost:8080/good-beers>. You should see the list of good beers in your browser.
157
 
92
 
158
-Install Ionic and Cordova: `yarn global add cordova ionic`
93
+![Good Beers JSON](static/good-beers-json.png) 
159
 
94
 
160
-From a terminal window, create a new application using the following command:
95
+## Create Ionic App
161
 
96
 
97
+To create an Ionic app to display data from your API, you'll first need to install Ionic CLI and Cordova: 
98
+
99
+```bash
100
+npm install -g ionic cordova
162
 ```
101
 ```
102
+
103
+The [Ionic CLI](http://ionicframework.com/docs/cli/) is a command-line tool that greatly reduces the time it takes to develop an Ionic app. It’s like a Swiss Army Knife: It brings together a bunch of miscellaneous tools under a single interface. The CLI contains a number of useful commands for Ionic development, such as `start`, `build`, `generate`, `serve`, and `run`.
104
+
105
+After installation completes, create a new application using the following command:
106
+
107
+```bash
163
 ionic start ionic-beer --v2
108
 ionic start ionic-beer --v2
164
 ```
109
 ```
165
 
110
 
166
 This may take a minute or two to complete, depending on your internet connection speed. In the same terminal window, change to be in your application’s directory and run it.
111
 This may take a minute or two to complete, depending on your internet connection speed. In the same terminal window, change to be in your application’s directory and run it.
167
 
112
 
168
-```
113
+```bash
169
 cd ionic-beer
114
 cd ionic-beer
170
 ionic serve
115
 ionic serve
171
 ```
116
 ```
172
 
117
 
173
 This will open your default browser on [http://localhost:8100](http://localhost:8100). You can click through the tabbed interface to see the default structure of the app.
118
 This will open your default browser on [http://localhost:8100](http://localhost:8100). You can click through the tabbed interface to see the default structure of the app.
174
 
119
 
175
-## Upgrade to Angular 2.3
120
+![Ionic shell with tabs](static/ionic-tabs.png)
176
 
121
 
177
-With Angular versions less than 2.3, you can’t extend components and override their templates. The Ionic pages for Stormpath module uses component extension to override the templates in its pages. Because of this, you have to upgrade your project to use Angular 2.3. The only downside to use Angular 2.3 with Ionic 2.0.0 is that you won’t be able to use the `--prod` build flag when compiling. This is because its compiler does not support Angular 2.3.
122
+## Create a Good Beers UI
178
 
123
 
179
-To begin, modify `package.json` so all the `angular` dependencies use version `2.3.1` rather than `2.2.1`.
124
+Run `ionic generate page beer` to create a component and a template to display the list of good beers. This creates a number of files in `src/app/pages/beer`:
180
 
125
 
181
-```json
182
-"dependencies": {
183
-  "@angular/common": "2.3.1",
184
-  "@angular/compiler": "2.3.1",
185
-  "@angular/compiler-cli": "2.3.1",
186
-  "@angular/core": "2.3.1",
187
-  "@angular/forms": "2.3.1",
188
-  "@angular/http": "2.3.1",
189
-  "@angular/platform-browser": "2.3.1",
190
-  "@angular/platform-browser-dynamic": "2.3.1",
191
-  "@angular/platform-server": "2.3.1",
126
+```
127
+beer.html
128
+beer.module.ts
129
+beer.scss
130
+beer.ts
192
 ```
131
 ```
193
 
132
 
194
-Run `yarn` to update to these versions.
133
+Open `beer.ts` and change the name of the class to be `BeerPage`.
195
 
134
 
196
-## Install Ionic Pages for Stormpath
135
+```typescript
136
+export class BeerPage {
197
 
137
 
198
-Install [Ionic pages for Stormpath](https://github.com/stormpath/stormpath-sdk-angular-ionic):
138
+  constructor(public navCtrl: NavController, public navParams: NavParams) {
139
+  }
199
 
140
 
200
-```
201
-yarn add angular-stormpath-ionic
141
+  ionViewDidLoad() {
142
+    console.log('ionViewDidLoad BeerPage');
143
+  }
144
+
145
+}
202
 ```
146
 ```
203
 
147
 
204
-Modify `src/app/app.module.ts` to define a `stormpathConfig` function. This function is used to configure the `endpointPrefix` to point to your Spring Boot API. Import `StormpathModule`, `StormpathIonicModule`, and override the provider of `StormpathConfiguration`. You’ll also need to append Stormpath's pre-built Ionic pages to `entryComponents`.
148
+Modify `beer.module.ts` to change the class name too.
205
 
149
 
206
 ```typescript
150
 ```typescript
207
-import { StormpathConfiguration, StormpathModule } from 'angular-stormpath';
208
-import { StormpathIonicModule, LoginPage, ForgotPasswordPage, RegisterPage } from 'angular-stormpath-ionic';
209
-
210
-export function stormpathConfig(): StormpathConfiguration {
211
-  let spConfig: StormpathConfiguration = new StormpathConfiguration();
212
-  spConfig.endpointPrefix = 'http://localhost:8080';
213
-  return spConfig;
214
-}
151
+import { NgModule } from '@angular/core';
152
+import { IonicModule } from 'ionic-angular';
153
+import { BeerPage } from './beer';
215
 
154
 
216
 @NgModule({
155
 @NgModule({
217
-  ...
218
-  imports: [
219
-    IonicModule.forRoot(MyApp),
220
-    StormpathModule,
221
-    StormpathIonicModule
156
+  declarations: [
157
+    BeerPage
222
   ],
158
   ],
223
-  bootstrap: [IonicApp],
224
-  entryComponents: [
225
-    ...
226
-    LoginPage,
227
-    ForgotPasswordPage,
228
-    RegisterPage
159
+  imports: [
160
+    IonicModule.forRoot(BeerPage),
229
   ],
161
   ],
230
-  providers: [
231
-    {provide: ErrorHandler, useClass: IonicErrorHandler},
232
-    {provide: StormpathConfiguration, useFactory: stormpathConfig}
162
+  exports: [
163
+    BeerPage
233
   ]
164
   ]
234
 })
165
 })
235
-export class AppModule {}
166
+export class BeerModule {}
236
 ```
167
 ```
237
 
168
 
238
-To render a login page before users can view the application, modify `src/app/app.component.ts` to use the `Stormpath` service and navigate to Stormpath's `LoginPage` if the user is not authenticated.
169
+Add `BeerModule` to the `imports` list in `app.module.ts`.
239
 
170
 
240
 ```typescript
171
 ```typescript
241
-import { Component } from '@angular/core';
242
-import { Platform } from 'ionic-angular';
243
-import { StatusBar, Splashscreen } from 'ionic-native';
244
-import { TabsPage } from '../pages/tabs/tabs';
245
-import { Stormpath } from 'angular-stormpath';
246
-import { LoginPage } from 'angular-stormpath-ionic';
247
-
248
-@Component({
249
-  templateUrl: 'app.html'
250
-})
251
-export class MyApp {
252
-  rootPage;
253
-
254
-  constructor(platform: Platform, private stormpath: Stormpath) {
255
-    stormpath.user$.subscribe(user => {
256
-      if (!user) {
257
-        this.rootPage = LoginPage;
258
-      } else {
259
-        this.rootPage = TabsPage;
260
-      }
261
-    });
262
-
263
-    platform.ready().then(() => {
264
-      // Okay, so the platform is ready and our plugins are available.
265
-      // Here you can do any higher level native things you might need.
266
-      StatusBar.styleDefault();
267
-      Splashscreen.hide();
268
-    });
269
-  }
270
-}
271
-```
272
-
273
-If you run `ionic serve`, you’ll likely see something similar to the following error in your browser’s console.
274
-
275
-```
276
-XMLHttpRequest cannot load http://localhost:8080/me. Response to preflight request
277
-doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on
278
-the requested resource. Origin 'http://localhost:8100 is therefore not allowed access.
279
-The response had HTTP status code 403.
280
-```
172
+import { BeerModule } from '../pages/beer/beer.module';
281
 
173
 
282
-To fix this, open your Spring Boot application's `src/main/resources/application.properties` and add the following line. This enables cross-origin resource sharing (CORS) from both the browser and the mobile client.
283
-
284
-```
285
-stormpath.web.cors.allowed.originUris = http://localhost:8100,file://
286
-```
287
-
288
-Restart Spring Boot and your Ionic app. You should see a login screen when you run `ionic serve`.
289
-
290
-![Stormpath Login for Ionic](./static/ionic-login.png)
291
-
292
-In `src/pages/home.html`, add a logout link to the header and a paragraph in the content section that shows the currently logged in user.
293
-
294
-```html
295
-<ion-header>
296
-  <ion-navbar>
297
-    <ion-title>Home</ion-title>
298
-    <ion-buttons end>
299
-      <button ion-button icon-only (click)="logout()">
300
-        Logout
301
-      </button>
302
-    </ion-buttons>
303
-  </ion-navbar>
304
-</ion-header>
305
-
306
-<ion-content padding>
174
+@NgModule({
307
   ...
175
   ...
308
-  <p *ngIf="(user$ | async)">
309
-    You are logged in as: <b>{{ ( user$ | async ).fullName }}</b>
310
-  </p>
311
-</ion-content>
312
-```
313
-
314
-If you login, the “Logout” button will render, but won’t work because there’s no `logout()` method in `src/pages/home.ts`. Similarly, the “You are logged in” message won’t appear because there’s no `user$` variable defined. Change the body of `home.ts` to retrieve `user$` from the `Stormpath` service and define the `logout()` method.
315
-
316
-```typescript
317
-import { Account, Stormpath } from 'angular-stormpath';
318
-import { Observable } from 'rxjs';
319
-...
320
-export class HomePage {
321
-  user$: Observable<Account | boolean>;
322
-
323
-  constructor(private stormpath: Stormpath) {
324
-    this.user$ = this.stormpath.user$;
325
-  }
326
-
327
-  logout(): void {
328
-    this.stormpath.logout();
329
-  }
330
-}
331
-```
332
-
333
-If you’re logged in, you should see a screen with a logout button and the name of the currently logged in user.
334
-
335
-![Logged in as: Hip User](./static/ionic-home.png)
336
-
337
-The `LoginPage` tries to auto-focus onto the `email` field when it loads. To auto-activate the keyboard you'll need to tell Cordova it’s OK to display the keyboard without user interaction. You can do this by adding the following to `config.xml` in the root directory.
338
-
339
-```xml
340
-<preference name="KeyboardDisplayRequiresUserAction" value="false"/>
341
-```
342
-
343
-Check your changes into Git.
344
-
345
-```
346
-git add .
347
-git commit -m "Add Stormpath"
176
+  imports: [
177
+    BrowserModule,
178
+    IonicModule.forRoot(MyApp),
179
+    BeerModule
180
+  ],
181
+  ...
182
+})
348
 ```
183
 ```
349
 
184
 
350
-## Build a Good Beers UI
351
-
352
-Run `ionic generate page beer` to create a component and a template to display the list of good beers.
185
+Run `ionic g provider beer-service` to create a service to fetch the beer list from the Spring Boot API.
353
 
186
 
354
-Add `BeerPage` to the `declarations` and `entryComponent` lists in `app.module.ts`.
355
-
356
-Run `ionic generate provider beer-service` to create a service to fetch the beer list from the Spring Boot API.
357
-
358
-Change `src/providers/beer-service.ts` to use have a `getGoodBeers()` method.
187
+Change `src/providers/beer-service.ts` to have constants for the API path and add a `getGoodBeers()` method.
359
 
188
 
360
 ```typescript
189
 ```typescript
361
 import { Injectable } from '@angular/core';
190
 import { Injectable } from '@angular/core';
362
-import { Http, Response, RequestOptions } from '@angular/http';
191
+import { Http, Response } from '@angular/http';
363
 import 'rxjs/add/operator/map';
192
 import 'rxjs/add/operator/map';
364
 import { Observable } from 'rxjs';
193
 import { Observable } from 'rxjs';
365
-import { StormpathConfiguration } from 'angular-stormpath';
366
 
194
 
367
 @Injectable()
195
 @Injectable()
368
 export class BeerService {
196
 export class BeerService {
369
-  public API;
370
-  public BEER_API;
197
+  public API = 'http://localhost:8080';
198
+  public BEER_API = this.API + '/beers';
371
 
199
 
372
-  constructor(public http: Http, public config: StormpathConfiguration) {
373
-    this.API = config.endpointPrefix;
374
-    this.BEER_API = this.API + '/beers';
375
-  }
200
+  constructor(private http: Http) {}
376
 
201
 
377
   getGoodBeers(): Observable<any> {
202
   getGoodBeers(): Observable<any> {
378
-    let options = new RequestOptions({ withCredentials: true });
379
-    return this.http.get(this.API + '/good-beers', options)
203
+    return this.http.get(this.API + '/good-beers')
380
       .map((response: Response) => response.json());
204
       .map((response: Response) => response.json());
381
   }
205
   }
382
 }
206
 }
383
 ```
207
 ```
384
 
208
 
385
-**TIP:** If you don’t want to pass in `withCredentials: true`, you can add the API URI as an `autoAuthorizeUri` in `StormpathConfiguration`.
386
-
387
-```typescript
388
-export function stormpathConfig(): StormpathConfiguration {
389
-  let spConfig: StormpathConfiguration = new StormpathConfiguration();
390
-  spConfig.endpointPrefix = 'http://localhost:8080';
391
-  spConfig.autoAuthorizedUris.push(new RegExp(spConfig.endpointPrefix + '/*'));
392
-  return spConfig;
393
-}
394
-```
395
-
396
 Modify `beer.html` to show the list of beers.
209
 Modify `beer.html` to show the list of beers.
397
 
210
 
398
 ```html
211
 ```html
405
 
218
 
406
 <ion-content padding>
219
 <ion-content padding>
407
   <ion-list>
220
   <ion-list>
408
-    <ion-item *ngFor="let beer of beers" >
221
+    <ion-item *ngFor="let beer of beers">
409
       <h2>{{beer.name}}</h2>
222
       <h2>{{beer.name}}</h2>
410
     </ion-item>
223
     </ion-item>
411
   </ion-list>
224
   </ion-list>
412
 </ion-content>
225
 </ion-content>
413
 ```
226
 ```
414
 
227
 
415
-Update `beer.ts` to import `BeerService` and add as a provider. Call the `getGoodBeers()` method in the `ionViewDidLoad()` lifecycle method.
228
+Modify `beer.module.ts` to import `BeerService` and add it as a provider. You could add it as a provider in each component, but adding it in the module allows all components to use it. 
229
+
230
+```typescript
231
+import { BeerService } from '../../providers/beer-service';
232
+
233
+@NgModule({
234
+  ...
235
+  providers: [
236
+    BeerService
237
+  ]
238
+})
239
+```
240
+
241
+Update `beer.ts` to import `BeerService` and add it as a dependency in the constructor. Call the `getGoodBeers()` method in the `ionViewDidLoad()` lifecycle method.
416
 
242
 
417
 ```typescript
243
 ```typescript
418
 import { Component } from '@angular/core';
244
 import { Component } from '@angular/core';
245
+import { IonicPage, NavController, NavParams } from 'ionic-angular';
419
 import { BeerService } from '../../providers/beer-service';
246
 import { BeerService } from '../../providers/beer-service';
420
 
247
 
248
+@IonicPage()
421
 @Component({
249
 @Component({
422
   selector: 'page-beer',
250
   selector: 'page-beer',
423
-  templateUrl: 'beer.html',
424
-  providers: [BeerService]
251
+  templateUrl: 'beer.html'
425
 })
252
 })
426
 export class BeerPage {
253
 export class BeerPage {
427
   private beers: Array<any>;
254
   private beers: Array<any>;
428
 
255
 
429
-  constructor(public beerService: BeerService) {
256
+  constructor(public navCtrl: NavController, public navParams: NavParams,
257
+              public beerService: BeerService) {
430
   }
258
   }
431
 
259
 
432
   ionViewDidLoad() {
260
   ionViewDidLoad() {
451
   templateUrl: 'tabs.html'
279
   templateUrl: 'tabs.html'
452
 })
280
 })
453
 export class TabsPage {
281
 export class TabsPage {
454
-  // this tells the tabs component which Pages
455
-  // should be each tab's root Page
456
   tab1Root: any = HomePage;
282
   tab1Root: any = HomePage;
457
   tab2Root: any = BeerPage;
283
   tab2Root: any = BeerPage;
458
   tab3Root: any = ContactPage;
284
   tab3Root: any = ContactPage;
459
   tab4Root: any = AboutPage;
285
   tab4Root: any = AboutPage;
460
 
286
 
461
-  constructor() {
462
-  }
287
+  constructor() {}
463
 }
288
 }
464
 ```
289
 ```
465
 
290
 
466
-Update `tabs.html` too!
291
+You'll also need to update `tabs.html` to have the new tab order.
467
 
292
 
468
 ```html
293
 ```html
469
 <ion-tabs>
294
 <ion-tabs>
474
 </ion-tabs>
299
 </ion-tabs>
475
 ```
300
 ```
476
 
301
 
477
-Add some fun with Giphy! Run `ionic generate provider giphy-service`. Replace the code in `src/providers/giphy-service.ts` with the following TypeScript:
302
+### Add Some Fun with Animated GIFs
303
+
304
+Run `ionic g provider giphy-service` to generate a `GiphyService` class. Replace the code in `src/providers/giphy-service.ts` with code that searches Giphy's API:
478
 
305
 
479
 ```typescript
306
 ```typescript
480
 import { Injectable } from '@angular/core';
307
 import { Injectable } from '@angular/core';
485
 // http://tutorials.pluralsight.com/front-end-javascript/getting-started-with-angular-2-by-building-a-giphy-search-application
312
 // http://tutorials.pluralsight.com/front-end-javascript/getting-started-with-angular-2-by-building-a-giphy-search-application
486
 export class GiphyService {
313
 export class GiphyService {
487
 
314
 
315
+  // Public beta key: https://github.com/Giphy/GiphyAPI#public-beta-key
488
   giphyApi = 'https://api.giphy.com/v1/gifs/search?api_key=dc6zaTOxFJmzC&q=';
316
   giphyApi = 'https://api.giphy.com/v1/gifs/search?api_key=dc6zaTOxFJmzC&q=';
489
 
317
 
490
   constructor(public http: Http) {
318
   constructor(public http: Http) {
504
 }
332
 }
505
 ```
333
 ```
506
 
334
 
507
-Update `beer.ts` to take advantage of `GiphyService`:
335
+Update `beer.module.ts` to import `GiphyService` and include it is as a provider.
336
+
337
+```typescript
338
+import { GiphyService } from '../../providers/giphy-service';
339
+
340
+@NgModule({
341
+  ...
342
+  providers: [
343
+    BeerService,
344
+    GiphyService
345
+  ]
346
+})
347
+```
348
+
349
+Modify `beer.ts` to import `GiphyService` and set a `giphyUrl` on each beer.
508
 
350
 
509
 ```typescript
351
 ```typescript
510
 import { Component } from '@angular/core';
352
 import { Component } from '@angular/core';
353
+import { IonicPage, NavController, NavParams } from 'ionic-angular';
511
 import { BeerService } from '../../providers/beer-service';
354
 import { BeerService } from '../../providers/beer-service';
512
 import { GiphyService } from '../../providers/giphy-service';
355
 import { GiphyService } from '../../providers/giphy-service';
513
 
356
 
357
+@IonicPage()
514
 @Component({
358
 @Component({
515
   selector: 'page-beer',
359
   selector: 'page-beer',
516
-  templateUrl: 'beer.html',
517
-  providers: [BeerService, GiphyService]
360
+  templateUrl: 'beer.html'
518
 })
361
 })
519
 export class BeerPage {
362
 export class BeerPage {
520
   private beers: Array<any>;
363
   private beers: Array<any>;
521
 
364
 
522
-  constructor(public beerService: BeerService, public giphyService: GiphyService) {
365
+  constructor(public navCtrl: NavController, public navParams: NavParams,
366
+              public beerService: BeerService, public giphyService: GiphyService) {
523
   }
367
   }
524
 
368
 
525
   ionViewDidLoad() {
369
   ionViewDidLoad() {
546
 </ion-item>
390
 </ion-item>
547
 ```
391
 ```
548
 
392
 
393
+Start the Spring Boot app in one terminal and run `ionic serve` in another. Open <http://localhost:8100> in your browser. Click on the Beer icon and you'll likely see an error in your browser.
394
+
395
+```
396
+Uncaught (in promise): Error: No provider for Http! 
397
+```
398
+
399
+![No provider for Http!](static/no-http-provider.png)
400
+
401
+This highlights one of the slick features of Ionic: errors are displayed in your browser, not just the browser's console. Add `HttpModule` to the list of imports in `src/app/app.module.ts` to solve this issue.
402
+
403
+```typescript
404
+import { HttpModule } from '@angular/http';
405
+
406
+@NgModule({
407
+  ...
408
+  imports: [
409
+    BrowserModule,
410
+    HttpModule,
411
+    IonicModule.forRoot(MyApp),
412
+    BeerModule
413
+  ],
414
+```
415
+
416
+After making this change, you’ll likely see the following error in your browser’s console.
417
+
418
+```
419
+XMLHttpRequest cannot load http://localhost:8080/good-beers. No 'Access-Control-Allow-Origin' 
420
+header is present on the requested resource. Origin 'http://localhost:8100' is therefore 
421
+not allowed access. The response had HTTP status code 401.
422
+```
423
+
424
+To fix this, open your Spring Boot application's `BeerController.java` class and change its `@CrossOrigin` annotation to allow `http://localhost:8100` and `file://`. This enables cross-origin resource sharing (CORS) from both the browser and the mobile client.
425
+
426
+```java
427
+@CrossOrigin(origins = {"http://localhost:8100","file://"})
428
+public Collection<Map<String, String>> goodBeers() {
429
+```
430
+
431
+Recompile this class and DevTools should restart the application.
432
+
549
 If everything works as expected, you should see a page similar to the one below in your browser.
433
 If everything works as expected, you should see a page similar to the one below in your browser.
550
 
434
 
551
-<p align="center">
552
-<img src="./static/good-beers-ui.png" width="600" alt="Good Beers UI">
553
-</p>
435
+![Good Beers UI](static/good-beers-ui.png)
554
 
436
 
555
 ### Add a Modal for Editing
437
 ### Add a Modal for Editing
556
 
438
 
578
 Add `ModalController` as a dependency in `BeerPage` and add an `openModal()` method.
460
 Add `ModalController` as a dependency in `BeerPage` and add an `openModal()` method.
579
 
461
 
580
 ```typescript
462
 ```typescript
581
-import { ModalController } from 'ionic-angular';
463
+import { IonicPage, ModalController, NavController, NavParams } from 'ionic-angular';
582
 
464
 
583
 export class BeerPage {
465
 export class BeerPage {
584
   private beers: Array<any>;
466
   private beers: Array<any>;
585
 
467
 
586
-  constructor(public beerService: BeerService, public giphyService: GiphyService,
468
+  constructor(public navCtrl: NavController, public navParams: NavParams,
469
+              public beerService: BeerService, public giphyService: GiphyService,
587
               public modalCtrl: ModalController) {
470
               public modalCtrl: ModalController) {
588
   }
471
   }
589
 
472
 
654
 }
537
 }
655
 ```
538
 ```
656
 
539
 
657
-Create `beer-modal.html` as a template for this page.
540
+Add the import for `BeerModalPage` to `beer.ts`, then create `beer-modal.html` as a template for this page.
658
 
541
 
659
 ```html
542
 ```html
660
 <ion-header>
543
 <ion-header>
699
 </ion-content>
582
 </ion-content>
700
 ```
583
 ```
701
 
584
 
702
-Add `BeerModalPage` to the `declarations` and `entryComponent` lists in `app.module.ts`.
585
+Add `BeerModalPage` to the `declarations` and `entryComponent` lists in `beer.module.ts`.
703
 
586
 
704
 You'll also need to modify `beer-service.ts` to have `get()` and `save()` methods.
587
 You'll also need to modify `beer-service.ts` to have `get()` and `save()` methods.
705
 
588
 
721
 }
604
 }
722
 ```
605
 ```
723
 
606
 
607
+At this point, if you try to add or edit a beer name, you'll likely see an error in your browser's console.
608
+
609
+```
610
+ModalCmp ionViewPreLoad error: No component factory found for BeerModalPage. 
611
+Did you add it to @NgModule.entryComponents?
612
+```
613
+
614
+Add `BeerModalPage` to the list of `entryComponents` in `app.module.ts`. While you're in there, add `BeerService` to the list of global providers so you don't have to add it to each component that uses it.
615
+
616
+```typescript
617
+@NgModule({
618
+  ...
619
+  entryComponents: [
620
+    ...
621
+    BeerModalPage
622
+  ],
623
+  providers: [
624
+    BeerService,
625
+    ...
626
+  ]
627
+})
628
+```
629
+
630
+Now if you try to edit a beer's name, you'll see another CORS in your browser's console. Add a `@CrossOrigin` annotation to `BeerRepository.java` (in your Spring Boot project) that matches the one in `BeerController`.
631
+ 
632
+```java
633
+@RepositoryRestResource
634
+@CrossOrigin(origins = {"http://localhost:8100","file://"})
635
+```
636
+
637
+Re-compile and now everything should work as expected. For example, below is a screenshot that shows I added a new beer and what it looks like when editing it.
638
+ 
639
+![Mmmmm, Guinness](static/beer-modal.png)
640
+
724
 ### Add Swipe to Delete
641
 ### Add Swipe to Delete
725
 
642
 
726
 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>`.
643
 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>`.
766
 Add `toastCtrl` as a dependency in the constructor so everything compiles.
683
 Add `toastCtrl` as a dependency in the constructor so everything compiles.
767
 
684
 
768
 ```typescript
685
 ```typescript
769
-constructor(public beerService: BeerService, public giphyService: GiphyService,
686
+constructor(public navCtrl: NavController, public navParams: NavParams,
687
+          public beerService: BeerService, public giphyService: GiphyService,
770
           public modalCtrl: ModalController, public toastCtrl: ToastController) {
688
           public modalCtrl: ModalController, public toastCtrl: ToastController) {
771
 }
689
 }
772
 ```
690
 ```
780
 }
698
 }
781
 ```
699
 ```
782
 
700
 
783
-After making these additions, you should be able to add, edit and delete beers.
701
+After making these additions, you should be able to delete beer names. To emulate a left swipe in your browser, click on the item and drag it to the left.
784
 
702
 
785
-<p align="center">
786
-<img src="./static/beer-modal.png" width="350">&nbsp;&nbsp;
787
-<img src="./static/beer-delete.png" width="350">
788
-</p>
703
+![Left swipe](static/beer-delete.png)
789
 
704
 
790
 ## PWAs with Ionic
705
 ## PWAs with Ionic
791
 
706
 
792
-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/).
707
+Ionic 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/). This blog post is still relevant for Ionic 3.
708
+
709
+If you run the [Lighthouse Chrome extension](https://developers.google.com/web/tools/lighthouse/) on this application, you’ll get a mediocre score (51/100).
793
 
710
 
794
-If you run the [Lighthouse Chrome extension](https://developers.google.com/web/tools/lighthouse/) on this application, you’ll get a mediocre score (54/100).
711
+![Lighthouse: 51](static/lighthouse-51.png)
795
 
712
 
796
-To register a service worker, and improve the app’s score, uncomment the following block in `index.html`.
713
+To register a service worker, and improve the app’s score, uncomment the following block in `src/index.html`.
797
 
714
 
798
 ```html
715
 ```html
799
 <!-- un-comment this code to enable service worker
716
 <!-- un-comment this code to enable service worker
800
 <script>
717
 <script>
801
-  if ('serviceWorker' in navigator) {
802
-    navigator.serviceWorker.register('service-worker.js')
803
-      .then(() => console.log('service worker installed'))
804
-      .catch(err => console.log('Error', err));
805
-  }
718
+if ('serviceWorker' in navigator) {
719
+  navigator.serviceWorker.register('service-worker.js')
720
+    .then(() => console.log('service worker installed'))
721
+    .catch(err => console.log('Error', err));
722
+}
806
 </script>-->
723
 </script>-->
807
 ```
724
 ```
808
 
725
 
809
-After making this change, the score should improve. In my tests, it increased to 69/100. The remaining issues were:
726
+After making this change, the score should improve. In my tests, it increased to 66/100. The remaining issues were:
810
 
727
 
811
-* The page body should render some content if its scripts are not available. This could likely be solved with [Angular’s app-shell directives](https://www.npmjs.com/package/@angular/app-shell).
812
-* Site is not on HTTPS and does not redirect HTTP to HTTPS.
813
 * A couple -1’s in performance for "Cannot read property 'ts' of undefined”.
728
 * A couple -1’s in performance for "Cannot read property 'ts' of undefined”.
729
+* Site is not progressively enhanced (page should contain some content when JavaScript is not available). This could likely be solved with [Angular’s app-shell directives](https://www.npmjs.com/package/@angular/app-shell).
730
+* Site is not on HTTPS and does not redirect HTTP to HTTPS.
814
 
731
 
815
 If you refresh the app and Chrome doesn’t prompt you to install the app (a PWA feature), you probably need to turn on a couple of features. Copy and paste the following URLs into Chrome and enable each feature.
732
 If you refresh the app and Chrome doesn’t prompt you to install the app (a PWA feature), you probably need to turn on a couple of features. Copy and paste the following URLs into Chrome and enable each feature.
816
 
733
 
819
 chrome://flags/#enable-add-to-shelf
736
 chrome://flags/#enable-add-to-shelf
820
 ```
737
 ```
821
 
738
 
822
-After enabling these flags, you’ll see an error in your browser’s console about `assets/imgs/logo.png` not being found. This files is referenced in `src/manifest.json`. You can fix this by copying a 512x512 PNG into this location or by modifying `manifest.json` accordingly.
739
+After enabling these flags, you’ll see an error in your browser’s console about `assets/imgs/logo.png` not being found. This file is referenced in `src/manifest.json`. You can fix this by copying a 512x512 PNG ([like this one](http://www.iconsdb.com/orange-icons/beer-icon.html)) into this location or by modifying `manifest.json` accordingly. At the very least, you should modify `manifest.json` to have your app's name.
823
 
740
 
824
 ## Deploy to a Mobile Device
741
 ## Deploy to a Mobile Device
825
 
742
 
827
 
744
 
828
 To see how your application will look on different devices you can run `ionic serve --lab`. The `--lab` flag opens opens a page in your browser that lets you see how your app looks on different devices.
745
 To see how your application will look on different devices you can run `ionic serve --lab`. The `--lab` flag opens opens a page in your browser that lets you see how your app looks on different devices.
829
 
746
 
747
+![Ionic Labs](static/ionic-labs.png)
748
+
830
 ### iOS
749
 ### iOS
831
 
750
 
832
 To emulate or deploy to an iOS device, you’ll need a Mac and a fresh installation of [Xcode](https://developer.apple.com/xcode/). If you’d like to build iOS apps on Windows, Ionic offers an [Ionic Package](http://ionic.io/cloud#packaging) service.
751
 To emulate or deploy to an iOS device, you’ll need a Mac and a fresh installation of [Xcode](https://developer.apple.com/xcode/). If you’d like to build iOS apps on Windows, Ionic offers an [Ionic Package](http://ionic.io/cloud#packaging) service.
854
 
773
 
855
 Select your phone as the target in Xcode and click the play button to run your app. The first time you do this, Xcode may spin for a while with a “Processing symbol files” message at the top.
774
 Select your phone as the target in Xcode and click the play button to run your app. The first time you do this, Xcode may spin for a while with a “Processing symbol files” message at the top.
856
 
775
 
857
-Deploying to your phone will likely fail because it won't be able to connect to `http://localhost:8080`. To fix this, copy [this script](./deploy.sh) to your hard drive. It expects to be in a directory above your apps. It also expects your apps to be named `client` and `server`.
776
+Deploying to your phone will likely fail because it won't be able to connect to `http://localhost:8080`. To fix this, copy [this script](./deploy.sh) to your hard drive. It expects to be in a directory above your apps. It also expects your apps to be named `ionic-beer` and `server`.
858
 
777
 
859
 If you don't have a Cloud Foundry account, you'll need to [create one](https://account.run.pivotal.io/z/uaa/sign-up) and install its command line tools for this script to work.
778
 If you don't have a Cloud Foundry account, you'll need to [create one](https://account.run.pivotal.io/z/uaa/sign-up) and install its command line tools for this script to work.
860
 
779
 
862
 brew tap cloudfoundry/tap && brew install cf-cli
781
 brew tap cloudfoundry/tap && brew install cf-cli
863
 ```
782
 ```
864
 
783
 
865
-Once you’re configured your phone, computer, and Apple ID to work, you should be able to open the app and see all the screens you created. Below are the ones the ones I captured on my iPhone 6s Plus.
784
+Once you’re configured your phone, computer, and Apple ID to work, you should be able to open the app and see the beer list you created. Below is how it looks on my iPhone 6s Plus.
866
 
785
 
867
-<p align="center">
868
-<img src="./static/iphone-login.png" width="250">&nbsp;&nbsp;
869
-<img src="./static/iphone-register.png" width="250">&nbsp;&nbsp;
870
-<img src="./static/iphone-forgot-password.png" width="250">
871
-</p>
786
+![iPhone Beer List](static/iphone-beer-list.png)
872
 
787
 
873
 ### Android
788
 ### Android
874
 
789
 
903
 
818
 
904
 After performing these steps, you should be able to run `ionic emulate android` and see your app running in the AVD.
819
 After performing these steps, you should be able to run `ionic emulate android` and see your app running in the AVD.
905
 
820
 
821
+![Android Beer List](static/android-beer-list.png)
822
+
906
 ## Learn More
823
 ## Learn More
907
-I hope you’ve enjoyed this tour of Ionic, Angular, and Stormpath. I like how Ionic takes your web development skills up a notch and allows you to create mobile applications that look and behave natively.
908
 
824
 
909
-To learn more about Ionic, Angular, or Stormpath, please see the following resources:
825
+I hope you’ve enjoyed this tour of Ionic and Angular. I like how Ionic takes your web development skills up a notch and allows you to create mobile applications that look and behave natively.
826
+
827
+You can find a completed version of the application created in this blog post [on GitHub](https://github.com/oktadeveloper/spring-boot-ionic-example).
828
+
829
+If you encountered issues, please [create an issue in GitHub](TODO) or hit me up on Twitter [@mraible](https://twitter.com/mraible).
830
+
831
+To learn more about Ionic or Angular, please see the following resources:
910
 
832
 
911
 * [Get started with Ionic Framework](http://ionicframework.com/getting-started/)
833
 * [Get started with Ionic Framework](http://ionicframework.com/getting-started/)
834
+* [Angular Authentication with OpenID Connect and Okta in 20 Minutes](http://developer.okta.com/blog/2017/04/17/angular-authentication-with-oidc)
912
 * [Getting Started with Angular](https://www.youtube.com/watch?v=Jq3szz2KOOs) A YouTube webinar by yours truly. ;)
835
 * [Getting Started with Angular](https://www.youtube.com/watch?v=Jq3szz2KOOs) A YouTube webinar by yours truly. ;)
913
-* [Stormpath Client API Guide](https://docs.stormpath.com/client-api/product-guide/latest/)

+ 0
- 40
client/config.xml View File

1
-<?xml version='1.0' encoding='utf-8'?>
2
-<widget id="com.ionicframework.ionicauth613285" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
3
-    <name>ionic-beer</name>
4
-    <description>An awesome Ionic/Cordova app.</description>
5
-    <author email="matt.raible@stormpath.com" href="https://stormpath.com/">Matt Raible</author>
6
-    <content src="index.html" />
7
-    <access origin="*" />
8
-    <allow-navigation href="http://ionic.local/*" />
9
-    <allow-intent href="http://*/*" />
10
-    <allow-intent href="https://*/*" />
11
-    <allow-intent href="tel:*" />
12
-    <allow-intent href="sms:*" />
13
-    <allow-intent href="mailto:*" />
14
-    <allow-intent href="geo:*" />
15
-    <platform name="android">
16
-        <allow-intent href="market:*" />
17
-    </platform>
18
-    <platform name="ios">
19
-        <allow-intent href="itms:*" />
20
-        <allow-intent href="itms-apps:*" />
21
-    </platform>
22
-    <preference name="webviewbounce" value="false" />
23
-    <preference name="UIWebViewBounce" value="false" />
24
-    <preference name="DisallowOverscroll" value="true" />
25
-    <preference name="android-minSdkVersion" value="16" />
26
-    <preference name="BackupWebStorage" value="none" />
27
-    <preference name="SplashMaintainAspectRatio" value="true" />
28
-    <preference name="FadeSplashScreenDuration" value="300" />
29
-    <preference name="SplashShowOnlyFirstTime" value="false" />
30
-    <preference name="KeyboardDisplayRequiresUserAction" value="false" />
31
-    <feature name="StatusBar">
32
-        <param name="ios-package" onload="true" value="CDVStatusBar" />
33
-    </feature>
34
-    <plugin name="ionic-plugin-keyboard" spec="~2.2.1" />
35
-    <plugin name="cordova-plugin-whitelist" spec="~1.3.1" />
36
-    <plugin name="cordova-plugin-console" spec="~1.0.5" />
37
-    <plugin name="cordova-plugin-statusbar" spec="~2.2.1" />
38
-    <plugin name="cordova-plugin-device" spec="~1.1.4" />
39
-    <plugin name="cordova-plugin-splashscreen" spec="~4.0.1" />
40
-</widget>

+ 0
- 29
client/src/app/app.component.ts View File

1
-import { Component } from '@angular/core';
2
-import { Platform } from 'ionic-angular';
3
-import { StatusBar, Splashscreen } from 'ionic-native';
4
-import { TabsPage } from '../pages/tabs/tabs';
5
-import { Stormpath, LoginPage } from 'angular-stormpath';
6
-
7
-@Component({
8
-  templateUrl: 'app.html'
9
-})
10
-export class MyApp {
11
-  rootPage;
12
-
13
-  constructor(platform: Platform, private stormpath: Stormpath) {
14
-    stormpath.user$.subscribe(user => {
15
-      if (!user) {
16
-        this.rootPage = LoginPage;
17
-      } else {
18
-        this.rootPage = TabsPage;
19
-      }
20
-    });
21
-
22
-    platform.ready().then(() => {
23
-      // Okay, so the platform is ready and our plugins are available.
24
-      // Here you can do any higher level native things you might need.
25
-      StatusBar.styleDefault();
26
-      Splashscreen.hide();
27
-    });
28
-  }
29
-}

+ 0
- 56
client/src/app/app.module.ts View File

1
-import { NgModule, ErrorHandler } from '@angular/core';
2
-import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular';
3
-import { MyApp } from './app.component';
4
-import { AboutPage } from '../pages/about/about';
5
-import { ContactPage } from '../pages/contact/contact';
6
-import { HomePage } from '../pages/home/home';
7
-import { TabsPage } from '../pages/tabs/tabs';
8
-import { StormpathConfiguration, StormpathModule } from 'angular-stormpath';
9
-import { StormpathIonicModule, LoginPage, RegisterPage, ForgotPasswordPage } from 'angular-stormpath-ionic';
10
-import { BeerPage } from '../pages/beer/beer';
11
-import { BeerService } from '../providers/beer-service';
12
-import { GiphyService } from '../providers/giphy-service';
13
-import { BeerModalPage } from '../pages/beer/beer-modal';
14
-
15
-export function stormpathConfig(): StormpathConfiguration {
16
-  let spConfig: StormpathConfiguration = new StormpathConfiguration();
17
-  spConfig.endpointPrefix = 'http://localhost:8080';
18
-  spConfig.autoAuthorizedUris.push(new RegExp(spConfig.endpointPrefix + '/*'));
19
-  return spConfig;
20
-}
21
-
22
-@NgModule({
23
-  declarations: [
24
-    MyApp,
25
-    AboutPage,
26
-    ContactPage,
27
-    HomePage,
28
-    TabsPage,
29
-    BeerPage,
30
-    BeerModalPage
31
-  ],
32
-  imports: [
33
-    IonicModule.forRoot(MyApp),
34
-    StormpathModule,
35
-    StormpathIonicModule
36
-  ],
37
-  bootstrap: [IonicApp],
38
-  entryComponents: [
39
-    MyApp,
40
-    AboutPage,
41
-    ContactPage,
42
-    HomePage,
43
-    TabsPage,
44
-    LoginPage,
45
-    ForgotPasswordPage,
46
-    RegisterPage,
47
-    BeerPage,
48
-    BeerModalPage
49
-  ],
50
-  providers: [
51
-    {provide: ErrorHandler, useClass: IonicErrorHandler},
52
-    {provide: StormpathConfiguration, useFactory: stormpathConfig},
53
-    BeerService, GiphyService
54
-  ]
55
-})
56
-export class AppModule {}

BIN
client/src/assets/imgs/logo.png View File


BIN
client/src/assets/imgs/stormpath-sdks.jpg View File


+ 0
- 18
client/src/pages/about/about.html View File

1
-<ion-header>
2
-  <ion-navbar>
3
-    <ion-title>
4
-      About
5
-    </ion-title>
6
-  </ion-navbar>
7
-</ion-header>
8
-
9
-<ion-content padding>
10
-  <p><a href="https://stormpath.com">Stormpath</a> is an API service that allows developers to create, edit, and
11
-    securely store user accounts and user account data, and connect them with one or multiple applications. We make user
12
-    account management a lot easier, more secure, and infinitely scalable.</p>
13
-
14
-  <p>
15
-    Click the image below to see all the language and framework integrations we provide.
16
-    <a href="https://docs.stormpath.com"><img src="assets/imgs/stormpath-sdks.jpg" alt="Stormpath SDKs"></a>
17
-  </p>
18
-</ion-content>

+ 0
- 20
client/src/pages/home/home.ts View File

1
-import { Component } from '@angular/core';
2
-
3
-import { Account, Stormpath } from 'angular-stormpath';
4
-import { Observable } from 'rxjs';
5
-
6
-@Component({
7
-  selector: 'page-home',
8
-  templateUrl: 'home.html'
9
-})
10
-export class HomePage {
11
-  user$: Observable<Account | boolean>;
12
-
13
-  constructor(private stormpath: Stormpath) {
14
-    this.user$ = this.stormpath.user$;
15
-  }
16
-
17
-  logout(): void {
18
-    this.stormpath.logout();
19
-  }
20
-}

+ 0
- 2971
client/yarn.lock
File diff suppressed because it is too large
View File


+ 3
- 9
deploy.sh View File

36
 
36
 
37
 cf a
37
 cf a
38
 
38
 
39
-# Stormpath
40
-stormpathApiKeyId=`cat ~/.stormpath/apiKey.properties | grep apiKey.id | cut -f3 -d\ `
41
-stormpathApiKeySecret=`cat ~/.stormpath/apiKey.properties | grep apiKey.secret | cut -f3 -d\ `
42
-
43
 # Deploy the server
39
 # Deploy the server
44
 cd $r/server
40
 cd $r/server
45
 mvn clean package
41
 mvn clean package
46
 cf push -p target/*jar ionic-server --no-start  --random-route
42
 cf push -p target/*jar ionic-server --no-start  --random-route
47
-cf set-env ionic-server STORMPATH_API_KEY_ID $stormpathApiKeyId
48
-cf set-env ionic-server STORMPATH_API_KEY_SECRET $stormpathApiKeySecret
49
 cf set-env ionic-server FORCE_HTTPS true
43
 cf set-env ionic-server FORCE_HTTPS true
50
 cf start ionic-server
44
 cf start ionic-server
51
 
45
 
53
 serverUri=https://`app_domain ionic-server`
47
 serverUri=https://`app_domain ionic-server`
54
 
48
 
55
 # Deploy the client
49
 # Deploy the client
56
-cd $r/client
50
+cd $r/ionic-beer
57
 npm run clean
51
 npm run clean
58
 # replace the server URL in the client
52
 # replace the server URL in the client
59
 sed -i -e "s|http://localhost:8080|$serverUri|g" src/app/app.module.ts
53
 sed -i -e "s|http://localhost:8080|$serverUri|g" src/app/app.module.ts
63
 ionic run ios
57
 ionic run ios
64
 
58
 
65
 # cleanup changed files
59
 # cleanup changed files
66
-git checkout $r/client
67
-rm $r/client/src/app/app.module.ts-e
60
+git checkout $r/ionic-beer
61
+rm $r/ionic-beer/src/app/app.module.ts-e

client/.editorconfig → ionic-beer/.editorconfig View File


client/.gitignore → ionic-beer/.gitignore View File


+ 39
- 0
ionic-beer/config.xml View File

1
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2
+<widget id="com.ionicframework.ionicbeer926596" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
3
+  <name>ionic-beer</name>
4
+  <description>An awesome Ionic/Cordova app.</description>
5
+  <author email="hi@ionicframework" href="http://ionicframework.com/">Ionic Framework Team</author>
6
+  <content src="index.html"/>
7
+  <access origin="*"/>
8
+  <allow-navigation href="http://ionic.local/*"/>
9
+  <allow-intent href="http://*/*"/>
10
+  <allow-intent href="https://*/*"/>
11
+  <allow-intent href="tel:*"/>
12
+  <allow-intent href="sms:*"/>
13
+  <allow-intent href="mailto:*"/>
14
+  <allow-intent href="geo:*"/>
15
+  <platform name="android">
16
+    <allow-intent href="market:*"/>
17
+  </platform>
18
+  <platform name="ios">
19
+    <allow-intent href="itms:*"/>
20
+    <allow-intent href="itms-apps:*"/>
21
+  </platform>
22
+  <preference name="webviewbounce" value="false"/>
23
+  <preference name="UIWebViewBounce" value="false"/>
24
+  <preference name="DisallowOverscroll" value="true"/>
25
+  <preference name="android-minSdkVersion" value="16"/>
26
+  <preference name="BackupWebStorage" value="none"/>
27
+  <preference name="SplashMaintainAspectRatio" value="true"/>
28
+  <preference name="FadeSplashScreenDuration" value="300"/>
29
+  <preference name="SplashShowOnlyFirstTime" value="false"/>
30
+  <feature name="StatusBar">
31
+    <param name="ios-package" onload="true" value="CDVStatusBar"/>
32
+  </feature>
33
+  <plugin name="ionic-plugin-keyboard" spec="~2.2.1"/>
34
+  <plugin name="cordova-plugin-whitelist" spec="1.3.1"/>
35
+  <plugin name="cordova-plugin-console" spec="1.0.5"/>
36
+  <plugin name="cordova-plugin-statusbar" spec="2.2.1"/>
37
+  <plugin name="cordova-plugin-device" spec="1.1.4"/>
38
+  <plugin name="cordova-plugin-splashscreen" spec="~4.0.1"/>
39
+</widget>

client/ionic.config.json → ionic-beer/ionic.config.json View File


client/package.json → ionic-beer/package.json View File

10
     "ionic:serve": "ionic-app-scripts serve"
10
     "ionic:serve": "ionic-app-scripts serve"
11
   },
11
   },
12
   "dependencies": {
12
   "dependencies": {
13
-    "@angular/common": "2.3.1",
14
-    "@angular/compiler": "2.3.1",
15
-    "@angular/compiler-cli": "2.3.1",
16
-    "@angular/core": "2.3.1",
17
-    "@angular/forms": "2.3.1",
18
-    "@angular/http": "2.3.1",
19
-    "@angular/platform-browser": "2.3.1",
20
-    "@angular/platform-browser-dynamic": "2.3.1",
21
-    "@angular/platform-server": "2.3.1",
22
-    "@ionic/storage": "1.1.7",
23
-    "angular-stormpath-ionic": "^0.0.3",
24
-    "ionic-angular": "2.0.0",
25
-    "ionic-native": "2.4.1",
13
+    "@angular/common": "4.0.0",
14
+    "@angular/compiler": "4.0.0",
15
+    "@angular/compiler-cli": "4.0.0",
16
+    "@angular/core": "4.0.0",
17
+    "@angular/forms": "4.0.0",
18
+    "@angular/http": "4.0.0",
19
+    "@angular/platform-browser": "4.0.0",
20
+    "@angular/platform-browser-dynamic": "4.0.0",
21
+    "@ionic-native/core": "3.4.2",
22
+    "@ionic-native/splash-screen": "3.4.2",
23
+    "@ionic-native/status-bar": "3.4.2",
24
+    "@ionic/storage": "2.0.1",
25
+    "ionic-angular": "3.0.1",
26
     "ionicons": "3.0.0",
26
     "ionicons": "3.0.0",
27
-    "rxjs": "5.0.0-beta.12",
27
+    "rxjs": "5.1.1",
28
     "sw-toolbox": "3.4.0",
28
     "sw-toolbox": "3.4.0",
29
-    "zone.js": "0.6.26"
29
+    "zone.js": "^0.8.4"
30
   },
30
   },
31
   "devDependencies": {
31
   "devDependencies": {
32
-    "@ionic/app-scripts": "1.0.0",
33
-    "typescript": "2.0.9"
32
+    "@ionic/app-scripts": "1.3.0",
33
+    "typescript": "~2.2.1"
34
   },
34
   },
35
   "cordovaPlugins": [
35
   "cordovaPlugins": [
36
+    "cordova-plugin-statusbar",
36
     "cordova-plugin-whitelist",
37
     "cordova-plugin-whitelist",
37
     "cordova-plugin-console",
38
     "cordova-plugin-console",
38
-    "cordova-plugin-statusbar",
39
     "cordova-plugin-device",
39
     "cordova-plugin-device",
40
     "cordova-plugin-splashscreen",
40
     "cordova-plugin-splashscreen",
41
     "ionic-plugin-keyboard"
41
     "ionic-plugin-keyboard"

BIN
ionic-beer/resources/android/icon/drawable-hdpi-icon.png View File


BIN
ionic-beer/resources/android/icon/drawable-ldpi-icon.png View File


BIN
ionic-beer/resources/android/icon/drawable-mdpi-icon.png View File


BIN
ionic-beer/resources/android/icon/drawable-xhdpi-icon.png View File


BIN
ionic-beer/resources/android/icon/drawable-xxhdpi-icon.png View File


BIN
ionic-beer/resources/android/icon/drawable-xxxhdpi-icon.png View File


client/resources/android/splash/drawable-land-hdpi-screen.png → ionic-beer/resources/android/splash/drawable-land-hdpi-screen.png View File


client/resources/android/splash/drawable-land-ldpi-screen.png → ionic-beer/resources/android/splash/drawable-land-ldpi-screen.png View File


client/resources/android/splash/drawable-land-mdpi-screen.png → ionic-beer/resources/android/splash/drawable-land-mdpi-screen.png View File


client/resources/android/splash/drawable-land-xhdpi-screen.png → ionic-beer/resources/android/splash/drawable-land-xhdpi-screen.png View File


client/resources/android/splash/drawable-land-xxhdpi-screen.png → ionic-beer/resources/android/splash/drawable-land-xxhdpi-screen.png View File


client/resources/android/splash/drawable-land-xxxhdpi-screen.png → ionic-beer/resources/android/splash/drawable-land-xxxhdpi-screen.png View File


client/resources/android/splash/drawable-port-hdpi-screen.png → ionic-beer/resources/android/splash/drawable-port-hdpi-screen.png View File


client/resources/android/splash/drawable-port-ldpi-screen.png → ionic-beer/resources/android/splash/drawable-port-ldpi-screen.png View File


client/resources/android/splash/drawable-port-mdpi-screen.png → ionic-beer/resources/android/splash/drawable-port-mdpi-screen.png View File


client/resources/android/splash/drawable-port-xhdpi-screen.png → ionic-beer/resources/android/splash/drawable-port-xhdpi-screen.png View File


client/resources/android/splash/drawable-port-xxhdpi-screen.png → ionic-beer/resources/android/splash/drawable-port-xxhdpi-screen.png View File


client/resources/android/splash/drawable-port-xxxhdpi-screen.png → ionic-beer/resources/android/splash/drawable-port-xxxhdpi-screen.png View File


client/resources/icon.png → ionic-beer/resources/icon.png View File


BIN
ionic-beer/resources/ios/icon/icon-40.png View File


BIN
ionic-beer/resources/ios/icon/icon-40@2x.png View File


BIN
ionic-beer/resources/ios/icon/icon-40@3x.png View File


BIN
ionic-beer/resources/ios/icon/icon-50.png View File


BIN
ionic-beer/resources/ios/icon/icon-50@2x.png View File


BIN
ionic-beer/resources/ios/icon/icon-60.png View File


BIN
ionic-beer/resources/ios/icon/icon-60@2x.png View File


BIN
ionic-beer/resources/ios/icon/icon-60@3x.png View File


BIN
ionic-beer/resources/ios/icon/icon-72.png View File


BIN
ionic-beer/resources/ios/icon/icon-72@2x.png View File


BIN
ionic-beer/resources/ios/icon/icon-76.png View File


BIN
ionic-beer/resources/ios/icon/icon-76@2x.png View File


BIN
ionic-beer/resources/ios/icon/icon-83.5@2x.png View File


BIN
ionic-beer/resources/ios/icon/icon-small.png View File


BIN
ionic-beer/resources/ios/icon/icon-small@2x.png View File


BIN
ionic-beer/resources/ios/icon/icon-small@3x.png View File


BIN
ionic-beer/resources/ios/icon/icon.png View File


BIN
ionic-beer/resources/ios/icon/icon@2x.png View File


client/resources/ios/splash/Default-568h@2x~iphone.png → ionic-beer/resources/ios/splash/Default-568h@2x~iphone.png View File


client/resources/ios/splash/Default-667h.png → ionic-beer/resources/ios/splash/Default-667h.png View File


client/resources/ios/splash/Default-736h.png → ionic-beer/resources/ios/splash/Default-736h.png View File


client/resources/ios/splash/Default-Landscape-736h.png → ionic-beer/resources/ios/splash/Default-Landscape-736h.png View File


client/resources/ios/splash/Default-Landscape@2x~ipad.png → ionic-beer/resources/ios/splash/Default-Landscape@2x~ipad.png View File


client/resources/ios/splash/Default-Landscape~ipad.png → ionic-beer/resources/ios/splash/Default-Landscape~ipad.png View File


client/resources/ios/splash/Default-Portrait@2x~ipad.png → ionic-beer/resources/ios/splash/Default-Portrait@2x~ipad.png View File


client/resources/ios/splash/Default-Portrait~ipad.png → ionic-beer/resources/ios/splash/Default-Portrait~ipad.png View File


client/resources/ios/splash/Default@2x~iphone.png → ionic-beer/resources/ios/splash/Default@2x~iphone.png View File


client/resources/ios/splash/Default~iphone.png → ionic-beer/resources/ios/splash/Default~iphone.png View File


client/resources/splash.png → ionic-beer/resources/splash.png View File


+ 22
- 0
ionic-beer/src/app/app.component.ts View File

1
+import { Component } from '@angular/core';
2
+import { Platform } from 'ionic-angular';
3
+import { StatusBar } from '@ionic-native/status-bar';
4
+import { SplashScreen } from '@ionic-native/splash-screen';
5
+
6
+import { TabsPage } from '../pages/tabs/tabs';
7
+
8
+@Component({
9
+  templateUrl: 'app.html'
10
+})
11
+export class MyApp {
12
+  rootPage:any = TabsPage;
13
+
14
+  constructor(platform: Platform, statusBar: StatusBar, splashScreen: SplashScreen) {
15
+    platform.ready().then(() => {
16
+      // Okay, so the platform is ready and our plugins are available.
17
+      // Here you can do any higher level native things you might need.
18
+      statusBar.styleDefault();
19
+      splashScreen.hide();
20
+    });
21
+  }
22
+}

client/src/app/app.html → ionic-beer/src/app/app.html View File


+ 46
- 0
ionic-beer/src/app/app.module.ts View File

1
+import { NgModule, ErrorHandler } from '@angular/core';
2
+import { BrowserModule } from '@angular/platform-browser';
3
+import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular';
4
+import { MyApp } from './app.component';
5
+
6
+import { AboutPage } from '../pages/about/about';
7
+import { ContactPage } from '../pages/contact/contact';
8
+import { HomePage } from '../pages/home/home';
9
+import { TabsPage } from '../pages/tabs/tabs';
10
+
11
+import { StatusBar } from '@ionic-native/status-bar';
12
+import { SplashScreen } from '@ionic-native/splash-screen';
13
+import { BeerModule } from '../pages/beer/beer.module';
14
+import { HttpModule } from '@angular/http';
15
+import { BeerModalPage } from '../pages/beer/beer-modal';
16
+
17
+@NgModule({
18
+  declarations: [
19
+    MyApp,
20
+    AboutPage,
21
+    ContactPage,
22
+    HomePage,
23
+    TabsPage
24
+  ],
25
+  imports: [
26
+    BrowserModule,
27
+    HttpModule,
28
+    IonicModule.forRoot(MyApp),
29
+    BeerModule
30
+  ],
31
+  bootstrap: [IonicApp],
32
+  entryComponents: [
33
+    MyApp,
34
+    AboutPage,
35
+    ContactPage,
36
+    HomePage,
37
+    TabsPage,
38
+    BeerModalPage
39
+  ],
40
+  providers: [
41
+    StatusBar,
42
+    SplashScreen,
43
+    {provide: ErrorHandler, useClass: IonicErrorHandler}
44
+  ]
45
+})
46
+export class AppModule {}

client/src/app/app.scss → ionic-beer/src/app/app.scss View File

14
 // To declare rules for a specific mode, create a child rule
14
 // To declare rules for a specific mode, create a child rule
15
 // for the .md, .ios, or .wp mode classes. The mode class is
15
 // for the .md, .ios, or .wp mode classes. The mode class is
16
 // automatically applied to the <body> element in the app.
16
 // automatically applied to the <body> element in the app.
17
-
18
-.alert {
19
-  padding: 15px;
20
-  margin-bottom: 20px;
21
-  border: 1px solid transparent;
22
-}
23
-
24
-.alert-danger {
25
-  color: #a94442;
26
-  background-color: #f2dede;
27
-  border-color: #ebccd1;
28
-}
29
-
30
-.alert-success {
31
-  color: #3c763d;
32
-  background-color: #dff0d8;
33
-  border-color: #d6e9c6;
34
-}

client/src/app/main.ts → ionic-beer/src/app/main.ts View File


BIN
ionic-beer/src/assets/icon/favicon.ico View File


BIN
ionic-beer/src/assets/imgs/logo.png View File


client/src/declarations.d.ts → ionic-beer/src/declarations.d.ts View File


client/src/index.html → ionic-beer/src/index.html View File

2
 <html lang="en" dir="ltr">
2
 <html lang="en" dir="ltr">
3
 <head>
3
 <head>
4
   <meta charset="UTF-8">
4
   <meta charset="UTF-8">
5
-  <title>Ionic Beer</title>
5
+  <title>Ionic App</title>
6
   <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
6
   <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
7
   <meta name="format-detection" content="telephone=no">
7
   <meta name="format-detection" content="telephone=no">
8
   <meta name="msapplication-tap-highlight" content="no">
8
   <meta name="msapplication-tap-highlight" content="no">
14
   <!-- cordova.js required for cordova apps -->
14
   <!-- cordova.js required for cordova apps -->
15
   <script src="cordova.js"></script>
15
   <script src="cordova.js"></script>
16
 
16
 
17
-  <!--<script>
17
+  <script>
18
     if ('serviceWorker' in navigator) {
18
     if ('serviceWorker' in navigator) {
19
       navigator.serviceWorker.register('service-worker.js')
19
       navigator.serviceWorker.register('service-worker.js')
20
         .then(() => console.log('service worker installed'))
20
         .then(() => console.log('service worker installed'))
21
         .catch(err => console.log('Error', err));
21
         .catch(err => console.log('Error', err));
22
     }
22
     }
23
-  </script>-->
23
+  </script>
24
 
24
 
25
   <link href="build/main.css" rel="stylesheet">
25
   <link href="build/main.css" rel="stylesheet">
26
 
26
 

client/src/manifest.json → ionic-beer/src/manifest.json View File

1
 {
1
 {
2
-  "name": "Ionic Beer",
3
-  "short_name": "Ionic Beer",
2
+  "name": "Ionic Beers",
3
+  "short_name": "Ionic Beers",
4
   "start_url": "index.html",
4
   "start_url": "index.html",
5
   "display": "standalone",
5
   "display": "standalone",
6
   "icons": [{
6
   "icons": [{

+ 11
- 0
ionic-beer/src/pages/about/about.html View File

1
+<ion-header>
2
+  <ion-navbar>
3
+    <ion-title>
4
+      About
5
+    </ion-title>
6
+  </ion-navbar>
7
+</ion-header>
8
+
9
+<ion-content padding>
10
+
11
+</ion-content>

client/src/pages/about/about.scss → ionic-beer/src/pages/about/about.scss View File


client/src/pages/about/about.ts → ionic-beer/src/pages/about/about.ts View File

1
 import { Component } from '@angular/core';
1
 import { Component } from '@angular/core';
2
-
3
 import { NavController } from 'ionic-angular';
2
 import { NavController } from 'ionic-angular';
4
 
3
 
5
 @Component({
4
 @Component({

client/src/pages/beer/beer-modal.html → ionic-beer/src/pages/beer/beer-modal.html View File


client/src/pages/beer/beer-modal.ts → ionic-beer/src/pages/beer/beer-modal.ts View File


client/src/pages/beer/beer.html → ionic-beer/src/pages/beer/beer.html View File

13
 
13
 
14
 <ion-content padding>
14
 <ion-content padding>
15
   <ion-list>
15
   <ion-list>
16
-    <ion-item-sliding *ngFor="let beer of beers" >
16
+    <ion-item-sliding *ngFor="let beer of beers">
17
       <ion-item (click)="openModal({id: beer.id})">
17
       <ion-item (click)="openModal({id: beer.id})">
18
         <ion-avatar item-left>
18
         <ion-avatar item-left>
19
           <img src="{{beer.giphyUrl}}">
19
           <img src="{{beer.giphyUrl}}">

+ 25
- 0
ionic-beer/src/pages/beer/beer.module.ts View File

1
+import { NgModule } from '@angular/core';
2
+import { IonicModule } from 'ionic-angular';
3
+import { BeerPage } from './beer';
4
+import { BeerModalPage } from './beer-modal';
5
+import { BeerService } from '../../providers/beer-service';
6
+import { GiphyService } from '../../providers/giphy-service';
7
+
8
+@NgModule({
9
+  declarations: [
10
+    BeerPage,
11
+    BeerModalPage
12
+  ],
13
+  imports: [
14
+    IonicModule.forRoot(BeerPage)
15
+  ],
16
+  exports: [
17
+    BeerPage,
18
+    BeerModalPage
19
+  ],
20
+  providers: [
21
+    BeerService,
22
+    GiphyService
23
+  ]
24
+})
25
+export class BeerModule {}

client/src/pages/beer/beer.scss → ionic-beer/src/pages/beer/beer.scss View File


client/src/pages/beer/beer.ts → ionic-beer/src/pages/beer/beer.ts View File

1
 import { Component } from '@angular/core';
1
 import { Component } from '@angular/core';
2
-import { ModalController, ToastController } from 'ionic-angular';
2
+import { IonicPage, ModalController, NavController, NavParams, ToastController } from 'ionic-angular';
3
 import { BeerService } from '../../providers/beer-service';
3
 import { BeerService } from '../../providers/beer-service';
4
 import { GiphyService } from '../../providers/giphy-service';
4
 import { GiphyService } from '../../providers/giphy-service';
5
 import { BeerModalPage } from './beer-modal';
5
 import { BeerModalPage } from './beer-modal';
6
 
6
 
7
+@IonicPage()
7
 @Component({
8
 @Component({
8
   selector: 'page-beer',
9
   selector: 'page-beer',
9
-  templateUrl: 'beer.html',
10
-  providers: [BeerService, GiphyService]
10
+  templateUrl: 'beer.html'
11
 })
11
 })
12
 export class BeerPage {
12
 export class BeerPage {
13
   private beers: Array<any>;
13
   private beers: Array<any>;
14
 
14
 
15
-  constructor(public beerService: BeerService, public giphyService: GiphyService,
16
-              public modalCtrl: ModalController, public toastCtrl: ToastController) {
17
-  }
15
+constructor(public navCtrl: NavController, public navParams: NavParams,
16
+          public beerService: BeerService, public giphyService: GiphyService,
17
+          public modalCtrl: ModalController, public toastCtrl: ToastController) {
18
+}
18
 
19
 
19
   ionViewDidLoad() {
20
   ionViewDidLoad() {
20
     this.beerService.getGoodBeers().subscribe(beers => {
21
     this.beerService.getGoodBeers().subscribe(beers => {

client/src/pages/contact/contact.html → ionic-beer/src/pages/contact/contact.html View File

11
     <ion-list-header>Follow us on Twitter</ion-list-header>
11
     <ion-list-header>Follow us on Twitter</ion-list-header>
12
     <ion-item>
12
     <ion-item>
13
       <ion-icon name="ionic" item-left></ion-icon>
13
       <ion-icon name="ionic" item-left></ion-icon>
14
-      <a href="https://twitter.com/ionicframework">@ionicframework</a>
15
-    </ion-item>
16
-    <ion-item>
17
-      <ion-icon item-left><img src="assets/imgs/logo.png" width="24"></ion-icon>
18
-      <a href="https://twitter.com/gostormpath">@goStormpath</a>
14
+      @ionicframework
19
     </ion-item>
15
     </ion-item>
20
   </ion-list>
16
   </ion-list>
21
 </ion-content>
17
 </ion-content>

client/src/pages/contact/contact.scss → ionic-beer/src/pages/contact/contact.scss View File


client/src/pages/contact/contact.ts → ionic-beer/src/pages/contact/contact.ts View File

1
 import { Component } from '@angular/core';
1
 import { Component } from '@angular/core';
2
-
3
 import { NavController } from 'ionic-angular';
2
 import { NavController } from 'ionic-angular';
4
 
3
 
5
 @Component({
4
 @Component({

client/src/pages/home/home.html → ionic-beer/src/pages/home/home.html View File

1
 <ion-header>
1
 <ion-header>
2
   <ion-navbar>
2
   <ion-navbar>
3
     <ion-title>Home</ion-title>
3
     <ion-title>Home</ion-title>
4
-    <ion-buttons end>
5
-      <button ion-button icon-only (click)="logout()">
6
-        Logout
7
-      </button>
8
-    </ion-buttons>
9
   </ion-navbar>
4
   </ion-navbar>
10
 </ion-header>
5
 </ion-header>
11
 
6
 
19
     Take a look at the <code>src/pages/</code> directory to add or change tabs,
14
     Take a look at the <code>src/pages/</code> directory to add or change tabs,
20
     update any existing page or create new pages.
15
     update any existing page or create new pages.
21
   </p>
16
   </p>
22
-  <p *ngIf="(user$ | async)">
23
-    You are logged in as: <b>{{ ( user$ | async ).fullName }}</b>
24
-  </p>
25
 </ion-content>
17
 </ion-content>

client/src/pages/home/home.scss → ionic-beer/src/pages/home/home.scss View File


+ 14
- 0
ionic-beer/src/pages/home/home.ts View File

1
+import { Component } from '@angular/core';
2
+import { NavController } from 'ionic-angular';
3
+
4
+@Component({
5
+  selector: 'page-home',
6
+  templateUrl: 'home.html'
7
+})
8
+export class HomePage {
9
+
10
+  constructor(public navCtrl: NavController) {
11
+
12
+  }
13
+
14
+}

client/src/pages/tabs/tabs.html → ionic-beer/src/pages/tabs/tabs.html View File


client/src/pages/tabs/tabs.ts → ionic-beer/src/pages/tabs/tabs.ts View File

1
 import { Component } from '@angular/core';
1
 import { Component } from '@angular/core';
2
 
2
 
3
-import { HomePage } from '../home/home';
4
 import { AboutPage } from '../about/about';
3
 import { AboutPage } from '../about/about';
5
 import { ContactPage } from '../contact/contact';
4
 import { ContactPage } from '../contact/contact';
5
+import { HomePage } from '../home/home';
6
 import { BeerPage } from '../beer/beer';
6
 import { BeerPage } from '../beer/beer';
7
 
7
 
8
 @Component({
8
 @Component({
9
   templateUrl: 'tabs.html'
9
   templateUrl: 'tabs.html'
10
 })
10
 })
11
 export class TabsPage {
11
 export class TabsPage {
12
-  // this tells the tabs component which Pages
13
-  // should be each tab's root Page
14
   tab1Root: any = HomePage;
12
   tab1Root: any = HomePage;
15
   tab2Root: any = BeerPage;
13
   tab2Root: any = BeerPage;
16
   tab3Root: any = ContactPage;
14
   tab3Root: any = ContactPage;
17
   tab4Root: any = AboutPage;
15
   tab4Root: any = AboutPage;
18
 
16
 
19
-  constructor() {
20
-
21
-  }
17
+  constructor() {}
22
 }
18
 }

client/src/providers/beer-service.ts → ionic-beer/src/providers/beer-service.ts View File

2
 import { Http, Response } from '@angular/http';
2
 import { Http, Response } from '@angular/http';
3
 import 'rxjs/add/operator/map';
3
 import 'rxjs/add/operator/map';
4
 import { Observable } from 'rxjs';
4
 import { Observable } from 'rxjs';
5
-import { StormpathConfiguration } from 'angular-stormpath';
6
 
5
 
7
 @Injectable()
6
 @Injectable()
8
 export class BeerService {
7
 export class BeerService {
9
-  public API;
10
-  public BEER_API;
8
+  public API = 'http://localhost:8080';
9
+  public BEER_API = this.API + '/beers';
11
 
10
 
12
-  constructor(public http: Http, public config: StormpathConfiguration) {
13
-    this.API = config.endpointPrefix;
14
-    this.BEER_API = this.API + '/beers';
15
-  }
11
+  constructor(private http: Http) {}
16
 
12
 
17
   getGoodBeers(): Observable<any> {
13
   getGoodBeers(): Observable<any> {
18
     return this.http.get(this.API + '/good-beers')
14
     return this.http.get(this.API + '/good-beers')
24
       .map((response: Response) => response.json());
20
       .map((response: Response) => response.json());
25
   }
21
   }
26
 
22
 
27
-  remove(id: string) {
28
-    return this.http.delete(this.BEER_API + '/' + id)
29
-      .map((response: Response) => response.json());
30
-  }
31
-
32
   save(beer: any): Observable<any> {
23
   save(beer: any): Observable<any> {
33
     let result: Observable<Response>;
24
     let result: Observable<Response>;
34
     if (beer['href']) {
25
     if (beer['href']) {
39
     return result.map((response: Response) => response.json())
30
     return result.map((response: Response) => response.json())
40
       .catch(error => Observable.throw(error));
31
       .catch(error => Observable.throw(error));
41
   }
32
   }
33
+
34
+  remove(id: string) {
35
+    return this.http.delete(this.BEER_API + '/' + id)
36
+      .map((response: Response) => response.json());
37
+  }
42
 }
38
 }

client/src/providers/giphy-service.ts → ionic-beer/src/providers/giphy-service.ts View File

6
 // http://tutorials.pluralsight.com/front-end-javascript/getting-started-with-angular-2-by-building-a-giphy-search-application
6
 // http://tutorials.pluralsight.com/front-end-javascript/getting-started-with-angular-2-by-building-a-giphy-search-application
7
 export class GiphyService {
7
 export class GiphyService {
8
 
8
 
9
+  // Public beta key: https://github.com/Giphy/GiphyAPI#public-beta-key
9
   giphyApi = 'https://api.giphy.com/v1/gifs/search?api_key=dc6zaTOxFJmzC&q=';
10
   giphyApi = 'https://api.giphy.com/v1/gifs/search?api_key=dc6zaTOxFJmzC&q=';
10
 
11
 
11
   constructor(public http: Http) {
12
   constructor(public http: Http) {

client/src/service-worker.js → ionic-beer/src/service-worker.js View File


client/src/theme/variables.scss → ionic-beer/src/theme/variables.scss View File

24
 // The "primary" color is the only required color in the map.
24
 // The "primary" color is the only required color in the map.
25
 
25
 
26
 $colors: (
26
 $colors: (
27
-  primary:    #387ef5,
27
+  primary:    #488aff,
28
   secondary:  #32db64,
28
   secondary:  #32db64,
29
   danger:     #f53d3d,
29
   danger:     #f53d3d,
30
   light:      #f4f4f4,
30
   light:      #f4f4f4,

client/tsconfig.json → ionic-beer/tsconfig.json View File


client/tslint.json → ionic-beer/tslint.json View File


+ 1
- 1
server/.mvn/wrapper/maven-wrapper.properties View File

1
-distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.9/apache-maven-3.3.9-bin.zip
1
+distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.9/apache-maven-3.3.9-bin.zip

+ 11
- 14
server/mvnw View File

184
 
184
 
185
 CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
185
 CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
186
 
186
 
187
+# For Cygwin, switch paths to Windows format before running java
188
+if $cygwin; then
189
+  [ -n "$M2_HOME" ] &&
190
+    M2_HOME=`cygpath --path --windows "$M2_HOME"`
191
+  [ -n "$JAVA_HOME" ] &&
192
+    JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
193
+  [ -n "$CLASSPATH" ] &&
194
+    CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
195
+fi
196
+
187
 # traverses directory structure from process work directory to filesystem root
197
 # traverses directory structure from process work directory to filesystem root
188
 # first directory with .mvn subdirectory is considered project base directory
198
 # first directory with .mvn subdirectory is considered project base directory
189
 find_maven_basedir() {
199
 find_maven_basedir() {
209
 export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)}
219
 export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)}
210
 MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
220
 MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
211
 
221
 
212
-# For Cygwin, switch paths to Windows format before running java
213
-if $cygwin; then
214
-  [ -n "$M2_HOME" ] &&
215
-    M2_HOME=`cygpath --path --windows "$M2_HOME"`
216
-  [ -n "$JAVA_HOME" ] &&
217
-    JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
218
-  [ -n "$CLASSPATH" ] &&
219
-    CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
220
-  [ -n "$MAVEN_PROJECTBASEDIR" ] &&
221
-    MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
222
-fi
223
-
224
 # Provide a "standardized" way to retrieve the CLI args that will
222
 # Provide a "standardized" way to retrieve the CLI args that will
225
 # work with both Windows and non-Windows executions.
223
 # work with both Windows and non-Windows executions.
226
 MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
224
 MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
228
 
226
 
229
 WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
227
 WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
230
 
228
 
231
-# avoid using MAVEN_CMD_LINE_ARGS below since that would loose parameter escaping in $@
232
 exec "$JAVACMD" \
229
 exec "$JAVACMD" \
233
   $MAVEN_OPTS \
230
   $MAVEN_OPTS \
234
   -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
231
   -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
235
   "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
232
   "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
236
-  ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
233
+  ${WRAPPER_LAUNCHER} "$@"

+ 0
- 0
server/mvnw.cmd View File


Some files were not shown because too many files changed in this diff