2

In my recent Angular 2 project, I try to add a component which displays all sorts of errors. I can manipulate and display the errors in the Chrome developer console, but I fail when it comes to actually generate output from them.

To achieve this, I added a service-class which extends the ErrorHandler of the Angular Core. It contains a Subject which you can subscribe to, to get new messages.

import { Injectable, ErrorHandler } from '@angular/core';
import { Subject } from 'rxjs/Rx';

export interface IMiaErrorMessage {
    exception: string,
    stackTrace?: any,
    reason?: string
}


@Injectable()
export class MiaErrorHandlingService extends ErrorHandler {

    public errors: Subject<IMiaErrorMessage> = new Subject<IMiaErrorMessage>();

    handleError(exception: any): void {
        console.groupCollapsed('Fehler');
        console.error(exception);
        console.groupEnd();
        let error: IMiaErrorMessage = {
            "exception": ""+exception
        }
        console.log('error', error);
        this.errors.next(error);

    }
}

Afterwards I added a component which should actually display the error messages. There is a subscribe-call which adds new error-messages the errors-Array. The two functions throwError and addError are for testing. When I simply add the exception text using the addError function everything works just fine, but not when i actually throw an error using throwError. The console output for both is the same.

import { Component, Input } from '@angular/core';
import { MiaErrorHandlingService, IMiaErrorMessage } from './../services/mia-error-handling.service';
import { Subject } from 'rxjs/Rx';

@Component({
    selector: 'mia-errors',
    templateUrl: 'errors.component.html',
    styleUrls: ['errors.component.scss'],
    providers: [MiaErrorHandlingService]

})
export class ErrorsComponent {
    title = 'Es ist ein Fehler aufgetreten';

    private errors: Array<IMiaErrorMessage> = [];

    constructor(private miaErrorHandlingService: MiaErrorHandlingService) {

        // Observer of the connection status
        this.miaErrorHandlingService.errors.subscribe(
            value => this.errors.push(value),
            error => console.info('ERROR', error),
            () => console.info('COMPLETE')
        );

    }

    private throwError() {
        console.log('throwError');
        throw Error('Hello again');
    }

    private addError() {
        console.log('addError');
        this.miaErrorHandlingService.handleError('Foo');
    }

}

The console output is

errors.component.ts:35 addError
mia-error-handling.service.ts:18 Fehler
mia-error-handling.service.ts:19 FooMiaErrorHandlingService.handleError @ mia-error-handling.service.ts:19 ErrorsComponent.addError @ errors.component.ts:36_View_ErrorsComponent0._handle_click_13_0 @ ErrorsComponent.ngfactory.js:164(anonymous function) @ view.js:404(anonymous function) @ dom_renderer.js:249(anonymous function) @ dom_events.js:26ZoneDelegate.invoke @ zone.js:203onInvoke @ ng_zone_impl.js:43ZoneDelegate.invoke @ zone.js:202Zone.runGuarded @ zone.js:110NgZoneImpl.runInnerGuarded @ ng_zone_impl.js:72NgZone.runGuarded @ ng_zone.js:236outsideHandler @ dom_events.js:26ZoneDelegate.invokeTask @ zone.js:236Zone.runTask @ zone.js:136ZoneTask.invoke @ zone.js:304
mia-error-handling.service.ts:24 error Objectexception: "Foo"__proto__: Object
errors.component.ts:30 throwError
mia-error-handling.service.ts:18 Fehler
mia-error-handling.service.ts:19 ViewWrappedError {_nativeError: Error: Error in ./ErrorsComponent class ErrorsComponent - inline template:10:8 caused by: Hello agai…, originalError: Error: Hello again
    at ErrorsComponent.throwError (http://localhost:4200/main.bundle.js:67888:15)…, context: DebugContext}MiaErrorHandlingService.handleError @ mia-error-handling.service.ts:19next @ application_ref.js:273schedulerFn @ async.js:82SafeSubscriber.__tryOrUnsub @ Subscriber.js:223SafeSubscriber.next @ Subscriber.js:172Subscriber._next @ Subscriber.js:125Subscriber.next @ Subscriber.js:89Subject.next @ Subject.js:55EventEmitter.emit @ async.js:74onError @ ng_zone.js:120onHandleError @ ng_zone_impl.js:64ZoneDelegate.handleError @ zone.js:207Zone.runGuarded @ zone.js:113NgZoneImpl.runInnerGuarded @ ng_zone_impl.js:72NgZone.runGuarded @ ng_zone.js:236outsideHandler @ dom_events.js:26ZoneDelegate.invokeTask @ zone.js:236Zone.runTask @ zone.js:136ZoneTask.invoke @ zone.js:304
mia-error-handling.service.ts:24 error Objectexception: "Error: Error in ./ErrorsComponent class ErrorsComponent - inline template:10:8 caused by: Hello again"__proto__: Object__defineGetter__: __defineGetter__()__defineSetter__: __defineSetter__()__lookupGetter__: __lookupGetter__()__lookupSetter__: __lookupSetter__()constructor: Object()hasOwnProperty: hasOwnProperty()isPrototypeOf: isPrototypeOf()propertyIsEnumerable: propertyIsEnumerable()toLocaleString: toLocaleString()toString: toString()valueOf: valueOf()get __proto__: __proto__()set __proto__: __proto__()

For the sake of completeness here is my view

    <dl *ngFor="let error of errors">
        <dt>
            <app-icon>warning</app-icon>
        </dt>
        <dd>{{error.exception}}</dd>
    </dl>
<button (click)="throwError()">Do something wrong!</button>
<button (click)="addError()">addError</button>

I'm thankful for every input I can get.

2 Answers 2

1

Cory Rylan has a great implementation of this in his blog post: https://coryrylan.com/blog/angular-2-form-builder-and-validation-management

I am using his method in my app:

Here is the error component:

import { Component, Input } from '@angular/core';
import { FormControl } from '@angular/forms';
import { ValidationService } from '../services/validation.service';

@Component({
  selector: 'kg-errorMessages',
  template: `<div *ngIf="errorMessage !== null">{{errorMessage}}</div>`
})
export class ErrorMessagesComponent {
  @Input() control: FormControl;
  @Input() name: string;

  constructor() { }

  get errorMessage() {
    for (let propertyName in this.control.errors) {
      if (this.control.errors.hasOwnProperty(propertyName) && this.control.touched) {
        return ValidationService.getValidatorErrorMessage(propertyName, this.control.errors[propertyName]);
      }
    }

    return null;
  }
}

Here is the validation service:

//Original version created by Cory Rylan: https://coryrylan.com/blog/angular-2-form-builder-and-validation-management
import { IsValidDate } from '../helpers/date.helper'


export class ValidationService {
    static getValidatorErrorMessage(validatorName: string, validatorValue?: any) {
        let config = {
            'required': 'Required',
            'invalidNumberField': 'Only numbers allowed',
            'invalidDateField': 'Not a valid date',
            'invalidCreditCard': 'Is invalid credit card number',
            'invalidEmailAddress': 'Invalid email address',
            'invalidPassword': 'Invalid password. Password must be at least 6 characters long, and contain a number.',
            'invalidPasswords': 'Invalid passwords. Passwords must match.',
            'minlength': `Minimum length ${validatorValue.requiredLength}`
        };

        // console.log(" config = " + JSON.stringify(config));
        // console.log(" validator name: " + validatorName);
        // console.log(" config = req " + JSON.stringify(config["required"]));
        // console.log(" config = nan " + JSON.stringify(config["invalidNumberField"]));
        return config[validatorName];
    }

    static numberFieldValidator(control) {
        // if (control.value.match(/^([0-9]|[0-9][0-9]|[1-9][0-9][0-9])$/)) {
        //     return null;
        // } else {
        //     return { 'invalidNumberField': true };
        // }

        return null;
    }

    static dateFieldValidator(control) {
        var e: boolean;

        if (IsValidDate(control.value)) {
            return null;
        } else {
            return { 'invalidDateField': true };
        }
    }

    static creditCardValidator(control) {
        // Visa, MasterCard, American Express, Diners Club, Discover, JCB
        if (control.value.match(/^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\d{3})\d{11})$/)) {
            return null;
        } else {
            return { 'invalidCreditCard': true };
        }
    }

    static emailValidator(control) {
        // RFC 2822 compliant regex
        if (control.value.match(/[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/)) {
            return null;
        } else {
            return { 'invalidEmailAddress': true };
        }
    }

    static passwordValidator(control) {
        // {6,100}           - Assert password is between 6 and 100 characters
        // (?=.*[0-9])       - Assert a string has at least one number
        if (control.value.match(/^(?=.*[0-9])[a-zA-Z0-9!@#$%^&*]{6,100}$/)) {
            return null;
        } else {
            return { 'invalidPassword': true };
        }
    }

    static passwordCompareValidator(fg) {
        if (fg.value.password === fg.value.confirmPassword) {
            return null;
        } else {
            return { 'invalidPasswords': true };
        }
    }
}
Sign up to request clarification or add additional context in comments.

Comments

0

This issue may also be of interest.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.