├── favicon.ico
├── app
├── app.component.css
├── main.ts
├── customers
│ ├── customer.ts
│ ├── customer.component.ts
│ └── customer.component.html
├── app.component.ts
├── app.module.ts
└── shared
│ └── generic-validator.ts
├── CHANGELOG.md
├── README.md
├── .vscode
├── settings.json
└── tasks.json
├── tsconfig.json
├── .gitignore
├── index.html
├── LICENSE
├── package.json
├── systemjs.config.js
└── tslint.json
/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DeborahK/angular-generic-validator/HEAD/favicon.ico
--------------------------------------------------------------------------------
/app/app.component.css:
--------------------------------------------------------------------------------
1 | li {
2 | font-size: large;
3 | }
4 |
5 | div.panel-heading {
6 | font-size: x-large;
7 | }
8 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # (2016-11-01)
2 |
3 | ### Changes
4 | * Add ViewChildren to access the blur event so required field validation messages can appear on blur.
5 | Thanks to Brandon Roberts who provided this technique.
6 |
--------------------------------------------------------------------------------
/app/main.ts:
--------------------------------------------------------------------------------
1 | // main entry point
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 | import { AppModule } from './app.module';
4 |
5 | platformBrowserDynamic().bootstrapModule(AppModule);
6 |
--------------------------------------------------------------------------------
/app/customers/customer.ts:
--------------------------------------------------------------------------------
1 | export class Customer {
2 |
3 | constructor(public firstName = '',
4 | public lastName = '',
5 | public email = '',
6 | public phone = '',
7 | public notification = 'email') {}
8 | }
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # angular-generic-validator
2 | Defines a generic validator for Angular 2 Reactive Forms
3 |
4 | The generic validator resides in shared/generic-validator.ts.
5 | The customer component is provided as an example usage of the generic validator.
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'my-app',
5 | template: `
6 |
7 |
8 |
9 | `
10 | })
11 | export class AppComponent { }
12 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | // Place your settings in this file to overwrite default and user settings.
2 | {
3 | "files.exclude": {
4 | "**/.git": true,
5 | "**/.DS_Store": true,
6 | "**/app/**/*.js": true,
7 | "**/*.map": true
8 | },
9 | // Controls auto save of dirty files. Accepted values: "off", "afterDelay", "onFocusChange". If set to "afterDelay" you can configure the delay in "files.autoSaveDelay".
10 | "files.autoSave": "afterDelay"
11 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "sourceMap": true,
7 | "emitDecoratorMetadata": true,
8 | "experimentalDecorators": true,
9 | "removeComments": false,
10 | "noImplicitAny": true,
11 | "suppressImplicitAnyIndexErrors": true,
12 | "typeRoots": [
13 | "./node_modules/@types/"
14 | ]
15 | },
16 | "exclude": [
17 | "node_modules/*"
18 | ]
19 | }
--------------------------------------------------------------------------------
/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { BrowserModule } from '@angular/platform-browser';
3 | import { ReactiveFormsModule } from '@angular/forms';
4 |
5 | import { AppComponent } from './app.component';
6 | import { CustomerComponent } from './customers/customer.component';
7 |
8 | @NgModule({
9 | imports: [
10 | BrowserModule,
11 | ReactiveFormsModule
12 | ],
13 | declarations: [
14 | AppComponent,
15 | CustomerComponent
16 | ],
17 | bootstrap: [AppComponent]
18 | })
19 | export class AppModule { }
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Generated files
2 | **/app/**/*.js
3 | **/app/**/*.map
4 |
5 | # Logs
6 | logs
7 | *.log
8 | npm-debug.log*
9 |
10 | # Runtime data
11 | pids
12 | *.pid
13 | *.seed
14 |
15 | # Directory for instrumented libs generated by jscoverage/JSCover
16 | lib-cov
17 |
18 | # Coverage directory used by tools like istanbul
19 | coverage
20 |
21 | # nyc test coverage
22 | .nyc_output
23 |
24 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
25 | .grunt
26 |
27 | # node-waf configuration
28 | .lock-wscript
29 |
30 | # Compiled binary addons (http://nodejs.org/api/addons.html)
31 | build/Release
32 |
33 | # Dependency directories
34 | node_modules
35 | jspm_packages
36 |
37 | # Optional npm cache directory
38 | .npm
39 |
40 | # Optional REPL history
41 | .node_repl_history
42 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Angular 2: Reactive Forms
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
24 |
25 |
26 |
27 | Loading ...
28 |
29 |
30 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Deborah Kurata
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "product-management",
3 | "version": "1.0.0",
4 | "author": "Deborah Kurata",
5 | "description": "Package for the Acme Product Management sample application",
6 | "scripts": {
7 | "start": "tsc && concurrently \"tsc -w\" \"lite-server\" ",
8 | "tsc": "tsc",
9 | "tsc:w": "tsc -w",
10 | "lint": "tslint ./app/**/*.ts -t verbose",
11 | "lite": "lite-server"
12 | },
13 | "license": "ISC",
14 | "dependencies": {
15 | "@angular/common": "~2.1.1",
16 | "@angular/compiler": "~2.1.1",
17 | "@angular/core": "~2.1.1",
18 | "@angular/forms": "~2.1.1",
19 | "@angular/http": "~2.1.1",
20 | "@angular/platform-browser": "~2.1.1",
21 | "@angular/platform-browser-dynamic": "~2.1.1",
22 | "@angular/router": "~3.1.1",
23 |
24 | "core-js": "^2.4.1",
25 | "reflect-metadata": "^0.1.8",
26 | "rxjs": "5.0.0-beta.12",
27 | "systemjs": "0.19.39",
28 | "zone.js": "^0.6.26",
29 |
30 | "bootstrap": "^3.3.7"
31 | },
32 | "devDependencies": {
33 | "concurrently": "^3.1.0",
34 | "lite-server": "^2.2.2",
35 | "tslint": "^3.15.1",
36 | "typescript": "^2.0.3",
37 |
38 | "@types/core-js": "^0.9.34",
39 | "@types/node": "^6.0.45"
40 | },
41 | "repository": {}
42 | }
--------------------------------------------------------------------------------
/systemjs.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * System configuration for Angular 2 samples
3 | * Adjust as necessary for your application needs.
4 | */
5 | (function (global) {
6 | System.config({
7 | paths: {
8 | // paths serve as alias
9 | 'npm:': 'node_modules/'
10 | },
11 | // map tells the System loader where to look for things
12 | map: {
13 | // our app is within the app folder
14 | app: 'app',
15 |
16 | // angular bundles
17 | '@angular/core': 'npm:@angular/core/bundles/core.umd.js',
18 | '@angular/common': 'npm:@angular/common/bundles/common.umd.js',
19 | '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
20 | '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
21 | '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
22 | '@angular/http': 'npm:@angular/http/bundles/http.umd.js',
23 | '@angular/router': 'npm:@angular/router/bundles/router.umd.js',
24 | '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
25 |
26 | // other libraries
27 | 'rxjs': 'npm:rxjs'
28 | },
29 | // packages tells the System loader how to load when no filename and/or no extension
30 | packages: {
31 | app: {
32 | main: './main.js',
33 | defaultExtension: 'js'
34 | },
35 | rxjs: {
36 | defaultExtension: 'js'
37 | }
38 | }
39 | });
40 | })(this);
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "class-name": true,
4 | "comment-format": [
5 | true,
6 | "check-space"
7 | ],
8 | "curly": true,
9 | "eofline": true,
10 | "forin": true,
11 | "indent": [
12 | true,
13 | "spaces"
14 | ],
15 | "label-position": true,
16 | "label-undefined": true,
17 | "max-line-length": [
18 | true,
19 | 140
20 | ],
21 | "member-access": false,
22 | "member-ordering": [
23 | true,
24 | "static-before-instance",
25 | "variables-before-functions"
26 | ],
27 | "no-arg": true,
28 | "no-bitwise": true,
29 | "no-console": [
30 | true,
31 | "debug",
32 | "info",
33 | "time",
34 | "timeEnd",
35 | "trace"
36 | ],
37 | "no-construct": true,
38 | "no-debugger": true,
39 | "no-duplicate-key": true,
40 | "no-duplicate-variable": true,
41 | "no-empty": false,
42 | "no-eval": true,
43 | "no-inferrable-types": true,
44 | "no-shadowed-variable": true,
45 | "no-string-literal": false,
46 | "no-switch-case-fall-through": true,
47 | "no-trailing-whitespace": true,
48 | "no-unused-expression": true,
49 | "no-unused-variable": true,
50 | "no-unreachable": true,
51 | "no-use-before-declare": true,
52 | "no-var-keyword": true,
53 | "object-literal-sort-keys": false,
54 | "one-line": [
55 | true,
56 | "check-open-brace",
57 | "check-catch",
58 | "check-else",
59 | "check-whitespace"
60 | ],
61 | "quotemark": [
62 | true,
63 | "single"
64 | ],
65 | "radix": true,
66 | "semicolon": [
67 | "always"
68 | ],
69 | "triple-equals": [
70 | true,
71 | "allow-null-check"
72 | ],
73 | "typedef-whitespace": [
74 | true,
75 | {
76 | "call-signature": "nospace",
77 | "index-signature": "nospace",
78 | "parameter": "nospace",
79 | "property-declaration": "nospace",
80 | "variable-declaration": "nospace"
81 | }
82 | ],
83 | "variable-name": false,
84 | "whitespace": [
85 | true,
86 | "check-branch",
87 | "check-decl",
88 | "check-operator",
89 | "check-separator",
90 | "check-type"
91 | ]
92 | }
93 | }
--------------------------------------------------------------------------------
/app/shared/generic-validator.ts:
--------------------------------------------------------------------------------
1 | import { FormGroup } from '@angular/forms';
2 |
3 | // Generic validator for Reactive forms
4 | // Implemented as a class, not a service, so it can retain state for multiple forms.
5 | export class GenericValidator {
6 |
7 | // Provide the set of valid validation messages
8 | // Stucture:
9 | // controlName1: {
10 | // validationRuleName1: 'Validation Message.',
11 | // validationRuleName2: 'Validation Message.'
12 | // },
13 | // controlName2: {
14 | // validationRuleName1: 'Validation Message.',
15 | // validationRuleName2: 'Validation Message.'
16 | // }
17 | constructor(private validationMessages: { [key: string]: { [key: string]: string } }) {
18 |
19 | }
20 |
21 | // Processes each control within a FormGroup
22 | // And returns a set of validation messages to display
23 | // Structure
24 | // controlName1: 'Validation Message.',
25 | // controlName2: 'Validation Message.'
26 | processMessages(container: FormGroup): { [key: string]: string } {
27 | let messages = {};
28 | for (let controlKey in container.controls) {
29 | if (container.controls.hasOwnProperty(controlKey)) {
30 | let c = container.controls[controlKey];
31 | // If it is a FormGroup, process its child controls.
32 | if (c instanceof FormGroup) {
33 | let childMessages = this.processMessages(c);
34 | Object.assign(messages, childMessages);
35 | } else {
36 | // Only validate if there are validation messages for the control
37 | if (this.validationMessages[controlKey]) {
38 | messages[controlKey] = '';
39 | if ((c.dirty || c.touched) &&
40 | c.errors) {
41 | for (let messageKey in c.errors) {
42 | if (c.errors.hasOwnProperty(messageKey) &&
43 | this.validationMessages[controlKey][messageKey]) {
44 | messages[controlKey] += this.validationMessages[controlKey][messageKey];
45 | }
46 | }
47 | }
48 | }
49 | }
50 | }
51 | }
52 | return messages;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/app/customers/customer.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, AfterViewInit, ViewChildren, ElementRef } from '@angular/core';
2 | import { FormGroup, FormBuilder, Validators, AbstractControl, FormControlName } from '@angular/forms';
3 |
4 | import 'rxjs/add/operator/debounceTime';
5 | import 'rxjs/add/observable/fromEvent';
6 | import 'rxjs/add/observable/merge';
7 | import { Observable } from 'rxjs/Observable';
8 |
9 | import { Customer } from './customer';
10 | import { GenericValidator } from '../shared/generic-validator';
11 |
12 | function emailMatcher(c: AbstractControl) {
13 | let emailControl = c.get('email');
14 | let confirmControl = c.get('confirmEmail');
15 |
16 | if (emailControl.pristine || confirmControl.pristine) {
17 | return null;
18 | }
19 |
20 | if (emailControl.value === confirmControl.value) {
21 | return null;
22 | }
23 | return { 'match': true };
24 | }
25 |
26 | @Component({
27 | selector: 'my-signup',
28 | templateUrl: './app/customers/customer.component.html'
29 | })
30 | export class CustomerComponent implements OnInit, AfterViewInit {
31 | @ViewChildren(FormControlName, { read: ElementRef }) formControls: ElementRef[];
32 |
33 | customerForm: FormGroup;
34 | customer: Customer = new Customer();
35 | displayMessage: { [key: string]: string } = {};
36 | genericValidator: GenericValidator;
37 |
38 | private validationMessages: { [key: string]: { [key: string]: string } } = {
39 | firstName: {
40 | required: 'Please enter your first name.',
41 | minlength: 'The first name must be longer than 3 characters.'
42 | },
43 | lastName: {
44 | required: 'Please enter your last name.',
45 | maxlength: 'The last name must be less than 50 characters.',
46 | },
47 | email: {
48 | required: 'Please enter your email address.',
49 | pattern: 'Please enter a valid email address.'
50 | }
51 | };
52 |
53 | constructor(private fb: FormBuilder) {
54 | // Create an instance of the generic validator
55 | this.genericValidator = new GenericValidator(this.validationMessages);
56 | }
57 |
58 | ngOnInit(): void {
59 | this.customerForm = this.fb.group({
60 | firstName: [null, [Validators.required, Validators.minLength(3)]],
61 | lastName: [null, [Validators.required, Validators.maxLength(50)]],
62 | emailGroup: this.fb.group({
63 | email: [null, [Validators.required, Validators.pattern('[a-z0-9._%+-]+@[a-z0-9.-]+')]],
64 | confirmEmail: [null, Validators.required]
65 | }, { validator: emailMatcher }),
66 | phone: null,
67 | notification: 'email'
68 | });
69 |
70 | this.customerForm.get('notification').valueChanges.subscribe(value => {
71 | this.setNotification(value);
72 | });
73 | }
74 |
75 | ngAfterViewInit() {
76 | let controlBlurs: Observable[] = this.formControls
77 | .map((formControl: ElementRef) => Observable.fromEvent(formControl.nativeElement, 'blur'));
78 |
79 | Observable.merge(this.customerForm.valueChanges, ...controlBlurs).debounceTime(1000).subscribe(value => {
80 | this.displayMessage = this.genericValidator.processMessages(this.customerForm);
81 | });
82 | }
83 |
84 | save(): void {
85 | console.log('Saved: ' + JSON.stringify(this.customerForm.value));
86 | }
87 |
88 | setNotification(notifyVia: string): void {
89 | let phoneControl = this.customerForm.get('phone');
90 | if (notifyVia === 'text') {
91 | phoneControl.setValidators(Validators.required);
92 | } else {
93 | phoneControl.clearValidators();
94 | }
95 | phoneControl.updateValueAndValidity();
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | // Available variables which can be used inside of strings.
2 | // ${workspaceRoot}: the root folder of the team
3 | // ${file}: the current opened file
4 | // ${fileBasename}: the current opened file's basename
5 | // ${fileDirname}: the current opened file's dirname
6 | // ${fileExtname}: the current opened file's extension
7 | // ${cwd}: the current working directory of the spawned process
8 |
9 | // A task runner that calls the Typescript compiler (tsc) and
10 | // Compiles a HelloWorld.ts program
11 | {
12 | "version": "0.1.0",
13 |
14 | // The command is tsc. Assumes that tsc has been installed using npm install -g typescript
15 | "command": "tsc",
16 |
17 | // The command is a shell script
18 | "isShellCommand": true,
19 |
20 | // Show the output window only if unrecognized errors occur.
21 | "showOutput": "silent",
22 |
23 | // args is the HelloWorld program to compile.
24 | "args": [ ],
25 |
26 | // use the standard tsc problem matcher to find compile problems
27 | // in the output.
28 | "problemMatcher": "$tsc"
29 | }
30 |
31 | // A task runner that calls the Typescript compiler (tsc) and
32 | // compiles based on a tsconfig.json file that is present in
33 | // the root of the folder open in VSCode
34 | /*
35 | {
36 | "version": "0.1.0",
37 |
38 | // The command is tsc. Assumes that tsc has been installed using npm install -g typescript
39 | "command": "tsc",
40 |
41 | // The command is a shell script
42 | "isShellCommand": true,
43 |
44 | // Show the output window only if unrecognized errors occur.
45 | "showOutput": "silent",
46 |
47 | // Tell the tsc compiler to use the tsconfig.json from the open folder.
48 | "args": ["-p", "."],
49 |
50 | // use the standard tsc problem matcher to find compile problems
51 | // in the output.
52 | "problemMatcher": "$tsc"
53 | }
54 | */
55 |
56 | // A task runner configuration for gulp. Gulp provides a less task
57 | // which compiles less to css.
58 | /*
59 | {
60 | "version": "0.1.0",
61 | "command": "gulp",
62 | "isShellCommand": true,
63 | "tasks": [
64 | {
65 | "taskName": "less",
66 | // Make this the default build command.
67 | "isBuildCommand": true,
68 | // Show the output window only if unrecognized errors occur.
69 | "showOutput": "silent",
70 | // Use the standard less compilation problem matcher.
71 | "problemMatcher": "$lessCompile"
72 | }
73 | ]
74 | }
75 | */
76 |
77 | // Uncomment the following section to use jake to build a workspace
78 | // cloned from https://github.com/Microsoft/TypeScript.git
79 | /*
80 | {
81 | "version": "0.1.0",
82 | // Task runner is jake
83 | "command": "jake",
84 | // Need to be executed in shell / cmd
85 | "isShellCommand": true,
86 | "showOutput": "silent",
87 | "tasks": [
88 | {
89 | // TS build command is local.
90 | "taskName": "local",
91 | // Make this the default build command.
92 | "isBuildCommand": true,
93 | // Show the output window only if unrecognized errors occur.
94 | "showOutput": "silent",
95 | // Use the redefined Typescript output problem matcher.
96 | "problemMatcher": [
97 | "$tsc"
98 | ]
99 | }
100 | ]
101 | }
102 | */
103 |
104 | // Uncomment the section below to use msbuild and generate problems
105 | // for csc, cpp, tsc and vb. The configuration assumes that msbuild
106 | // is available on the path and a solution file exists in the
107 | // workspace folder root.
108 | /*
109 | {
110 | "version": "0.1.0",
111 | "command": "msbuild",
112 | "args": [
113 | // Ask msbuild to generate full paths for file names.
114 | "/property:GenerateFullPaths=true"
115 | ],
116 | "taskSelector": "/t:",
117 | "showOutput": "silent",
118 | "tasks": [
119 | {
120 | "taskName": "build",
121 | // Show the output window only if unrecognized errors occur.
122 | "showOutput": "silent",
123 | // Use the standard MS compiler pattern to detect errors, warnings
124 | // and infos in the output.
125 | "problemMatcher": "$msCompile"
126 | }
127 | ]
128 | }
129 | */
130 |
131 | // Uncomment the following section to use msbuild which compiles Typescript
132 | // and less files.
133 | /*
134 | {
135 | "version": "0.1.0",
136 | "command": "msbuild",
137 | "args": [
138 | // Ask msbuild to generate full paths for file names.
139 | "/property:GenerateFullPaths=true"
140 | ],
141 | "taskSelector": "/t:",
142 | "showOutput": "silent",
143 | "tasks": [
144 | {
145 | "taskName": "build",
146 | // Show the output window only if unrecognized errors occur.
147 | "showOutput": "silent",
148 | // Use the standard MS compiler pattern to detect errors, warnings
149 | // and infos in the output.
150 | "problemMatcher": [
151 | "$msCompile",
152 | "$lessCompile"
153 | ]
154 | }
155 | ]
156 | }
157 | */
158 | // A task runner example that defines a problemMatcher inline instead of using
159 | // a predefined one.
160 | /*
161 | {
162 | "version": "0.1.0",
163 | "command": "tsc",
164 | "isShellCommand": true,
165 | "args": ["HelloWorld.ts"],
166 | "showOutput": "silent",
167 | "problemMatcher": {
168 | // The problem is owned by the typescript language service. Ensure that the problems
169 | // are merged with problems produced by Visual Studio's language service.
170 | "owner": "typescript",
171 | // The file name for reported problems is relative to the current working directory.
172 | "fileLocation": ["relative", "${cwd}"],
173 | // The actual pattern to match problems in the output.
174 | "pattern": {
175 | // The regular expression. Matches HelloWorld.ts(2,10): error TS2339: Property 'logg' does not exist on type 'Console'.
176 | "regexp": "^([^\\s].*)\\((\\d+|\\d+,\\d+|\\d+,\\d+,\\d+,\\d+)\\):\\s+(error|warning|info)\\s+(TS\\d+)\\s*:\\s*(.*)$",
177 | // The match group that denotes the file containing the problem.
178 | "file": 1,
179 | // The match group that denotes the problem location.
180 | "location": 2,
181 | // The match group that denotes the problem's severity. Can be omitted.
182 | "severity": 3,
183 | // The match group that denotes the problem code. Can be omitted.
184 | "code": 4,
185 | // The match group that denotes the problem's message.
186 | "message": 5
187 | }
188 | }
189 | }
190 | */
--------------------------------------------------------------------------------
/app/customers/customer.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Sign Up!
4 |
5 |
6 |
148 |
--------------------------------------------------------------------------------