Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for Reactive form validation #18

Closed
hotdog1987 opened this issue Feb 22, 2018 · 3 comments
Closed

Support for Reactive form validation #18

hotdog1987 opened this issue Feb 22, 2018 · 3 comments

Comments

@hotdog1987
Copy link

Hello,
Thank you for such a wonderful query builder plugin. It truly helps a lot to our user interface.
I was just curious, if I have a parent component inside which i have a child component and this child component has the query builder, then how to propagate the reactive form validation to the parent?
Since, the use of (ngModel) request for a formControlName and formGroup, I was thinking if its even posiible. this is what i have so far:

<query-builder id="triggerQuery" class="wfTrigger-query-builder"
                               [(ngModel)]="query"
                               [config]="config"
                               [operatorMap]="operatorMap"
                               [formControl]="parentFormGroup.controls['triggerQuery']"
                               (input)="onQueryChanged($event, query, parentFormGroup)" formArrayName="queryRows">
                    <div *ngFor="let queryRow of parentFormGroup.get('queryRows').controls; let i=index" [formGroupName]="i">
                        <ng-container *queryButtonGroup="let ruleset; let addRule=addRule; let addRuleSet=addRuleSet; let removeRuleSet=removeRuleSet">
                            <div class="btn-group" style="margin-left: 50px;">
                                <button class="btn btn-primary" (click)="addRowRule();addRule()"><i class="fa fa-plus" aria-hidden="true"></i>Rule</button>
                                <button class="btn btn-success" (click)="addRowRule();addRuleSet()"><i class="fa fa-plus" aria-hidden="true"></i>Ruleset</button>
                                <button class="btn btn-danger" (click)="removeRowRule(i);removeRuleSet()" *ngIf="ruleset.id !== 0"><i class="fa fa-minus" aria-hidden="true"></i>Ruleset</button>
                            </div>
                        </ng-container>

                        <ng-container *queryField="let rule; let fields=fields; let changeField=changeField">
                            <select id="alertMetaSelection" class="form-control"
                                    [(ngModel)]="rule.field"
                                    formControlName="alertMetaSelection" required>
                                <option *ngFor="let field of fields" [ngValue]="field.value">
                                    {{field.name}}
                                </option>
                            </select>
                        </ng-container>

                        <ng-container *queryOperator="let rule; let operators=operators">
                            <select id="alertOperatorSelection" class="form-control"
                                    [(ngModel)]="rule.operator"
                                    formControlName="alertOperatorSelection" required>
                                <option *ngFor="let value of operators" [ngValue]="value">
                                    {{value}}
                                </option>
                            </select>
                        </ng-container>

                        <ng-container *queryInput="let rule; type: 'string'">
                            <input type="text" id="alertValue" class="form-control"
                                   [(ngModel)]="rule.value"
                                   formControlName="alertValue" required>
                        </ng-container>

                        <ng-container *queryRemoveButton="let rule; let removeRule=removeRule">
                            <a href="#" (click)="$event.preventDefault();removeRule(rule);removeRowRule(i)" style="height:32px;margin-left:5px;color:#d9534f">
                                <i class="fa fa-2x fa-times"></i>
                            </a>
                        </ng-container>
                    </div>
                </query-builder>
addRowRule(): void {
        debugger
        // control refers to your formarray
        const control = <FormArray>this.parentFormGroup.controls['queryRows'];
        // add new formgroup
        control.push(this.parentFormGroup.group({
            // list all your form controls here, which belongs to your form array
            alertMetaSelection: new FormControl('', [
                Validators.required
            ]),
            alertOperatorSelection: new FormControl('', [
                Validators.required
            ]),
            alertValue: new FormControl('', [
                Validators.required
            ])
        }));
    }

    removeRowRule(index: number): void {
        debugger
        // control refers to your formarray
        const control = <FormArray>this.parentFormGroup.controls['queryRows'];
        // remove the chosen row
        control.removeAt(index);
    }

Since i introduced a div tag between query-builder and ng-template, all my styles have broken.
Also, the form fields are always valid.

Any help is greatly appreciated!

@zebzhao
Copy link
Owner

zebzhao commented Feb 22, 2018

Hi, I am looking into this one. Did you get this working? Just a question, can you use ng-container instead of div so the css styles do not break?

@hotdog1987
Copy link
Author

@zebzhao No i was not able to get this working. The parent form is always "valid" even though the query-builder is invalid. Also, changing the div to ng-container did not work.

@hotdog1987
Copy link
Author

hotdog1987 commented Feb 22, 2018

Funny, I was reading this issue on angular github: angular/angular#9230 and thanks to @kara, a new attribute was added, ngNoForm, which helps us use ngModel w/o needing form control names. Thanks to this, I was able to achieve this:

child.html:

<div ngNoForm>
                <query-builder id="triggerQuery"
                               [(ngModel)]="query"
                               [config]="config"
                               [operatorMap]="operatorMap">
                    <ng-container *queryButtonGroup="let ruleset; let addRule=addRule; let addRuleSet=addRuleSet; let removeRuleSet=removeRuleSet">
                        <div class="btn-group" style="margin-left: 50px;">
                            <button class="btn btn-primary" (click)="addRowRule();addRule()"><i class="fa fa-plus" aria-hidden="true"></i>Rule</button>
                            <button class="btn btn-success" (click)="addRuleSet()"><i class="fa fa-plus" aria-hidden="true"></i>Ruleset</button>
                            <button class="btn btn-danger" (click)="removeRuleSet()" ><i class="fa fa-minus" aria-hidden="true"></i>Ruleset</button>
                        </div>
                    </ng-container>
                    <ng-container *queryRemoveButton="let rule; let removeRule=removeRule">
                        <a href="#" (click)="$event.preventDefault();removeRule(rule)" style="height:32px;margin-left:5px;color:#d9534f">
                            <i class="fa fa-2x fa-times"></i>
                        </a>
                    </ng-container>

                    <ng-container *queryField="let rule; let fields=fields; let changeField=changeField">
                        <select id="alertMetaSelection" class="form-control"
                                [(ngModel)]="rule.field" (input)="onQueryChanged($event, rule)" required>
                            <option *ngFor="let field of fields" [ngValue]="field.value">
                                {{field.name}}
                            </option>
                        </select>
                    </ng-container>

                    <ng-container *queryOperator="let rule; let operators=operators">
                        <select id="alertOperatorSelection" class="form-control"
                                [(ngModel)]="rule.operator" (input)="onQueryChanged($event, rule)" required>
                            <option *ngFor="let value of operators" [ngValue]="value">
                                {{value}}
                            </option>
                        </select>
                    </ng-container>

                    <ng-container *queryInput="let rule; type: 'string'">
                        <input type="text" id="alertValue" class="form-control"
                               [(ngModel)]="rule.value" (input)="onQueryChanged($event, rule)" required>
                    </ng-container>
                </query-builder>
            </div>

child.component.ts:

addRowRule(): void {
        const control = this.parentFormGroup.controls['triggerQuery'];
        control.setErrors({ 'required': true });
    }

    onQueryChanged(e: any, rule: any): void {
        const control = this.parentFormGroup.controls['triggerQuery'];
        if (rule.field && rule.operator && rule.value) {
            control.setErrors(null);
        }
        // else make for invalid with required flag
        else {
            control.setErrors({ 'required': true });
        }
    }

Hope this helps anyone out there who faces similar issue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants