{"id":22560,"date":"2018-09-04T20:42:29","date_gmt":"2018-09-04T17:42:29","guid":{"rendered":"http:\/\/www.webcodegeeks.com\/?p=22560"},"modified":"2018-09-19T09:44:16","modified_gmt":"2018-09-19T06:44:16","slug":"single-page-application-node-angular","status":"publish","type":"post","link":"https:\/\/www.webcodegeeks.com\/javascript\/node-js\/single-page-application-node-angular\/","title":{"rendered":"Build a CRUD-y Single Page Application with Node and Angular"},"content":{"rendered":"<p><span style=\"font-size: 20px;\"><b>\u201cI love writing authentication and authorization code.\u201d ~ No Web Developer Ever.<\/b> Tired of building the same login screens over and over? <a href=\"https:\/\/developer.okta.com\/signup\/?utm_source=Web%20Code%20Geeks&#038;utm_medium=Content%20Syndication&#038;utm_campaign=node%20angular%20crud\">Try the Okta API for hosted authentication, authorization, and multi-factor auth.<\/a> <\/span><\/p>\n<p>Even before the release of Angular 6, Angular had gone through some changes over the years. The biggest one was the jump from AngularJS (v1.x) to Angular (v2+), which included a lot of breaking syntax changes and made TypeScript the default language instead of JavaScript. TypeScript is actually a superset of JavaScript, but it allows you to have strongly typed functions and variables, and it will get compiled down to JavaScript so that it can still run in your browser. Given the popularity of of Angular and Node, it wouldn\u2019t be shocking if you were considering this stack for your next project.<\/p>\n<p>Today I\u2019ll show you how you to build a secure single-page app with basic CRUD functionality. You\u2019ll use <a href=\"https:\/\/developer.okta.com\/docs\/api\/resources\/oidc?utm_source=Web%20Code%20Geeks&amp;utm_medium=Content%20Syndication&amp;utm_campaign=node%20angular%20crud\">Okta\u2019s OpenID Connect (OIDC) API<\/a> to handle authentication. Okta provides a simple to use <a href=\"https:\/\/github.com\/okta\/okta-oidc-js\/tree\/master\/packages\/okta-angular\">Angular SDK<\/a> to get you up and running very quickly. On the backend, I\u2019ll show you how to use the <a href=\"https:\/\/github.com\/okta\/okta-oidc-js\/tree\/master\/packages\/jwt-verifier\">Okta JWT Verifier<\/a> to ensure that the user is properly authenticated before serving any sensitive content.<\/p>\n<p>We\u2019ll be working with Angular 6 for this project, so you can get a feel for some of the changes and news features (read more about them in our <a href=\"https:\/\/developer.okta.com\/blog\/2018\/05\/09\/upgrade-to-angular-6?utm_source=Web%20Code%20Geeks&amp;utm_medium=Content%20Syndication&amp;utm_campaign=node%20angular%20crud\">Angular 6: What\u2019s New, and Why Upgrade? post<\/a>).<\/p>\n<p>Let\u2019s get started!<\/p>\n<h2>Create Your Angular 6 App<\/h2>\n<p>The Angular team maintains a wonderful command line interface called the <a href=\"https:\/\/cli.angular.io\/\">Angular CLI<\/a> that makes creating new Angular apps a breeze. It also has a ton of blueprints for generating new classes, components, services, and more. To install it with <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">npm<\/code>, run the following command:<\/p>\n<pre class=\"brush:bash; gutter: false\">npm i -g @angular\/cli@6.0.8<\/pre>\n<blockquote><p><strong>Note<\/strong>: I\u2019m including version numbers used at the time of writing to help ensure this tutorial works in the future. It\u2019s possible that newer versions may still work, but some adjustments might be necessary.<\/p><\/blockquote>\n<p>You should now have the CLI installed as a command called <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">ng<\/code>. To bootstrap a new app, type the following:<\/p>\n<pre class=\"brush:bash; gutter: false\">ng new okta-node-angular-example\r\ncd okta-node-angular-example<\/pre>\n<p>Angular CLI will automatically install packages for you after creating the folder with the bare project. It will also initialize a git repository for you with an initial commit ready to go, so you can start tracking changes very easily.<\/p>\n<p>To start the app, run the following:<\/p>\n<pre class=\"brush:bash; gutter: false\">npm start<\/pre>\n<p>You should now be able to access a very simple default app at <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">http:\/\/localhost:4200<\/code>. When you make changes to the code, the page will automatically refresh with the latest changes.<\/p>\n<p><a href=\"http:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/ng-homepage-ddbda9b0addc1b39ff05d07bc213c5d43aa91939a16f41672c02d36a9ddb8e09.png\"><img decoding=\"async\" class=\"aligncenter wp-image-22563\" style=\"border: none;\" src=\"http:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/ng-homepage-ddbda9b0addc1b39ff05d07bc213c5d43aa91939a16f41672c02d36a9ddb8e09.png\" alt=\"Single Page Application\" width=\"820\" height=\"941\" srcset=\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/ng-homepage-ddbda9b0addc1b39ff05d07bc213c5d43aa91939a16f41672c02d36a9ddb8e09.png 1219w, https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/ng-homepage-ddbda9b0addc1b39ff05d07bc213c5d43aa91939a16f41672c02d36a9ddb8e09-261x300.png 261w, https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/ng-homepage-ddbda9b0addc1b39ff05d07bc213c5d43aa91939a16f41672c02d36a9ddb8e09-768x881.png 768w, https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/ng-homepage-ddbda9b0addc1b39ff05d07bc213c5d43aa91939a16f41672c02d36a9ddb8e09-892x1024.png 892w\" sizes=\"(max-width: 820px) 100vw, 820px\" \/><\/a><\/p>\n<h2>Create a Basic Homepage with Material UI<\/h2>\n<p>To keep things looking nice without writing a lot of extra CSS, you can use a UI framework. The Angular team at Google maintains <a href=\"https:\/\/material.angular.io\/\">Angular Material<\/a>, a great framework for Angular that implements <a href=\"https:\/\/material.io\/\">Google\u2019s Material Design<\/a> principles.<\/p>\n<p>To add the dependencies needed for Angular Material, run the following command:<\/p>\n<pre class=\"brush:bash; gutter: false\">npm i @angular\/material@6.4.1 @angular\/cdk@6.4.1 hammerjs@2.0.8<\/pre>\n<p>The idea here will be to make an app bar across the top of the page that will be used for navigation. This will stay consistent throughout the app. The part that will change will be below and will vary from page to page. For now, create a very basic homepage component.<\/p>\n<pre class=\"brush:bash; gutter: false\">ng generate component home-page<\/pre>\n<p>This creates a few new files: one for the component\u2019s TypeScript logic, one for the CSS, one for the HTML template, and one for testing the component.<\/p>\n<p>To keep this super simple, just change the template to look like this:<\/p>\n<p><strong>src\/app\/home-page\/home-page.component.html<\/strong><\/p>\n<pre class=\"brush:bash; gutter: false\"><span class=\"nt\">&lt;h1&gt;<\/span>Welcome Home!<span class=\"nt\">&lt;\/h1&gt;<\/span><\/pre>\n<p>You can leave the other generated files the same.<\/p>\n<p>In Angular, you need to add new components to your app\u2019s module. This was done automatically for you with the <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">HomePageComponent<\/code>, but you\u2019ll need to add a few more to set up Angular Material.<\/p>\n<p>Right now, just add the Toolbar module and the animations module (the following diff also shows you the <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">HomePageComponent<\/code> that should have been added for you already):<\/p>\n<p><strong>src\/app\/app.module.ts<\/strong><\/p>\n<pre class=\"brush:bash; gutter: false\">@@ -1,14 +1,20 @@\r\n import { BrowserModule } from '@angular\/platform-browser';\r\n+import { BrowserAnimationsModule } from '@angular\/platform-browser\/animations';\r\n import { NgModule } from '@angular\/core';\r\n+import { MatToolbarModule } from '@angular\/material';\r\n\r\n import { AppComponent } from '.\/app.component';\r\n+import { HomePageComponent } from '.\/home-page\/home-page.component';\r\n\r\n @NgModule({\r\n   declarations: [\r\n-    AppComponent\r\n+    AppComponent,\r\n+    HomePageComponent\r\n   ],\r\n   imports: [\r\n-    BrowserModule\r\n+    BrowserModule,\r\n+    BrowserAnimationsModule,\r\n+    MatToolbarModule,\r\n   ],\r\n   providers: [],\r\n   bootstrap: [AppComponent]<\/pre>\n<p>Angular Material uses <a href=\"https:\/\/hammerjs.github.io\/\">Hammer.JS<\/a> for better touchscreen support. You already added the dependency earlier, so to add it to the page all you need to do is import it at the top of the app\u2019s entry script.<\/p>\n<p><strong>src\/main.ts<\/strong><\/p>\n<pre class=\"brush:bash; gutter: false\"><span class=\"k\">import<\/span> <span class=\"s1\">'hammerjs'<\/span><span class=\"p\">;<\/span><\/pre>\n<p>For the CSS, the default entry point is <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">src\/styles.css<\/code>, but each component also has its own CSS file for styles specific to that component. To finish setting up Angular Material and set some decent defaults to your page, add these styles:<\/p>\n<p><strong>src\/styles.css<\/strong><\/p>\n<pre class=\"brush:bash; gutter: false\">@import \"~@angular\/material\/prebuilt-themes\/indigo-pink.css\";\r\n@import \"https:\/\/fonts.googleapis.com\/icon?family=Material+Icons\";\r\n\r\nbody {\r\n  margin: 0;\r\n  font-family: Roboto, sans-serif;\r\n}\r\n\r\n* {\r\n  box-sizing: border-box;\r\n}<\/pre>\n<p>I went with <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">indigo-pink<\/code>, but there are a couple other prebuilt themes if you want something a little different. Here are the other prebuilt options at the time of this writing:<\/p>\n<ul>\n<li><code class=\"highlighter-rouge\" style=\"font-size: 13px;\">deeppurple-amber.css<\/code><\/li>\n<li><code class=\"highlighter-rouge\" style=\"font-size: 13px;\">pink-bluegrey.css<\/code><\/li>\n<li><code class=\"highlighter-rouge\" style=\"font-size: 13px;\">purple-green.css<\/code><\/li>\n<\/ul>\n<p>The toolbar itself is pretty simple. Go ahead and rewrite the <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">app<\/code> component template to look like this:<\/p>\n<p><strong>src\/app\/app.component.html<\/strong><\/p>\n<pre class=\"brush:bash; gutter: false\">&lt;mat-toolbar color=\"primary\"&gt;\r\n  &lt;span&gt;{{ title }}&lt;\/span&gt;\r\n&lt;\/mat-toolbar&gt;\r\n\r\n&lt;main&gt;\r\n  &lt;app-home-page&gt;&lt;\/app-home-page&gt;\r\n&lt;\/main&gt;<\/pre>\n<p>For now, <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">main<\/code> just contains the home page you created. Later on, you\u2019ll be replacing that with a router so that when the URL changes it renders a different page there.<\/p>\n<p>The <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">mat-toolbar<\/code> component was defined earlier in the <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">MatToolbarModule<\/code> you added to the <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">app<\/code> module.<\/p>\n<p>To fix the padding on the page, change the app\u2019s CSS like so:<\/p>\n<p><strong>src\/app\/app.component.css<\/strong><\/p>\n<pre class=\"brush:bash; gutter: false\">main {\r\n  padding: 16px;\r\n  width: 100%;\r\n}<\/pre>\n<p>That should be it to get a basic homepage up and running. Your site should now look like this:<\/p>\n<p><a href=\"http:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/basic-crud-homepage-7163c463a944ecdde482b33391a1b2609b07c73f4cc5339ec72cbc1983babdb2.png\"><img decoding=\"async\" class=\"aligncenter wp-image-22564\" style=\"border: none;\" src=\"http:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/basic-crud-homepage-7163c463a944ecdde482b33391a1b2609b07c73f4cc5339ec72cbc1983babdb2.png\" alt=\"Single Page Application\" width=\"820\" height=\"715\" srcset=\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/basic-crud-homepage-7163c463a944ecdde482b33391a1b2609b07c73f4cc5339ec72cbc1983babdb2.png 1098w, https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/basic-crud-homepage-7163c463a944ecdde482b33391a1b2609b07c73f4cc5339ec72cbc1983babdb2-300x261.png 300w, https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/basic-crud-homepage-7163c463a944ecdde482b33391a1b2609b07c73f4cc5339ec72cbc1983babdb2-768x669.png 768w, https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/basic-crud-homepage-7163c463a944ecdde482b33391a1b2609b07c73f4cc5339ec72cbc1983babdb2-1024x893.png 1024w\" sizes=\"(max-width: 820px) 100vw, 820px\" \/><\/a><\/p>\n<h2>Add Authentication to Your Node + Angular App with Okta<\/h2>\n<p>You would never ship your new app out to the Internet without secure <a href=\"https:\/\/developer.okta.com\/product\/user-management\/?utm_source=Web%20Code%20Geeks&amp;utm_medium=Content%20Syndication&amp;utm_campaign=node%20angular%20crud\">identity management<\/a>, right? Well, Okta makes that a lot easier and more scalable than what you\u2019re probably used to. Okta is a cloud service that allows developers to create, edit, and securely store user accounts and user account data, and connect them with one or multiple applications. Our API enables you to:<\/p>\n<ul>\n<li><a href=\"https:\/\/developer.okta.com\/product\/authentication\/?utm_source=Web%20Code%20Geeks&amp;utm_medium=Content%20Syndication&amp;utm_campaign=node%20angular%20crud\">Authenticate<\/a> and <a href=\"https:\/\/developer.okta.com\/product\/authorization\/?utm_source=Web%20Code%20Geeks&amp;utm_medium=Content%20Syndication&amp;utm_campaign=node%20angular%20crud\">authorize<\/a> your users<\/li>\n<li>Store data about your users<\/li>\n<li>Perform password-based and <a href=\"https:\/\/developer.okta.com\/authentication-guide\/social-login\/?utm_source=Web%20Code%20Geeks&amp;utm_medium=Content%20Syndication&amp;utm_campaign=node%20angular%20crud\">social login<\/a><\/li>\n<li>Secure your application with <a href=\"https:\/\/developer.okta.com\/use_cases\/mfa\/?utm_source=Web%20Code%20Geeks&amp;utm_medium=Content%20Syndication&amp;utm_campaign=node%20angular%20crud\">multi-factor authentication<\/a><\/li>\n<li>And much more! Check out our <a href=\"https:\/\/developer.okta.com\/documentation\/?utm_source=Web%20Code%20Geeks&amp;utm_medium=Content%20Syndication&amp;utm_campaign=node%20angular%20crud\">product documentation<\/a><\/li>\n<\/ul>\n<p>If you don\u2019t already have one, <a href=\"https:\/\/developer.okta.com\/signup\/?utm_source=Web%20Code%20Geeks&amp;utm_medium=Content%20Syndication&amp;utm_campaign=node%20angular%20crud\">sign up for a forever-free developer account<\/a>. You\u2019ll be given an Organization URL when you sign up, which will be how you log in to your developer console. After you log in to your developer console, navigate to <strong>Applications<\/strong>, then click <strong>Add Application<\/strong>. Select <strong>Single-Page App<\/strong>, then click <strong>Next<\/strong>.<\/p>\n<p>Since the app generated from Angular CLI runs on port 4200 by default, you should set that as the Base URI and Login Redirect URI. Your settings should look like the following:<\/p>\n<p><a href=\"http:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/create-new-application-settings-0fcc92ff623498d1363f98678f3bde1ad0adcfc35cd0e8c6ec46e0412d8f24f5.png\"><img decoding=\"async\" class=\"aligncenter wp-image-22565\" style=\"border: none;\" src=\"http:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/create-new-application-settings-0fcc92ff623498d1363f98678f3bde1ad0adcfc35cd0e8c6ec46e0412d8f24f5.png\" alt=\"Single Page Application\" width=\"820\" height=\"752\" srcset=\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/create-new-application-settings-0fcc92ff623498d1363f98678f3bde1ad0adcfc35cd0e8c6ec46e0412d8f24f5.png 1440w, https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/create-new-application-settings-0fcc92ff623498d1363f98678f3bde1ad0adcfc35cd0e8c6ec46e0412d8f24f5-300x275.png 300w, https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/create-new-application-settings-0fcc92ff623498d1363f98678f3bde1ad0adcfc35cd0e8c6ec46e0412d8f24f5-768x704.png 768w, https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/create-new-application-settings-0fcc92ff623498d1363f98678f3bde1ad0adcfc35cd0e8c6ec46e0412d8f24f5-1024x939.png 1024w\" sizes=\"(max-width: 820px) 100vw, 820px\" \/><\/a><\/p>\n<p>Click <strong>Done<\/strong> to save your app, then copy your <strong>Client ID<\/strong>.<\/p>\n<p>Create a new file in your project called <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">src\/environments\/.env.js<\/code>. In it you should add two variables:<\/p>\n<ul>\n<li><em>oktaOrgURL<\/em>: This will be the organization URL you received when you signed up for Okta: <code class=\"highlighter-rouge\" style=\"font-size: 13px;\"><span class=\"okta-preview-domain\">https:\/\/{yourOktaDomain}<\/span><\/code><\/li>\n<li><em>oktaClientId<\/em>: This is the Client ID you received when creating the new application in the Okta Developer Console<\/li>\n<\/ul>\n<p>You\u2019ll also be using this file in the Node server later, which won\u2019t be using TypeScript, so make sure this uses <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">module.exports<\/code> instead of the es6 <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">export<\/code> syntax:<\/p>\n<p><strong>src\/environments\/.env.js<\/strong><\/p>\n<pre class=\"brush:bash; gutter: false\">module.exports = {\r\n  oktaOrgURL: '{yourOktaDomain}',\r\n  oktaClientId: '{clientId}'\r\n};<\/pre>\n<p>Angular CLI by default loads environment variables for development and production in two separate files that are stored in source control. To keep sensitive information out of source control and make it so that others can reuse the code easily, you can import this newly created file inside both of those. Prevent it from getting added to git by adding it to <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">.gitignore<\/code>:<\/p>\n<pre class=\"brush:bash; gutter: false\">echo .env.js &gt;&gt; .gitignore<\/pre>\n<p>Now add it to your dev and production environments:<\/p>\n<p><strong>src\/environments\/environment.ts<\/strong><\/p>\n<pre class=\"brush:bash; gutter: false\">import dotenvVariables from '.\/.env.js';\r\n\r\nexport const environment = {\r\n  production: false,\r\n  ...dotenvVariables\r\n};<\/pre>\n<p><strong>src\/environments\/environment.prod.ts<\/strong><\/p>\n<pre class=\"brush:bash; gutter: false\">import dotenvVariables from '.\/.env.js';\r\n\r\nexport const environment = {\r\n  production: true,\r\n  ...dotenvVariables\r\n};<\/pre>\n<p>The easiest way to add Authentication with Okta to an Angular app is to use <a href=\"https:\/\/github.com\/okta\/okta-oidc-js\/tree\/master\/packages\/okta-angular\">Okta\u2019s Angular SDK<\/a>. It was written for an older version of RxJS, so you\u2019ll need to add <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">rxjs-compat<\/code> as well to allow it to work with the older modules.<\/p>\n<pre class=\"brush:bash; gutter: false\">npm i @okta\/okta-angular@1.0.1 rxjs-compat@6.2.2<\/pre>\n<p>I\u2019ll show you how to create a Post Manager. For now, just let Angular CLI create a component for you:<\/p>\n<pre class=\"brush:bash; gutter: false\">ng g c posts-manager<\/pre>\n<p>To get Okta Angular set up, you\u2019ll need to import the module in your <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">app<\/code> module. You\u2019ll also need to create a route for the callback, so now would also be a good time to add routes for your different pages. You\u2019ll also need to add the <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">MatButtonModule<\/code> in order to create buttons (and links that look like buttons) in your app.<\/p>\n<p><strong>src\/app.module.ts<\/strong><\/p>\n<pre class=\"brush:bash; gutter: false\">import { Routes, RouterModule } from '@angular\/router';\r\nimport {\r\n  MatToolbarModule,\r\n  MatButtonModule,\r\n} from '@angular\/material';\r\nimport { OktaAuthGuard, OktaAuthModule, OktaCallbackComponent } from '@okta\/okta-angular';\r\n\r\nimport { environment } from '..\/environments\/environment';\r\nimport { AuthGuard } from '.\/auth.guard';\r\nimport { HomePageComponent } from '.\/home-page\/home-page.component';\r\nimport { PostsManagerComponent } from '.\/posts-manager\/posts-manager-component';\r\n\r\nconst oktaConfig = {\r\n  issuer: `${environment.oktaOrgURL}\/oauth2\/default`,\r\n  redirectUri: `${window.location.origin}\/implicit\/callback`,\r\n  clientId: environment.oktaClientId,\r\n};\r\n\r\nconst appRoutes: Routes = [\r\n  {\r\n    path: '',\r\n    component: HomePageComponent,\r\n  },\r\n  {\r\n    path: 'posts-manager',\r\n    component: PostsManagerComponent,\r\n    canActivate: [OktaAuthGuard],\r\n  },\r\n  {\r\n    path: 'implicit\/callback',\r\n    component: OktaCallbackComponent,\r\n  },\r\n];\r\n\r\n\/\/ Later on in the @NgModule decorator:\r\n\r\n@NgModule({\r\n  \/\/ ...\r\n  imports: [\r\n    \/\/ After the other imports already in the file...\r\n    MatButtonModule,\r\n    RouterModule.forRoot(appRoutes),\r\n    OktaAuthModule.initAuth(oktaConfig),\r\n  ],\r\n  providers: [OktaAuthGuard],\r\n  \/\/ ...\r\n})\r\n\/\/ ...<\/pre>\n<p>The OktaAuthGuard provider will make it so that when you try to go to the Posts Manager page, you will be sent to Okta for authentication. You should only be able to load the page if you\u2019re securely authenticated.<\/p>\n<p>You\u2019ll need to modify your app component in a few ways as well. For the toolbar, you\u2019ll want to add some navigation links and a button to log in and out of the app. Also, instead of always displaying the homepage component, you\u2019ll give the router handle that by giving it an outlet.<\/p>\n<p><strong>src\/app\/app.component.html<\/strong><\/p>\n<pre class=\"brush:bash; gutter: false\">&lt;mat-toolbar color=\"primary\"&gt;\r\n  &lt;span class=\"title\"&gt;{{ title }}&lt;\/span&gt;\r\n\r\n  &lt;a mat-button routerLink=\"\/\"&gt;Home&lt;\/a&gt;\r\n  &lt;a mat-button routerLink=\"\/posts-manager\"&gt;Posts Manager&lt;\/a&gt;\r\n\r\n  &lt;span class=\"spacer\"&gt;&lt;\/span&gt;\r\n\r\n  &lt;button *ngIf=\"!isAuthenticated\" mat-button (click)=\"login()\"&gt;Login&lt;\/button&gt;\r\n  &lt;button *ngIf=\"isAuthenticated\" mat-button (click)=\"logout()\"&gt;Logout&lt;\/button&gt;\r\n&lt;\/mat-toolbar&gt;\r\n\r\n&lt;main&gt;\r\n  &lt;router-outlet&gt;&lt;\/router-outlet&gt;\r\n&lt;\/main&gt;<\/pre>\n<p>Now add some styles to the end of the <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">app<\/code> component\u2019s CSS file make it so the login button appears on the far right, and there\u2019s a little space between the app\u2019s title and the navigation links:<\/p>\n<p><strong>src\/app\/app.component.css<\/strong><\/p>\n<pre class=\"brush:bash; gutter: false\">.title {\r\n  margin-right: 16px;\r\n}\r\n\r\n.spacer {\r\n  flex: 1;\r\n}<\/pre>\n<p>The component class at this point doesn\u2019t actually know whether it\u2019s authenticated or not though, so <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">isAuthenticated<\/code> in the template will just always be falsy. There\u2019s also no <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">login<\/code> or <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">logout<\/code> function yet. To add those, make the following changes to your <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">app<\/code> component:<\/p>\n<p><strong>src\/app\/app.component.ts<\/strong><\/p>\n<pre class=\"brush:bash; gutter: false\">@@ -1,10 +1,30 @@\r\n-import { Component } from '@angular\/core';\r\n+import { Component, OnInit } from '@angular\/core';\r\n+import { OktaAuthService } from '@okta\/okta-angular';\r\n\r\n @Component({\r\n   selector: 'app-root',\r\n   templateUrl: '.\/app.component.html',\r\n   styleUrls: ['.\/app.component.css']\r\n })\r\n-export class AppComponent {\r\n+export class AppComponent implements OnInit {\r\n   title = 'My Angular App';\r\n+  isAuthenticated: boolean;\r\n+\r\n+  constructor(public oktaAuth: OktaAuthService) {\r\n+    this.oktaAuth.$authenticationState.subscribe(\r\n+      (isAuthenticated: boolean) =&gt; this.isAuthenticated = isAuthenticated\r\n+    );\r\n+  }\r\n+\r\n+  async ngOnInit() {\r\n+    this.isAuthenticated = await this.oktaAuth.isAuthenticated();\r\n+  }\r\n+\r\n+  login() {\r\n+    this.oktaAuth.loginRedirect();\r\n+  }\r\n+\r\n+  logout() {\r\n+    this.oktaAuth.logout();\r\n+  }\r\n }<\/pre>\n<p>You should now be able to log in and out via Okta, and you should only be able to access the Posts Manager page once you\u2019re authenticated. When you click the Login button or try to go to the Posts Manager, you\u2019ll be redirected to your Okta organization URL to handle authentication. You can log in with the same credentials you use in your developer console.<\/p>\n<p>Your app should now look like this:<\/p>\n<p><a href=\"http:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/okta-sign-in-9d686e86e4d63d4e5ceb12e5cd266f3224311a8ca59d1caacfb040c7dc7de5d0.png\"><img decoding=\"async\" class=\"aligncenter wp-image-22566\" style=\"border: none;\" src=\"http:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/okta-sign-in-9d686e86e4d63d4e5ceb12e5cd266f3224311a8ca59d1caacfb040c7dc7de5d0.png\" alt=\"Single Page Application\" width=\"820\" height=\"629\" srcset=\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/okta-sign-in-9d686e86e4d63d4e5ceb12e5cd266f3224311a8ca59d1caacfb040c7dc7de5d0.png 1208w, https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/okta-sign-in-9d686e86e4d63d4e5ceb12e5cd266f3224311a8ca59d1caacfb040c7dc7de5d0-300x230.png 300w, https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/okta-sign-in-9d686e86e4d63d4e5ceb12e5cd266f3224311a8ca59d1caacfb040c7dc7de5d0-768x589.png 768w, https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/okta-sign-in-9d686e86e4d63d4e5ceb12e5cd266f3224311a8ca59d1caacfb040c7dc7de5d0-1024x785.png 1024w\" sizes=\"(max-width: 820px) 100vw, 820px\" \/><\/a><\/p>\n<p><a href=\"http:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/homepage-with-nav-00824599aef6756b8b036ac61ac1b31d8b9423355a370c8d228913936c170ad5.png\"><img decoding=\"async\" class=\"aligncenter wp-image-22567\" style=\"border: none;\" src=\"http:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/homepage-with-nav-00824599aef6756b8b036ac61ac1b31d8b9423355a370c8d228913936c170ad5.png\" alt=\"Single Page Application\" width=\"820\" height=\"488\" srcset=\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/homepage-with-nav-00824599aef6756b8b036ac61ac1b31d8b9423355a370c8d228913936c170ad5.png 1608w, https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/homepage-with-nav-00824599aef6756b8b036ac61ac1b31d8b9423355a370c8d228913936c170ad5-300x179.png 300w, https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/homepage-with-nav-00824599aef6756b8b036ac61ac1b31d8b9423355a370c8d228913936c170ad5-768x457.png 768w, https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/homepage-with-nav-00824599aef6756b8b036ac61ac1b31d8b9423355a370c8d228913936c170ad5-1024x609.png 1024w\" sizes=\"(max-width: 820px) 100vw, 820px\" \/><\/a><a href=\"http:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/blank-posts-manager-27f8c3816b090630dec1c7d35a3e46ee4a34a95f6d2c14136e1fb8b5602b1ac1.png\"><img decoding=\"async\" class=\"aligncenter wp-image-22568\" style=\"border: none;\" src=\"http:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/blank-posts-manager-27f8c3816b090630dec1c7d35a3e46ee4a34a95f6d2c14136e1fb8b5602b1ac1.png\" alt=\"Single Page Application\" width=\"820\" height=\"488\" srcset=\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/blank-posts-manager-27f8c3816b090630dec1c7d35a3e46ee4a34a95f6d2c14136e1fb8b5602b1ac1.png 1608w, https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/blank-posts-manager-27f8c3816b090630dec1c7d35a3e46ee4a34a95f6d2c14136e1fb8b5602b1ac1-300x179.png 300w, https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/blank-posts-manager-27f8c3816b090630dec1c7d35a3e46ee4a34a95f6d2c14136e1fb8b5602b1ac1-768x457.png 768w, https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/blank-posts-manager-27f8c3816b090630dec1c7d35a3e46ee4a34a95f6d2c14136e1fb8b5602b1ac1-1024x609.png 1024w\" sizes=\"(max-width: 820px) 100vw, 820px\" \/><\/a><\/p>\n<h2>Add a Backend REST API Server<\/h2>\n<p>Now that users can securely authenticate, you can build the REST API server to perform CRUD operations on a post model. You\u2019ll need to add quite a few dependencies to your project at this point:<\/p>\n<pre class=\"brush:bash; gutter: false\"># dependencies\r\nnpm i @okta\/jwt-verifier@0.0.12 body-parser@1.18.3 cors@2.8.4 epilogue@0.7.1 express@4.16.3 sequelize@4.38.0 sqlite@2.9.2\r\n\r\n# dev dependencies (-D is short for --save-dev)\r\nnpm i -D npm-run-all@4.1.3 nodemon@1.18.3<\/pre>\n<p>Create a new folder for the server under the src directory:<\/p>\n<pre class=\"brush:bash; gutter: false\">mkdir src\/server<\/pre>\n<p>Now create a new file <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">src\/server\/index.js<\/code>. To keep this simple we will just use a single file, but you could have a whole subtree of files in this folder. Keeping it in a separate folder lets you watch for changes just in this subdirectory and reload the server only when making changes to this file, instead of anytime any file in <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">src<\/code> changes. I\u2019ll post the whole file and then explain some key sections below.<\/p>\n<p><strong>src\/server\/index.js<\/strong><\/p>\n<pre class=\"brush:bash; gutter: false\">const express = require('express');\r\nconst cors = require('cors');\r\nconst bodyParser = require('body-parser');\r\nconst Sequelize = require('sequelize');\r\nconst epilogue = require('epilogue');\r\nconst OktaJwtVerifier = require('@okta\/jwt-verifier');\r\n\r\nconst { oktaClientId, oktaOrgURL } = require('..\/environments\/.env.js');\r\n\r\nconst oktaJwtVerifier = new OktaJwtVerifier({\r\n  clientId: oktaClientId,\r\n  issuer: `{yourOktaDomain}\/oauth2\/default`\r\n});\r\n\r\nconst app = express();\r\napp.use(cors());\r\napp.use(bodyParser.json());\r\n\r\napp.use(async (req, res, next) =&gt; {\r\n  try {\r\n    if (!req.headers.authorization)\r\n      throw new Error('Authorization header is required');\r\n\r\n    const accessToken = req.headers.authorization.trim().split(' ')[1];\r\n    await oktaJwtVerifier.verifyAccessToken(accessToken);\r\n    next();\r\n  } catch (error) {\r\n    next(error.message);\r\n  }\r\n});\r\n\r\nconst database = new Sequelize({\r\n  dialect: 'sqlite',\r\n  storage: '.\/test.sqlite'\r\n});\r\n\r\nconst Post = database.define('posts', {\r\n  title: Sequelize.STRING,\r\n  body: Sequelize.TEXT\r\n});\r\n\r\nepilogue.initialize({ app, sequelize: database });\r\n\r\nepilogue.resource({\r\n  model: Post,\r\n  endpoints: ['\/posts', '\/posts\/:id']\r\n});\r\n\r\nconst port = process.env.SERVER_PORT || 4201;\r\n\r\ndatabase.sync().then(() =&gt; {\r\n  app.listen(port, () =&gt; {\r\n    console.log(`Listening on port ${port}`);\r\n  });\r\n});<\/pre>\n<p>This sets up the JWT verifier using your okta credentials.<\/p>\n<pre class=\"brush:bash; gutter: false\">const { oktaClientId, oktaOrgURL } = require('..\/environments\/.env.js');\r\n\r\nconst oktaJwtVerifier = new OktaJwtVerifier({\r\n  clientId: oktaClientId,\r\n  issuer: `{yourOktaDomain}\/oauth2\/default`\r\n});<\/pre>\n<p>This sets up the HTTP server and adds some settings to allow for Cross-Origin Resource Sharing (CORS) and will automatically parse JSON.<\/p>\n<pre class=\"brush:bash; gutter: false\">const app = express();\r\napp.use(cors());\r\napp.use(bodyParser.json());<\/pre>\n<p>Here is where you check that a user is properly authenticated. First, throw an error if there is no <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">Authorization<\/code> header, which is how you will send the authorization token. The token will actually look like <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">Bearer aLongBase64String<\/code>. You want to pass the Base 64 string to the Okta JWT Verifier to check that the user is properly authenticated. The verifier will initially send a request to the issuer to get a list of valid signatures, and will then check locally that the token is valid. On subsequent requests, this can be done locally unless it finds a claim that it doesn\u2019t have signatures for yet.<\/p>\n<p>If everything looks good, the call to <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">next()<\/code> tells Express to go ahead and continue processing the request. If however, the claim is invalid, an error will be thrown. The error is then passed into <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">next<\/code> to tell Express that something went wrong. Express will then send an error back to the client instead of proceeding.<\/p>\n<pre class=\"brush:bash; gutter: false\">app.use(async (req, res, next) =&gt; {\r\n  try {\r\n    if (!req.headers.authorization)\r\n      throw new Error('Authorization header is required');\r\n\r\n    const accessToken = req.headers.authorization.trim().split(' ')[1];\r\n    await oktaJwtVerifier.verifyAccessToken(accessToken);\r\n    next();\r\n  } catch (error) {\r\n    next(error.message);\r\n  }\r\n});<\/pre>\n<p>Here is where you set up Sequelize. This is a quick way of creating database models. You can Sequelize with a wide variety of databases, but here you can just use SQLite to get up and running quickly without any other dependencies.<\/p>\n<pre class=\"brush:bash; gutter: false\">const database = new Sequelize({\r\n  dialect: 'sqlite',\r\n  storage: '.\/test.sqlite'\r\n});\r\n\r\nconst Post = database.define('posts', {\r\n  title: Sequelize.STRING,\r\n  body: Sequelize.TEXT\r\n});<\/pre>\n<p>Epilogue works well with Sequelize and Express. It binds the two together like glue, creating a set of CRUD endpoints with just a couple lines of code. First, you initialize Epilogue with the Express app and the Sequelize database model. Next, you tell it to create your endpoints for the <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">Post<\/code> model: one for a list of posts, which will have <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">POST<\/code> and <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">GET<\/code> methods; and one for individual posts, which will have <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">GET<\/code>, <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">PUT<\/code>, and <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">DELETE<\/code> methods.<\/p>\n<pre class=\"brush:bash; gutter: false\">epilogue.initialize({ app, sequelize: database });\r\n\r\nepilogue.resource({\r\n  model: Post,\r\n  endpoints: ['\/posts', '\/posts\/:id']\r\n});<\/pre>\n<p>The last part of the server is where you tell Express to start listening for HTTP requests. You need to tell sequelize to initialize the database, and when it\u2019s done it\u2019s OK for Express to start listening on the port you decide. By default, since the Angular app is using <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">4200<\/code>, we\u2019ll just add one to make it port <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">4201<\/code>.<\/p>\n<pre class=\"brush:bash; gutter: false\">const port = process.env.SERVER_PORT || 4201;\r\n\r\ndatabase.sync().then(() =&gt; {\r\n  app.listen(port, () =&gt; {\r\n    console.log(`Listening on port ${port}`);\r\n  });\r\n});<\/pre>\n<p>Now you can make a couple small changes to <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">package.json<\/code> to make it easier to run both the frontend and backend at the same time. Replace the default <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">start<\/code> script and add a couple others, so your scripts section looks like this:<\/p>\n<p><strong>package.json<\/strong><\/p>\n<pre class=\"brush:bash; gutter: false\">\"scripts\": {\r\n    \"ng\": \"ng\",\r\n    \"start\": \"npm-run-all --parallel watch:server start:web\",\r\n    \"start:web\": \"ng serve\",\r\n    \"start:server\": \"node src\/server\",\r\n    \"watch:server\": \"nodemon --watch src\/server src\/server\",\r\n    \"build\": \"ng build\",\r\n    \"test\": \"ng test\",\r\n    \"lint\": \"ng lint\",\r\n    \"e2e\": \"ng e2e\"\r\n  },<\/pre>\n<p>Now you can simply run <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">npm start<\/code> and both the server and the Angular app will run at the same time, reloading whenever relevant changes are made. If you need to change the port for any reason, you can change the Angular app\u2019s port and the server\u2019s port with the <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">PORT<\/code> and <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">SERVER_PORT<\/code> environment variables, respectively. For example, <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">PORT=8080 SERVER_PORT=8081 npm start<\/code>.<\/p>\n<h2>Add the Posts Manager Page<\/h2>\n<p>Now that you have a backend to manage your posts, you can link up the frontend by adding another page. This will send requests to fetch, create, edit, and delete posts. It will also send the required authorization token along with each request so the server knows that you\u2019re a valid user.<\/p>\n<p>There are a couple utilities that will come in handy, so go ahead and add those as dependencies:<\/p>\n<pre class=\"brush:bash; gutter: false\">npm i lodash@4.17.10 moment@2.22.2<\/pre>\n<p>You\u2019ll also need a few more Material modules, as well as a Forms module that comes with angular:<\/p>\n<p><strong>src\/app\/app.module.ts<\/strong><\/p>\n<pre class=\"brush:bash; gutter: false\">@@ -2,9 +2,14 @@ import { BrowserModule } from '@angular\/platform-browser';\r\n import { BrowserAnimationsModule } from '@angular\/platform-browser\/animations';\r\n import { Routes, RouterModule } from '@angular\/router';\r\n import { NgModule } from '@angular\/core';\r\n+import { FormsModule } from '@angular\/forms';\r\n import {\r\n   MatToolbarModule,\r\n   MatButtonModule,\r\n+  MatIconModule,\r\n+  MatExpansionModule,\r\n+  MatFormFieldModule,\r\n+  MatInputModule,\r\n } from '@angular\/material';\r\n import { OktaAuthModule, OktaCallbackComponent } from '@okta\/okta-angular';\r\n\r\n@@ -46,8 +51,14 @@ const appRoutes: Routes = [\r\n     BrowserModule,\r\n     BrowserAnimationsModule,\r\n\r\n+    FormsModule,\r\n+\r\n     MatToolbarModule,\r\n     MatButtonModule,\r\n+    MatIconModule,\r\n+    MatExpansionModule,\r\n+    MatFormFieldModule,\r\n+    MatInputModule,\r\n\r\n     RouterModule.forRoot(appRoutes),\r\n     OktaAuthModule.initAuth(oktaConfig),<\/pre>\n<h3>Create a Post Class<\/h3>\n<p>Create a new file in the <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">posts-manager<\/code> folder to define what a Post should look like. The <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">Post<\/code> class will contain some data as well as have some functions to help manage the post itself. Again, I\u2019ll show you the full file then explain each part in detail:<\/p>\n<p><strong>src\/app\/posts-manager\/post.ts<\/strong><\/p>\n<pre class=\"brush:bash; gutter: false\">import * as moment from 'moment';\r\n\r\nimport { PostsManagerComponent } from '.\/posts-manager.component';\r\n\r\nexport interface PostData {\r\n  id?: number;\r\n  title?: string;\r\n  body?: string;\r\n  updatedAt?: string;\r\n}\r\n\r\nexport class Post implements PostData {\r\n  id: number;\r\n  title: string;\r\n  body: string;\r\n  updatedAt: string;\r\n\r\n  loading = false;\r\n  open = false;\r\n\r\n  constructor(private data: PostData, private manager: PostsManagerComponent) {\r\n    Object.assign(this, this.data);\r\n  }\r\n\r\n  get isDirty(): boolean {\r\n    return this.data.title !== this.title || this.data.body !== this.body;\r\n  }\r\n\r\n  get updatedAtString(): string {\r\n    const { updatedAt } = this;\r\n    return updatedAt ? `Updated ${moment(updatedAt).fromNow()}` : '';\r\n  }\r\n\r\n  serialize(data: Post | PostData = this) {\r\n    const { id, title, body, updatedAt } = data;\r\n    return { id, title, body, updatedAt };\r\n  }\r\n\r\n  toJSON() {\r\n    return this.serialize();\r\n  }\r\n\r\n  reset() {\r\n    Object.assign(this, this.serialize(this.data));\r\n  }\r\n\r\n  async save() {\r\n    this.loading = true;\r\n\r\n    const data = await this.manager.api.savePost(this);\r\n\r\n    if (data) {\r\n      Object.assign(this.data, data);\r\n      this.reset();\r\n    }\r\n\r\n    this.loading = false;\r\n  }\r\n\r\n  async delete() {\r\n    this.loading = true;\r\n\r\n    if (await this.manager.api.deletePost(this)) {\r\n      this.manager.posts.splice(this.manager.posts.indexOf(this), 1);\r\n    }\r\n\r\n    this.loading = false;\r\n  }\r\n}<\/pre>\n<p>TypeScript allows you to define interfaces, or types, to define how some data should look. In this case, all the data fields are optional (the <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">?<\/code> at the end of the key): in a new post, none of these values will exist yet.<\/p>\n<pre class=\"brush:bash; gutter: false\">export interface PostData {\r\n  id?: number;\r\n  title?: string;\r\n  body?: string;\r\n  updatedAt?: string;\r\n}<\/pre>\n<p>You can also ensure that a class implements an interface. This means you will get an error unless the class you\u2019re creating has the fields that are required in the interface. It also means that if something is expecting <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">PostData<\/code>, then a <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">Post<\/code> should work as well because it\u2019s guaranteed to have the same fields.<\/p>\n<pre class=\"brush:bash; gutter: false\">export class Post implements PostData {\r\n  id: number;\r\n  title: string;\r\n  body: string;\r\n  updatedAt: string;\r\n\r\n  \/\/ ...\r\n}<\/pre>\n<p>The template that renders the posts will use <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">open<\/code> to determine whether it should be showing details for the post, and <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">loading<\/code> to determine whether certain elements should be disabled or not.<\/p>\n<pre class=\"brush:bash; gutter: false\">loading = false;\r\nopen = false;<\/pre>\n<p>The Post will need to access a few properties from the Post Manager. For one, this lets you delete a post from the Post class itself. Also, the Post Manager will have a service injected into it that connects to the backend. By setting <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">private data: PostData<\/code> in the constructor, you\u2019re saying that the Post Manager should pass in some data, and it will get assigned to <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">this.data<\/code> (likewise, the Post Manager should pass itself in, and it will get assigned to <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">this.manager<\/code>).<\/p>\n<p>The <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">Object.assign<\/code> call takes the values on <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">data<\/code> and assigns them to itself. Initially then, <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">this.title<\/code> should be identical to <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">this.data.title<\/code>. By creating a getter function of <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">isDirty<\/code>, that allows you to check if the data has changed at all, so you know if it needs to be saved.<\/p>\n<pre class=\"brush:bash; gutter: false\">constructor(private data: PostData, private manager: PostsManagerComponent) {\r\n    Object.assign(this, this.data);\r\n  }\r\n\r\n  get isDirty(): boolean {\r\n    return (\r\n      this.data.title !== this.title ||\r\n      this.data.body !== this.body\r\n    );\r\n  }<\/pre>\n<p>The <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">updatedAt<\/code> value will just be a machine-readable date string. It doesn\u2019t look very pretty though. You can use <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">moment<\/code> to format it in a way that is nicer for humans to read. The following will give you strings like <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">Updated a few seconds ago<\/code> or <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">Updated 2 days ago<\/code>.<\/p>\n<pre class=\"brush:bash; gutter: false\">  get updatedAtString(): string {\r\n    const { updatedAt } = this;\r\n    return updatedAt ? `Updated ${moment(updatedAt).fromNow()}` : '';\r\n  }<\/pre>\n<p>There are a couple points where you\u2019ll need to send data to the backend, but you won\u2019t want to send a bunch of extra information. Here is a function that will serialize the data you give it, and by default it just gets the data from itself. The <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">toJSON<\/code> function is called automatically within <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">JSON.stringify<\/code>, so anything that tries to serialize a Post won\u2019t have to type <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">Post.serialize()<\/code> &#8211; it will just work like magic!<\/p>\n<p>The <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">reset<\/code> function will be used by a \u201cCancel\u201d button to update the properties on the Post back to its original values.<\/p>\n<pre class=\"brush:bash; gutter: false\"> serialize(data: Post | PostData = this) {\r\n    const { id, title, body, updatedAt } = data;\r\n    return { id, title, body, updatedAt };\r\n  }\r\n\r\n  toJSON() {\r\n    return this.serialize();\r\n  }\r\n\r\n  reset() {\r\n    Object.assign(this, this.serialize(this.data));\r\n  }<\/pre>\n<p>The <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">save<\/code> and <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">delete<\/code> functions are asynchronous. First, it flags the Post as <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">loading<\/code> to trigger the UI changes. Then it sends a request to the API to either save or delete the post. Once it\u2019s done, it sets <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">loading<\/code> back to false to trigger another UI update.<\/p>\n<p>If the <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">save<\/code> function is successful, it will update the <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">data<\/code> variable with its new data returned from the REST API. Then it will reset itself to make sure the data is in sync with the Post.<\/p>\n<p>If the <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">delete<\/code> function is successful, the Post will remove itself from the Post Manager\u2019s list of posts.<\/p>\n<pre class=\"brush:bash; gutter: false\"> async save() {\r\n    this.loading = true;\r\n\r\n    const data = await this.manager.api.savePost(this);\r\n\r\n    if (data) {\r\n      Object.assign(this.data, data);\r\n      this.reset();\r\n    }\r\n\r\n    this.loading = false;\r\n  }\r\n\r\n  async delete() {\r\n    this.loading = true;\r\n\r\n    if (await this.manager.api.deletePost(this)) {\r\n      this.manager.posts.splice(this.manager.posts.indexOf(this), 1);\r\n    }\r\n\r\n    this.loading = false;\r\n  }<\/pre>\n<h3>Create a Post API Service<\/h3>\n<p>Your API locally will be hosted at <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">http:\/\/localhost:4201<\/code>. However, this might change if you\u2019re deploying it on another server somewhere in production. For now, add an <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">api<\/code> variable to your environments file:<\/p>\n<p><strong>src\/environments\/environment.ts<\/strong><\/p>\n<pre class=\"brush:bash; gutter: false\">@@ -6,6 +6,7 @@ import dotenvVariables from '.\/.env.js';\r\n\r\n export const environment = {\r\n   production: false,\r\n+  api: 'http:\/\/localhost:4201',\r\n   ...dotenvVariables,\r\n };<\/pre>\n<p>You can create a new service with the Angular CLI using <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">ng<\/code> generate service PostAPI within the <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">posts-manager<\/code> folder. This will create a couple of files. Modify <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">post-api.service.ts<\/code> to look like the following:<\/p>\n<p><strong>src\/app\/posts-manager\/post-api.service.ts<\/strong><\/p>\n<pre class=\"brush:bash; gutter: false\">import { Injectable } from '@angular\/core';\r\nimport { OktaAuthService } from '@okta\/okta-angular';\r\n\r\nimport { environment } from '..\/..\/environments\/environment';\r\nimport { Post } from '.\/post';\r\n\r\n@Injectable({\r\n  providedIn: 'root'\r\n})\r\nexport class PostAPIService {\r\n  constructor(private oktaAuth: OktaAuthService) {}\r\n\r\n  private async fetch(method: string, endpoint: string, body?: any) {\r\n    try {\r\n      const response = await fetch(`${environment.api}${endpoint}`, {\r\n        method,\r\n        body: body &amp;&amp; JSON.stringify(body),\r\n        headers: {\r\n          'content-type': 'application\/json',\r\n          accept: 'application\/json',\r\n          authorization: `Bearer ${await this.oktaAuth.getAccessToken()}`\r\n        }\r\n      });\r\n      return await response.json();\r\n    } catch (error) {\r\n      console.error(error);\r\n    }\r\n  }\r\n\r\n  async getPosts() {\r\n    return (await this.fetch('get', '\/posts')) || [];\r\n  }\r\n\r\n  async savePost(post: Post) {\r\n    return post.id\r\n      ? this.fetch('put', `\/posts\/${post.id}`, post)\r\n      : this.fetch('post', '\/posts', post);\r\n  }\r\n\r\n  async deletePost(post: Post) {\r\n    if (window.confirm(`Are you sure you want to delete \"${post.title}\"`)) {\r\n      await this.fetch('delete', `\/posts\/${post.id}`);\r\n      return true;\r\n    }\r\n\r\n    return false;\r\n  }\r\n}<\/pre>\n<p>The <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">@Injectable<\/code> decorator allows for this service to be injected into a component via the constructor.<\/p>\n<pre class=\"brush:bash; gutter: false\">@Injectable({\r\n  providedIn: 'root'\r\n})<\/pre>\n<p>Here you\u2019re setting up a simple helper function to send a request to the server. This uses the <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">fetch<\/code> function that\u2019s built into all modern browsers. The helper accepts a <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">method<\/code> (e.g. <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">get<\/code>, <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">post<\/code>, <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">delete<\/code>), an <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">endpoint<\/code> (here it would either be <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">\/posts<\/code> or a specific post like <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">\/posts\/3<\/code>), and a <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">body<\/code> (some optional JSON value, in this case the post content).<\/p>\n<p>Since this is just a helper function and should only be used internally within this service, we make the function <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">private<\/code>.<\/p>\n<p>This also sets some headers to tell the backend that any body it sends will be in JSON format, and it sets the authorization header by fetching the access token from Okta. Okta returns a promise, so we need to <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">await<\/code> the response.<\/p>\n<pre class=\"brush:bash; gutter: false\">private async fetch(method: string, endpoint: string, body?: any) {\r\n  try {\r\n    const response = await fetch(`${environment.api}${endpoint}`, {\r\n      method,\r\n      body: body &amp;&amp; JSON.stringify(body),\r\n      headers: {\r\n        'content-type': 'application\/json',\r\n        accept: 'application\/json',\r\n        authorization: `Bearer ${await this.oktaAuth.getAccessToken()}`,\r\n      },\r\n    });\r\n    return await response.json();\r\n  } catch (error) {\r\n    console.error(error);\r\n  }\r\n}<\/pre>\n<p>The other functions (<code class=\"highlighter-rouge\" style=\"font-size: 13px;\">getPosts<\/code>, <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">savePost<\/code>, and <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">deletePost<\/code>) use the <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">fetch<\/code> helper to access the API.<\/p>\n<p>The <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">getPosts<\/code> function makes sure to return an empty array in case there is an error fetching (the error will be logged to the console).<\/p>\n<p>If <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">savePost<\/code> is given a post without an ID, that means it\u2019s a new post, so it sends a <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">POST<\/code> request to the REST API. Otherwise, it uses <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">PUT<\/code> to update the post.<\/p>\n<p>Before actually deleting a post, <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">deletePost<\/code> will send a message to the user via the browser\u2019s built-in <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">confirm<\/code> function. This is probably not the best way to do this from a User Experience perspective as it blocks the UI, but it\u2019s a quick and dirty way of getting a response without writing a lot of extra code.<\/p>\n<pre class=\"brush:bash; gutter: false\"> async getPosts() {\r\n    return (await this.fetch('get', '\/posts')) || [];\r\n  }\r\n\r\n  async savePost(post: Post) {\r\n    return post.id\r\n      ? this.fetch('put', `\/posts\/${post.id}`, post)\r\n      : this.fetch('post', '\/posts', post);\r\n  }\r\n\r\n  async deletePost(post: Post) {\r\n    if (window.confirm(`Are you sure you want to delete \"${post.title}\"`)) {\r\n      await this.fetch('delete', `\/posts\/${post.id}`);\r\n      return true;\r\n    }\r\n\r\n    return false;\r\n  }<\/pre>\n<h3>Write the Posts Manager Page<\/h3>\n<p>You should now have all the pieces needed to create the Posts Manager. In your Posts Manager class, you\u2019ll need to inject the API Service to access the API. When the component is initialized, it will fetch a list of posts and create Post objects from those, then set it as a public value that can be accessed within the template.<\/p>\n<p>In order to add a new post, there will be a button that you can click. It will need an <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">addPost<\/code> function in order to create the new post. In this case, if you\u2019re already editing a post, just have it open that post instead of creating another new one. You can also make sure the posts are sorted with the most recent posts at the top.<\/p>\n<p><strong>src\/app\/posts-manager\/posts-manager.component.ts<\/strong><\/p>\n<pre class=\"brush:bash; gutter: false\">import { Component, OnInit } from '@angular\/core';\r\nimport { sortBy } from 'lodash';\r\n\r\nimport { Post } from '.\/post';\r\nimport { PostAPIService } from '.\/post-api.service';\r\n\r\n@Component({\r\n  selector: 'app-posts-manager',\r\n  templateUrl: '.\/posts-manager.component.html',\r\n  styleUrls: ['.\/posts-manager.component.css']\r\n})\r\nexport class PostsManagerComponent implements OnInit {\r\n  posts: Post[] = [];\r\n\r\n  constructor(public api: PostAPIService) {}\r\n\r\n  async ngOnInit() {\r\n    \/\/ Do the initial fetch of posts, and map them to Post objects\r\n    this.posts = (await this.api.getPosts()).map(data =&gt; new Post(data, this));\r\n  }\r\n\r\n  \/\/ The add button will be disabled if you're already editing a new post and it's open\r\n  get newIsOpen() {\r\n    const newPost = this.posts.find(post =&gt; !post.id);\r\n    return !!(newPost &amp;&amp; newPost.open);\r\n  }\r\n\r\n  \/\/ If you're already editing a post, but it's closed, then trigger the UI to open it\r\n  addPost() {\r\n    let newPost = this.posts.find(post =&gt; !post.id);\r\n\r\n    if (!newPost) {\r\n      \/\/ Create a new, empty post and add it to the beginning of the list of posts\r\n      newPost = new Post({}, this);\r\n      this.posts.unshift(newPost);\r\n    }\r\n\r\n    newPost.open = true;\r\n  }\r\n\r\n  get sortedPosts() {\r\n    return sortBy(this.posts, ['updatedAt']).reverse();\r\n  }\r\n}<\/pre>\n<p>The template is a bit more complex, so I\u2019ll explain the various pieces. Here\u2019s what it should look like in full:<\/p>\n<p><strong>src\/app\/posts-manager\/posts-manager.component.html<\/strong><\/p>\n<pre class=\"brush:bash; gutter: false\">&lt;h1&gt;Posts Manager&lt;\/h1&gt;\r\n&lt;mat-accordion&gt;\r\n  &lt;mat-expansion-panel\r\n    *ngFor=\"let post of sortedPosts\"\r\n    [expanded]=\"post.open\"\r\n    (opened)=\"post.open = true\"\r\n    (closed)=\"post.open = false\"\r\n  &gt;\r\n    &lt;mat-expansion-panel-header&gt;\r\n      &lt;mat-panel-title&gt;{{post.title || '(new post)'}}&lt;\/mat-panel-title&gt;\r\n      &lt;mat-panel-description&gt;\r\n        {{post.updatedAtString}}\r\n      &lt;\/mat-panel-description&gt;\r\n    &lt;\/mat-expansion-panel-header&gt;\r\n    &lt;form&gt;\r\n      &lt;div class=\"input-container\"&gt;\r\n        &lt;mat-form-field&gt;\r\n          &lt;input\r\n            matInput\r\n            [(ngModel)]=\"post.title\"\r\n            name=\"title\"\r\n            placeholder=\"Title\"\r\n            required\r\n          \/&gt;\r\n        &lt;\/mat-form-field&gt;\r\n        &lt;mat-form-field&gt;\r\n          &lt;textarea\r\n            matInput\r\n            placeholder=\"Body\"\r\n            required\r\n            [(ngModel)]=\"post.body\"\r\n            name=\"body\"\r\n            cdkTextareaAutosize\r\n            cdkAutosizeMinRows=\"4\"\r\n            cdkAutosizeMaxRows=\"10\"\r\n          &gt;&lt;\/textarea&gt;\r\n        &lt;\/mat-form-field&gt;\r\n      &lt;\/div&gt;\r\n      &lt;mat-action-row&gt;\r\n        &lt;button\r\n          mat-button\r\n          color=\"primary\"\r\n          [disabled]=\"post.loading || !post.isDirty\"\r\n          (click)=\"post.save()\"\r\n        &gt;\r\n          &lt;span *ngIf=\"post.loading\"&gt;Saving...&lt;\/span&gt;\r\n          &lt;span *ngIf=\"!post.loading\"&gt;Save&lt;\/span&gt;\r\n        &lt;\/button&gt;\r\n        &lt;button\r\n          mat-button\r\n          type=\"button\"\r\n          [disabled]=\"post.loading || !post.isDirty\"\r\n          (click)=\"post.reset()\"\r\n        &gt;\r\n          Cancel\r\n        &lt;\/button&gt;\r\n        &lt;button\r\n          mat-button\r\n          type=\"button\"\r\n          color=\"warn\"\r\n          [disabled]=\"post.loading\"\r\n          (click)=\"post.delete()\"\r\n        &gt;\r\n          Delete\r\n        &lt;\/button&gt;\r\n      &lt;\/mat-action-row&gt;\r\n    &lt;\/form&gt;\r\n  &lt;\/mat-expansion-panel&gt;\r\n&lt;\/mat-accordion&gt;\r\n&lt;button mat-fab class=\"add-button\" (click)=\"addPost()\" [disabled]=\"newIsOpen\"&gt;\r\n  &lt;mat-icon aria-label=\"Create new post\"&gt;add&lt;\/mat-icon&gt;\r\n&lt;\/button&gt;<\/pre>\n<p>The Accordion (<code class=\"highlighter-rouge\" style=\"font-size: 13px;\">mat-accordion<\/code>) allows you to create items that expand and contract with an animation. It should typically only show one item expanded at a time, except during the transition.<\/p>\n<p>The Expansion Panel (<code class=\"highlighter-rouge\" style=\"font-size: 13px;\">mat-expansion-panel<\/code>) creates a list of items. You can click on one of the items to expand it. The <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">*ngFor<\/code> directive tells Angular that it should create a new one of these for each <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">post<\/code> in <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">sortedPosts<\/code>.<\/p>\n<p>The brackets (<code class=\"highlighter-rouge\" style=\"font-size: 13px;\">[]<\/code>) around an attribute tells Angular that you want to assign a value to that parameter. In this case, whenever <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">post.open<\/code> changes, it updates <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">expanded<\/code>.<\/p>\n<p>The parentheses (<code class=\"highlighter-rouge\" style=\"font-size: 13px;\">()<\/code>) around an attribute tells Angular that you want to react to changes from a value. In this case, whenever <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">opened<\/code> is triggered, <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">open<\/code> will be set to <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">true<\/code> for that particular Post. Likewise, when the panel is closed, <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">post.open<\/code> is set to <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">false<\/code>.<\/p>\n<pre class=\"brush:bash; gutter: false\">&lt;mat-accordion&gt;\r\n  &lt;mat-expansion-panel\r\n    *ngFor=\"let post of sortedPosts\"\r\n    [expanded]=\"post.open\"\r\n    (opened)=\"post.open = true\"\r\n    (closed)=\"post.open = false\"\r\n  &gt;\r\n    &lt;!-- ... --&gt;\r\n  &lt;\/mat-expansion-panel&gt;\r\n&lt;\/mat-accordion&gt;<\/pre>\n<p>The Expansion Panel Header (<code class=\"highlighter-rouge\" style=\"font-size: 13px;\">mat-expansion-panel-header<\/code>) is the part of the panel that is always shown. This is where you set the title of the post and a very brief description.<\/p>\n<pre class=\"brush:bash; gutter: false\">&lt;mat-expansion-panel-header&gt;\r\n  &lt;mat-panel-title&gt;{{post.title || '(new post)'}}&lt;\/mat-panel-title&gt;\r\n  &lt;mat-panel-description&gt;\r\n    {{post.updatedAtString}}\r\n  &lt;\/mat-panel-description&gt;\r\n&lt;\/mat-expansion-panel-header&gt;<\/pre>\n<p>When using Angular Forms, the <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">form<\/code> element automatically handles forms in a more Single-Page App friendly way, rather than defaulting to sending POST data to the URL. Inside the <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">form<\/code> element we put our models.<\/p>\n<p>The <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">matInput<\/code> directive uses Material Design\u2019s inputs to make it much more stylish. Without it, you just get a basic input box, but with it, you get floating placeholders, better error handling, and styling that matches the rest of the UI.<\/p>\n<p>Earlier you saw that wrapping an attribute with <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">[]<\/code> meant that it would set some values. Wrapping it in <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">()<\/code> meant that it could receive values. For two-way binding, you can wrap the attribute in both, and <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">ngModel<\/code> is a form directive. Putting it all together, <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">[(ngModel)]<\/code> will update the <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">input<\/code> whenever the Post values change and will update the Post whenever a user changes the input values.<\/p>\n<p>The <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">input-container<\/code> class will allow us to easily style the container later.<\/p>\n<pre class=\"brush:bash; gutter: false\">&lt;div class=\"input-container\"&gt;\r\n  &lt;mat-form-field&gt;\r\n    &lt;input\r\n      matInput\r\n      [(ngModel)]=\"post.title\"\r\n      name=\"title\"\r\n      placeholder=\"Title\"\r\n      required\r\n    \/&gt;\r\n  &lt;\/mat-form-field&gt;\r\n  &lt;mat-form-field&gt;\r\n    &lt;textarea\r\n      matInput\r\n      placeholder=\"Body\"\r\n      required\r\n      [(ngModel)]=\"post.body\"\r\n      name=\"body\"\r\n      cdkTextareaAutosize\r\n      cdkAutosizeMinRows=\"4\"\r\n      cdkAutosizeMaxRows=\"10\"\r\n    &gt;&lt;\/textarea&gt;\r\n  &lt;\/mat-form-field&gt;\r\n&lt;\/div&gt;<\/pre>\n<p>Also inside the <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">form<\/code> are the action buttons. By keeping them inside the <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">form<\/code> element you get the bonus of having the submit button work when you press the <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">Enter<\/code> key on your keyboard.<\/p>\n<p>The <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">mat-action-row<\/code> component creates a separate row and puts the buttons off to the side.<\/p>\n<p>Here the \u201cCancel\u201d button will trigger the post to <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">reset<\/code> back to the original values. Since it only makes sense to reset the values if they are different from the original, we check to see if the post <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">isDirty<\/code>. You also wouldn\u2019t want to reset values while it\u2019s in the middle of saving or deleting, so you can check for <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">post.loading<\/code> as well.<\/p>\n<p>The \u201cSave\u201d button makes sense to be disabled for the same reasons as the \u201cCancel\u201d button, so it uses the same logic for <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">disabled<\/code>. When you click the button, it should tell the post to <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">save<\/code>. In case the save times are taking a while, you can update the UI to show either <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">Saving...<\/code> while the post is loading, or <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">Save<\/code> otherwise. To do that, use the special <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">*ngIf<\/code> directive.<\/p>\n<p>The \u201cDelete\u201d button should be disabled if the post is waiting on an API response but otherwise shouldn\u2019t care if the post is dirty or not.<\/p>\n<pre class=\"brush:bash; gutter: false\">&lt;mat-action-row&gt;\r\n  &lt;button\r\n    mat-button\r\n    color=\"primary\"\r\n    [disabled]=\"post.loading || !post.isDirty\"\r\n    (click)=\"post.save()\"\r\n  &gt;\r\n    &lt;span *ngIf=\"post.loading\"&gt;Saving...&lt;\/span&gt;\r\n    &lt;span *ngIf=\"!post.loading\"&gt;Save&lt;\/span&gt;\r\n  &lt;\/button&gt;\r\n  &lt;button\r\n    mat-button\r\n    type=\"button\"\r\n    [disabled]=\"post.loading || !post.isDirty\"\r\n    (click)=\"post.reset()\"\r\n  &gt;\r\n    Cancel\r\n  &lt;\/button&gt;\r\n  &lt;button\r\n    mat-button\r\n    type=\"button\"\r\n    color=\"warn\"\r\n    [disabled]=\"post.loading\"\r\n    (click)=\"post.delete()\"\r\n  &gt;\r\n    Delete\r\n  &lt;\/button&gt;\r\n&lt;\/mat-action-row&gt;<\/pre>\n<p>In order to add a new post, you need a button. Material Design often has a Floating Action Button (FAB) at the bottom-right of the screen. Adding a class <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">add-button<\/code> will make it easier to style this later. When the post is already<\/p>\n<pre class=\"brush:bash; gutter: false\">&lt;button mat-fab class=\"add-button\" (click)=\"addPost()\" [disabled]=\"newIsOpen\"&gt;\r\n  &lt;mat-icon aria-label=\"Create new post\"&gt;add&lt;\/mat-icon&gt;\r\n&lt;\/button&gt;<\/pre>\n<h3>A Touch of Style<\/h3>\n<p>Just to wrap up the Posts Manager component, add a little bit of styling. Above, the inputs were wrapped in a <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">div<\/code> with the class <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">input-container<\/code>. Adding the following code will make it so the inputs each get their own row, instead of being stacked side by side.<\/p>\n<p>Also, to make the <em>Floating<\/em> Action Button actually \u201cfloat\u201d, you\u2019ll want to give it a fixed position in the bottom-right corner of the screen.<\/p>\n<p><strong>src\/app\/posts-manager\/posts-manager.component.css<\/strong><\/p>\n<pre class=\"brush:bash; gutter: false\">.input-container {\r\n  display: flex;\r\n  flex-direction: column;\r\n}\r\n\r\n.add-button {\r\n  position: fixed;\r\n  right: 24px;\r\n  bottom: 24px;\r\n}<\/pre>\n<h2>Test your Angular + Node CRUD App<\/h2>\n<p>You now have a fully functioning single page app, connected to a REST API server, secured with authentication via Okta\u2019s OIDC.<\/p>\n<p>Go ahead and test out the app now. If they\u2019re not already running, make sure to start the server and the frontend. In your terminal run <code class=\"highlighter-rouge\" style=\"font-size: 13px;\">npm start<\/code> from your project directory.<\/p>\n<p>Navigate to <a href=\"http:\/\/localhost:4200\">http:\/\/localhost:4200<\/a>. You should be able to add, edit, view, and delete posts to your heart\u2019s desire!<\/p>\n<p><a href=\"http:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/new-post-0fb305a87c7ed83a360f1ba4a6ed74ccecf2f23912168490a85bd58801e7dfeb.png\"><img decoding=\"async\" class=\"aligncenter wp-image-22569\" style=\"border: none;\" src=\"http:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/new-post-0fb305a87c7ed83a360f1ba4a6ed74ccecf2f23912168490a85bd58801e7dfeb.png\" alt=\"Single Page Application\" width=\"820\" height=\"810\" srcset=\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/new-post-0fb305a87c7ed83a360f1ba4a6ed74ccecf2f23912168490a85bd58801e7dfeb.png 1296w, https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/new-post-0fb305a87c7ed83a360f1ba4a6ed74ccecf2f23912168490a85bd58801e7dfeb-300x296.png 300w, https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/new-post-0fb305a87c7ed83a360f1ba4a6ed74ccecf2f23912168490a85bd58801e7dfeb-768x759.png 768w, https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/new-post-0fb305a87c7ed83a360f1ba4a6ed74ccecf2f23912168490a85bd58801e7dfeb-1024x1011.png 1024w, https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/new-post-0fb305a87c7ed83a360f1ba4a6ed74ccecf2f23912168490a85bd58801e7dfeb-70x70.png 70w\" sizes=\"(max-width: 820px) 100vw, 820px\" \/><\/a><a href=\"http:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/posts-list-0ee4fbe792169823b836a560ba31780fcaef3d03dd1ff7a675014c0521312ff2.png\"><img decoding=\"async\" class=\"aligncenter wp-image-22570\" style=\"border: none;\" src=\"http:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/posts-list-0ee4fbe792169823b836a560ba31780fcaef3d03dd1ff7a675014c0521312ff2.png\" alt=\"Single Page Application\" width=\"820\" height=\"930\" srcset=\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/posts-list-0ee4fbe792169823b836a560ba31780fcaef3d03dd1ff7a675014c0521312ff2.png 1296w, https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/posts-list-0ee4fbe792169823b836a560ba31780fcaef3d03dd1ff7a675014c0521312ff2-264x300.png 264w, https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/posts-list-0ee4fbe792169823b836a560ba31780fcaef3d03dd1ff7a675014c0521312ff2-768x871.png 768w, https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2018\/09\/posts-list-0ee4fbe792169823b836a560ba31780fcaef3d03dd1ff7a675014c0521312ff2-903x1024.png 903w\" sizes=\"(max-width: 820px) 100vw, 820px\" \/><\/a><\/p>\n<h2>Learn More About Angular, Node, and App Security<\/h2>\n<p>I hope you\u2019ve enjoyed this article and found it helpful. If you\u2019re experimenting with JavaScript frameworks or backend languages and haven\u2019t decided on your stack yet, you may want to check out these similar tutorials:<\/p>\n<ul>\n<li><a href=\"https:\/\/developer.okta.com\/blog\/2018\/02\/15\/build-crud-app-vuejs-node?utm_source=Web%20Code%20Geeks&amp;utm_medium=Content%20Syndication&amp;utm_campaign=node%20angular%20crud\">Build a Basic CRUD App with Vue.js and Node<\/a><\/li>\n<li><a href=\"https:\/\/developer.okta.com\/blog\/2018\/07\/10\/build-a-basic-crud-app-with-node-and-react?utm_source=Web%20Code%20Geeks&amp;utm_medium=Content%20Syndication&amp;utm_campaign=node%20angular%20crud\">Build a Basic CRUD App with Node and React<\/a><\/li>\n<li><a href=\"https:\/\/developer.okta.com\/blog\/2018\/07\/02\/build-a-secure-crud-app-with-aspnetcore-and-react?utm_source=Web%20Code%20Geeks&amp;utm_medium=Content%20Syndication&amp;utm_campaign=node%20angular%20crud\">Build a Secure CRUD App with ASP.NET Core and React<\/a><\/li>\n<li><a href=\"https:\/\/developer.okta.com\/blog\/2018\/04\/26\/build-crud-app-aspnetcore-angular?utm_source=Web%20Code%20Geeks&amp;utm_medium=Content%20Syndication&amp;utm_campaign=node%20angular%20crud\">Build a CRUD App with ASP.NET Core and Angular<\/a><\/li>\n<\/ul>\n<p>If you\u2019re itching for more information, check out some of these other great articles or explore the Okta developer blog.<\/p>\n<ul>\n<li><a href=\"https:\/\/developer.okta.com\/blog\/2017\/07\/20\/the-ultimate-guide-to-progressive-web-applications?utm_source=Web%20Code%20Geeks&amp;utm_medium=Content%20Syndication&amp;utm_campaign=node%20angular%20crud\">The Ultimate Guide to Progressive Web Applications<\/a><\/li>\n<li><a href=\"https:\/\/developer.okta.com\/blog\/2018\/06\/28\/tutorial-build-a-basic-crud-app-with-node?utm_source=Web%20Code%20Geeks&amp;utm_medium=Content%20Syndication&amp;utm_campaign=node%20angular%20crud\">Tutorial: Build a Basic CRUD App with Node.js<\/a><\/li>\n<li><a href=\"https:\/\/developer.okta.com\/blog\/2017\/12\/04\/basic-crud-angular-and-spring-boot?utm_source=Web%20Code%20Geeks&amp;utm_medium=Content%20Syndication&amp;utm_campaign=node%20angular%20crud\">Build a Basic CRUD App with Angular 5.0 and Spring Boot 2.0<\/a><\/li>\n<li><a href=\"https:\/\/developer.okta.com\/blog\/2018\/05\/09\/upgrade-to-angular-6?utm_source=Web%20Code%20Geeks&amp;utm_medium=Content%20Syndication&amp;utm_campaign=node%20angular%20crud\">Angular 6: What\u2019s New, and Why Upgrade<\/a><\/li>\n<li><a href=\"https:\/\/developer.okta.com\/blog\/2017\/06\/13\/add-authentication-angular-pwa?utm_source=Web%20Code%20Geeks&amp;utm_medium=Content%20Syndication&amp;utm_campaign=node%20angular%20crud\">Add Authentication to Your Angular PWA<\/a><\/li>\n<\/ul>\n<p>And as always, we\u2019d love to hear from you. Hit us up with questions or feedback in the comments, or on Twitter <a href=\"https:\/\/twitter.com\/oktadev\">@oktadev<\/a>.<\/p>\n<p><span style=\"font-size: 20px;\"><b>\u201cI love writing authentication and authorization code.\u201d ~ No Web Developer Ever.<\/b> Tired of building the same login screens over and over? <a href=\"https:\/\/developer.okta.com\/signup\/?utm_source=Web%20Code%20Geeks&#038;utm_medium=Content%20Syndication&#038;utm_campaign=node%20angular%20crud\">Try the Okta API for hosted authentication, authorization, and multi-factor auth.<\/a> <\/span><\/p>\n<p><a href=\"https:\/\/developer.okta.com\/blog\/2018\/08\/07\/node-angular-crud?utm_source=Web%20Code%20Geeks&#038;utm_medium=Content%20Syndication&#038;utm_campaign=node%20angular%20crud\" target=\"_blank\">Build a CRUD-y Single Page Application with Node and Angular<\/a> was originally published on the Okta developer blog on August 07, 2018.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u201cI love writing authentication and authorization code.\u201d ~ No Web Developer Ever. Tired of building the same login screens over and over? Try the Okta API for hosted authentication, authorization, and multi-factor auth. Even before the release of Angular 6, Angular had gone through some changes over the years. The biggest one was the jump &hellip;<\/p>\n","protected":false},"author":8215,"featured_media":924,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[26],"tags":[519],"class_list":["post-22560","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-node-js","tag-angular-js"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.5 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Build a CRUD-y Single Page Application with Node and Angular - Web Code Geeks - 2026<\/title>\n<meta name=\"description\" content=\"Interested to learn more about single page application? Check out our article where we Build a CRUD-y SPA with Node and Angular!\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/developer.okta.com\/blog\/2018\/08\/07\/node-angular-crud\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Build a CRUD-y Single Page Application with Node and Angular - Web Code Geeks - 2026\" \/>\n<meta property=\"og:description\" content=\"Interested to learn more about single page application? Check out our article where we Build a CRUD-y SPA with Node and Angular!\" \/>\n<meta property=\"og:url\" content=\"https:\/\/developer.okta.com\/blog\/2018\/08\/07\/node-angular-crud\" \/>\n<meta property=\"og:site_name\" content=\"Web Code Geeks\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/webcodegeeks\" \/>\n<meta property=\"article:published_time\" content=\"2018-09-04T17:42:29+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2018-09-19T06:44:16+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/10\/nodejs-logo.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"150\" \/>\n\t<meta property=\"og:image:height\" content=\"150\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Braden Kelley\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@webcodegeeks\" \/>\n<meta name=\"twitter:site\" content=\"@webcodegeeks\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Braden Kelley\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"33 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/developer.okta.com\/blog\/2018\/08\/07\/node-angular-crud#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/javascript\/node-js\/single-page-application-node-angular\/\"},\"author\":{\"name\":\"Braden Kelley\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/d4c772abac1dfb78ec8fdc0955c6f095\"},\"headline\":\"Build a CRUD-y Single Page Application with Node and Angular\",\"datePublished\":\"2018-09-04T17:42:29+00:00\",\"dateModified\":\"2018-09-19T06:44:16+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/javascript\/node-js\/single-page-application-node-angular\/\"},\"wordCount\":4312,\"commentCount\":1,\"publisher\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#organization\"},\"image\":{\"@id\":\"https:\/\/developer.okta.com\/blog\/2018\/08\/07\/node-angular-crud#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/10\/nodejs-logo.jpg\",\"keywords\":[\"Angular.js\"],\"articleSection\":[\"Node.js\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/developer.okta.com\/blog\/2018\/08\/07\/node-angular-crud#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.webcodegeeks.com\/javascript\/node-js\/single-page-application-node-angular\/\",\"url\":\"https:\/\/developer.okta.com\/blog\/2018\/08\/07\/node-angular-crud\",\"name\":\"Build a CRUD-y Single Page Application with Node and Angular - Web Code Geeks - 2026\",\"isPartOf\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/developer.okta.com\/blog\/2018\/08\/07\/node-angular-crud#primaryimage\"},\"image\":{\"@id\":\"https:\/\/developer.okta.com\/blog\/2018\/08\/07\/node-angular-crud#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/10\/nodejs-logo.jpg\",\"datePublished\":\"2018-09-04T17:42:29+00:00\",\"dateModified\":\"2018-09-19T06:44:16+00:00\",\"description\":\"Interested to learn more about single page application? Check out our article where we Build a CRUD-y SPA with Node and Angular!\",\"breadcrumb\":{\"@id\":\"https:\/\/developer.okta.com\/blog\/2018\/08\/07\/node-angular-crud#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/developer.okta.com\/blog\/2018\/08\/07\/node-angular-crud\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/developer.okta.com\/blog\/2018\/08\/07\/node-angular-crud#primaryimage\",\"url\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/10\/nodejs-logo.jpg\",\"contentUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/10\/nodejs-logo.jpg\",\"width\":150,\"height\":150},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/developer.okta.com\/blog\/2018\/08\/07\/node-angular-crud#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.webcodegeeks.com\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"JavaScript\",\"item\":\"https:\/\/www.webcodegeeks.com\/category\/javascript\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Node.js\",\"item\":\"https:\/\/www.webcodegeeks.com\/category\/javascript\/node-js\/\"},{\"@type\":\"ListItem\",\"position\":4,\"name\":\"Build a CRUD-y Single Page Application with Node and Angular\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#website\",\"url\":\"https:\/\/www.webcodegeeks.com\/\",\"name\":\"Web Code Geeks\",\"description\":\"Web Developers Resource Center\",\"publisher\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.webcodegeeks.com\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#organization\",\"name\":\"Exelixis Media P.C.\",\"url\":\"https:\/\/www.webcodegeeks.com\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png\",\"contentUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png\",\"width\":864,\"height\":246,\"caption\":\"Exelixis Media P.C.\"},\"image\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/webcodegeeks\",\"https:\/\/x.com\/webcodegeeks\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/d4c772abac1dfb78ec8fdc0955c6f095\",\"name\":\"Braden Kelley\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/f7a19118235ccd8ddc4c8719de325cfaa654cc4269b3f00eb2b680029fa2774c?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/f7a19118235ccd8ddc4c8719de325cfaa654cc4269b3f00eb2b680029fa2774c?s=96&d=mm&r=g\",\"caption\":\"Braden Kelley\"},\"sameAs\":[\"https:\/\/developer.okta.com\/blog\/\"],\"url\":\"https:\/\/www.webcodegeeks.com\/author\/braden-kelley\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Build a CRUD-y Single Page Application with Node and Angular - Web Code Geeks - 2026","description":"Interested to learn more about single page application? Check out our article where we Build a CRUD-y SPA with Node and Angular!","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/developer.okta.com\/blog\/2018\/08\/07\/node-angular-crud","og_locale":"en_US","og_type":"article","og_title":"Build a CRUD-y Single Page Application with Node and Angular - Web Code Geeks - 2026","og_description":"Interested to learn more about single page application? Check out our article where we Build a CRUD-y SPA with Node and Angular!","og_url":"https:\/\/developer.okta.com\/blog\/2018\/08\/07\/node-angular-crud","og_site_name":"Web Code Geeks","article_publisher":"https:\/\/www.facebook.com\/webcodegeeks","article_published_time":"2018-09-04T17:42:29+00:00","article_modified_time":"2018-09-19T06:44:16+00:00","og_image":[{"width":150,"height":150,"url":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/10\/nodejs-logo.jpg","type":"image\/jpeg"}],"author":"Braden Kelley","twitter_card":"summary_large_image","twitter_creator":"@webcodegeeks","twitter_site":"@webcodegeeks","twitter_misc":{"Written by":"Braden Kelley","Est. reading time":"33 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/developer.okta.com\/blog\/2018\/08\/07\/node-angular-crud#article","isPartOf":{"@id":"https:\/\/www.webcodegeeks.com\/javascript\/node-js\/single-page-application-node-angular\/"},"author":{"name":"Braden Kelley","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/d4c772abac1dfb78ec8fdc0955c6f095"},"headline":"Build a CRUD-y Single Page Application with Node and Angular","datePublished":"2018-09-04T17:42:29+00:00","dateModified":"2018-09-19T06:44:16+00:00","mainEntityOfPage":{"@id":"https:\/\/www.webcodegeeks.com\/javascript\/node-js\/single-page-application-node-angular\/"},"wordCount":4312,"commentCount":1,"publisher":{"@id":"https:\/\/www.webcodegeeks.com\/#organization"},"image":{"@id":"https:\/\/developer.okta.com\/blog\/2018\/08\/07\/node-angular-crud#primaryimage"},"thumbnailUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/10\/nodejs-logo.jpg","keywords":["Angular.js"],"articleSection":["Node.js"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/developer.okta.com\/blog\/2018\/08\/07\/node-angular-crud#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.webcodegeeks.com\/javascript\/node-js\/single-page-application-node-angular\/","url":"https:\/\/developer.okta.com\/blog\/2018\/08\/07\/node-angular-crud","name":"Build a CRUD-y Single Page Application with Node and Angular - Web Code Geeks - 2026","isPartOf":{"@id":"https:\/\/www.webcodegeeks.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/developer.okta.com\/blog\/2018\/08\/07\/node-angular-crud#primaryimage"},"image":{"@id":"https:\/\/developer.okta.com\/blog\/2018\/08\/07\/node-angular-crud#primaryimage"},"thumbnailUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/10\/nodejs-logo.jpg","datePublished":"2018-09-04T17:42:29+00:00","dateModified":"2018-09-19T06:44:16+00:00","description":"Interested to learn more about single page application? Check out our article where we Build a CRUD-y SPA with Node and Angular!","breadcrumb":{"@id":"https:\/\/developer.okta.com\/blog\/2018\/08\/07\/node-angular-crud#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/developer.okta.com\/blog\/2018\/08\/07\/node-angular-crud"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/developer.okta.com\/blog\/2018\/08\/07\/node-angular-crud#primaryimage","url":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/10\/nodejs-logo.jpg","contentUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/10\/nodejs-logo.jpg","width":150,"height":150},{"@type":"BreadcrumbList","@id":"https:\/\/developer.okta.com\/blog\/2018\/08\/07\/node-angular-crud#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.webcodegeeks.com\/"},{"@type":"ListItem","position":2,"name":"JavaScript","item":"https:\/\/www.webcodegeeks.com\/category\/javascript\/"},{"@type":"ListItem","position":3,"name":"Node.js","item":"https:\/\/www.webcodegeeks.com\/category\/javascript\/node-js\/"},{"@type":"ListItem","position":4,"name":"Build a CRUD-y Single Page Application with Node and Angular"}]},{"@type":"WebSite","@id":"https:\/\/www.webcodegeeks.com\/#website","url":"https:\/\/www.webcodegeeks.com\/","name":"Web Code Geeks","description":"Web Developers Resource Center","publisher":{"@id":"https:\/\/www.webcodegeeks.com\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.webcodegeeks.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.webcodegeeks.com\/#organization","name":"Exelixis Media P.C.","url":"https:\/\/www.webcodegeeks.com\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/","url":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png","contentUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png","width":864,"height":246,"caption":"Exelixis Media P.C."},"image":{"@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/webcodegeeks","https:\/\/x.com\/webcodegeeks"]},{"@type":"Person","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/d4c772abac1dfb78ec8fdc0955c6f095","name":"Braden Kelley","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/f7a19118235ccd8ddc4c8719de325cfaa654cc4269b3f00eb2b680029fa2774c?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/f7a19118235ccd8ddc4c8719de325cfaa654cc4269b3f00eb2b680029fa2774c?s=96&d=mm&r=g","caption":"Braden Kelley"},"sameAs":["https:\/\/developer.okta.com\/blog\/"],"url":"https:\/\/www.webcodegeeks.com\/author\/braden-kelley\/"}]}},"_links":{"self":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/posts\/22560","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/users\/8215"}],"replies":[{"embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/comments?post=22560"}],"version-history":[{"count":0,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/posts\/22560\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/media\/924"}],"wp:attachment":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/media?parent=22560"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/categories?post=22560"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/tags?post=22560"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}