How to fix Error in email validator Angular - html

I'm trying to create an email centre using the regex pattern but I'm getting an error in validator in HTML. Installed ngx-chips and angular-editor, imported all the modules and the dependencies
HTML-
<div class="row class-spacing">
<div class="col-2">To</div>
<div class="col-8 set-border">
<tag-input
[ngModel]="toEmails"
[displayBy]="'label'"
[identifyBy]="'label'"
[theme]="'bootstrap'"
name="toEmails"
placeholder="+ To"
[separatorKeyCodes]="[32, 188]"
secondaryPlaceholder="To"
[errorMessages]="errorMessages"
[validators]="validators"
[removable]="true"
>
<tag-input-dropdown
[dynamicUpdate]="false"
[zIndex]="10000"
[displayBy]="'label'"
[identifyBy]="'label'"
[showDropdownIfEmpty]="true"
>
<ng-template let-item="item" let-index="index">
{{ item.label }}
</ng-template>
</tag-input-dropdown>
</tag-input>
</div>
</div>
The TypeScript file-
export class EmailComponent implements OnInit {
public errorMessages = {
pattern: 'Email must be in format abc#abc.com',
};
public validators = (this.checkPattern);
#ViewChild('form', { static: false })
form!: NgForm;
public dataModel: any;
public editorTextCount = 0;
public isSubmitted: boolean = false;
public isFieldEmpty: boolean = false;
public isMaxLengthLimitReached: boolean = false;
public config: AngularEditorConfig = {
editable: true,
spellcheck: true,
height: '200px',
width: 'auto',
enableToolbar: true,
showToolbar: true,
placeholder: 'Enter your email',
fonts: [
{ class: 'arial', name: 'Arial' },
{ class: 'times-new-roman', name: 'Times New Roman' },
{ class: 'calibri', name: 'Calibri' },
],
sanitize: true,
toolbarPosition: 'top',
toolbarHiddenButtons: [
[],
['customClasses', 'insertImage', 'insertVideo', 'toggleEditorMode'],
],
};
body: any;
subject: any;
toEmails: any;
public formDetails = {
toEmails: '',
subject: '',
body: '',
};
public items = ['Pizza', 'Pasta', 'Parmesan'];
constructor() {}
ngOnInit() {}
public sendEmail(): void {
console.log(this.form.value);
}
private checkPattern(control: FormControl) {
const patternRegex = /^[A-Za-z0-9._%+-]+#[a-z0-9.-]+\.[a-z]{2,4}$/;
if (patternRegex.test(control.value)) {
return console.log('Match exists.');
} else {
return { pattern: true };
}
}
}
The error I'm getting in the line-
[validators]="validators"
The error I'm getting-
Type '(control: FormControl) => void | { pattern: boolean; }' is not
assignable to type 'ValidatorFn[]'.

Implement a custom validator function requires:
Only one input argument is expected, which is of type AbstractControl. The validator function can obtain the value to be validated via the control.value property
The validator function needs to return null if no errors were found in the field value, meaning that the value is valid
If any validation errors are found, the function needs to return an object of type ValidationErrors
Reference: Angular Custom Form Validators: Complete Guide (How to write a Validator function section)
private checkPattern(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const patternRegex = /^[A-Za-z0-9._%+-]+#[a-z0-9.-]+\.[a-z]{2,4}$/;
if (patternRegex.test(control.value)) {
console.log('Match exists.');
return null;
} else {
return { pattern: true };
}
};
}
Assign validators with a ValidatorFn array containing checkPattern ValidatorFn.
public validators = [this.checkPattern];

Related

Observable data on Angular not displaying

I'm trying to pass the data of an Observable retrieved by API request to a component variable in order to display it but I just can't make it work. Please help, here is my code. Regards,
TypeScript Service : Request to API to get Observable
export class ServeurService {
serverCharacter = 'https://cegep.fdtt.space/v1/characters';
serverSecret = 'https://cegep.fdtt.space/v1/secret';
personnages: any[] = [];
persoName = '';
constructor(private http_client: HttpClient) { }
getPersonnage(): Observable<ICharactere[]> {
return this.http_client.get<ICharactere[]>(this.serverCharacter).pipe(retry(4));
}
getAllInfosById(id: string): Observable<ICharactere> {
const myUrl = 'https://cegep.fdtt.space/v1/character/' + id;
return this.http_client.get<ICharactere>(myUrl)?.pipe();
}
setPersonnageName(name: string) {
this.persoName = name;
}
getPersonnageName():string {
return this.persoName;
}
getPersonnages() {
this.http_client.get<any>('https://cegep.fdtt.space/v1/characters').subscribe({
next: (val) => {
val.data.forEach((element: { data: any; }) => {
this.personnages.push(element);
});
}
});
return this.personnages;
}
getPersonnageById(id: string) {
const persoSelectionne = this.getPersonnages().find((x: { id: string; }) => x.id === id);
return persoSelectionne;
}
getPersonnageIdByName(name: string) {
const persoSelectionne = this.getPersonnages().find((n: {name: string; }) => n.name === name);
console.log("perso name service", persoSelectionne)
return persoSelectionne.id;
}
TypeScript Component : Passing the Observable to a variable
export class StatsComponent implements OnInit {
myCharactere!: any;
statLookup = [
{ key: 'str', prefix: $localize`str`, suffix: $localize`enght`, couleur: 'bg-danger' },
{ key: 'dex', prefix: $localize`dex`, suffix: $localize`terity`, couleur: 'bg-primary' },
{ key: 'con', prefix: $localize`con`, suffix: $localize`stitution`, couleur: 'bg-warning' },
{ key: 'int', prefix: $localize`int`, suffix: $localize`elligence`, couleur: 'bg-success' },
{ key: 'sag', prefix: $localize`wis`, suffix: $localize`dom`, couleur: 'bg-info' },
{ key: 'cha', prefix: $localize`cha`, suffix: $localize`risma`, couleur: 'bg-dark' }
];
constructor(public myService: ServeurService) { }
ngOnInit(): void {
this.myService.getAllInfosById(this.myService.getPersonnageIdByName(this.myService.getPersonnageName())).subscribe(result => {
this.myCharactere = result
});
console.log("stats component perso", this.myCharactere)
}
getModifier(stat: number): string {
const mod = Math.floor((stat-10)/2)
return (mod<0)?'-':'+'+ mod.toString();
}
}
HTML : Displaying the variable
<div class="row text-center text-light bg-secondary mt-2 bg-transparent">
<div class="row mt-5">
<div class="col-2" *ngFor="let stat of statLookup">
<div class="{{stat.couleur}} mx-xxl-4 mx-2 mx-md-1 rounded-4">
<div class="fw-bold">{{stat.prefix}}<span class="d-none d-lg-inline">{{stat.suffix}}</span>
</div>
<div class="h2">
{{myCharactere && myCharactere.statistics && myCharactere.statistics[stat.key] ? getModifier(myCharactere.statistics[stat.key]) : ''}}
</div>
<div class="">
{{myCharactere?.data.statistics[stat.key]}}
</div>
</div>
</div>
</div>
If it helps, here is the model too :
export interface ICharactere {
error: string;
data: {
id: string;
name: string;
statistics: { [ key : string ]: number }
race: string;
player: string;
classe : string;
sousclasses: string;
level: number;
background: string;
synopsis: string;
image: string;
health: number;
currentHealth: number;
traits: {
trait: string;
description: string;
}[];
// Should be computed
armorClass: number;
initiative: number;
speed: number;
};
}
You have some fundamental concepts mixed up here. First, you're calling getPersonnages() synchronously and it is making an HTTP call which is an asynchronous operation. I understand what you're trying to do, but if you are going to use observables for your search result, then I suggest you make all of your function calls consistent that way. Here's an example:
getPersonnages(): Observable<any> {
return this.http.get<any>('https://cegep.fdtt.space/v1/characters');
}
getPersonnageIdByName(name: string) {
return new Observable(observer => {
this.getPersonnages().subscribe(
(results => {
observer.next(results.data.find(x => x.name === name))
observer.complete();
})
)
})
}
Now you can search for the ID value you want like this:
this.getPersonnageIdByName("Michel Michaud").subscribe(
(searchResult => {
// here I can get the id
const id = searchResult.id;
})
);

Send Angular API GET request value to a variable

I am trying to display the data of an object on Angular like so
{{myCharactere && myCharactere.statistics && myCharactere.statistics[stat.key] || ''}}.
The object is issue from an API GET request but I'm not able to send it's value to my local variable myCharactere. Thank you for helping me out! Edit: Added code for clarification
Here is what I tried
TypeScript component
export class StatsComponent implements OnInit {
myCharactere: any;
statLookup = [
// Pourquoi est-ce un mauvais choix???
{ key: 'str', prefix: $localize`str`, suffix: $localize`enght`, couleur: 'bg-danger' },
{ key: 'dex', prefix: $localize`dex`, suffix: $localize`terity`, couleur: 'bg-primary' },
{ key: 'con', prefix: $localize`con`, suffix: $localize`stitution`, couleur: 'bg-warning' },
{ key: 'int', prefix: $localize`int`, suffix: $localize`elligence`, couleur: 'bg-success' },
{ key: 'sag', prefix: $localize`wis`, suffix: $localize`dom`, couleur: 'bg-info' },
{ key: 'cha', prefix: $localize`cha`, suffix: $localize`risma`, couleur: 'bg-dark' }
];
constructor(public myService: ServeurService) { }
ngOnInit(): void {
this.myService.getAllInfosById(this.myService.getPersonnageIdByName("Xefoul Snaromers")).subscribe(result => {
this.myCharactere = result
console.log(this.myCharactere);
});
getModifier(stat: number): string {
const mod = Math.floor((stat-10)/2)
return (mod<0)?'-':'+'+ mod.toString();
}
}
TypeScript Service
export class ServeurService {
personnages: any[] = [];
persoName = '';
constructor(private http_client: HttpClient) { }
getPersonnage(): Observable<ICharactere[]> {
return this.http_client.get<ICharactere[]>(this.serverCharacter).pipe(retry(4));
}
getAllInfosById(id: string) {
const myUrl = 'https://cegep.fdtt.space/v1/character/' + id;
return this.http_client.get<ICharactere>(myUrl).pipe();
}
setPersonnageName(name: string) {
this.persoName = name;
}
getPersonnageName():string {
return this.persoName;
}
getPersonnages() {
this.http_client.get<any>('https://cegep.fdtt.space/v1/characters').subscribe({
next: (val) => {
val.data.forEach((element: { data: any; }) => {
this.personnages.push(element);
});
}
});
return this.personnages;
}
getPersonnageById(id: string) {
const persoSelectionne = this.getPersonnages().find((x: { id: string; }) => x.id === id);
return persoSelectionne;
}
getPersonnageIdByName(name: string) {
const persoSelectionne = this.getPersonnages().find((n: {name: string; }) => n.name === name);
console.log("perso name service", persoSelectionne)
return persoSelectionne.id;
}
}
HTML to display
<div class="row text-center text-light bg-secondary mt-2 bg-transparent">
<div class="row mt-5">
<div class="col-2" *ngFor="let stat of statLookup">
<div class="{{stat.couleur}} mx-xxl-4 mx-2 mx-md-1 rounded-4">
<div class="fw-bold">{{stat.prefix}}<span class="d-none d-lg-inline">{{stat.suffix}}</span>
</div>
<div class="h2">
{{myCharactere && myCharactere.statistics && myCharactere.statistics[stat.key] ? getModifier(myCharactere.statistics[stat.key]) : ''}}
</div>
<div class="">
{{myCharactere && myCharactere.statistics && myCharactere.statistics[stat.key] || ''}}
</div>
</div>
</div>
</div>
Model if it helps
export interface ICharactere {
error: string;
id: string;
name: string;
statistics: { [ key : string ]: number }
race: string;
player: string;
classe : string;
sousclasses: string;
level: number;
background: string;
synopsis: string;
image: string;
health: number;
currentHealth: number;
traits: {
trait: string;
description: string;
}[];
armorClass: number;
initiative: number;
speed: number;
}
Summarized from comments:
In your component you can use
serviceName.getAllInfosById("demochar").subscribe(console.log)
to manually make the request to the API and log the result. Please be aware that this.http_client.get<ICharactere>(myUrl) returns a cold observable. This means that nothing will be done until you actually call .subscribe to it.
Best practice:
Usually when you want to display data from an observable in your HTML template you define an observable and subscribe to it using async pipe.
The way to do this is to first define the observable in your component, like: info$ = this.serviceName.getAllInfosById("demochar").
Now in your HTML template you can use {{ (info$ | async).name }} to first subscribe to the observable (async pipe does this for you) and display the name property of the emitted value.
If you are actually using an observable like this.http_client.get<ICharactere>(myUrl), another way is to await the return value and store it in a this.myCharactere:
async getInfos(): void {
this.myCharactere = await firstValueFrom(this.myService.getAllInfosById(this.myService.getIdPersonnageByName("Xefoul Snaromers")));
}

Pass data from parent component to child component using input, how to implement?

There is a parent component with all the logic of the chart, you need to transfer data to the child component using #Input (), that is, so that I can display the chart in any of the components using #Input.
The parent component is logs.component and the child component is echarts.component. It is necessary to pass the data to LoadEcharts(), it contains all the logic of the Chart, that is, that I could call it on any html component
logs.components.ts
export class LogsComponent implements OnInit {
sideNavStatus: boolean = false;
subscription!: Subscription;
logs!: Logs[];
constructor(private dataService: DataService) {
}
columnDefs = [
{ headerName: 'Username', field: 'username', flex: 1},
{ headerName: 'Event', field: 'event', flex: 1 },
{ headerName: 'Date', field: 'date', flex: 1 }
];
ngOnInit() {
this.LoadLogs();
this.LoadEcharts();
}
LoadLogs(): void {
this.dataService.getLogs().subscribe(logs => this.logs = logs);
}
LoadEcharts(): void {
const chartDom: HTMLElement = document.getElementById('main') as HTMLElement;
const myChart = echarts.init(chartDom);
this.subscription = this.dataService.getLogs().subscribe(data => {
myChart.setOption(this.initBasicEchart(data))
})
}
private initBasicEchart(data: Logs[]) {
const result: any = {};
data.forEach(el => {
const date = el.date.toString().substring(0, 10);
if (!result[el.event]) {
result[el.event] = {};
if (!result[el.event][date]) {
result[el.event][date] = 1;
}
} else {
if (!result[el.event][date]) {
result[el.event][date] = 1;
} else {
result[el.event][date] += 1;
}
}
});
const login = {
x: Object.keys(result.Login),
y: Object.values(result.Login)};
const reg = {
c: Object.keys(result.Registration),
z: Object.values(result.Registration)};
return {
title: {
text: 'Graphic login and registration.'
},
tooltip: {},
xAxis: {
type: 'category',
data: (reg.c, login.x)
},
yAxis: {
},
series: [
{
name: 'Login',
type: 'bar',
data: login.y,
},
{
name: 'Registration',
type: 'bar',
data: reg.z,
}
]
};
}
}
logs.component.html
<div class="container-fluid g-0">
<app-header (sideNavToggled)="sideNavStatus = $event;"></app-header>
<main>
<app-sidebar [sideNavStatus]="sideNavStatus"
[ngClass]="{'app-side-nav-open': sideNavStatus}"></app-sidebar>
<div class="display-area p-3" [ngClass]="{'display-area-shrink': sideNavStatus}">
<p class="fs-1 fw-bold fst-italic text-center">Login and registration statistics.
</p>
<app-aggreed
*ngIf="logs"
[logs]="logs"
[columnDefs]="columnDefs"
></app-aggreed>
</div>
</main>
</div>
<app-echarts
></app-echarts>
<app-footer></app-footer>
echarts.component.html
<div class="container-fluid g-0">
<app-header (sideNavToggled)="sideNavStatus = $event;"></app-header>
<main>
<app-sidebar [sideNavStatus]="sideNavStatus"
[ngClass]="{'app-side-nav-open': sideNavStatus}"></app-sidebar>
<div class="display-area p-3" [ngClass]="{'display-area-shrink': sideNavStatus}">
<div
id="main"
style="width: 500px; height: 300px"
>
</div >
</div>
</main>
</div>
<app-footer></app-footer>
export class EchartsComponent implements OnInit {
sideNavStatus: boolean = false;
subscription!: Subscription;
constructor(private dataService: DataService) {
}
ngOnInit(): void {
}
}
I tried to pass methods to the child component through the input but nothing comes out
From your code sample, I'm assuming you're trying to access the EchartsComponent ViewChild in the logs component and pass data to it.
Here's an example of how you can do that, minus some pieces from your sample code for brevity...
class LogsComponent {
#ViewChild(EchartsComponent)
echartsComponent: EchartsComponent;
LoadEcharts(): void {
const chartDom: HTMLElement = document.getElementById('main') as HTMLElement; // <-- not sure what this is
const myChart = echarts.init(chartDom); // <-- not sure what this is
this.subscription = this.dataService.getLogs().subscribe(data => {
this.echartsComponent.logs = data;
// `logs` doesn't exist in the current code for EchartsComponent.
// You'll need to add it.
});
}
}
What I don't see in your EchartsComponent is a logs property to set. If you're using a ViewChild in the parent component, you don't have to use #Input on the ViewChild instance, you have programmatic access to the component and can set properties or call methods.
If you want to use #Input(), you can do that too:
class EchartsComponent {
#Input()
logs: Logs[];
}
// logs.component.ts
class LogsComponent {
LoadEcharts(): void {
this.subscription = this.dataService.getLogs().subscribe(data => {
this.logs = data;
})
}
}
// logs.component.html
<app-echarts [logs]="logs"></app-echarts>
In this scenario, when the observable for getLogs() emits, the property logs is set to the new value. That value, being a new reference is passed to the child component via its input.
Hope that helps you out.

angular ngx-treeview from Json object error : A text of item must be string object

this treeview item text is confusing me for the past week
this is how I load the items into the tree view:
ngOnInit(): void {
this.items = this.getItems([JSON.stringify(this.json_obj)]);
}
getItems(parentChildObj: any[]) {
let itemsArray: TreeviewItem[] = [];
parentChildObj.forEach((set: TreeItem) => {
itemsArray.push(new TreeviewItem(set,true))
});
return itemsArray;
}
and this is how I create the nested Json object from non-nested Json file :
this.departments.forEach(element => {
if(element.ParentID == 0){
p_counter++;
this.json_obj.push(
{
text: element.DepartmentName ,
value: 'p'+p_counter.toString() ,
children: [],
id: element.DepartmentID.toString() ,
} as never
);
element.DepartmentName = 'fixed';
}
});
the template is simple as that:
<ngx-treeview [items]="items" dir ="rtl"></ngx-treeview>
btw- it creates a perfect nesting but it cant read the object properties in
new TreeviewItem(set,true);
because it's undefined.
Error : A text of item must be string object at new TreeviewItem
please help me figure this out, what can I do to make it work?
You have used
text: element.DepartmentName ,
value: 'p'+p_counter.toString() ,
children: [],
id: element.DepartmentID.toString()
It seems you have not followed TreeItem interface given in treeview-item.d.ts
export interface TreeItem {
text: string;
value: any;
disabled?: boolean;
checked?: boolean;
collapsed?: boolean;
children?: TreeItem[];
}
you should remove id because that is not property of TreeItem interface.
import { Component,OnInit } from '#angular/core';
import { TreeviewItem } from 'ngx-treeview';
#Component({
selector: 'my-app',
template: `<ngx-treeview [items]="items"></ngx-treeview>`
})
export class AppComponent implements OnInit {
items: TreeviewItem[];
ngOnInit() {
this.items = this.getItems();
}
getItems(){
// fetch api response
// convert response into this format (object can be nested, should contain below keys only with given type)
// {
// text: string;
// value: any;
// disabled ?: boolean;
// checked ?: boolean;
// collapsed ?: boolean;
// children ?: TreeItem[];
// }
const item = new TreeviewItem({
text: 'Children', value: 1, children: [
{ text: 'Baby 3-5', value: 11 },
{ text: 'Baby 6-8', value: 12 },
{ text: 'Baby 9-12', value: 13 }
]
});
return [ item ];
}
}
stackblitz example

How to get a directive to react to an EventEmitter in a component

I have a CustomComponent which emits a value (let's just call it "error") if a http request to the back end api returns an error. How can I get a directive (call it Form Directive), applied to this form, to recognize when the "error" value is emitted by CustomComponent?
Code for CustomComponent:
export class CustomComponent extends FormComponent<Custom> {
constructor(
protected fb: FormBuilder,
private httpService: HttpService) {
super(fb);
}
currentVal: string = '';
inputType: string = 'password';
showPasswordTitle: string = 'Show Password';
showPasswordStatus: boolean = false;
form: FormGroup;
#Output() invalidOnError = new EventEmitter<string>();
protected buildForm(): FormGroup {
return this.form = this.fb.group({
fieldA: ['', Validators.required],
fieldB: ['', Validators.required],
fieldC: [''],
fieldD: ['', [Validators.required, Validators.pattern('[0-9]{10}')]]
}
protected doSubmit(): Observable<Custom> {
return this.httpService.callDatabase<Custom>('post', '/api/users/custom', this.value);
};
protected get value(): Registration {
return {
fieldA: this.fieldA.value,
fieldB: this.fieldB.value,
fieldC: this.fieldC.value,
fieldD: this.fieldD.value
};
}
get fieldA() { return this.form.get('fieldA'); }
get fieldB() { return this.form.get('fieldB'); }
get fieldC() { return this.form.get('fieldC'); }
get fieldD() { return this.form.get('fieldD'); }
protected onError() {
if (this.error.length) {//error.length indicates some of the fields in the form are already registered in the database
Object.keys(this.error).forEach(element => {
let formControl = this.form.get(this.error[element])
this.currentVal = formControl.value;
formControl.setValidators(formControl.validator ? [formControl.validator, unique(this.currentVal)] : unique(this.currentVal))
formControl.updateValueAndValidity()
this.invalidOnError.emit('error');
})
}
}
Code for FormComponent:
export abstract class FormComponent<T> implements OnInit {
protected form: FormGroup = null;
submitted = false;
completed = false;
error: string = null;
constructor(protected fb: FormBuilder) {}
ngOnInit() {
this.form = this.buildForm();
}
onSubmit() {
this.submitted = true;
if (this.form.valid) {
this.doSubmit().subscribe(
() => {
this.error = null;
this.onSuccess();
},
err => {
this.error = err
this.onError();
},
() => {
this.submitted = false;
this.completed = true;
}
)
}
}
protected abstract get value(): T;
protected abstract buildForm(): FormGroup;
protected abstract doSubmit(): Observable<T>;
protected onSuccess() {}
protected onError() {}
}
Code for Form Directive (works well when user clicks Submit button, which triggers onSubmit event in CustomComponent):
#Directive({
selector: 'form'
})
export class FormSubmitDirective {
submit$ = fromEvent(this.element, 'submit').pipe(shareReplay(1));
constructor(private host: ElementRef<HTMLFormElement>) {}
get element() {
return this.host.nativeElement;
}
}
I was hoping something like this could be the solution to my question, but this for sure doesn't work.
invalidOnError$ = fromEvent(this.element, 'error').pipe(shareReplay(1));
The idea is to use submit$ or invalidOnError$ from the directive to focus on the first invalid field in the form. Works fine for submit$, but not invalidOnError$. Appreciate some help - still fairly new to Angular.
I got this to work in a round about manner, by using the #Input decorator in another form directive which also imports submit$ from Form Directive.
No changes to code for FormComponent and Form Directive vs. what's shown in the question.
Relevant code from Custom component:
export class CustomComponent extends FormComponent<Custom> {
invalidOnError: string = '';
form: FormGroup;
protected buildForm(): FormGroup {
return this.form = this.fb.group({
fieldA: ['', Validators.required],
fieldB: ['', Validators.required],
fieldC: [''],
fieldD: ['', [Validators.required, Validators.pattern('[0-9]{10}')]]
}
protected doSubmit(): Observable<Custom> {
invalidOnError = '';
return this.httpService.callDatabase<Custom>('post', '/api/users/custom', this.value);
};
protected get value(): Registration {
return {
fieldA: this.fieldA.value,
fieldB: this.fieldB.value,
fieldC: this.fieldC.value,
fieldD: this.fieldD.value
};
}
get fieldA() { return this.form.get('fieldA'); }
get fieldB() { return this.form.get('fieldB'); }
get fieldC() { return this.form.get('fieldC'); }
get fieldD() { return this.form.get('fieldD'); }
protected onError() {
if (this.error.length) {//error.length indicates some of the fields in the form are already registered in the database
invalidOnError = 'invalid'
Object.keys(this.error).forEach(element => {
let formControl = this.form.get(this.error[element])
this.currentVal = formControl.value;
formControl.setValidators(formControl.validator ? [formControl.validator, unique(this.currentVal)] : unique(this.currentVal))
formControl.updateValueAndValidity()
this.invalidOnError.emit('error');
})
}
}
Relevant code from CustomComponentTemplate:
<form class="bg-light border" appFocus="FieldA" [formGroup]="CustomForm"
[invalidOnError]="invalidOnError" (ngSubmit)="onSubmit()">
Relevant code from invalidFormControlDirective (imports submit$ from Form Directive):
#Directive({
selector: 'form[formGroup]'
})
export class FormInvalidControlDirective {
private form: FormGroup;
private submit$: Observable<Event>;
#Input() invalidOnError: string = ''; //this is the #Input variable invalidOnError
constructor(
#Host() private formSubmit: FormDirective,
#Host() private formGroup: FormGroupDirective,
#Self() private el: ElementRef<HTMLFormElement>
) {
this.submit$ = this.formSubmit.submit$;
}
ngOnInit() {
this.form = this.formGroup.form;
this.submit$.pipe(untilDestroyed(this)).subscribe(() => {
if (this.form.invalid) {
const invalidName = this.findInvalidControlsRecursive(this.form)[0];
this.getFormElementByControlName(invalidName).focus();
}
});
}
ngOnChanges(){
of(this.invalidOnError).pipe(filter(val => val == 'invalid')).subscribe(() => {
if (this.form.invalid) {
const invalidName = this.findInvalidControlsRecursive(this.form)[0];
this.getFormElementByControlName(invalidName).focus();
}
});
}
ngOnDestroy() { }
// findInvalidControlsRecursive and getFormElementByControlName defined functions to get invalid controls references
}
That said, I'd be interested in 1) somehow bringing code under onChanges lifecyle into ngOnInit lifecyle in invalidFormControlDirective (couldn't get that to work), and 2) find out if there is some way to emitting an event and processing it with Rxjs fromEventPattern as opposed to passing the #Input variable invalidOnError into invalidFormControlDirective.