Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to decorate/intercept a built-in directive in Angular 4 without monkey patching?

Problem

I am trying to add functionality to the built-in NgForm directive by intercepting the onSubmit function in order to prevent double-submission and invalid submission, but I haven't been able to find a way to do so without monkey patching.

Failed Attempt 1: Decorator via Dependency Injection

I didn't really expect this to work with directives since they aren't really "providers", but I tried it anyway (to no avail).

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

@Injectable()
export class NgFormDecorator extends NgForm {
    constructor() {
        super(null, null);
    }

    onSubmit($event: Event): boolean {
        // TODO: Prevent submission if in progress
        return super.onSubmit($event);
    }
}

// Module configuration
providers: [{
    provide: NgForm,
    useClass: NgFormDecorator
}]

Working Attempt 2: Monkey Patch with Secondary Directive

This works great but is obviously not ideal.

import { Directive, Output, EventEmitter } from '@angular/core';
import { NgForm } from '@angular/forms';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/finally';
import { noop } from 'rxjs/util/noop';

@Directive({
    selector: 'form',
    exportAs: 'NgFormExtension'
})
export class NgFormExtensionDirective {
    private onSubmitBase: ($event: Event) => void;
    submitting: boolean;

    constructor(private ngForm: NgForm) {
        this.onSubmitBase = ngForm.onSubmit;
        ngForm.onSubmit = this.onSubmit.bind(this);
    }

    private onSubmit($event: FormSubmitEvent): boolean {
        if (this.submitting || this.ngForm.invalid) {
            return false;
        }
        this.submitting = true;
        const result = this.onSubmitBase.call(this.ngForm, $event);
        if ($event.submission) {
            $event.submission
                .finally(() => this.submitting = false)
                .subscribe(null, noop);
        } else {
            this.submitting = false;
        }
        return result;
    }
}

export class FormSubmitEvent extends Event {
     submission: Observable<any>;
}

Question

Is there a way to decorate/intercept a built-in directive in Angular 4 without monkey patching?

like image 788
Taylor Buchanan Avatar asked Sep 27 '17 14:09

Taylor Buchanan


People also ask

What is the difference between @component and @directive in Angular?

The Component is used to break up the application into smaller components. That is why components are widely used in later versions of Angular to make things easy and build a total component-based model. The Directive is used to design reusable components, which are more behavior-oriented.

Can a directive have a template in Angular?

Components are directives with templates. The only difference between Components and the other two types of directives is the Template. Attribute and Structural Directives don't have Templates. So, we can say that the Component is a cleaner version of the Directive with a template, which is easier to use.

What does asterisk mean in Angular?

The asterisk is "syntactic sugar". It simplifies ngIf and ngFor for both the writer and the reader. Under the hood, Angular replaces the asterisk version with a more verbose form. The next two ngIf examples are effectively the same and we may write in either style: <!--

What are different types of directives in Angular?

The three types of directives in Angular are attribute directives, structural directives, and components.


1 Answers

You can always just override the ngForm selector, and extend the NgForm class:

@Directive({
  selector: 'form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]',
})
export class CNgFormDirective extends NgForm {
 constructor(
      @Optional() @Self() @Inject(NG_VALIDATORS) validators: any[],
      @Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: any[]) {
    super(validators, asyncValidators);
  }

  onSubmit($event: Event): boolean {
    console.log(`I'm custom!`);

    return super.onSubmit($event);
  }
}

working stack

like image 87
Poul Kruijt Avatar answered Nov 12 '22 18:11

Poul Kruijt