Multi-Step Form

A multi-step form consists of one or more steps. Each step contains a set of controls and can have their own layout.
A common example is a longer form which is broken down into several steps, e.g. a payment account where one page is asking for personal information, another of the card information and so on.

The main advantage of multi-step forms is allowing customers and leads to complete their information in smaller chunks, which creates a positive user experience and increases conversions.

The following example demonstrates a multi-step form with a validation of the controls on each step:

import { FormGroup, FormControl, Validators } from '@angular/forms';
import { Component, ViewEncapsulation, ViewChild } from '@angular/core';

import { StepperComponent } from '@progress/kendo-angular-layout';

@Component({
    selector: 'my-app',
    template: `
        <div class="example">
            <kendo-stepper
                #stepper
                [steps]="steps"
                [stepType]="'full'"
                [(currentStep)]="currentStep"
                [style.width.px]="550"
            >
            </kendo-stepper>

            <div class="content">
                <form class="k-form" [formGroup]="form">
                    <account-details
                        *ngIf="currentStep === 0"
                        [accountDetails]="currentGroup">
                    </account-details>

                    <personal-details
                        *ngIf="currentStep === 1"
                        [personalDetails]="currentGroup">
                    </personal-details>

                    <payment-details
                        *ngIf="currentStep === 2"
                        [paymentDetails]="currentGroup">
                    </payment-details>

                    <span class="k-form-separator"></span>

                    <div class="k-form-buttons k-buttons-end">
                        <span class="page">Step {{ currentStep + 1 }} of 3</span>
                        <div>
                            <button
                                class="k-button prev"
                                *ngIf="currentStep !== 0"
                                (click)="prev()"
                            >
                                Previous
                            </button>
                            <button class="k-button k-primary" (click)="next()" *ngIf="currentStep !== 2">
                                Next
                            </button>
                            <button class="k-button k-primary" (click)="submit()" *ngIf="currentStep === 2">Submit</button>
                        </div>
                    </div>
                </form>
            </div>
        </div>
    `,
    encapsulation: ViewEncapsulation.None,
    styles: [`
        .example {
            display: flex;
            flex-direction: column;
            justify-content: center;
        }
        .k-stepper {
            align-self: center;
        }
        .k-step {
            pointer-events: none;
        }
        .k-button.prev {
            margin-right: 16px;
        }
        .example .content {
            align-self: center;
        }
        .k-form-separator {
            margin-top: 40px;
        }
        .k-buttons-end {
            justify-content: space-between;
            align-content: center;
        }
        .page {
            align-self: center;
        }
        .k-form {
            width: 450px;
        }
    `]
})
export class AppComponent {
    public currentStep = 0;

    @ViewChild('stepper', { static: true })
    public stepper: StepperComponent;

    private isStepValid = (index: number): boolean => {
        return this.getGroupAt(index).valid || this.currentGroup.untouched;
    }

    private shouldValidate = (index: number): boolean => {
        return this.getGroupAt(index).touched && this.currentStep >= index;
    }

    public steps = [
        {
            label: 'Account Details',
            isValid: this.isStepValid,
            validate: this.shouldValidate
        },
        {
            label: 'Personal Details',
            isValid: this.isStepValid,
            validate: this.shouldValidate
        },
        {
            label: 'Payment Details',
            isValid: this.isStepValid,
            validate: this.shouldValidate
        }
    ];

  public form = new FormGroup({
        accountDetails: new FormGroup({
            userName: new FormControl('', Validators.required),
            email: new FormControl('', [Validators.required, Validators.email]),
            password: new FormControl('', Validators.required),
            avatar: new FormControl(null)
        }),
        personalDetails: new FormGroup({
            fullName: new FormControl('', [Validators.required]),
            country: new FormControl('', [Validators.required]),
            gender: new FormControl(null, [Validators.required]),
            about: new FormControl('')
        }),
        paymentDetails: new FormGroup({
            paymentType: new FormControl(null, Validators.required),
            cardNumber: new FormControl('', Validators.required),
            cvc: new FormControl('', [
                Validators.required,
                Validators.maxLength(3),
                Validators.minLength(3)
            ]),
            expirationDate: new FormControl('', Validators.required),
            cardHolder: new FormControl('', Validators.required)
        })
    });

    public get currentGroup(): FormGroup {
        return this.getGroupAt(this.currentStep);
    }

    public next(): void {
        if (this.currentGroup.valid && this.currentStep !== this.steps.length) {
            this.currentStep += 1;
            return;
        }

        this.currentGroup.markAllAsTouched();
        this.stepper.validateSteps();
    }

    public prev(): void {
        this.currentStep -= 1;
    }

    public submit(): void {
        if (!this.currentGroup.valid) {
            this.currentGroup.markAllAsTouched();
            this.stepper.validateSteps();
        }
        if (this.form.valid) {
            console.log('Submitted data', this.form.value);
        }
    }

    private getGroupAt(index: number): FormGroup {
        const groups = Object.keys(this.form.controls).map(groupName =>
            this.form.get(groupName)
            ) as FormGroup[];

        return groups[index];
    }
}

import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpProgressEvent, HttpEventType, HttpResponse } from '@angular/common/http';
import { Observable, of, concat } from 'rxjs';
import { delay } from 'rxjs/operators';

@Injectable()
export class UploadInterceptor implements HttpInterceptor {
    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        if (req.url === 'saveUrl') {
            const events: Observable<HttpEvent<any>>[] = [0, 30, 60, 100].map((x) => of(<HttpProgressEvent>{
                type: HttpEventType.UploadProgress,
                loaded: x,
                total: 100
            }).pipe(delay(1000)));

            const success = of(new HttpResponse({ status: 200 })).pipe(delay(1000));
            events.push(success);

            return concat(...events);
        }

        if (req.url === 'removeUrl') {
            return of(new HttpResponse({ status: 200 }));
        }

        return next.handle(req);
      }
}
export const countries: Array<string> = [
    'Albania',
    'Andorra',
    'Armenia',
    'Austria',
    'Azerbaijan',
    'Belarus',
    'Belgium',
    'Bosnia & Herzegovina',
    'Bulgaria',
    'Croatia',
    'Cyprus',
    'Czech Republic',
    'Denmark',
    'Estonia',
    'Finland',
    'France',
    'Georgia',
    'Germany',
    'Greece',
    'Hungary',
    'Iceland',
    'Ireland',
    'Italy',
    'Kosovo',
    'Latvia',
    'Liechtenstein',
    'Lithuania',
    'Luxembourg',
    'Macedonia',
    'Malta',
    'Moldova',
    'Monaco',
    'Montenegro',
    'Netherlands',
    'Norway',
    'Poland',
    'Portugal',
    'Romania',
    'Russia',
    'San Marino',
    'Serbia',
    'Slovakia',
    'Slovenia',
    'Spain',
    'Sweden',
    'Switzerland',
    'Turkey',
    'Ukraine',
    'United Kingdom',
    'Vatican City'
];
import { Component, Input } from '@angular/core';
import { FormGroup } from '@angular/forms';

import { FileRestrictions } from '@progress/kendo-angular-upload';

@Component({
    selector: 'account-details',
    template: `
        <ng-container [formGroup]="accountDetails">
            <kendo-formfield>
                <kendo-label [for]="username" text="Username"></kendo-label>
                <input [formControlName]="'userName'" kendoTextBox #username />
                <kendo-formerror>Username is required</kendo-formerror>
            </kendo-formfield>

            <kendo-formfield>
                <kendo-label [for]="email" text="Email"></kendo-label>
                <input [formControlName]="'email'" kendoTextBox #email/>
                <kendo-formerror *ngIf="accountDetails.controls.email.errors?.required">Email is required</kendo-formerror>
                <kendo-formerror *ngIf="accountDetails.controls.email.errors?.email">Not a valid email format</kendo-formerror>
            </kendo-formfield>

            <kendo-formfield>
                <kendo-label [for]="password" text="Password">
                </kendo-label>
                <input type="password" [formControlName]="'password'" kendoTextBox #password />
                <kendo-formerror>Password is required</kendo-formerror>
            </kendo-formfield>

            <kendo-formfield>
                <kendo-label [for]="avatar" [optional]="true" [text]="'Avatar'"></kendo-label>
                <kendo-upload
                    #avatar
                    [formControlName]="'avatar'"
                    [saveUrl]="uploadSaveUrl"
                    [removeUrl]="uploadRemoveUrl"
                    [restrictions]="restrictions">
                </kendo-upload>

                <kendo-formhint>Allowed extensions are jpg, jpeg or png</kendo-formhint>
            </kendo-formfield>
      </ng-container>
    `
})
export class AccountDetailsComponent {
    public uploadSaveUrl = 'saveUrl'; // should represent an actual API endpoint
    public uploadRemoveUrl = 'removeUrl'; // should represent an actual API endpoint

    public restrictions: FileRestrictions = {
        allowedExtensions: ['jpg', 'jpeg', 'png']
    };

    @Input() public accountDetails: FormGroup;
}
import { Component, Input, ViewEncapsulation } from '@angular/core';
import { FormGroup } from '@angular/forms';

import { countries } from './countries';

@Component({
    selector: 'personal-details',
    template: `
        <ng-container [formGroup]="personalDetails">
            <kendo-formfield>
                <kendo-label [for]="fullName" text="Full Name"></kendo-label>
                <input kendoTextBox #fullName [formControlName]="'fullName'" />
                <kendo-formerror>Full Name is required</kendo-formerror>
            </kendo-formfield>

            <kendo-formfield [showHints]="'initial'" [showErrors]="'initial'">
                <kendo-label [for]="country" text="Country"></kendo-label>
                <kendo-autocomplete
                    #country
                    [data]="countries"
                    [formControlName]="'country'"
                >
                </kendo-autocomplete>

                <kendo-formhint>Only Eroupean countries</kendo-formhint>
                <kendo-formerror *ngIf="!(personalDetails.controls.country.required)">Country is required</kendo-formerror>
            </kendo-formfield>

            <kendo-formfield [orientation]="'horizontal'" [showHints]="'initial'">
                 <label class="k-label">Gender</label>

                <ul class="k-radio-list k-list-horizontal">
                    <li class="k-radio-item">
                        <input type="radio" #male value="male" kendoRadioButton [formControlName]="'gender'" />
                        <kendo-label class="k-radio-label" [for]="male" text="Male"></kendo-label>
                    </li>

                    <li class="k-radio-item">
                        <input type="radio" #female value="female" kendoRadioButton [formControlName]="'gender'" />
                        <kendo-label class="k-radio-label" [for]="female" text="Female"></kendo-label>
                    </li>

                    <li class="k-radio-item">
                        <input type="radio" #other value="other" kendoRadioButton [formControlName]="'gender'" />
                        <kendo-label class="k-radio-label" [for]="other" text="Other"></kendo-label>
                    </li>
                </ul>

                  <kendo-formerror>This field is required</kendo-formerror>
              </kendo-formfield>

            <kendo-formfield>
                <kendo-label [for]="about" text="About" [optional]="true"></kendo-label>
                <textarea kendoTextArea #about [formControlName]="'about'" placeholder="Who you are..."></textarea>
            </kendo-formfield>
        </ng-container>
    `,
    encapsulation: ViewEncapsulation.None,
    styles: [`
        .k-list-horizontal .k-radio-item,
        .k-radio-list .k-radio-item:first-child {
            margin: 0 12px 0 0;
        }

        .k-radio+.k-radio-label, .k-radio-label+.k-radio {
            margin-left: 6px;
        }
    `]
})
export class PersonalDetailsComponent {
    public countries: Array<string> = countries;

    @Input() public personalDetails: FormGroup;
}
import { Component, Input, ViewEncapsulation } from '@angular/core';
import { FormGroup } from '@angular/forms';

@Component({
    selector: 'payment-details',
    template: `
          <ng-container [formGroup]="paymentDetails">
            <kendo-formfield [orientation]="'horizontal'" class="payment-type">
                <label class="k-label">Payment Type</label>

                <ul class="k-radio-list k-list-horizontal">
                    <li class="k-radio-item">
                        <input type="radio" #visa value="visa" kendoRadioButton [formControlName]="'paymentType'" />
                        <label [for]="visa" class="card visa"></label>
                    </li>

                    <li class="k-radio-item">
                        <input type="radio" #masterCard value="masterCard" kendoRadioButton [formControlName]="'paymentType'" />
                        <label [for]="masterCard" class="card mastercard"></label>
                    </li>

                    <li class="k-radio-item">
                        <input type="radio" #paypal value="paypal" kendoRadioButton [formControlName]="'paymentType'" />
                        <label [for]="paypal" class="card paypal"></label>
                    </li>
                </ul>
            </kendo-formfield>

             <div class="wrap">
                <kendo-formfield class="card-number">
                    <kendo-label [for]="cardNumber" text="Card Number"></kendo-label>
                    <kendo-maskedtextbox
                        #cardNumber
                        [formControlName]="'cardNumber'"
                        [mask]="mask">
                    </kendo-maskedtextbox>
                    <kendo-formerror>Card Number is required format</kendo-formerror>
                </kendo-formfield>

                <kendo-formfield>
                    <kendo-label [for]="cvc" text="CVC Number"></kendo-label>
                    <kendo-maskedtextbox
                        #cvc
                        [formControlName]="'cvc'"
                        [mask]="cvcMask">
                    </kendo-maskedtextbox>

                    <kendo-formhint>The last 3 digids on the back</kendo-formhint>
                    <kendo-formerror>Card CVC Number is required</kendo-formerror>
                </kendo-formfield>
              </div>

            <kendo-formfield>
                <kendo-label [for]="expiration" text="Expiration Date"></kendo-label>
                <kendo-dateinput #expiration [formControlName]="'expirationDate'"></kendo-dateinput>

                <kendo-formerror>Expiration Date is required</kendo-formerror>
            </kendo-formfield>

            <kendo-formfield>
                <kendo-label [for]="cardHolder" text="Card Holder Name"></kendo-label>
                <input [formControlName]="'cardHolder'" kendoTextBox #cardHolder />

                <kendo-formerror>Card Holder Name is required</kendo-formerror>
            </kendo-formfield>
          </ng-container>
    `,
    encapsulation: ViewEncapsulation.None,
    styles: [`
        .wrap {
            display: flex;
            justify-content: space-between;
        }
        .payment-type input {
            visibility: hidden;
            position: absolute;
        }

        .visa { background-image: url(https://image.flaticon.com/icons/svg/196/196578.svg);}
        .mastercard { background-image: url(https://image.flaticon.com/icons/svg/196/196561.svg);}
        .paypal { background-image: url(https://image.flaticon.com/icons/svg/196/196565.svg);}

        .payment-type input:active +.card { opacity: .9; }
        .payment-type input:checked +.card {
            filter: none;
        }
        .card {
            cursor: pointer;
            background-size: auto;
            background-repeat: no-repeat;
            background-position: center;
            display: inline-block;
            width: 138px;
            height: 70px;
            border: 1px solid;
            filter: brightness(1.8) grayscale(1) opacity(.7);
        }
        .card:hover {
            filter: brightness(1.2) grayscale(.5) opacity(.9);
        }
        .card-number {
            width: 75%;
            margin-right: 25px;
        }
    `]
})
export class PaymentDetailsComponent {
    public mask: string = "0000-0000-0000-0000";
    public cvcMask: string = "000";

    @Input() public paymentDetails: FormGroup;
}
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';

import { LayoutModule } from '@progress/kendo-angular-layout';
import { LabelModule } from '@progress/kendo-angular-label';
import { InputsModule } from '@progress/kendo-angular-inputs';
import { UploadsModule } from '@progress/kendo-angular-upload';
import { DropDownsModule } from '@progress/kendo-angular-dropdowns';
import { DateInputsModule } from '@progress/kendo-angular-dateinputs';

import { UploadInterceptor } from './upload-interceptor';
import { AppComponent } from './app.component';
import { AccountDetailsComponent } from './account-details.component';
import { PersonalDetailsComponent } from './personal-details.component';
import { PaymentDetailsComponent } from './payment-details.component';

@NgModule({
    imports: [
        BrowserModule,
        BrowserAnimationsModule,
        HttpClientModule,
        FormsModule,
        ReactiveFormsModule,
        LayoutModule,
        InputsModule,
        LabelModule,
        UploadsModule,
        DropDownsModule,
        DateInputsModule
    ],
    declarations: [ AppComponent, AccountDetailsComponent, PersonalDetailsComponent, PaymentDetailsComponent ],
    bootstrap:    [ AppComponent ],
     providers: [
        {
            provide: HTTP_INTERCEPTORS,
            useClass: UploadInterceptor,
            multi: true
        }
    ]
})
export class AppModule {}
import { enableProdMode, NgModule } from '@angular/core';
import { AppModule } from './app.module.ts';

enableProdMode();

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
platformBrowserDynamic().bootstrapModule(AppModule)
  .catch(err => console.error(err));

The following example demonstrates a multi-step form with a validation of the controls on submit:

import { FormGroup, FormControl, Validators } from '@angular/forms';
import { Component, ViewEncapsulation, ViewChild } from '@angular/core';

import { StepperComponent } from '@progress/kendo-angular-layout';

@Component({
    selector: 'my-app',
    template: `
        <div class="example">
            <kendo-stepper
                #stepper
                [steps]="steps"
                [stepType]="'full'"
                [(currentStep)]="currentStep"
                [style.width.px]="550"
            >
            </kendo-stepper>

            <div class="content">
                <form class="k-form" [formGroup]="form">
                    <account-details
                        *ngIf="currentStep === 0"
                        [accountDetails]="currentGroup">
                    </account-details>

                    <personal-details
                        *ngIf="currentStep === 1"
                        [personalDetails]="currentGroup">
                    </personal-details>

                    <payment-details
                        *ngIf="currentStep === 2"
                        [paymentDetails]="currentGroup">
                    </payment-details>

                    <span class="k-form-separator"></span>

                    <div class="k-form-buttons k-buttons-end">
                        <span class="page">Step {{ currentStep + 1 }} of 3</span>
                        <div>
                            <button
                                class="k-button prev"
                                *ngIf="currentStep !== 0"
                                (click)="prev()"
                            >
                                Previous
                            </button>
                            <button class="k-button k-primary" (click)="next()" *ngIf="currentStep !== 2">
                                Next
                            </button>
                            <button class="k-button k-primary" (click)="submit()" *ngIf="currentStep === 2">Submit</button>
                        </div>
                    </div>
                </form>
            </div>
        </div>
    `,
    encapsulation: ViewEncapsulation.None,
    styles: [`
        .example {
            display: flex;
            flex-direction: column;
            justify-content: center;
        }
        .k-stepper {
            align-self: center;
        }
        .k-button.prev {
            margin-right: 16px;
        }
        .example .content {
            align-self: center;
        }
        .k-form-separator {
            margin-top: 40px;
        }
        .k-buttons-end {
            justify-content: space-between;
            align-content: center;
        }
        .page {
            align-self: center;
        }
        .k-form {
            width: 450px;
        }
    `]
})
export class AppComponent {
    @ViewChild('stepper', { static: true })
    public stepper: StepperComponent;

    public currentStep = 0;

    private sumbitted = false;

    private isStepValid = (index: number): boolean => {
        return this.getGroupAt(index).valid
    }

    private shouldValidate = (): boolean => {
        return this.sumbitted === true;
    }

    public steps = [
        {
            label: 'Account Details',
            isValid: this.isStepValid,
            validate: this.shouldValidate
        },
        {
            label: 'Personal Details',
            isValid: this.isStepValid,
            validate: this.shouldValidate
        },
        {
            label: 'Payment Details',
            isValid: this.isStepValid,
            validate: this.shouldValidate
        }
    ];

  public form = new FormGroup({
        accountDetails: new FormGroup({
            userName: new FormControl('', Validators.required),
            email: new FormControl('', [Validators.required, Validators.email]),
            password: new FormControl('', Validators.required),
            avatar: new FormControl(null)
        }),
        personalDetails: new FormGroup({
            fullName: new FormControl('', [Validators.required]),
            country: new FormControl('', [Validators.required]),
            gender: new FormControl(null, [Validators.required]),
            about: new FormControl('')
        }),
        paymentDetails: new FormGroup({
            paymentType: new FormControl(null, Validators.required),
            cardNumber: new FormControl('', Validators.required),
            cvc: new FormControl('', [
                Validators.required,
                Validators.maxLength(3),
                Validators.minLength(3)
            ]),
            expirationDate: new FormControl('', Validators.required),
            cardHolder: new FormControl('', Validators.required)
        })
    });

    public get currentGroup(): FormGroup {
        return this.getGroupAt(this.currentStep);
    }

    public next(): void {
        this.currentStep += 1;
    }

    public prev(): void {
        this.currentStep -= 1;
    }

    public submit(): void {
        this.sumbitted = true;

        if (!this.form.valid) {
          this.form.markAllAsTouched();
          this.stepper.validateSteps();
        }

        console.log('Submitted data', this.form.value);
    }

    private getGroupAt(index: number): FormGroup {
        const groups = Object.keys(this.form.controls).map(groupName =>
            this.form.get(groupName)
            ) as FormGroup[];

        return groups[index];
    }
}

import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpProgressEvent, HttpEventType, HttpResponse } from '@angular/common/http';
import { Observable, of, concat } from 'rxjs';
import { delay } from 'rxjs/operators';

@Injectable()
export class UploadInterceptor implements HttpInterceptor {
    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        if (req.url === 'saveUrl') {
            const events: Observable<HttpEvent<any>>[] = [0, 30, 60, 100].map((x) => of(<HttpProgressEvent>{
                type: HttpEventType.UploadProgress,
                loaded: x,
                total: 100
            }).pipe(delay(1000)));

            const success = of(new HttpResponse({ status: 200 })).pipe(delay(1000));
            events.push(success);

            return concat(...events);
        }

        if (req.url === 'removeUrl') {
            return of(new HttpResponse({ status: 200 }));
        }

        return next.handle(req);
      }
}
export const countries: Array<string> = [
    'Albania',
    'Andorra',
    'Armenia',
    'Austria',
    'Azerbaijan',
    'Belarus',
    'Belgium',
    'Bosnia & Herzegovina',
    'Bulgaria',
    'Croatia',
    'Cyprus',
    'Czech Republic',
    'Denmark',
    'Estonia',
    'Finland',
    'France',
    'Georgia',
    'Germany',
    'Greece',
    'Hungary',
    'Iceland',
    'Ireland',
    'Italy',
    'Kosovo',
    'Latvia',
    'Liechtenstein',
    'Lithuania',
    'Luxembourg',
    'Macedonia',
    'Malta',
    'Moldova',
    'Monaco',
    'Montenegro',
    'Netherlands',
    'Norway',
    'Poland',
    'Portugal',
    'Romania',
    'Russia',
    'San Marino',
    'Serbia',
    'Slovakia',
    'Slovenia',
    'Spain',
    'Sweden',
    'Switzerland',
    'Turkey',
    'Ukraine',
    'United Kingdom',
    'Vatican City'
];
import { Component, Input } from '@angular/core';
import { FormGroup } from '@angular/forms';

import { FileRestrictions } from '@progress/kendo-angular-upload';

@Component({
    selector: 'account-details',
    template: `
        <ng-container [formGroup]="accountDetails">
            <kendo-formfield>
                <kendo-label [for]="username" text="Username"></kendo-label>
                <input [formControlName]="'userName'" kendoTextBox #username />
                <kendo-formerror>Username is required</kendo-formerror>
            </kendo-formfield>

            <kendo-formfield>
                <kendo-label [for]="email" text="Email"></kendo-label>
                <input [formControlName]="'email'" kendoTextBox #email/>
                <kendo-formerror *ngIf="accountDetails.controls.email.errors?.required">Email is required</kendo-formerror>
                <kendo-formerror *ngIf="accountDetails.controls.email.errors?.email">Not a valid email format</kendo-formerror>
            </kendo-formfield>

            <kendo-formfield>
                <kendo-label [for]="password" text="Password">
                </kendo-label>
                <input type="password" [formControlName]="'password'" kendoTextBox #password />
                <kendo-formerror>Password is required</kendo-formerror>
            </kendo-formfield>

            <kendo-formfield>
                <kendo-label [for]="avatar" [optional]="true" [text]="'Avatar'"></kendo-label>
                <kendo-upload
                    #avatar
                    [formControlName]="'avatar'"
                    [saveUrl]="uploadSaveUrl"
                    [removeUrl]="uploadRemoveUrl"
                    [restrictions]="restrictions">
                </kendo-upload>

                <kendo-formhint>Allowed extensions are jpg, jpeg or png</kendo-formhint>
            </kendo-formfield>
      </ng-container>
    `
})
export class AccountDetailsComponent {
    public uploadSaveUrl = 'saveUrl'; // should represent an actual API endpoint
    public uploadRemoveUrl = 'removeUrl'; // should represent an actual API endpoint

    public restrictions: FileRestrictions = {
        allowedExtensions: ['jpg', 'jpeg', 'png']
    };

    @Input() public accountDetails: FormGroup;
}
import { Component, Input, ViewEncapsulation } from '@angular/core';
import { FormGroup } from '@angular/forms';

import { countries } from './countries';

@Component({
    selector: 'personal-details',
    template: `
        <ng-container [formGroup]="personalDetails">
            <kendo-formfield>
                <kendo-label [for]="fullName" text="Full Name"></kendo-label>
                <input kendoTextBox #fullName [formControlName]="'fullName'" />
                <kendo-formerror>Full Name is required</kendo-formerror>
            </kendo-formfield>

            <kendo-formfield [showHints]="'initial'" [showErrors]="'initial'">
                <kendo-label [for]="country" text="Country"></kendo-label>
                <kendo-autocomplete
                    #country
                    [data]="countries"
                    [formControlName]="'country'"
                >
                </kendo-autocomplete>

                <kendo-formhint>Only Eroupean countries</kendo-formhint>
                <kendo-formerror *ngIf="!(personalDetails.controls.country.required)">Country is required</kendo-formerror>
            </kendo-formfield>

            <kendo-formfield [orientation]="'horizontal'" [showHints]="'initial'">
                 <label class="k-label">Gender</label>

                <ul class="k-radio-list k-list-horizontal">
                    <li class="k-radio-item">
                        <input type="radio" #male value="male" kendoRadioButton [formControlName]="'gender'" />
                        <kendo-label class="k-radio-label" [for]="male" text="Male"></kendo-label>
                    </li>

                    <li class="k-radio-item">
                        <input type="radio" #female value="female" kendoRadioButton [formControlName]="'gender'" />
                        <kendo-label class="k-radio-label" [for]="female" text="Female"></kendo-label>
                    </li>

                    <li class="k-radio-item">
                        <input type="radio" #other value="other" kendoRadioButton [formControlName]="'gender'" />
                        <kendo-label class="k-radio-label" [for]="other" text="Other"></kendo-label>
                    </li>
                </ul>

                  <kendo-formerror>This field is required</kendo-formerror>
              </kendo-formfield>

            <kendo-formfield>
                <kendo-label [for]="about" text="About" [optional]="true"></kendo-label>
                <textarea kendoTextArea #about [formControlName]="'about'" placeholder="Who you are..."></textarea>
            </kendo-formfield>
        </ng-container>
    `,
    encapsulation: ViewEncapsulation.None,
    styles: [`
        .k-list-horizontal .k-radio-item,
        .k-radio-list .k-radio-item:first-child {
            margin: 0 12px 0 0;
        }

        .k-radio+.k-radio-label, .k-radio-label+.k-radio {
            margin-left: 6px;
        }
    `]
})
export class PersonalDetailsComponent {
    public countries: Array<string> = countries;

    @Input() public personalDetails: FormGroup;
}
import { Component, Input, ViewEncapsulation } from '@angular/core';
import { FormGroup } from '@angular/forms';

@Component({
    selector: 'payment-details',
    template: `
          <ng-container [formGroup]="paymentDetails">
            <kendo-formfield [orientation]="'horizontal'" class="payment-type">
                <label class="k-label">Payment Type</label>

                <ul class="k-radio-list k-list-horizontal">
                    <li class="k-radio-item">
                        <input type="radio" #visa value="visa" kendoRadioButton [formControlName]="'paymentType'" />
                        <label [for]="visa" class="card visa"></label>
                    </li>

                    <li class="k-radio-item">
                        <input type="radio" #masterCard value="masterCard" kendoRadioButton [formControlName]="'paymentType'" />
                        <label [for]="masterCard" class="card mastercard"></label>
                    </li>

                    <li class="k-radio-item">
                        <input type="radio" #paypal value="paypal" kendoRadioButton [formControlName]="'paymentType'" />
                        <label [for]="paypal" class="card paypal"></label>
                    </li>
                </ul>
            </kendo-formfield>

             <div class="wrap">
                <kendo-formfield class="card-number">
                    <kendo-label [for]="cardNumber" text="Card Number"></kendo-label>
                    <kendo-maskedtextbox
                        #cardNumber
                        [formControlName]="'cardNumber'"
                        [mask]="mask">
                    </kendo-maskedtextbox>
                    <kendo-formerror>Card Number is required format</kendo-formerror>
                </kendo-formfield>

                <kendo-formfield>
                    <kendo-label [for]="cvc" text="CVC Number"></kendo-label>
                    <kendo-maskedtextbox
                        #cvc
                        [formControlName]="'cvc'"
                        [mask]="cvcMask">
                    </kendo-maskedtextbox>

                    <kendo-formhint>The last 3 digids on the back</kendo-formhint>
                    <kendo-formerror>Card CVC Number is required</kendo-formerror>
                </kendo-formfield>
              </div>

            <kendo-formfield>
                <kendo-label [for]="expiration" text="Expiration Date"></kendo-label>
                <kendo-dateinput #expiration [formControlName]="'expirationDate'"></kendo-dateinput>

                <kendo-formerror>Expiration Date is required</kendo-formerror>
            </kendo-formfield>

            <kendo-formfield>
                <kendo-label [for]="cardHolder" text="Card Holder Name"></kendo-label>
                <input [formControlName]="'cardHolder'" kendoTextBox #cardHolder />

                <kendo-formerror>Card Holder Name is required</kendo-formerror>
            </kendo-formfield>
          </ng-container>
    `,
    encapsulation: ViewEncapsulation.None,
    styles: [`
        .wrap {
            display: flex;
            justify-content: space-between;
        }
        .payment-type input {
            visibility: hidden;
            position: absolute;
        }

        .visa { background-image: url(https://image.flaticon.com/icons/svg/196/196578.svg);}
        .mastercard { background-image: url(https://image.flaticon.com/icons/svg/196/196561.svg);}
        .paypal { background-image: url(https://image.flaticon.com/icons/svg/196/196565.svg);}

        .payment-type input:active +.card { opacity: .9; }
        .payment-type input:checked +.card {
            filter: none;
        }
        .card {
            cursor: pointer;
            background-size: auto;
            background-repeat: no-repeat;
            background-position: center;
            display: inline-block;
            width: 138px;
            height: 70px;
            border: 1px solid;
            filter: brightness(1.8) grayscale(1) opacity(.7);
        }
        .card:hover {
            filter: brightness(1.2) grayscale(.5) opacity(.9);
        }
        .card-number {
            width: 75%;
            margin-right: 25px;
        }
    `]
})
export class PaymentDetailsComponent {
    public mask: string = "0000-0000-0000-0000";
    public cvcMask: string = "000";

    @Input() public paymentDetails: FormGroup;
}
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';

import { LayoutModule } from '@progress/kendo-angular-layout';
import { LabelModule } from '@progress/kendo-angular-label';
import { InputsModule } from '@progress/kendo-angular-inputs';
import { UploadsModule } from '@progress/kendo-angular-upload';
import { DropDownsModule } from '@progress/kendo-angular-dropdowns';
import { DateInputsModule } from '@progress/kendo-angular-dateinputs';

import { UploadInterceptor } from './upload-interceptor';
import { AppComponent } from './app.component';
import { AccountDetailsComponent } from './account-details.component';
import { PersonalDetailsComponent } from './personal-details.component';
import { PaymentDetailsComponent } from './payment-details.component';

@NgModule({
    imports: [
        BrowserModule,
        BrowserAnimationsModule,
        HttpClientModule,
        FormsModule,
        ReactiveFormsModule,
        LayoutModule,
        InputsModule,
        LabelModule,
        UploadsModule,
        DropDownsModule,
        DateInputsModule
    ],
    declarations: [ AppComponent, AccountDetailsComponent, PersonalDetailsComponent, PaymentDetailsComponent ],
    bootstrap:    [ AppComponent ],
     providers: [
        {
            provide: HTTP_INTERCEPTORS,
            useClass: UploadInterceptor,
            multi: true
        }
    ]
})
export class AppModule {}
import { enableProdMode, NgModule } from '@angular/core';
import { AppModule } from './app.module.ts';

enableProdMode();

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
platformBrowserDynamic().bootstrapModule(AppModule)
  .catch(err => console.error(err));

In this article