2

I am new to angular 2 and I am struggling to get the values from Dynamic HTML. My requirement is I will have some Form Input and I need to inject Dynamic HTML in between which will contain some more inputs.

I have used the example from @Rene Hamburger and created the Dynamic Form Input.

If you look into the example It has 3 Inputs 2 in the Template (Name, LastName). I am injecting the address using addcomponent.

I am using Form Builder to build all the 3 controls, but when you click submit you could see the values Name & Last Name shows up, but could not get the values of address.

I am not now sure how to get the values. I am requesting the community gurus to help me out.

http://plnkr.co/edit/fcS1hdfLErjgChcFsRiX?p=preview

app/app.component.ts

import {AfterViewInit,OnInit, Compiler, Component, NgModule, ViewChild,
  ViewContainerRef} from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'
import { FormGroup, FormControl, FormArray, FormBuilder, Validators } from '@angular/forms';
import { DomSanitizer, SafeHtml } from "@angular/platform-browser";

@Component({
  selector: 'my-app',
  template: `
    <h1>Dynamic template:</h1>

    <form [formGroup]="myForm" (ngSubmit)="onSubmit()">
     <div  class="form-row">
      <label for="">Name</label>
      <input type="text" class="form-control" formControlName="name">
            <small [hidden]="myForm.controls.name.valid || (myForm.controls.name.pristine && !submitted)" class="text-danger">
            Name is required (minimum 5 characters).
          </small>
    </div>

        <div  class="form-row">
      <label for="">Last Name</label>
      <input type="text" class="form-control" formControlName="lastname">
            <small [hidden]="myForm.controls.name.valid || (myForm.controls.name.pristine && !submitted)" class="text-danger">
            Name is required (minimum 5 characters).
          </small>
    </div>

       <div #container></div>

      <div class="form-row">
      <button type="submit">Submit</button>
      </div>
       <div *ngIf="payLoad" class="form-row">
            <strong>Saved the following values</strong><br>{{payLoad}}
        </div>


    </form>
  `,
})
export class AppComponent implements OnInit , AfterViewInit {
  @ViewChild('container', { read: ViewContainerRef }) container: ViewContainerRef;
  public myForm: FormGroup; // our model driven form
  public payLoad: string;

    public onSubmit() {
        this.payLoad = JSON.stringify(this.myForm.value);
    }

  constructor(private compiler: Compiler,private formBuilder: FormBuilder,private sanitizer: DomSanitizer) {}

ngOnInit() {
      this.myForm = this.formBuilder.group({
            name: ['', [<any>Validators.required, <any>Validators.minLength(5)]],
            lastname: ['', [<any>Validators.required, <any>Validators.minLength(5)]],
            address: ['', [<any>Validators.required, <any>Validators.minLength(5)]]
            });
}
  ngAfterViewInit() {

    this.addComponent('<div  class="form-row"> <label for="">Address</label> <input type="text" class="form-control" formControlName="address">  </div>');
  }

  private addComponent(template: string) {
    @Component({template: template})
    class TemplateComponent {}

    @NgModule({declarations: [TemplateComponent]})
    class TemplateModule {}

    const mod = this.compiler.compileModuleAndAllComponentsSync(TemplateModule);
    const factory = mod.componentFactories.find((comp) =>
      comp.componentType === TemplateComponent
    );
    const component = this.container.createComponent(factory);
  }
}

The Plunker does not work, so I added the example in stackbliz.

https://stackblitz.com/edit/angular-t3mmg6

This example is dynamic Form controls is in add component (This is where you can get the Formcontrols from the server). If you see addcomponent method you can see the Forms Controls. In this example I am not using angular material,but It works (I am using @ work). This is target to angular 6, but works in all previous version.

Need to add JITComplierFactory for AngularVersion 5 and above.

Thanks

Vijay

Samuel Liew
  • 76,741
  • 107
  • 159
  • 260

2 Answers2

1

The problem is that you add the group address to the formbuilder groups in the parent component but the html is added as a child component which cannot update your formgroup values.

Using the parent-child approach, you need to output the change of the value from the child component to the parent component when the value change and then set the value of your form group manually, take a look here for some different ways of communicating between parent-child components: https://angular.io/docs/ts/latest/cookbook/component-communication.html

To me, it looks easier if you could use ngFor or ngIf directives to control your dynamic form instead of adding child components. Take a look here for an example of how to do this: https://angular.io/docs/ts/latest/cookbook/dynamic-form.html

hagner
  • 1,050
  • 9
  • 12
  • @hanger thanks for the reply, but my situation is that I dont have the luxury of working here with dynamic forms. The html comes from the table which need to be manipulated to add inputs to be presented to the user to fill it in. I do understand i added the form group in the top, I am not sure how to handle it after the dynamic HTML is displayed. I have worked on dynamic forms, but dynamic forms is not going to work since the content of Forms Inputs are part of html from database and need to display it dynamically. – Vijay Anand Kannan Dec 06 '16 at 14:30
  • If you get html from your database you can bind the innerHtml property of a div to your html string to have the html displayed. But if this solution does not work then you need to use one of the ways to communicate between parent and child that is outlined in the angular cookbook in the original answer. – hagner Dec 06 '16 at 16:10
  • I tried using inner html to bind and it works fine and it shows the html and the input values, but the problem is I am not able to get the values back to parent. That is where i need help. – Vijay Anand Kannan Dec 06 '16 at 16:16
  • Ok, would you mind updating your post to show how you did when binding to inner html. To be clear - I talk about updating your template with a div like this:
    . There is no parent-child relationship with this approach because the html string and the values will all be on the parent.
    – hagner Dec 06 '16 at 16:38
  • Here you go. This is using div InnerHTML. I am using safeHTML so the values Input fields are not stripeed http://plnkr.co/edit/Pgd81dg5tBuraSXkDBe6?p=preview When you add some values and submit, you will not get the values for address. This is the problem I am having – Vijay Anand Kannan Dec 06 '16 at 16:48
  • Thanks for the example, I can see the problem in this approach as well, I dont think binding to innerHtml will work here. I have created an example from your first approach and using a service to communicate changes to the parent component: http://plnkr.co/edit/DeYGuZSOYvxT76YI8SRU – hagner Dec 06 '16 at 20:17
  • Thanks for your help. I tested out and it looks good the way I need. I will implement in my application and will let you know. Hopefully I can take this approach. Another approach suggest by my colleague to go for jquery to get the value and it works. I need to try both way and see which one will work better. – Vijay Anand Kannan Dec 06 '16 at 20:35
  • I have one more question. I am not familiar with rxjs subject. It is possible to pass multiple values and subscribe to it. The reason for it I will have multiple input in dynamic html. I want to pass the name of the input property and set the value. In your example you are hard coding 'address'. Just want to know if that is possible – Vijay Anand Kannan Dec 06 '16 at 22:10
  • Yes, rxjs subjects will allow you to pass any type of type or class. I used a pure string in this example but you can use a tuple or a class of you want. Take a look at this links for more information about subjects: http://stackoverflow.com/questions/34849873/what-are-the-semantics-of-different-rxjs-subjects – hagner Dec 06 '16 at 22:21
  • Thanks again. I will play with it. – Vijay Anand Kannan Dec 06 '16 at 22:28
  • I have another question on this. I could use array to handle the data, now I want to do the validation and display the error when the user clicks submit. Can you give your thoughts on how I can approach it. – Vijay Anand Kannan Dec 07 '16 at 21:06
  • Here is a simple example of how to show an error message on submit: http://plnkr.co/edit/6fFWf0v7mV6hSrRgTpR5 . Also, please remember to mark the answer as accepted if you found it useful. – hagner Dec 09 '16 at 08:59
  • I have posted another way to access the Form Values directly. Here is the link. http://plnkr.co/edit/xOPZ1hVJPTz0hBoqgFvT?p=preview. Thanks again for all your help. – Vijay Anand Kannan Dec 14 '16 at 21:48
1

My Colleague (Justin) helped me on how to access the Form Values from the Dynamic HTML. @Hagner (http://plnkr.co/edit/DeYGuZSOYvxT76YI8SRU?p=preview) answer was one way in which you can do. This involves Services. The method below does not involve service and is is more straight forward way to access the values. I thought I will post for those with these scenarios.

-- app/app.component.ts

    import {
  AfterContentInit, AfterViewInit, AfterViewChecked, OnInit, Compiler, Component, NgModule, ViewChild,
  ViewContainerRef, forwardRef, Injectable, ChangeDetectorRef
} from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { ReactiveFormsModule, FormGroup, FormControl, FormsModule, FormArray, FormBuilder, Validators } from '@angular/forms';
import { DomSanitizer, SafeHtml } from "@angular/platform-browser";

@Injectable()
export class DynamicControlClass {
  constructor(public Key: string,
    public Validator: boolean,
    public minLength: number,
    public maxLength: number,
    public defaultValue: string,
    public requiredErrorString: string,
    public minLengthString: string,
    public maxLengthString: string,
    public ControlType: string
  ) { }
}

@Component({
  selector: 'my-app',
  template: `
    <h1>Dynamic template:</h1>

<div class="container">
    <form [formGroup]="myForm" (ngSubmit)="onSubmit()" novalidate>



     <div  class="form-row">
      <label for="">First Name</label>
      <input type="text" class="form-control" formControlName="firstname" required>
              <div *ngIf="formErrors.firstname" class="alert alert-danger">
                {{ formErrors.firstname }}
              </div>

    </div>

        <div  class="form-row">
      <label for="">Last Name</label>
      <input type="text" class="form-control" formControlName="lastname"  required>

                            <div *ngIf="formErrors.lastname" class="alert alert-danger">
                {{ formErrors.lastname }}
              </div>
      </div>

       <div #container></div>
<!--
<div  class="form-row">

    <input type="radio" formControlName="Medical_Flu_Concent_Decline_medical_flu_concent_decline_rule1" value="1"> <b>Concent Template </b>
    <br>
    <input type="radio" formControlName="Medical_Flu_Concent_Decline_medical_flu_concent_decline_rule1" value="2"> <b>Decline  Template</b>
</div>
-->

       <br>
       <!--
            <button type="submit" class="btn btn-default"
             [disabled]="!myForm.valid">Submit</button>
       -->

                   <button type="submit" class="btn btn-default" >Submit</button>

       <div *ngIf="payLoad" class="form-row">
            <strong>Saved the following values</strong><br>{{payLoad}}
        </div>

        <div> Is Form Valid : {{myForm.valid}}</div>
    </form>

    </div>
  `
  ,
  styles: ['h1 {color: #369;font-family: Arial, Helvetica, sans-serif;font-size: 250%;} input[required]:valid {border-left: 5px solid #42A948; /* green */ } input[required]:invalid {border-left: 5px solid #a94442; /* red */ } .radioValidation input:invalid{outline: 2px solid  #a94442;} .radioValidation input:valid{outline: 2px solid #42A948;}'],

})
export class AppComponent implements AfterContentInit {
  @ViewChild('container', { read: ViewContainerRef }) container: ViewContainerRef;
  public myForm: FormGroup; // our model driven form
  public payLoad: string;
  public controlData: [string, boolean, number];
  public ctlClass: DynamicControlClass[];
  public formErrors: any = {};
  public group: any = {};
  public submitted: boolean = false;
  public setValidatorValue: boolean = false;

  public onSubmit() {

    this.submitted = true;
    this.setValidatorValue = false;
    this.onValueChanged();

    if (this.myForm.valid) {

      const form = this.myForm

      const control = form.get('Medical_Flu_Concent_Decline_medical_flu_concent_decline');

      if (control) {
        if (control.value === '1') {
          const controlreset = form.get('Medical_Flu_Decline_Details_medical_flu_decline_details');

          if ((controlreset) && (controlreset.value)) {
            this.myForm.patchValue({ Medical_Flu_Decline_Details_medical_flu_decline_details: null });
          }
        }
      }
      this.payLoad = JSON.stringify(this.myForm.value);
    }

  }

  constructor(private compiler: Compiler, private formBuilder: FormBuilder, private sanitizer: DomSanitizer) {

    this.ctlClass = [
      new DynamicControlClass('firstname', true, 5, 0, '', 'Please enter First Name', 'First Name must be Minimum of 5 Characters', '', 'textbox'),
      new DynamicControlClass('lastname', true, 5, 0, '', 'Please enter LastName', 'Last Name must be Minimum of 5 Characters', '', 'textbox'),
      new DynamicControlClass('address', true, 5, 0, 'Default Address', 'Please enter Address', 'Address must be Minimum of 5 Characters', '', 'textbox'),
      new DynamicControlClass('Medical_Flu_Concent_Decline_medical_flu_concent_decline', true, 0, 0, null, 'Please Select one of the Radio option', '', '', 'radio'),
      new DynamicControlClass('Medical_Flu_Decline_Details_medical_flu_decline_details', false, 0, 0, null, 'Please Select one of the Decline option', '', '', 'radio'),
      new DynamicControlClass('city', true, 5, 0, 'Enter City', 'Please enter City', 'City must be Minimum of 5 Characters', '', 'textbox')]
  };



  ngAfterContentInit() {




    this.ctlClass.forEach(dyclass => {

      let minValue: number = dyclass.minLength;
      let maxValue: number = dyclass.maxLength;

      if (dyclass.Validator) {

        this.formErrors[dyclass.Key] = '';

        if ((dyclass.ControlType === 'radio') || (dyclass.ControlType === 'checkbox')) {
          this.group[dyclass.Key] = new FormControl(dyclass.defaultValue || null, [Validators.required]);
        }
        else {

          if ((minValue > 0) && (maxValue > 0)) {
            this.group[dyclass.Key] = new FormControl(dyclass.defaultValue || '', [Validators.required, <any>Validators.minLength(minValue), <any>Validators.maxLength(maxValue)]);
          }
          else if ((minValue > 0) && (maxValue === 0)) {
            this.group[dyclass.Key] = new FormControl(dyclass.defaultValue || '', [Validators.required, <any>Validators.minLength(minValue)]);
          }
          else if ((minValue === 0) && (maxValue > 0)) {
            this.group[dyclass.Key] = new FormControl(dyclass.defaultValue || '', [Validators.required, <any>Validators.maxLength(maxValue)]);
          }
          else if ((minValue === 0) && (maxValue === 0)) {
            this.group[dyclass.Key] = new FormControl(dyclass.defaultValue || '', [Validators.required]);
          }
        }
      }
      else {

        if (dyclass.Key === 'Medical_Flu_Decline_Details_medical_flu_decline_details') {
          this.formErrors[dyclass.Key] = 'null';
          this.group[dyclass.Key] = new FormControl(dyclass.defaultValue);
        }
        else {
          this.group[dyclass.Key] = new FormControl(dyclass.defaultValue || '');
        }


      }



    });

    this.myForm = new FormGroup(this.group);

    this.myForm.valueChanges.subscribe(data => this.onValueChanged(data));

    this.onValueChanged(); // (re)set validation messages now


    this.addComponent('<div [formGroup]="_parent.myForm" class="form-row">  <label for="">Address</label> <input type="text" class="form-control" formControlName="address"  required> <div *ngIf="_parent.formErrors.address" class="alert alert-danger">{{ _parent.formErrors.address }}</div><\div><div [formGroup]="_parent.myForm" class="form-row">   <label for="">City</label> <input type="text" class="form-control" formControlName="city"  required> <div *ngIf="_parent.formErrors.city" class="alert alert-danger">{{ _parent.formErrors.city }}</div><\div><div  [formGroup]="_parent.myForm" class="form-row radioValidation" > <input type="radio" formControlName="Medical_Flu_Concent_Decline_medical_flu_concent_decline"  id="Medical_Flu_Concent_Decline_medical_flu_concent_decline_1" name="Medical_Flu_Concent_Decline_medical_flu_concent_decline" value="1" required> <b>CONSENT.</b><br><br> Here is my Consent. <br><br><input type="radio"  formControlName="Medical_Flu_Concent_Decline_medical_flu_concent_decline" name="Medical_Flu_Concent_Decline_medical_flu_concent_decline" id="Medical_Flu_Concent_Decline_medical_flu_concent_decline_2" value="2" required> <b>DECLINE. </b><br/> I am  choosing to decline for the following reason: <br><br> <div *ngIf="_parent.formErrors.Medical_Flu_Concent_Decline_medical_flu_concent_decline" class="alert alert-danger">{{ _parent.formErrors.Medical_Flu_Concent_Decline_medical_flu_concent_decline }}</div></div><div [formGroup]="_parent.myForm" class="form-row"> <input type="radio" formControlName="Medical_Flu_Decline_Details_medical_flu_decline_details" id="Medical_Flu_Decline_Details_medical_flu_decline_details_1"   name="Medical_Flu_Decline_Details_medical_flu_decline_details"   value="1"  > I am not interested<br><br><input type="radio" formControlName="Medical_Flu_Decline_Details_medical_flu_decline_details" id="Medical_Flu_Decline_Details_medical_flu_decline_details_2"   name="Medical_Flu_Decline_Details_medical_flu_decline_details"   value="2"  > I have already received <br><br><input type="radio" formControlName="Medical_Flu_Decline_Details_medical_flu_decline_details" id="Medical_Flu_Decline_Details_medical_flu_decline_details_3"   name="Medical_Flu_Decline_Details_medical_flu_decline_details"   value="3"  > I am declining for other reasons<br><br><div *ngIf="_parent.formErrors.Medical_Flu_Decline_Details_medical_flu_decline_details" class="alert alert-danger">{{ _parent.formErrors.Medical_Flu_Decline_Details_medical_flu_decline_details }}</div></div>');




  }

  public onValueChanged(data?: any) {
    if (!this.myForm) { return; }
    const form = this.myForm;

    for (const field in this.formErrors) {
      // clear previous error message (if any)
      this.formErrors[field] = '';
      const control = form.get(field);


      if (field === 'Medical_Flu_Decline_Details_medical_flu_decline_details') {
        if ((this.myForm.value['Medical_Flu_Concent_Decline_medical_flu_concent_decline']) && (this.myForm.value['Medical_Flu_Concent_Decline_medical_flu_concent_decline'] === "2")) {
          control.setValidators(Validators.required);
          control.updateValueAndValidity({ onlySelf: false, emitEvent: false })
        }
        else if ((this.myForm.value['Medical_Flu_Concent_Decline_medical_flu_concent_decline']) && (this.myForm.value['Medical_Flu_Concent_Decline_medical_flu_concent_decline'] === "1")) {
          control.setValidators(null);
          control.updateValueAndValidity({ onlySelf: false, emitEvent: false })

          const controlreset = form.get('Medical_Flu_Decline_Details_medical_flu_decline_details');

          if ((controlreset) && (controlreset.value)) {
            this.myForm.patchValue({ Medical_Flu_Decline_Details_medical_flu_decline_details: null });
          }          

        }
      }



      if ((control && control.dirty && !control.valid) || (this.submitted)) {

        let objClass: any;

        this.ctlClass.forEach(dyclass => {
          if (dyclass.Key === field) {
            objClass = dyclass;
          }
        });

        for (const key in control.errors) {
          if (key === 'required') {
            this.formErrors[field] += objClass.requiredErrorString + ' ';
          }
          else if (key === 'minlength') {
            this.formErrors[field] += objClass.minLengthString + ' ';
          }
          else if (key === 'maxLengthString') {
            this.formErrors[field] += objClass.minLengthString + ' ';
          }
        }



      }
    }

  }



  private addComponent(template: string) {
    @Component({
      template: template,
      styles: ['h1 {color: #369;font-family: Arial, Helvetica, sans-serif;font-size: 250%;} input[required]:valid {border-left: 5px solid #42A948; /* green */ } input[required]:invalid {border-left: 5px solid #a94442; /* red */ } .radioValidation input:invalid{outline: 2px solid  #a94442;} .radioValidation input:valid{outline: 2px solid #42A948;}'],


      // alternatively:  [{provide: TemplateContainer, useExisting: forwardRef(() => AppComponent)}]
    })
    class TemplateComponent {
      constructor(public _parent: AppComponent) {

        console.log("parent component", this._parent);

      }
    }
    @NgModule({ imports: [ReactiveFormsModule, FormsModule, BrowserModule], declarations: [TemplateComponent] })
    class TemplateModule { }

    const mod = this.compiler.compileModuleAndAllComponentsSync(TemplateModule);
    const factory = mod.componentFactories.find((comp) =>
      comp.componentType === TemplateComponent
    );
    const component = this.container.createComponent(factory);
  }
}



-- app/app.module.ts


import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { COMPILER_PROVIDERS } from '@angular/compiler';

import { AppComponent }   from './app.component';


@NgModule({
  imports:      [BrowserModule, ReactiveFormsModule],
  declarations:  [AppComponent],
  bootstrap:    [ AppComponent ]
})

export class AppModule { }


-- app/main.ts

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app.module';

const platform = platformBrowserDynamic();

platform.bootstrapModule(AppModule);


-- config.js

System.config({
  //use typescript for compilation
  transpiler: 'typescript',
  //typescript compiler options
  typescriptOptions: {
    emitDecoratorMetadata: true
  },
  paths: {
    'npm:': 'https://unpkg.com/'
  },
  //map tells the System loader where to look for things
  map: {

    'app': 'app',

      '@angular/core': 'npm:@angular/core/bundles/core.umd.js',
      '@angular/common': 'npm:@angular/common/bundles/common.umd.js',
      '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
      '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
      '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
      '@angular/http': 'npm:@angular/http/bundles/http.umd.js',
      '@angular/router': 'npm:@angular/router/bundles/router.umd.js',
      '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',

      // angular testing umd bundles
      '@angular/core/testing': 'npm:@angular/core/bundles/core-testing.umd.js',
      '@angular/common/testing': 'npm:@angular/common/bundles/common-testing.umd.js',
      '@angular/compiler/testing': 'npm:@angular/compiler/bundles/compiler-testing.umd.js',
      '@angular/platform-browser/testing': 'npm:@angular/platform-browser/bundles/platform-browser-testing.umd.js',
      '@angular/platform-browser-dynamic/testing': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic-testing.umd.js',
      '@angular/http/testing': 'npm:@angular/http/bundles/http-testing.umd.js',
      '@angular/router/testing': 'npm:@angular/router/bundles/router-testing.umd.js',
      '@angular/forms/testing': 'npm:@angular/forms/bundles/forms-testing.umd.js',

      // other libraries
      'rxjs':                       'npm:rxjs',
      'lodash':                       'npm:lodash/lodash.min.js',
      'angular2-in-memory-web-api': 'npm:angular2-in-memory-web-api',
      'ts':                         'npm:plugin-typescript/lib/plugin.js',
      'typescript':                 'npm:typescript/lib/typescript.js',
  },
  //packages defines our app package
  packages: {
    app: {
      main: './main.ts',
      defaultExtension: 'ts'
    },
    rxjs: {
      defaultExtension: 'js'
    }
  }
});

-- index.html

<!DOCTYPE html>
<html>

  <head>
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css">

<script src="https://unpkg.com/zone.js@0.6.21/dist/zone.js"></script>
<script src="https://unpkg.com/reflect-metadata@0.1.3/Reflect.js"></script>
<script src="https://unpkg.com/systemjs@0.19.31/dist/system.js"></script>
<script src="https://unpkg.com/typescript@1.8.10/lib/typescript.js"></script>
<script src="config.js"></script>

    <script src="config.js"></script>
    <script>
      System.import('app').catch(function(err){ console.error(err); });
    </script>     
  </head>

  <body>
    <my-app>Loading...</my-app>
  </body>

</html>

http://plnkr.co/edit/rELaWPJ2cDJyCB55deTF?p=preview

Full Credit to Justin for helping me out.

  • If you want to work with AOT here is what you need to do import { JitCompilerFactory } from '@angular/compiler'; private compiler: Compiler = new JitCompilerFactory([{ useDebug: false, useJit: true }]).createCompiler(); Create complier @ where you need. Remove the complier from constructor. This will work with AOT. Updated the plnkr to work with AOT Compilation http://plnkr.co/edit/2qAWGh?p=preview – Vijay Anand Kannan Feb 09 '17 at 15:05