Angular Testing

Links

Introductory Rant

What happens when code is untested? At first, nothing major will happen. The first generation of team members knows the Ins- and Outs of the code. They created each part at their preferred speed. Large applications grow over several months or years and the knowledge is stored in the heads of the developers of the first generation. Due to untested code, there will be some bugs but the bugs can be solved by the devs because they know exactly who wrote the code, what causes the bugs and how to fix them quickly. 

Bad things happen months and years later. The price is paid by the second generation of developers. Once people leave for new jobs, the team eventually is cycled out and the second generation takes over. Or maybe the A-Team of developers is put onto another project and the B-Team takes over. Lack of knowledge transfer and documentation leads to a phase of utter chaos. A vast, undocumented, untested code base is dumped onto a team that has no experience in the field whatsoever. Unexperienced people now get the job assigned to reengineer complex interactions in a short amount of time and to quickly implement new working features in a potentially broken code base. I argue that this task is almost as difficult as creating the original project although the difficulties lie not in the engineering part but in the understanding of the existing codebase.

Now nobody knows, what the code is actually supposed to do as there are no constraints described by unit tests on what the code currently does. People do not know if after changing the code, the app still works at the customer’s site because there is no test coverage that checks if parts of the application broke due to unwanted side effects.

People will shy away from changing the application instead, they will leave the company in search for a sane working environment and the app will finally be replaced altogether, when yet another new generation of developers or managers step in.

One part of the solution is to start unit testing as early as possible and to add integration testing with automated tooling support.

Tests in Angular

Angular was designed to be testable when Angular was invented and developed.

In Angular, there are unit tests written with Jasmine and Karma and end-to-end (e2e) tests implemented with Protractor. Both can be executed by the continuous integration tool or on every save during development.

Coming from other programming languages where unit tests also exist, understanding Jasmine Behaviour Driven Tests is not that hard, because the concepts of test suite, a setup and a tear-down step and individual tests within a suite correspond with other languages.

Where it gets hard is when Angular specific parts are mixed into the Jasmine tests. Understanding those Angular specific parts that are involved in an Angular unit tests for components is hard, because these parts simply are not existent in other programming languages.

Testing with Jasmine and Karma

Jasmine is a behaviour driven testing framework for JavaScript. Karma is a test runner for JavaScript. It starts a web server serving the testing code and allows a browser to access the served code. The browser can be controlled by Jasmine using a web driver.

The combination of Jasmine and Karma are used extensively by Angular. Angular adds Angular specifics to the otherwise JavaScript base tools Jasmine and Karma.

Angular Specifics

The Angular specific parts in Jasmine Unit Tests are the ComponentFixture and the TestBed. The TestBed forms the environment for dependency injection by creating a NgModule just for running a test. The ComponentFixture wraps the component instance under test.

TestBed

The TestBed is used to create an Angular module on the fly. That module is only used for the unit test at hand in contrast to modules you use to organize your code. It is used to contain all the services, spies, mocks and all other resources needed to successfully run the unit test. When the unit test ends, that module is removed from memory, it only lives during the execution of the test suite. 

The TestBed will then be used to create the ComponentFixture through a call to it’s createComponent() method. (createComponent() is usually called in beforeEach()).

beforeEach(() => { 
fixture = TestBed.createComponent(ContactEditComponent);
component = fixture.componentInstance;
fixture.detectChanges();
...
}

The ComponentFixture is actually not the instance of the component, which is tested! It is not the system under test. In the snippet above, you can see the line of code:

component = fixture.componentInstance;

The ComponentFixture can be asked for the system under test using the componentInstance property. It will return the instance of the component under test.

It seems as if a ComponentFixture wraps the instance of the Component that is tested.

Here is what is so very confusing to me: The TestBed.createComponent() method, despite being named ‘createComponent’ does not return a component! Instead it returns a ComponentFixture!

Because the ComponentFixture was created from the TestBed, it will use the providers and services that have been configured into the TestingModule which was created in the first step. That means your spies and mocks are now used by the fixture.

The ComponentFixture is used to run changeDetection() manually because in UnitTests, the Angular ChangeDetection system is not running at all. You have to trigger the system manually so all changes are reflected in the DOM before you can query the changes in your assertions.

ComponentFixture

A ComponentFixture is an object, which wraps the instance of the Component under test. The component instance uses the mocks and spies configured into the TestBed it was created by.

In the individual unit tests, that is in the describe() and it() methods, the component is used to call methods on and to check how it’s state changes.

beforeEach(() => { 
fixture = TestBed.createComponent(ContactEditComponent);
component = fixture.componentInstance;
fixture.detectChanges();
...
}


describe('that, when using the FavoriteComponent', () => {
it('should display a star when clicked', fakeAsync(() => {
...
component.click();
...
expect(element.nativeElement.value).toBe('selected');
...
}
}

Angular Data Flow

This post lists the ways you can send data around in a angular application which will be referred to as data flow.

Using interpolation, data in a component’s properties can be output to the HTML template. But then there is also property-, class- and other bindings such as two-way binding (Banana in a Box). Data can be exchanged between child (@ViewChild, @ViewChildren, @ContentChild, @ContentChildren decorators) and parent components. Events can be sent (EventEmitter). Forms can be used to submit data with validation. But why are forms needed in the first place, when we have data binding?

To a beginner all these concepts are confusing, this post lists all interactions and explains their major benefit and when to use them.

Interpolation

The value stored in a component’s property can be output on a template using the interpolation syntax.

import { Component } from '@angular/core';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
title = 'cockpit';
}
{{title}}

Interpolation also allows you to perform calculations and function calls.

{{10 + 20}}
{{functionCall()}}

The value can be placed anywhere in the template and is rendered as is. If you want to put a value from a component into an attribute of a DOM element or child component, do not use interpolation but use property binding.

Interpolation using getters

If your component contains a getter

import { Component } from '@angular/core';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
title = 'cockpit';
username = 'username';

get user(): string {
return this.username;
}
}

interpolation also works with the getter:

{{user}}

Getters and Change Detection

As angular uses an elaborate change detection mechanism, the getter should not return a new object each time it is called but instead should return an immutable object which remains identical unless the data actually did change.

For an object to be immutable, the object is never modified but recreated from scratch each time data changes. If the data stays the same, the getter will return the same immutable object instance instead of returning a new string or a new object every time it is called even if the data is the same.

That way, using immutable objects, change detection can compare objects returned by getters correctly an only redraw that part of the page, when a new object is present, which means that data did actually change.

Refrain from constructing new objects on the fly and returning them from a getter as this throws change detection into a loop and causes angular to perform unnecessary work when rendering the page.

Naming fields and their get and set methods

Coming from a Java background, where getters and setters are also available, the expectation is the following syntax:

public class MyClass {
private String fieldname;

public getFiendname() {
return this.fieldname;
}

public setFieldname(const String fieldname) {
this.fieldname = fieldname;
}
}

This means, getters and setters are in essence normal member functions or methods. They have the conventional set and get prefixes to make them immediately identifiable as getters and setters. This convention is also mandated by the definition of a Java bean.

In Angular/Typescript, the keywords get and set exist and they are special syntax for getters and setters. The downside of this explicit notation is that getters and setters basically use the identifier that would be used for the field itself! There is a naming conflict here! How to resolve that conflict? This depends on the conventions you agree upon in your project. There is an underscore convention which is outdated apparently. Then you can choose a different name for the field and use the compact identifier for the getters and setters. Ultimately there is no definite solution to the name conflict issue.

Event Binding – Calling methods in components from the UI

Event Binding uses braces around attributes like this: (attr)=”eventHandlerMethod()”

You can bind to the click event of a button and call a function in your component.

login() {
this.authService.login();
}

logout() {
this.authService.logout();
}

In the template:

<button (click)="login()">Login</button>
<button (click)="logout()">Logout</button>

Another example of EventBinding is to bind a method of a component to the submit event of an Angular template-driven form (https://angular.io/guide/forms).

<form (ngSubmit)="onSubmit()" #heroForm="ngForm"></form>

When the form is submitted using the submit button, the onSubmit() method in the component is called.

@Input – Property Binding – Passing data from a parent to a DOM element or child component

Property Binding uses square brackets like this: [attr]=”value”.

As the template of a component can contain other components, a parent child hierarchy is formed. The parent, the component owning the template can put a value into an attribute of a child component. The child component can make use of the attribute by mapping that attribute to a field in it’s own class.

The mapping is created by annotating a field using the @Input() annotation. Now that field is mapped to an attribute of the same name in the template. To pass a value to the child, the attribute has to be surrounded by square brackets (= the binding operator).  The binding operator takes the value inside the square brackets and tries to find a matching field in the component. It will then set the value to that field. 

Imagine a DetailsComponent that is supposed to display a model object. An example could be a UserDetailsComponent displaying a user domain model.

@Input()
user: User;

The template of the parent component contains:

<user-details [user]="user"></user-details>

In this example, the user variable used as value can be created by a *ngFor directive or can come from somewhere else. It will be supplied by the parent component in most cases.

The NgClass Directive

The NgClass directive can add or remove CSS classes to a DOM element. It expects an object as a parameter. The object contains the intividual CSS classes as properties and boolean values that either add that particular class when the boolean value is true or remove that class when the value is false. Instead of hardcoded booleans, the boolean value can be returned from a method in the component.

<div [ngClass]="{selected: selected(), alarm: alarm()}"></div>

Two-way Data Binding

Two-way Binding uses square brackets containing braces, containing the value like this: [(attr)]=”value”

The banana-in-a-box syntax is used to send data from the frontend to the component and also from the component to the ui.

<input type="text" class="form-control" id="name" required       [(ngModel)]="model.name" name="name">

The code snippet above contains an input field from a template-driven form. The two-way binding is applied to the ngModel directive which is defined in the FormsModule. ngModel connects input from the template to the component.

The assignment to the Two-way data bound ngModel is using model.name as a value. model.name refers to the name field in the model field in the component that houses the template form.

This is a sentence that nobody will understand ever, so let me rephrase it. model.name refers to application defined fields. model is not a special keyword. It use a convention to name the target object that the form input is stored into, model. model.name is an example for a field of the model, which is called name. The two-way binding to model.name will store the user input into the model field of the component and inside the model it will store the input into the name property of the model object. If the object you store your data in is not called model, that is fine too, just specify the correct names in the two-way binding value.

Sending Events from a child to a Parent

Difference between a Content Child and a View Child

Angular ultimately renders a tree of components. A tree data structure defines nodes and their children. Children are nested into their parent nodes. In Angular components are nested into components.

There are two ways to nest components in Angular:

  1. View Children
  2. Content Children

Nesting a child component into a parent component is done by using the child components selector / tag in a template.

The distinction between view and component child is made by where, in which template the child component is used.

If the child component is used directly in the parent’s template, then the nesting is view nesting and the child is a ViewChild.

If the parent component is used in some “third-party” template and child components are used inside the opening and closing tag of the parent in the same arbitrary “third-party” template, then this is referred to as content nesting and the children are content children.

An example for view nesting is a fixed combination of components. A ColorPicker component might have a nested view child that draws the color space into a square or circle for the user to click into to select color in a explorative manner. Lets call this component ColorField. The ColorPicker component might have another nested view that represents the current color using sliders for the color’s red, green, blue and alpha values. Let’s call this component ColorCoordinates. In ColorPicker’s own template, the ColorField and ColorCoordinates components are  used, which by definition makes them view children.

An example for content nesting is a Tab component which is used in the template of a Dashboard component. As content children, the Tab component will have several TabPane components, that the user can switch between. Instead of inserting TabPane components directly into the Tab component’s template as view children, the TabPane components are added as nested tags to the Tab-tag in the Dashboard component’s template. This allows the user of the Dashboard component to add as many TabPanes to the Tab component as they want or need. Applying this idea further, the content on the TabPane components again is added directly in the Dashboard component’s template which makes it another example for content nesting.

A component can have View and Content children at the same time. A component can also have neither View nor Component children at all.

The interesting question is, when a component has ViewChild or Content components, what can it do with those? The answer is that the component class will have references to those children and call call methods on those children components inside it’s own code.

Sometimes, classes annotated with the @Component decorator are refered to as the component’s controllers. Having View- or Content-Children is a way for the parent component’s controller to access the controllers of the component’s children.

Angular Deep Dive

In Angular you define components and their templates in Angular’s syntax. The browser understands JavaScript. How does Angular translate all your components, bindings and templates to typescript and from typescript to JavaScript? This article contains the information I could find.

Links

Ahead-of-time (AOT) compilation
Explanation Video of the Angular Compiler
Angular Code on GitHub

The need for a Compiler

The answer to the question how Angular converts the Angular syntax to JavaScript is that Angular contains it’s own compiler. This compiler converts templates to TypeScript and feeds that TypeScript to a TypeScript compiler for finding type errors. It will then output messages for mistakes you did in writing your templates. This is necessary because Angular templates can contain logic such as referencing variables defined elsewhere, using pipes or using directives (ngIf, ngFor, ngSwitch, ngModel, ngStyle, …). The code generated for type checking templates is never going to be executed in the browser, it is purely for outputting errors to the user!

Also the compiler will generate typescript code for the components you write. This code will actually run inside the browser.

The need for a Runtime

The compiler takes a component definition including the template and after type checking (see above) turns it into a ComponentDefinition. The runtime can execute the ComponentDefinition inside the browser.

The runtime can understand the and execute the ComponentDefinitions. The question is, why is a ComponentDefinition not capable of running by itself as it is converted to JavaScript from TypeScript and JS is runnable in a browser!

The answer why a runtime is required is: 

Creating an Angular Application

Generating the Application using ng new

Angular uses the ng tool to generate all sorts of code for you. In fact ng is used to generate all of the angular items from components to the entire application.

First install the current long term support version of npm:

nvm install --lts
nvm use node --lts

Alternatively use the latest release:

$ nvm ls
v8.11.2
        v8.12.0
         v9.3.0
       v10.14.2
       v10.15.3
       v10.16.0
        v11.4.0
        v12.0.0
        v12.2.0
       v12.13.1
       v13.11.0
       v14.17.0
->     v14.17.3
        v16.1.0
         system

Now install and use the lastest version:

$ nvm install v16.1.0
$ nvm use v16.1.0

ng is added to your system by installing the Angular cli globally using the node package manager npm:

npm install -g @angular/cli

You can check the angular CLI version:

ng version

At the time of this writing, the version Angular CLI: 12.1.3 is current.

The global ng installation is used to generate a new application:

ng new <APPLICATION_NAME> --style=scss --routing

This will create a folder called <APPLICATION_NAME> in the current working folder containing the new project. It will use the sass processor for CSS stylesheets using the scss syntax. It will automatically use routing.

You can also go through a interactive process where the angular CLI asks you about all options before creating the project.

ng new <APPLICATION_NAME> --interactive

Once a project was generated using the global version of the angular CLI, you change into the project folder and from there on out, the local version of the angular CLI as specified in the package.json is used. This means even when the global Angular CLI is updated to a newer version, your application will not break because it locally uses the version as specified in the package.json file.

This helps project stability as you can update the global angular CLI version for new projects and keep the local angular CLI version to prevent the angular application from breaking due to version differences.

Additional Dependencies

For the NgRx store architecture:

npm install @ngrx/store --save
npm install @ngrx/effects --save

Starting the Application

You can open the application folder using Visual Studio Code. Withing Visual Studio Code, open a terminal using the Terminal menu item in the menu bar and the New Terminal Item within it.

To start the application, type npm start which will internally call ng serve.

npm run start

You can also call ng serve directly

ng serve

The application is running at http://localhost:4200/

Adding a Module

Angular is particularly useful because it has a rigid scheme for organizing code which benefits structure and ultimately the quality your application code will have in the long run.

That scheme consists of the use of Typescript Modules which contain components.

There are two types of modules: modules and feature modules.

Initially, ng generates a module which is the app module containing the app component, which is used as a starting point for the application (called bootstrapping).

Starting from the main property inside the angular.json file, a main.ts typescript file is configured. WebPack will execute this main.ts / main.js file when starting the application.

Inside main.ts, a call to bootstrapModule() is made and the app module (AppModule) is specified as a parameter.

Looking into app/app.module.ts, you can see the bootstrap property of the @NgModule decorator. It contains the AppComponent. That means, when angular starts the app module, it will initialize the AppComponent first and use it as an entry point into the application.

Feature modules can be added to the application to extent the application with new functionality. For each separate feature, you create a new feature module so that the feature modules stay independent from each other (strong cohesion, weak coupling). A module will contain all components, services, interceptors, decorators, pipes, model classes and everything else to make a feature work as a independent unit.

Move common functionality such as utilities into their own modules to reuse them from several modules.

The angular CLI allows you to add a module:

ng generate module <MODULE_NAME>

To generate a module that houses all user-related code, execute

ng generate module user

Note that the name was choosen to be user and not user-module or anything. Angular CLI will automatically generate a user folder and a user.module.ts file. The CLI will postfix the module identifier to the generated files for you!

Lazy Loaded Feature Modules

Documentation is: https://angular.io/guide/lazy-loading-ngmodules

A word of advice: lazy loaded feature modules have been updated in newer versions of angular. This article shows console output from version 12. If your output differs, consider migrating to the newest angular version. 

When you want a lazy loaded module, do not import the module into the app module, instead use the router. The idea behind a lazy loaded module is to only load it into memory, when the router navigates to the module.

Therefore the router is used to load the module when a certain route is visited. To setup the lazy loading, update the file app-routing.module.ts

const routes: Routes = [
{
path: 'projects',
loadChildren: () =>
import('./projects/projects.module').then((mod) => mod.ProjectsModule),
},
];

Here, once the path ‘projects’ is followed, the router will execute the import() function which loads the lazy loaded projects module in this case.

The question is, which component will be rendered when the path is visited? The code in the app-routing.module.ts file does not specify a component to load within the projects module! The projects module itself will again contain a routing configuration file called projects-routing.module.ts which specifies all the routes and components.

The file looks like this:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { ProjectContainerComponent } from './components/project-container/project-container.component';

const routes: Routes = [
{
path: '',
component: ProjectContainerComponent,
},
];

@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class ProjectsRoutingModule {}

One last change is necessary, in the lazy loaded feature module, import the ProjectsRoutingModule from the projects-routing.module.ts file and add it to the imports of the FeatureModule so it partakes in the routing:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ProjectContainerComponent } from './components/project-container/project-container.component';

import { ProjectsRoutingModule } from './projects-routing.module';

@NgModule({
declarations: [
ProjectContainerComponent
],
imports: [
CommonModule, ProjectsRoutingModule
]
})
export class ProjectsModule { }

When you start your app, you should see a listing of Chunks. Chunks are the files containing the application code, that are ultimately downloaded to the client to run your app. You should see your lazy loaded module listed as a Lazy Chunk File as opposed to the Initital Chunk File option for eagerly loaded modules.

Initial Chunk Files                    | Names         |      Size
vendor.js                              | vendor        |   2.39 MB
polyfills.js                           | polyfills     | 128.55 kB
runtime.js                             | runtime       |  12.51 kB
main.js                                | main          |   9.50 kB
styles.css                             | styles        | 118 bytes

                                       | Initial Total |   2.54 MB

Lazy Chunk Files                       | Names         |      Size
src_app_projects_projects_module_ts.js | -             |   5.81 kB

Adding a Service

To add a service into a module, you can use the Angular CLI.

ng generate module auth
cd src/app/module
ng generate service services/auth

This will create the auth.service.ts file inside the auth module.

import { Injectable } from '@angular/core';

@Injectable({
providedIn: 'root'
})
export class AuthService {

constructor() { }
}

@Injectable providedIn: root means that, the provider for this service is added to the root provider. This is documented here: https://angular.io/guide/providers

A provider is responsible to retrieve an instance for a dependency used in dependency injection.

Building an Angular Example App

We will build an application called ‘cash’ that helps you organize your finances. The code is available on github.

The cash application manages accounts.

Real Accounts mirror an existing account that you own within your bank institution.

Virtual Accounts are accounts that you can create within cash and which do not exist in your bank institution.

Virtual Accounts can have zero or one real accounts connected to them.
That means virtual accounts can exist, that are not backed by a real account. A real account can be connected to at most one virtual account.
That means it is not possible to connect a real account to two virtual accounts.

If a virtual account is connected to a real account, the real account is hidden by the virtual account. The amount of money that is stored in the real account is now accesseable only via it’s virtual account.

The way the cash application is structured is that whenever you add a real account to the cash application, cash will automatically create a virtual account for you and connect it to the real account.

You basically only are working with virtual accounts when you use the cash application. The layer of virtual accounts hides the layer of real accounts beneath it. You only interface with virtual accounts.

You can create any number of virtual accounts. For example if you want to structure your income and if you want to save up some money for a new PC or anything, you can create a virtual account for your savings and transfer money in between virtual accounts onto your savings account.

That way your income is subtracted by the amount of money you want to save and you can clearly see how much money there is left to spend after putting away the saved amount of money.

Money is transferred between accounts in transactions. A transaction starts at at most one virtual account and ends at at most another virtual account. A transaction transfers a non-negative amount of money and has a date as well as a description. Transaction between the same virtual accounts are not allowed.

For a transaction, there are the following cases:
A) Source Virtual Account (SVA) and Target Virtual Account (TVA) both are not backed by real accounts.
B) SVA is backed by a real account but TVA is not.
C) SVA is not backed by a real account but TVA is.
D) SVA and TVA are both backed by real accounts.

There are special cases:

Incoming amounts of money are transactions without a source account.
Expenses are transactions that have no target.

E) There is no SVA. The transaction adds income to your bank accounts.
F) There is no TVA. The transaction denotes an expense you made.

If SVA and TVA are both backed by real accounts (case D), then the money of the transaction is also transferred between the real accounts.

If SVA and TVA are both not backed (case A) the real accounts are not altered.

If there is a sequence of transactions of cases B – A* – C, then the money that made it from the real source account to the real target account is also transferred between the real accounts.

B – A* – C means that the sequence starts with a transaction of type B, then there are arbitrary many transactions of type A, then the sequence ends with a transaction of type C. That means, money travels over several hops over virtual accounts and in a global perspective between real accounts.

The amount of money in an account over a time frame can be displayed as a graph.

Transactions are stored in a transaction log which you can open and inspect at any time.

After every transaction, ‘cash’ will compute the amount of money in all real accounts and the money in all virtual accounts. The two amounts of money have to be the same at all times.

Let’s implement this as an angular application. It might be a bit ambitious but it is a usefull example that you can really make use of in your real life.

CRUD for Accounts

The first step will be CRUD for accounts. CRUD stands for Create, Retrieve, Update and Delete. I will use the acronym for an application that provides a user interface and backend code that allows the user to manage items (Create, Retrieve, Update and Delete). The user interface will consist of forms that allow the user to input data for accounts or edit existing accounts. The backend code will persist the accounts to a SQL datastore using Express and the excellent Sequelize SQL-ORM mapper.

First a SQL schema is created to manage real and virtual accounts. Then an API is created for the account CRUD operations in the backend. Once the backend and data storage are functional, a Form is needed to add new accounts. A list is needed to display existing accounts for editing, inspection and deletion.

Accessing Virtual Account via the API

First, lets create a service that covers the API calls. Creating services is done via the angular cli.

ng generate service <service_name>
ng generate service Account
ng generate service AccountDetails

If you want an AccountService, you have to generate with the name ‘Account’. ng will add the name service for you.

Angular Template-Driven Forms

There are two types of forms in angular
1. reactive (or model-driven) forms
2. template-driven forms

This article is a short reminder on how to find information and on how to work with template-driven forms.

Documentation
The official angular documentation is https://angular.io/guide/forms

Prepare node
Install the latest node Long Term Support (LTS) with nvm.
nvm install --lts

Use the latest version
nvm use node --lts

Start the app
npm start

Create a test application called angular-forms
ng new angular-forms

Generate the data object that is submitted by the form
ng generate class Hero

Create a form component
An Angular form has two parts:
1. an HTML-based template
2. A component class to handle data and user interactions programmatically.

Generate the form component
ng generate component HeroForm

Update the form component’s html

<div class="container">
<h1>Hero Form</h1>
<form (ngSubmit)="onSubmit()" #heroForm="ngForm">

{{diagnostic}}

<div class="form-group">
<label for="name">Name</label>
<input type="text" class="form-control" id="name" [(ngModel)]="model.name" name="name" required #spy>
</div>
TODO: remove this: {{spy.className}}

<div class="form-group">
<label for="alterEgo">Alter Ego</label>
<input type="text" class="form-control" id="alterEgo" [(ngModel)]="model.alterEgo" name="alterEgo">
</div>

<div class="form-group">
<label for="power">Hero Power</label>
<select class="form-control" id="power" [(ngModel)]="model.power" name="power" required>
<option *ngFor="let pow of powers" [value]="pow">{{pow}}</option>
</select>
</div>

<button type="submit" class="btn btn-success" [disabled]="!heroForm.form.valid">Submit</button>

</form>
</div>

 

Update the app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HeroFormComponent } from './hero-form/hero-form.component';

@NgModule({
declarations: [
AppComponent,
HeroFormComponent
],
imports: [
BrowserModule,
AppRoutingModule,
FormsModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

 

Update hero-form.component.ts

import { Component } from '@angular/core';

import { Hero } from '../hero';

@Component({
selector: 'app-hero-form',
templateUrl: './hero-form.component.html',
styleUrls: ['./hero-form.component.css']
})
export class HeroFormComponent {

powers = ['Really Smart', 'Super Flexible',
'Super Hot', 'Weather Changer'];

model = new Hero(18, 'Dr IQ 3000', this.powers[0], 'Chuck OverUnderStreet');

submitted = false;

onSubmit() {
console.log('Submit clicked');
console.log(JSON.stringify(this.model));

this.submitted = true;
}

// TODO: Remove this when we're done
get diagnostic() { return JSON.stringify(this.model); }
}

OnSubmit()
When the submit button is clicked, onSubmit() is called in the form component. To persist, you can create a service and send the object to the backend in json form. The backend then persists the object.

With SpringBoot, you would add a JerseyResource for the endpoint, a JPA repository for the model item and a facade and a service to save the model via the JPA repository.