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

How to model a custom control #159

Closed
MonsieurMan opened this issue Mar 25, 2020 · 5 comments
Closed

How to model a custom control #159

MonsieurMan opened this issue Mar 25, 2020 · 5 comments
Labels
flag: can be closed? This issue or PR should probably be closed now type: RFC/discussion/question This issue is about RFC, discussion or a question

Comments

@MonsieurMan
Copy link

So as I was saying in #112, we have a form where the user can select someone from the LDAP in a modal and add it to a list.

Below is a screenshot of the existing form :

image

We display the firstname and lastname, but only really care about wether the user should be notified, the bell, and it's id, here 9817054L.
That's a first thing I dont know how to do with the library. Having data that should be displayed, but that's not really part of the form.

The main problem I have is, how can I create a custom control that I could use with this library :

<form>
   <app-user-control [formControlName]="formControlNames.user"></app-user-control>
</form>

I already worked out the array part of users

Thanks again for helping me out ! 😃

@MonsieurMan
Copy link
Author

So I finished figuring it out, I simply needed to implement ControlValueAccessor for my app-user-control.

It's out of the scope of the library, but maybe a little section in the documentation stating that for custom controls you must implement ControlValueAcessor could help some people ?
I could open a PR if you're interested. I'll be presenting the library to the rest of the team this week to ask if they're interested in making it our standard way of creating forms, so it could at least help them too.

@maxime1992
Copy link
Contributor

Wait. You never need to do that, it's one of the key point of the lib :): Saving you from implementing all that boilerplate.

You probably just forgot to import the provider, see the doc: https://github.com/cloudnc/ngx-sub-form#second-component-level-optional

@Component({
  selector: 'app-vehicle-product',
  templateUrl: './vehicle-product.component.html',
  styleUrls: ['./vehicle-product.component.scss'],
  providers: subformComponentProviders(VehicleProductComponent), // <------------- Add this
})
export class VehicleProductComponent {}

@maxime1992 maxime1992 added the type: RFC/discussion/question This issue is about RFC, discussion or a question label Mar 26, 2020
@MonsieurMan
Copy link
Author

Here's a snippet of my TeamMemberControl:

team-member-control.component.ts

@Component({
    selector: 'app-team-member-control',
    template: `
        <mat-icon>account_circle</mat-icon>
        <span>{{ val.firstName }} {{ val.lastName }}</span>

        <button mat-icon-button
                (click)="toggleNotification()">
            <mat-icon>{{ val.shouldBeNotified ? 'notifications' : 'notifications_off' }}</mat-icon>
        </button>
   `,
   providers: [{
        provide: NG_VALUE_ACCESSOR,
        useExisting: forwardRef(() => TeamMemberControlComponent),
        multi: true
    }]
}
export class TeamMemberControl implements ControlValueAcessor {
    val: TeamMember;

    set value(val: TeamMember) {
        if (val !== undefined && val !== this.val) {
            this.val = val;
            this.onChange(val);
            this.onTouched(val);
        }
    }

    toggleNotification() {
        this.value = {
            ...this.val,
            shouldBeNotified: !this.val.shouldBeNotified
        };
    }
    /// region ControlValueAccessor
    onChange: any = () => {};
    onTouched: any = () => {};

    /** @override */
    registerOnChange(fn: Function): void {
        this.onChange = fn;
    }

    /** @override */
    registerOnTouched(fn: Function): void {
        this.onTouched = fn;
    }

    /** @override */
    writeValue(val?: any | TeamMember): void {
        this.value = val;
    }
    /// endregion
}

Which I then use like so:

team-members.component.ts

@Component({
    selector: 'app-team-members',
    template: `
        <form [formGroup]="formGroup">
            <div [formArrayName]="formControlNames.teamMembers"
                 *ngFor="let member of formGroupControls.teamMembers.controls">
                <app-team-member-control [formControl]="member"></app-team-member-control>
            </div>
        </form>
    `,
    providers: subformComponentProviders(TeamMembersComponent)
})
export class TeamMembersComponent extends NgxSubFormRemapComponent <TeamMember[], TeamMembersForm>
    implements NgxFormWithArrayControls <TeamMembersForm> {
    public addMember(): void {
        doStuffWithLdap().subscribe(user =>
                this.formGroupControls.teamMembers.push(
                    this.createFormArrayControl('teamMembers', {
                        firstName: user.firstName,
                        shouldBeNotified: true,
                        lastName: user.lastName,
                    })
                )
        );
    }

    /// region NgxSubFormRemapComponent
    /*** @override */
    protected transformToFormGroup(teamMembers: TeamMember[]): TeamMembersForm {
        return {
            teamMembers: !teamMembers ? [] : teamMembers,
        };
    }
    /*** @override */
    protected transformFromFormGroup(formValue: TeamMembersForm): TeamMember[] {
        return formValue.teamMembers;
    }
    /*** @override */
    protected getFormControls(): Controls<TeamMembersForm> {
        return {
            teamMembers: new FormArray([])
        };
    }
    /// endregion
    /// region NgxFormWithArrayControls
    /*** @override */
    createFormArrayControl(
        key: ArrayPropertyKey<TeamMembersForm>,
        value: ArrayPropertyValue<TeamMembersForm>
    ): FormControl {
        switch (key) {
            case 'teamMembers':
                return new FormControl(value, [Validators.required]);

            default:
                return new FormControl(value);
        }
    }
    /// endregion
}

If I were to use SubFormComponent in TeamMemberControl I could not bind the [formControlName] to anything as the user is not intended to change the values by hand, except the notification status.

Is there something I'm misunderstanding ?

@maxime1992
Copy link
Contributor

Hi @MonsieurMan!

I had another go to show you what I meant. Here's a stackblitz: https://stackblitz.com/edit/ngx-sub-form-159

Let me know if it helps 👍

@maxime1992 maxime1992 added the flag: can be closed? This issue or PR should probably be closed now label May 11, 2020
@maxime1992
Copy link
Contributor

Closing as it seems that I answered this question and got no activity in over a month :)

Please reopen if needed @MonsieurMan 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
flag: can be closed? This issue or PR should probably be closed now type: RFC/discussion/question This issue is about RFC, discussion or a question
Projects
None yet
Development

No branches or pull requests

2 participants