Part 1: Hello World

This part lays the foundation for the tutorial. Each subsequent part builds upon this, adding features to create a custom inbox interface powered by ProcessMaker.

Completed code: live example / download example

Overview

After completing this part, the updated app confirms that you and your IDE are ready to begin creating an Angular inbox, powered via the ProcessMaker REST API.

Step 1: Setting Up

In this step, you'll set up a basic hello world Angular app, ensuring your development environment is ready for the upcoming sections.

Application Structure

We are going to build out a very simple app structure, bare bones, and then add to it. This is what the project should look like for this part:

NPM

Let's get started by creating the folder that will house the app we are going to build. You can name it angular4pm which is what we will be using as the default name as we go through the tutorial.

Inside your root directory of your project, create your package.json as seen in the screenshot above and add the following, making sure to fill in the blank fields as you desire:

package.json
{
  "name": "<project-name>",
  "version": "<version>",
  "scripts": {
    "ng": "ng",
    "start": "ng serve --live-reload --configuration development",
    "build": "ng build",
    "watch": "ng build --watch --configuration development"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "^14.2.0",
    "@angular/common": "^14.2.0",
    "@angular/compiler": "^14.2.0",
    "@angular/core": "^14.2.0",
    "@angular/forms": "^14.2.0",
    "@angular/platform-browser": "^14.2.0",
    "@angular/platform-browser-dynamic": "^14.2.0",
    "@angular/router": "^14.2.0",
    "@mdi/font": "^6.5.95",
    "angular-oauth2-oidc": "^14.0.1",
    "angular-oauth2-oidc-jwks": "^14.0.1",
    "cross-env": "^7.0.3",
    "ngx-pagination": "^5.1.1",
    "rxjs": "~7.5.0",
    "swagger-ui": "^4.14.3",
    "tslib": "^2.3.0",
    "zone.js": "~0.11.4"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "^14.2.1",
    "@angular/cli": "^14.2.1",
    "@angular/compiler-cli": "^14.2.0",
    "@types/jasmine": "~3.10.0",
    "@types/node": "^12.11.1",
    "jasmine-core": "~4.0.0",
    "karma": "~6.3.0",
    "karma-chrome-launcher": "~3.1.0",
    "karma-coverage": "~2.1.0",
    "karma-jasmine": "~4.0.0",
    "karma-jasmine-html-reporter": "~1.7.0",
    "typescript": "~4.8.2"
  }
}

Git Ignore

If you plan to use version control, you will want to add a gitignore file for your environment.ts file since it will contain sensitive information like oauth secrets and such. Here is one you can start with and tweak as you like.

.gitignore
.DS_STORE
.vscode
/dist/
*.log
node_modules

# Include when developing application packages.
pubspec.lock
.c9
.idea/
.devcontainer/*
!.devcontainer/README.md
!.devcontainer/recommended-devcontainer.json
!.devcontainer/recommended-Dockerfile
.settings/
.vscode/launch.json
.vscode/settings.json
.vscode/tasks.json
.angular
*.swo
*.swp
modules/.settings
modules/.vscode
.vimrc
.nvimrc

# Don't check in secret files
*secret.js

# Ignore npm/yarn debug log
npm-debug.log
yarn-error.log

# build-analytics
.build-analytics

# rollup-test output
/modules/rollup-test/dist/

# User specific bazel settings
.bazelrc.user

# User specific ng-dev settings
.ng-dev.user*

.notes.md
baseline.json

# Ignore .history for the xyz.local-history VSCode extension
.history

# Husky
.husky/

environments/environment.ts

Angular Configuration

angular.json
{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "cli": {
    "analytics": false
  },
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "ProcessmakerNG": {
      "projectType": "application",
      "schematics": {
        "@schematics/angular:application": {
          "strict": true
        }
      },
      "root": "",
      "sourceRoot": "src",
      "prefix": "app",
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "outputPath": "dist/processmaker-ng",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "tsconfig.app.json",
            "assets": [
              "src/favicon.ico",
              "src/assets"
            ],
            "styles": [
              "src/styles.css"
            ],
            "scripts": []
          },
          "configurations": {
            "production": {
              "budgets": [
                {
                  "type": "initial",
                  "maximumWarning": "1mb",
                  "maximumError": "2mb"
                },
                {
                  "type": "anyComponentStyle",
                  "maximumWarning": "2kb",
                  "maximumError": "4kb"
                }
              ],
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ],
              "outputHashing": "all"
            },
            "development": {
              "buildOptimizer": false,
              "optimization": false,
              "vendorChunk": true,
              "extractLicenses": false,
              "sourceMap": true,
              "namedChunks": true
            }
          },
          "defaultConfiguration": "production"
        },
        "serve": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "configurations": {
            "production": {
              "browserTarget": "ProcessmakerNG:build:production",
              "port": 4200
            },
            "development": {
              "browserTarget": "ProcessmakerNG:build:development",
              "port": 4200
            }
          },
          "defaultConfiguration": "development"
        },
        "extract-i18n": {
          "builder": "@angular-devkit/build-angular:extract-i18n",
          "options": {
            "browserTarget": "ProcessmakerNG:build"
          }
        },
        "test": {
          "builder": "@angular-devkit/build-angular:karma",
          "options": {
            "main": "src/test.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "tsconfig.spec.json",
            "karmaConfig": "karma.conf.js",
            "assets": [
              "src/favicon.ico",
              "src/assets"
            ],
            "scripts": []
          }
        }
      }
    }
  }
}

Typescript Configuration

tsconfig.json
{
	"compileOnSave": false,
	"compilerOptions": {
		"baseUrl": "./",
		"outDir": "./dist/out-tsc",
		"forceConsistentCasingInFileNames": true,
		"strict": true,
		"noImplicitOverride": true,
		"noPropertyAccessFromIndexSignature": true,
		"noImplicitReturns": true,
		"noFallthroughCasesInSwitch": true,
		"sourceMap": true,
		"declaration": false,
		"downlevelIteration": true,
		"experimentalDecorators": true,
		"moduleResolution": "node",
		"importHelpers": true,
		"target": "es2020",
		"module": "es2020",
		"lib": ["es2020", "dom"],
		"strictPropertyInitialization": false
	},
	"angularCompilerOptions": {
		"enableI18nLegacyMessageIdFormat": false,
		"strictInjectionParameters": true,
		"strictInputAccessModifiers": true,
		"strictTemplates": true
	}
}
tsconfig.app.json
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "outDir": "./out-tsc/app",
    "types": []
  },
  "files": [
    "src/main.ts",
    "src/polyfills.ts"
  ],
  "include": [
    "src/**/*.d.ts",
    "sdk.js"
  ]
}

These are the main configuration files that are needed for the app to run properly. Feel free to modify as needed if you are comfortable with Angular and Typescript.

Now that you have setup the basic configuration files, you can begin executing some commands in the terminal.

npm install

In order to run the application, you can use this command which is an Angular command and includes hot reloads for ease of development.

ng serve

Step 2: The src Directory

Custom CSS

The application comes with some optional CSS that you may include if you want the look and feel of the app as we have made it.

style.css
@import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;500;600&family=Poppins:ital,wght@0,100;0,200;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap');

* {
	/* color:var(--text-color); */
	font-family: 'Poppins', sans-serif;
}

body {
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
}

hr {
	margin: unset !important;
}

h2 {
	font-weight: 400;
	margin-bottom: unset;
}

h5 {
	font-weight: 400 !important;
}

img {
	/* width: 150px; */
	height: 120px;
}

th {
	padding: 1rem 0.5rem !important;
	font-size: 0.875rem;
	margin-bottom: 1rem !important;
	background-color: white !important;
	color: var(--text-color) !important;
	font-weight: 600 !important;
}

th:last-child {
	border-top-right-radius: 12px;
}

th:first-child {
	border-top-left-radius: 12px;
}

/* //Removes border from last table row */

tr td:last-child {
	text-align: right;
}

.left-main,
.right-main {
	height: 100vh;
}

.rightside {
	background-color: #929fac;
	/* background-color: #E8EBEE; */
}

.leftside {
	background-color: rgb(250, 250, 250);
}

.right-main,
.left-main {
	display: grid;
	grid-template-columns: [first] 40px [line2] 50px [line3] auto [col4-start] 50px [five] 40px [end];
	grid-template-rows: [row1-start] 25% [row1-end] 200px [third-line] auto [last-line];
}
.left-main {
	display: grid;
	grid-template-columns: [first] 40px [line2] 50px [line3] auto [col4-start] 50px [five] 40px [end];
	grid-template-rows: [row1-start] 25% [row1-end] 200px [third-line] 100px [last-line];
}

.square {
	width: 100%;
	height: auto;
	/* background-color: #1E2328; */
	box-shadow: 0px 8px 10px 1px rgb(0 0 0 / 14%),
		0px 3px 14px 2px rgb(0 0 0 / 12%), 0px 5px 5px -3px rgb(0 0 0 / 20%);
	grid-column: 3 / span 3;
	grid-row: third-line / 3;
	border-top-left-radius: 15px;
	background-image: url(/assets/img/screen.jpg);
}

.text-block,
.logo-container {
	grid-column: line3 / span 3;
	grid-row: row1-end / 3;
}

.header-container {
	grid-column: line3 / span 3;
	grid-row: third-line / 3;
}

.text-block {
	color: rgb(250, 250, 250);
	/* color: #212529; */
	font-family: 'Poppins', SF Pro Display;
}

/* .btn-login {
	width: 70%;
	padding: 0.6rem;
	color: #1572c2 !important;
	border-color: #1572c2 !important;
} */

.btn-login:hover {
	color: #1572c2 !important;
	background-color: rgb(250, 250, 250) !important;
	border-color: #1572c2 !important;
}

.title {
	font-family: 'Poppins', sans-serif;
	letter-spacing: -0.144px;
}

.sub {
	font-family: 'Poppins', sans-serif;
}

.sub-title {
	display: inline-block;
}

.link {
	text-decoration: none;
	color: #1572c2 !important;
}

:root {
	--text-color: rgba(34, 42, 66, 0.7);
	--success-text: #0d6832;
	--primary-text: #273e63;
	--warning-text: #73510d;
	--danger-text: #a61001;
	--success-bg: #d6f0e0;
	--primary-bg: #dfe7f6;
	--warning-bg: #fbf0da;
	--danger-bg: #ffebe9;
	--primary-btn-text: #3b71ca;
}

.main {
	background-color: #f5f6f8bb !important;
	height: 100vh;
}

.text-primary {
	color: var(--primary-btn-text) !important;
}

.fw-bold {
	font-weight: 500 !important;
}

/* //Badge color overwirte */

.badge-success {
	color: var(--success-text);
	background-color: var(--success-bg);
}

.badge-primary {
	color: var(--primary-text);
	background-color: var(--primary-bg);
}

.badge-warning {
	color: var(--warning-text);
	background-color: var(--warning-bg);
}

.badge-danger {
	color: var(--danger-text);
	background-color: var(--danger-bg);
}

.rounded-corners {
	border-radius: 12px;
}
/* //Badge color overwirte end */

/* table styling */

/* primary table container  */
.table-container {
	box-shadow: 0px 1px 2px 0px rgb(60 64 67 / 25%),
		0px 2px 6px 2px rgb(60 64 67 / 10%);
	padding: 1rem;
	border-radius: 12px;
	background-color: white;
}
/* Changing TH bottom border color*/
.table > :not(:last-child) > :last-child > * {
	border-bottom-color: rgba(128, 128, 128, 0.277) !important;
}

/* table styling end*/

/*  button styling */
.btn-link {
	font-weight: 500;
	color: var(--primary-text);
	text-decoration: none;
	/* border:1px solid #2c58a094 !important; */
	border-radius: 8px;
}

.logout-btn {
	text-decoration: none;
	font-size: 1rem;
}

.page-link {
	border: unset !important;
	color: var(--primary-btn-text);
}

[selector='img-container'] {
	display: flex;
	justify-content: right;
}

input {
	padding: 1.4rem;
	border-radius: 8px;
	font-size: 18px;
	background-color: #f6f8fa;
}

.form-check-input {
	height: 20px;
	width: 20px;
	border-radius: 8px;
	cursor: pointer;
}

input {
	background-color: #f6f8fa;
}

.form-check input {
	margin-top: 7px;
	width: 20px;
	height: 20px;
	margin-right: 0.7rem;
	border-radius: 4px;
}

.display-4 {
	font-size: 2.5rem;
}

.display-5 {
	font-size: 2rem !important;
	font-weight: 300;
}

label {
	font-weight: 600;
	font-size: 18px;
}

.lead {
	font-size: 1.5rem;
}

p {
	font-size: 18px;
}

[selector='submit-button'] {
	display: flex;
	justify-content: right;
}

Main Typescript

main.ts
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';

if (environment.production) {
  enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppModule)
  .catch(err => console.error(err));

Entry File

index.html
<html lang="en">
	<head>
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta charset="utf-8" />
		<meta
			name="viewport"
			content="width=device-width, initial-scale=1, shrink-to-fit=no" />
		<link
			rel="stylesheet"
			href="https://cdn.jsdelivr.net/npm/bootstrap@4.1.3/dist/css/bootstrap.min.css"
			integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO"
			crossorigin="anonymous" />
		<link
			rel="stylesheet"
			href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.2/font/bootstrap-icons.css" />
		<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.1.3/dist/js/bootstrap.bundle.min.js"></script>

		<script
			src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
			integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
			crossorigin="anonymous"></script>
		<script
			src="https://cdn.jsdelivr.net/npm/popper.js@1.14.3/dist/umd/popper.min.js"
			integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49"
			crossorigin="anonymous"></script>
		<script
			src="https://cdn.jsdelivr.net/npm/bootstrap@4.1.3/dist/js/bootstrap.min.js"
			integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy"
			crossorigin="anonymous"></script>
		<title>ProcessMaker 4 - Angular</title>
		<base href="/" />
		<meta name="viewport" content="width=device-width, initial-scale=1" />
		<link rel="icon" type="image/x-icon" href="favicon.ico" />
	</head>
	<body>
		<app-root></app-root>
	</body>
</html>

Environments Directory

Depending on where and how you deploy your application, you will probably want to be able to have different settings or configurations for dev vs prod, such as more verbose logging, extra dependencies in your package.json for better debugging, unit testing, etc.

The default environment file is located within the src directory, at src/environments/environment.example.ts

Here is the contents:

environment.example.ts
// This file can be replaced during build by using the `fileReplacements` array.
// `ng build` replaces `environment.ts` with `environment.prod.ts`.
// The list of file replacements can be found in `angular.json`.

//Below is the hard coded Endpoint URL for the PM4 instance we are wanting to connect to.
//Eventually we will create a text box to enter this on the login form
//This is then used in the auth.service.ts - this.httpClient.post(environment.apiUrl

export const environment = {
	production: false,
	apiDomain: '',
	apiPath: '/api/1.0',
	apiProtocol: 'https://',
	clientId: '',
	clientSecret: '',
	oauthUrl: '',
	redirectUri: '',
	oauthAuthorizeUrl: '',
	customCss: false,
	calcProps: true,
};

/*
 * For easier debugging in development mode, you can import the following file
 * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
 *
 * This import should be commented out in production mode because it will have a negative impact
 * on performance if an error is thrown.
 */
// import 'zone.js/plugins/zone-error';  // Included with Angular CLI.

Assets Directory

This contains things like images, logos, apache .htaccess files, etc.

App Directory

This contains the majority of the code. At this stage of the application, it is very bare bones. But, as we continue to add new features and functionality to the application, it will grow.

AppModule

app.component.ts is the source file that describes the app-root component. This is the top-level Angular component in the app. A component is the basic building block of an Angular application. The component description includes the component's code, HTML template, and styles, which can be described in this file, or in separate files.

This is the current contents of the app.module.ts. However, as we continue, we will add more to this file.

app.module.ts
import { NgModule } from '@angular/core';
import '@angular/compiler';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { NgxPaginationModule } from 'ngx-pagination';
import { CommonModule } from '@angular/common';
import { AppComponent } from './components/app.component';

@NgModule({
	declarations: [AppComponent],
	imports: [
		BrowserModule,
		HttpClientModule,
		NgxPaginationModule,
		FormsModule,
		ReactiveFormsModule,
		CommonModule,
	],
	providers: [],
	bootstrap: [AppComponent],
})
export class AppModule {}

Components Directory

As this is the hello world first part, we only have 1 component that is very simple. All it does is print out a greeting. You can see below how we are using interpolation to dynamically insert the name, in this case, John.

app.component.ts
import { Component } from '@angular/core';

@Component({
	selector: 'app-root',
	template: `<h1>Hello, {{ name }}</h1>`,
})
export class AppComponent {
	name = 'John';
}

Step 3: Test your hello world app

In a Web browser, open http://localhost:4200. Confirm that the default Web site appears in the browser. You should see a page just like below.

You can leave ng serve running as you complete the next steps, since it is configured with live/hot reload.

If the page in your browser stops working, make sure to check that you still have your terminal open with the ng serve command running and not displaying any errors.

If ng serve is working and there are no errors at compilation time, you should see something like this in your terminal.

However, if you made a typo or something and your app broke, it would look something like this. I deleted the line where we set the property name just to show what it would look like in your terminal.

And on your Web page you would see something like this.

Review

In this section, you set up a basic Angular app displaying Hello John. You also became familiar with the ng serve command, allowing local testing with ease.

Remember, if you have any trouble, you can always refer back to the examples live example / download example

More information

For more information about the topics covered in this part, visit:

Next steps

pagePart 2: Services & Dependencies

Last updated