Sunday, May 12, 2024
 Popular · Latest · Hot · Upcoming
47
rated 0 times [  51] [ 4]  / answers: 1 / hits: 15503  / 6 Years ago, fri, june 1, 2018, 12:00:00

I created an Angular Directive, that uses CSS selectors to automatically trim inputs in my application, it looks like so...



import { Directive, HostListener, forwardRef } from '@angular/core';
import { DefaultValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

export const TRIM_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => TrimInputDirective),
multi: true
};

/**
* The trim accessor for writing trimmed value and listening to changes that is
* used by the {@link NgModel}, {@link FormControlDirective}, and
* {@link FormControlName} directives.
*/
/* tslint:disable */
@Directive({
selector: `
input
:not([type=checkbox])
:not([type=radio])
:not([type=password])
:not([readonly])
:not(.ng-trim-ignore)
[formControlName],

input
:not([type=checkbox])
:not([type=radio])
:not([type=password])
:not([readonly])
:not(.ng-trim-ignore)
[formControl],

input
:not([type=checkbox])
:not([type=radio])
:not([type=password])
:not([readonly])
:not(.ng-trim-ignore)
[ngModel],

textarea
:not([readonly])
:not(.ng-trim-ignore)
[formControlName],

textarea
:not([readonly])
:not(.ng-trim-ignore)
[formControl],

textarea
:not([readonly])
:not(.ng-trim-ignore)[ngModel],
:not([readonly])
:not(.ng-trim-ignore)
[ngDefaultControl]'
`,
providers: [ TRIM_VALUE_ACCESSOR ]
})
/* tslint:enable */
export class TrimInputDirective extends DefaultValueAccessor {

protected _onTouched: any;

/**
* ngOnChange - Lifecycle hook that is called when any data-bound property of a directive changes.
* @param {string} val - trim value onChange.
*/
@HostListener('input', ['$event.target.value'])
public ngOnChange = (val: string) => {
this.onChange(val.trim());
}

/**
* applyTrim - trims the passed value
* @param {string} val - passed value.
*/
@HostListener('blur', ['$event.target.value'])
public applyTrim(val: string) {
this.writeValue(val.trim());
this._onTouched();
}

/**
* writeValue - trims the passed value
* @param {any} value - passed value.
*/
public writeValue(value: any): void {
if (typeof value === 'string') {
value = value.trim();
}

super.writeValue(value);
}

/**
* registerOnTouched Registers a callback function that should be called when the control receives a blur event.
* @param {function} fn - The user information.
*/
public registerOnTouched(fn: any): void {
this._onTouched = fn;
}
}


Now being a good developer I must right some unit tests... so I start to put a file together, here it is



import {Component} from '@angular/core';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {TrimInputDirective} from './trim-input.directive';

import {expect} from 'chai';

@Component({
selector: 'my-directive-test-component',
template: ''
})
class TestComponent {
}

describe('Trim Directive', () => {
let fixture: ComponentFixture<TestComponent>;
let inputDebugElement: any;
let directive: TrimInputDirective;

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
TestComponent,
TrimInputDirective
],
providers: []
}).overrideComponent(TestComponent, {
set: {
template: '<input type=text>'
}
}).compileComponents().then(() => {
fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
inputDebugElement = fixture.debugElement.query(By.css('input'));
directive = inputDebugElement.injector.get(TrimInputDirective);
});
}));

it('should trim the input', () => {
directive.ngOnChange(' 1234.56 ')
expect('1234.56').to.be('1234.56'); // I know this isn't the correct test... I will amend this
});
});


Now I wish to run my tests just to make sure that the setup in the spec file is correct, but I get the following error:




HeadlessChrome 0.0.0 (Mac OS X 10.12.6) Trim Directive before each
hook for should trim the input FAILED Uncaught (in promise): Error:
StaticInjectorError(DynamicTestModule)[TrimInputDirective]:

StaticInjectorError(Platform: core)[TrimInputDirective]:
NullInjectorError: No provider for TrimInputDirective! Error: StaticInjectorError(DynamicTestModule)[TrimInputDirective]:




I don't understand why I get this error, why do I have to provide the directive? I don't think this is necessary, also if I must provide what do I provide? Providing the actual directive doesn't work / resolve the error? I am very confused. If anyone can tell me how to resolve the issue or why I get it I would be most appreciative.



Please note that this is a legacy Angular App and was built before the
AngularCLI was available. So it is a little unorthodox (for example it isn't using Jasmin).


More From » angular

 Answers
7

1) You don't need to provide your directive, you just need to declare it in the TestingModule. Then, it will be used in the template with the corresponding selectors.



2) Your selector does not correspond to the one used on your input. Remove formControlName if you want it to apply to all inputs of certain types or change your test.



input
:not([type=checkbox])
:not([type=radio])
:not([type=password])
:not([readonly])
:not(.ng-trim-ignore)
[formControlName],
^^^^^^^^^^^^^^^^^^


3) The Directive gets triggered on certain events. You need to simulate those events to see an effect. Have a look at this simplified example. (Stackblitz)



@Directive({
selector: `
input
:not([type=checkbox])
:not([type=radio])
:not([type=password])
:not([readonly])
:not(.ng-trim-ignore)
`
})
export class TrimInputDirective {
constructor(private el: ElementRef) { }

@HostListener('blur') onLeave() {
if (this.el.nativeElement.value)
this.el.nativeElement.value = this.el.nativeElement.value.trim();
}

}


And the test:



describe('Trim Directive', () => {
let fixture: ComponentFixture<TestComponent>;
let inputDebugElement: any;
let directive: TrimInputDirective;

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
TestComponent,
TrimInputDirective
],
imports: [FormsModule],
providers: []
}).overrideComponent(TestComponent, {
set: {
template: '<input type=text>'
}
}).compileComponents().then(() => {
fixture = TestBed.createComponent(TestComponent);
inputDebugElement = fixture.debugElement.query(By.css('input')).nativeElement;
^^^^^^^^^^^^
});
}));

it('should trim the input', () => {
inputDebugElement.value = ' 1234.56 ';
inputDebugElement.dispatchEvent(new Event('blur'));
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
fixture.detectChanges();
expect(inputDebugElement.value).toBe('1234.56');
});
});

[#54297] Tuesday, May 29, 2018, 6 Years  [reply] [flag answer]
Only authorized users can answer the question. Please sign in first, or register a free account.
clarkulisesa

Total Points: 422
Total Questions: 93
Total Answers: 112

Location: Austria
Member since Thu, Jan 7, 2021
3 Years ago
clarkulisesa questions
Mon, Feb 24, 20, 00:00, 4 Years ago
Mon, Aug 12, 19, 00:00, 5 Years ago
;