So i try to do some unit testing on a component. But I have some problems with Input() parameter. And then especially a component in the component
SO I have this component:
export class EcheqDisplayComponent implements OnInit {
echeq: EcheqSubmissionApi;
constructor(
private route: ActivatedRoute
) {
this.echeq = this.route.snapshot.data['submission'];
}
ngOnInit() {
}
getAnswers(page: EcheqPageApi): any[] {
return page.elements.map(element => this.echeq.answers[element.name]);
}
}
and the template:
<div class="echeq-display" *ngIf="echeq">
<header class="echeq-display-header header">
<div class="echeq-display-info">
<h1 class="heading echeq-display-heading">
{{ echeq.definition.title }}
</h1>
<div class="sub-heading echeq-display-subheading">
<span class="echeq-display-creator">
Toegekend door:
{{ echeq.assignedByProfName ? echeq.assignedByProfName : 'Het Systeem' }}
</span>
<span class="echeq-display-date">{{
echeq.definition.createdOnUtc | date: 'dd MMM'
}}</span>
</div>
</div>
<app-meta-box
[metadata]="{
numPages: echeq.definition.numPages,
vPoints: echeq.definition.awardedVPoints
}"
></app-meta-box>
</header>
<main class="echeq-display-questions body">
<app-echeq-question
*ngFor="let page of echeq.definition.pages; let i = index"
[page]="page"
[readonly]="true"
[number]="i + 1"
[answers]="getAnswers(page)"
></app-echeq-question>
</main>
</div>
and the unit test:
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { EcheqDisplayComponent } from './echeq-display.component';
import { ParticipantEcheqModule } from '../../participant-echeq.module';
import { RouterModule, ActivatedRoute } from '#angular/router';
import { MockActivatedRoute } from 'src/app/shared/mocks/MockActivatedRoute';
import { MetaData } from '../meta-box/meta-box.component';
describe('EcheqDisplayComponent', () => {
let component: EcheqDisplayComponent;
let fixture: ComponentFixture<EcheqDisplayComponent>;
const metaData: MetaData = new MetaData();
// const metaDataInfo = fixture.debugElement.componentInstance;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ],
providers: [
{ provide: ActivatedRoute, useClass: MockActivatedRoute }
],
imports:[
ParticipantEcheqModule
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(EcheqDisplayComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
fit('should create component', () => {
metaData.numPages = 20;
expect(component).toBeTruthy();
});
});
But it stay saying:
TypeError: Cannot read property 'numPages' of undefined.
So what I have to change that it is working?
Thank you
So the MetaData is from the app-meta-box component.it looks like this:
export class MetaData {
numPages: number;
vPoints: number;
}
#Component({
selector: 'app-meta-box',
templateUrl: './meta-box.component.html',
styleUrls: ['./meta-box.component.scss']
})
export class MetaBoxComponent implements OnInit {
#Input() metadata: MetaData;
constructor() {}
ngOnInit() {}
}
This is the mock class:
export class MockActivatedRoute {
public snapshot = {
data: {
submission: {
answers: {}
}
}
};
}
I have it now like this:
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { EcheqDisplayComponent } from './echeq-display.component';
import { ParticipantEcheqModule } from '../../participant-echeq.module';
import { RouterModule, ActivatedRoute } from '#angular/router';
import { MockActivatedRoute } from 'src/app/shared/mocks/MockActivatedRoute';
import { MetaData } from '../meta-box/meta-box.component';
describe('EcheqDisplayComponent', () => {
let component: EcheqDisplayComponent;
let fixture: ComponentFixture<EcheqDisplayComponent>;
const metaData: MetaData = new MetaData();
// const metaDataInfo = fixture.debugElement.componentInstance;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ],
providers: [
{ provide: ActivatedRoute, useValue: new MockActivatedRoute().withData({submission:{ answers:{} } }) }
],
imports:[
ParticipantEcheqModule
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(EcheqDisplayComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
fit('should create component', () => {
component.echeq = {
definition: {
title: 'test title',
awardedVPoints: 0,
numPages: 9
}
}
expect(component).toBeTruthy();
});
});
But then I get this error:
Property 'answers' is missing in type '{ definition: { title: string; awardedVPoints: number; numPages: number; }; }' but required in type 'EcheqSubmissionApi'.ts(2741)
echeqSubmissionApi.ts(59, 5): 'answers' is declared here.
I have it now like this:
fit('should create component', () => {
component.echeq = {
definition: {
title: 'test title',
awardedVPoints: 0,
numPages:9
}
} as EcheqSubmissionApi;
expect(component).toBeTruthy();
});
export interface EcheqSubmissionApi {
/**
* Primary key of this submission (server set).
*/
id?: string;
definition?: EcheqDefinitionApi;
/**
* Id of the prof who assigned this eCheq (server set).
*/
assignedByProfId?: string;
/**
* Name of the prof who assigned this eCheq (server set). Only set on get operations. May be null if assigned by the system.
*/
assignedByProfName?: string;
/**
* Id of the organisation who assigned this eCheq (server set).
*/
assignedByOrgId?: number;
/**
* Participant ID of the patient this eCheq is assigned to (server set).
*/
assignedToId?: string;
/**
* When this submission was assigned (UTC, server set).
*/
assignedOnUtc?: Date;
/**
* If set until when the eCheq can be submitted.
*/
validUntilUtc?: Date;
/**
* Whether the eCheq has been started and whether it has been submitted (server set).
*/
status?: EcheqSubmissionApi.StatusEnum;
/**
* When this submission was completed (UTC, server set).
*/
submittedOnUtc?: Date;
/**
* Answers of form. In the form of a json object with name and value of the questions { \"nameOfQuestion\" : \"valueOfQuestion\" }
*/
answers: object;
/**
* initialValues of form. In the form of a json object with name and value of variables { \"nameOfQuestion\" : \"valueOfQuestion\" }
*/
initialValues?: object;
/**
* The page the participant is currently on
*/
currentPage?: number;
/**
* The progress of the echeq in percentage
*/
progress?: number;
}
export namespace EcheqSubmissionApi {
export type StatusEnum = 'New' | 'Active' | 'Submitted';
export const StatusEnum = {
New: 'New' as StatusEnum,
Active: 'Active' as StatusEnum,
Submitted: 'Submitted' as StatusEnum
};
}
But if I run the unit test, I still get this error:
TypeError: Cannot read property 'numPages' of undefined
To answer your question:
component.metadata = /// whatever you want this to be
You have your error with numPages: echeq.definition.numPages. Endeed, echeq is also undefined.
You can try :
component.echeq = {
definition: {
numPages: 9
}
}
Or better way would be to returns this value from this.route.snapshot.data['submission']; so from MockActivatedRoute
UPDATE:
And update MockActivatedRoute to allows dynamic parameters:
export class MockActivatedRoute {
snapshot = {
data: {}
};
constructor(){}
withData(data:any): MockActivatedRoute {
this.snapchot.data = data;
return this;
}
}
So now in your test, you can use it :
{ provide: ActivatedRoute, useValue: new MockActivatedRoute().withData({submission:{ answers:{} } }) }
No problem. It was an easy:
beforeEach(async(() => {
TestBed.configureTestingModule({
providers: [
],
imports:[
ParticipantEcheqModule,
RouterTestingModule
]
})
.compileComponents();
}));
Related
I am making a search page in my app (Angular 14 + Ionic 6) that is searching via API call using GET method and am having some trouble with it. It keeps returning 'undefined' to my console. And there is also the problem with the pipe that after I type some text in the input I get this error in console: TypeError: Cannot read properties of undefined (reading 'filter')
Could someone take a look and help me out please? :)
search.service.ts:
searchCall(term: string) {
return from(Preferences.get({key: 'TOKEN_KEY'})).pipe(
switchMap(token => {
const headers = new HttpHeaders().set('Authorization', `Bearer ${token.value}`);
let params = new HttpParams();
params = params.append('term', term);
return this.httpClient.get(`${environment.apiUrl}search`, {headers, observe: 'response', params});
}),
catchError(err => {
console.log(err.status);
if (err.status === 400) {
console.log(err.error.message);
}
if (err.status === 401) {
this.authService.logout();
this.router.navigateByUrl('/login', {replaceUrl: true});
}
return EMPTY;
}),
);
}
search.page.ts:
export class SearchPage implements OnInit {
term = '';
products: any = {
id: '',
name: '',
product_code: '',
};
constructor(
private searchService: SearchService,
) { }
ngOnInit() {
this.search(this.term);
}
search(term: string) {
this.searchService.searchCall(term).subscribe(
(data: any) => {
console.log('Search: ' + data.body.products);
},
error => {
console.log('Error', error);
}
);
}
}
search.page.html:
<ion-content [fullscreen]="true" class="ion-padding">
<ion-searchbar [debounce]="1000" placeholder="Search" show-clear-button="focus" [(ngModel)]="term"></ion-searchbar>
<ion-list>
<ion-item *ngFor="let produkt of products?.results | filter : term">
<ion-label>{{ produkt.product_code }} {{ produkt.name }}</ion-label>
</ion-item>
</ion-list>
</ion-content>
filter.pipe.ts:
export class FilterPipe implements PipeTransform {
public transform(value: any[], filterText: string) {
return filterText.length > 3 ? value.filter(x => x.name.toLowerCase().includes(filterText.toLowerCase())) : value;
}
}
EDIT: As requested in comments I am also adding the code from import modules:
My filter pipe is included in the shared.module.ts file and here is the code:
import { NgModule } from '#angular/core';
import { CommonModule } from '#angular/common';
import { FooterComponent } from '../navigation/footer/footer.component';
import { RouterLink } from '#angular/router';
import { IonicModule } from '#ionic/angular';
import { SideMenuComponent } from '../navigation/side-menu/side-menu.component';
import { SafeHtmlPipe } from '../pipes/safe-html.pipe';
import { FilterPipe } from '../pipes/filter.pipe';
#NgModule({
declarations: [FooterComponent, SideMenuComponent, SafeHtmlPipe, FilterPipe],
imports: [
CommonModule,
RouterLink,
IonicModule
],
exports: [FooterComponent, SideMenuComponent, SafeHtmlPipe, FilterPipe]
})
export class SharedModule { }
JSON response from API looks like this:
[
{
"id": 3,
"name": "test",
"product_code": "45623146546"
},
]
Your issue is mostly likely with how the filter is called, since your updated question shows the pipe itself is imported correctly.
Please try adding a console.log to your pipe like so:
export class FilterPipe implements PipeTransform {
public transform(value: any[], filterText: string) {
console.log('value', value, 'filterText', filterText);
return filterText.length > 3 ? value.filter(x => x.name.toLowerCase().includes(filterText.toLowerCase())) : value;
}
}
You will likely see that your expected input of value is not an array.
I'm currently working on an Angular project and I am creating unit testing for a component using Karma + Jasmine, so I have HTML that has a ngIf calling the API Service as:
HTML
<div class="row" *ngIf="apiService.utilsService.userBelongsTo('operations')"></div">
TS
export class CashFlowSalariesComponent implements OnInit, OnChanges {
constructor(
public apiService: ApiService,
) {}
SPECT.TS
describe('CashFlowSalariesComponent', () => {
let fixture: ComponentFixture < CashFlowSalariesComponent > ;
let mockCashFlowService;
let data;
beforeEach(async(() => {
data = [{
id: 1006,
role: "Developer",
...
}]
mockCashFlowService = jasmine.createSpyObj(['createTableData'])
TestBed.configureTestingModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
imports: [
RouterTestingModule,
FormsModule,
ReactiveFormsModule,
BrowserModule,
HttpClientTestingModule,
ToastrModule.forRoot({
positionClass: 'toast-bottom-right'
})
],
declarations: [
CashFlowSalariesComponent,
],
providers: [{
provide: ApiService,
useValue: mockCashFlowService
}, UserService, ProfileService, VettingStatusService, ApplicationRoleService,
SeniorityLevelService, PlacementStatusService, EducationLevelService, UtilsService, ShirtSizeService,
CountryService, CityService, PostalCodeService, StateService, ClientSectorService, JobService, ProfileActivityService, ProfileSalaryActivityService, ClientService, RequestTimeOffService, TimeOffTypeService, PulsecheckDetailService, PulsecheckMasterService,
PulsecheckQuestionService, ExpenseService, DepartmentService, ExchangeRateService, SkillCategoriesService, ProfileRoleService,
ToastrService
]
})
fixture = TestBed.createComponent(CashFlowSalariesComponent);
}));
it('should create', () => {
expect(CashFlowSalariesComponent).toBeTruthy();
});
it('should set salaries data correctly', () => {
mockCashFlowService.userBelongsTo = 'operations'
mockCashFlowService.createTableData.and.returnValue( of (data))
debugger;
fixture.detectChanges();
expect(fixture.componentInstance.dataHeaders.length).toBe(10);
})
As you see, I tried to set userBelongsTo as: mockCashFlowService.userBelongsTo = 'operations' but I get an error:
TypeError: Cannot set properties of undefined (setting
'userBelongsTo')
ApiService
#Injectable()
export class ApiService {
public utilsService: UtilsService;
constructor(private injector: Injector) {
this.utilsService = injector.get(UtilsService);
}
}
Utils.Service:
userBelongsTo(groupName: string) {
return this.groups.split(',').reduce((c, g) => c || g.toUpperCase() == groupName.toUpperCase(), false);
}
It is undefined because you have not defined it. You need to add it to your mock and then have it return something.
mockCashFlowService = jasmine.createSpyObj(['createTableData', 'userBelongsTo']);
https://volaresoftware.com/en/technical-posts/mocking-calls-with-jasmine
Hi Every one here
I face problem with fetching data to array but when I put data to array the editor said not defined array
Error Message:
Failed to compile.
src/app/customers/customers-list/customers-list.component.ts:111:14 - error TS2551: Property 'CUSTOMERS' does not exist on type 'CustomersListComponent'. Did you mean 'customers$'?
111 this.CUSTOMERS = posts;
~~~~~~~~~
src/app/customers/customers-list/customers-list.component.ts:64:3
64 customers$: Observable<Customer[]>;
~~~~~~~~~~
'customers$' is declared here.
This is the CODE
import {
Component,
OnInit,
PipeTransform, // table
} from '#angular/core';
import { DecimalPipe } from '#angular/common'; // table
import { FormControl } from '#angular/forms'; // table
import { Observable } from 'rxjs'; // table
import { map, startWith } from 'rxjs/operators'; // table
import {NgbModal} from '#ng-bootstrap/ng-bootstrap'; // modal
import {AddCustomerComponent} from '../add-customer/add-customer.component'; // modal
import { faFolderPlus, faPencilAlt, faTrashAlt } from '#fortawesome/free-solid-svg-icons'; // fontawsome icons
import {HttpClient} from '#angular/common/http';
// table
interface Customer {
id: number;
name: string;
company: string;
remaining: number;
email: string;
mobile: number;
whats_up: number;
}
let CUSTOMERS: Customer[] = [
{
id: 12,
name: 'jack',
company: 'SDTE',
remaining: 580,
email: 'test#test.com',
mobile: +456456456456,
whats_up: +456456456
}
];
function search(text: string, pipe: PipeTransform): Customer[] {
return CUSTOMERS.filter(customer => {
const term = text.toLowerCase();
return customer.name.toLowerCase().includes(term)
|| customer.company.toLowerCase().includes(term)
|| pipe.transform(customer.remaining).includes(term)
|| customer.email.toLowerCase().includes(term)
|| pipe.transform(customer.mobile).includes(term)
|| pipe.transform(customer.whats_up).includes(term);
});
}
#Component({
selector: 'app-customers-list',
templateUrl: './customers-list.component.html',
styleUrls: ['./customers-list.component.css'],
providers: [DecimalPipe] // table
})
export class CustomersListComponent implements OnInit {
// table
customers$: Observable<Customer[]>;
filter = new FormControl('');
faFolderPlus = faFolderPlus;
faPencilAlt = faPencilAlt;
faTrashAlt = faTrashAlt;
constructor(
pipe: DecimalPipe, // table
private modalService: NgbModal, // modal
private http: HttpClient // Get All Data
) {
// table
this.customers$ = this.filter.valueChanges.pipe(
startWith(''),
map(text => search(text, pipe))
);
}
ngOnInit(): void {
this.getAllData();
}
// modal
openPopupModal() {
const modalRef = this.modalService.open(AddCustomerComponent,{ centered: true, size: 'lg' });
modalRef.componentInstance.name = 'World';
}
private getAllData() {
this.http
.get('http://localhost:3000/customers')
.subscribe(
posts => {
console.log('GET all Data works');
this.CUSTOMERS = posts; // <<<<< Here is the problem ************ How can I to Fix it.
});
}
}
I
this.CUSTOMERS = posts; this refers to current class CustomersListComponent but your variable is outside the class so you need to assign directly CUSTOMERS = posts; :)
You need to specify the return type.
You could try using this:
private getAllData() {
this.http
.get<Customer[]>('http://localhost:3000/customers') // <<<<< Try using this.
.subscribe(
posts => {
console.log('GET all Data works');
CUSTOMERS = posts;
});
}
So I am doing some unit tests. And the following component I try to unit test:
export class EcheqDisplayComponent implements OnInit {
echeq: EcheqSubmissionApi;
orgId = 1;
constructor(
private route: ActivatedRoute
) {
this.echeq = this.route.snapshot.data['submission'];
}
ngOnInit() {
}
getAnswers(page: EcheqPageApi): any[] {
return page.elements.map(element => this.echeq.answers[element.name]);
}
And this is the unit test for it:
describe('EcheqDisplayComponent', () => {
let component: EcheqDisplayComponent;
let fixture: ComponentFixture<EcheqDisplayComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ],
imports:[ParticipantEcheqModule,
RouterModule.forRoot([])
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(EcheqDisplayComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
fit('should create component', () => {
expect(component).toBeTruthy();
});
});
So I dont get errors.
But my question is. Is this the correct way to do ti?
Because is it not better to use a mock for ActivatedRoute?
An if yes, how to do it then the correct way?
Thank you
sO THIS IS the template of it:
<div class="echeq-display" *ngIf="echeq">
<header class="echeq-display-header header">
<div class="echeq-display-info">
<h1 class="heading echeq-display-heading">
{{ echeq.definition.title }}
</h1>
<div class="sub-heading echeq-display-subheading">
<span class="echeq-display-creator">
Toegekend door:
{{ echeq.assignedByProfName ? echeq.assignedByProfName : 'Het Systeem' }}
</span>
<span class="echeq-display-date">{{
echeq.definition.createdOnUtc | date: 'dd MMM'
}}</span>
</div>
</div>
<app-meta-box
[metadata]="{
numPages: echeq.definition.numPages,
vPoints: echeq.definition.awardedVPoints
}"
></app-meta-box>
</header>
<main class="echeq-display-questions body">
<app-echeq-question
*ngFor="let page of echeq.definition.pages; let i = index"
[page]="page"
[readonly]="true"
[number]="i + 1"
[answers]="getAnswers(page)"
></app-echeq-question>
</main>
</div>
and the unit test like this:
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ],
providers: [
{ provide: ActivatedRoute, useClass: MockActivatedRoute }
],
imports:[
ParticipantEcheqModule
]
})
.compileComponents();
}));
and this is the app-meta-box component:
export class MetaData {
numPages: number;
vPoints: number;
}
#Component({
selector: 'app-meta-box',
templateUrl: './meta-box.component.html',
styleUrls: ['./meta-box.component.scss']
})
export class MetaBoxComponent implements OnInit {
#Input() metadata: MetaData;
constructor() {}
ngOnInit() {}
}
You can simply provide mock ActivatedRoute object with necessary variable set in "providers" property when "TestBed.configureTestingModule({...})" is called to configure testing module.
class MockActivatedRoute {
public snapshot = {
data: {
submission: {
answers: {}
}
}
};
}
TestBed.configureTestingModule({
declarations: [ ],
providers: [
{ provide: ActivatedRoute, useClass: MockActivatedRoute }
]
imports:[
ParticipantEcheqModule,
// RouterModule.forRoot([]) // Not required after mocking the "ActivatedRoute"
]
})
.compileComponents();
This way you can test your component for different values in activatedRoute.
So I followed a tutorial on how to add recaptcha to a form to prevent spam. I followed all the steps, but I still get the following error:
error inside log Error: No value accessor for form control with name: 'captcha'
at _throwError (forms.js:2139)
at setUpControl (forms.js:2008)
at FormGroupDirective.addControl (forms.js:5144)
at FormControlName._setUpControl (forms.js:5743)
at FormControlName.ngOnChanges (forms.js:5684)
at checkAndUpdateDirectiveInline (core.js:11642)
at checkAndUpdateNodeInline (core.js:13252)
I have a reactive signup form where I have the following element that's supposed to be the captcha and a formControl. BUt unfortunately I see nothing.
<div nbRecaptcha key="6LfNfHYUAAAAAFaa7dVi2au1tnfod-2eR3Rb2enM" formControlName="captcha"></div>
This is the nbRecaptcha directive:
declare const grecaptcha: any;
declare global {
interface Window {
grecaptcha: any;
reCaptchaLoad: () => void
}
}
#Directive({
selector: '[nbRecaptcha]',
providers: [ReCaptchaAsyncValidator]
})
export class RecaptchaDirective implements OnInit, ControlValueAccessor, AfterViewInit {
#Input() key: string;
#Input() config: ReCaptchaConfig = {};
#Input() lang: string;
private control: FormControl;
private onChange: ( value: string ) => void;
private onTouched: ( value: string ) => void;
private widgetId: number;
constructor(
private element: ElementRef,
private ngZone: NgZone,
private injector: Injector,
private reCaptchaAsyncValidator: ReCaptchaAsyncValidator){}
ngOnInit() {
this.registerReCaptchaCallback();
this.addScript();
}
ngAfterViewInit() {
this.control = this.injector.get(NgControl).control;
this.setValidator();
}
addScript() {
let script = document.createElement('script');
const lang = this.lang ? '&hl=' + this.lang : '';
script.src = `https://www.google.com/recaptcha/api.js?onload=reCaptchaLoad&render=explicit${lang}`;
script.async = true;
script.defer = true;
document.body.appendChild(script);
}
// We need to notify the formControl that it’s valid if we get the token
// from the onSuccess function or that it’s invalid if the onExpired function is called.
onExpired() {
this.ngZone.run(() => {
this.onChange(null);
this.onTouched(null);
});
}
onSuccess( token: string ) {
this.ngZone.run(() => {
this.verifyToken(token);
this.onChange(token);
this.onTouched(token);
});
}
// these are the three methods that controlValueAccessor requires
writeValue( obj: any ): void {
}
registerOnChange( fn: any ): void {
this.onChange = fn;
}
registerOnTouched( fn: any ): void {
this.onTouched = fn;
}
private setValidator() {
this.control.setValidators(Validators.required);
this.control.updateValueAndValidity();
}
registerReCaptchaCallback() {
window.reCaptchaLoad = () => {
const config = {
...this.config,
'sitekey': this.key,
'callback': this.onSuccess.bind(this),
'expired-callback': this.onExpired.bind(this)
};
this.widgetId = this.render(this.element.nativeElement, config);
};
}
private render( element: HTMLElement, config ): number {
return grecaptcha.render(element, config);
}
verifyToken( token : string ) {
this.control.setAsyncValidators(this.reCaptchaAsyncValidator.validateToken(token))
this.control.updateValueAndValidity();
}
}
And the signup component form:
this.mySignupForm = new FormGroup({
captcha: new FormControl()
});
Can Anyone see what I did wrong?
The error No value accessor for form control with name: 'captcha'. This can be resolved by adding the directive/control to list of all available NG_VALUE_ACCESSORs using forwardRef. You are effectively extending the multi-provider for NG_VALUE_ACCESSOR so that this directive can access ControlValueAccessor.
#Directive({
selector: '[nbRecaptcha]',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => RecaptchaDirective),
multi: true
},
ReCaptchaAsyncValidator
]
})
Here is a mostly working example with that specific error being resolved. I had to remove some of the custom classes/configurations you had in your example as I wasn't aware of their exact implementations.
I could not find official documentation for creating custom controls, but there are a few solid articles out there discussing creating custom controls that reference this necessary registration.
Hopefully that helps!
Sometimes we get this kind of error when import it in app.module instead of the own module where we need to use . for example
///app.module.ts
import { RecaptchaModule, RecaptchaFormsModule } from "ng-recaptcha";
imports: [
BrowserModule,
RecaptchaModule,
RecaptchaFormsModule
]
move it to --->
///x.module.ts
import { RecaptchaModule, RecaptchaFormsModule } from "ng-recaptcha";
#NgModule({
imports: [
FormsModule,
RecaptchaModule,
RecaptchaFormsModule,
],
providers:[
{
provide: RECAPTCHA_SETTINGS,
useValue: { siteKey: environment.recaptcha.siteKey } as RecaptchaSettings,
},
]