5

I'm trying to fill a Angular 4+ form using console (devtools).

This is what I'm doing now:

function fillForm(){
    let el = document.querySelector('input[ng-reflect-name="my_input"]');
    let elProbe = ng.probe(el);
    elProbe._debugContext.component.value = 'new-value';
}

Some references I'm trying to use (if it helps anyone):

leonardomerlin
  • 120
  • 1
  • 7

3 Answers3

3

There are two options. The first one is to work with component property bound to a form and it requires manual change detection triggering. The second one is to work with the form control associated with the input and it doesn't require manual change detection.

Neither is better.

For the first option see @yurzui's answer. Here is the answer for the second option - update the form control directly without the need for change detection:

function fillForm(){
    let el = document.querySelector('input[ng-reflect-name="my_input"]');
    let elProbe = ng.probe(el);

    const NgControlClassReference = elProbe.providerTokens.find((p)=>{ 
        return p.name === 'NgControl';
    });

    let directive = elProbe.injector.get(NgControlClassReference);
    let control = directive.control;

    control.setValue('some');
}

In this case you don't need change detection because when you call setValue on the control directly it notifies valueAccessor about the change:

FormControl.prototype.setValue = function (value, options) {
  ...
  this._onChange.forEach(function (changeFn) { 
      return changeFn(_this._value, options.emitViewToModelChange !== false); });

where changeFn is a subscriber added in the setUpContorl function:

  control.registerOnChange((newValue: any, emitModelEvent: boolean) => {
    // control -> view
    dir.valueAccessor !.writeValue(newValue);

which calls writeValue on the accessor directly and it in turn writes the value into input:

export class DefaultValueAccessor implements ControlValueAccessor {
  ...

  writeValue(value: any): void {
    const normalizedValue = value == null ? '' : value;
    this._renderer.setElementProperty(this._elementRef.nativeElement, 'value', normalizedValue);
  }

You also might find this article useful

Everything you need to know about debugging Angular applications

Max Koretskyi
  • 101,079
  • 60
  • 333
  • 488
  • Whats your context in `$0` ? – leonardomerlin Jul 07 '17 at 14:39
  • @leonardomerlin, it's `elProbe`, I've updated the details – Max Koretskyi Jul 07 '17 at 14:58
  • I choose the @yurzui as a correct answer because I dont discover what referencet to put in the place of `$0` reference and nothing about the magic number "3" =/ Sorry. – leonardomerlin Jul 07 '17 at 14:59
  • @leonardomerlin, mine and yurzui's answers are different. I'm updating the form control directly and don't need to invoke change detection manually, while his solution updates the property on the parent component and requires manual trigger to change detection. The magical `3` is a reference to the `NgControl` class. You can choose whatever you like – Max Koretskyi Jul 07 '17 at 15:01
  • https://plnkr.co/edit/rvXQ4xl6TEXchueasDS5?p=preview Are you sure that index will be 3? – yurzui Jul 07 '17 at 15:07
  • no, indeed that's the problem, the index can be different. `el.value = 'some';` - yeah. I'm wondering how the form looks like, how it's bound to `value` on component – Max Koretskyi Jul 07 '17 at 15:13
  • @Maximus Don't forget to refer to me :) I don't see your comment – yurzui Jul 07 '17 at 15:16
  • @yurzui, improved the solution to not depend on the index :) – Max Koretskyi Jul 07 '17 at 15:18
  • Yeah it will be correct now. But we can find one more solution – yurzui Jul 07 '17 at 15:21
2

Simple way of doing this could be running change detection cycle using core token for ApplicationRef

let el = document.querySelector('input[ng-reflect-name="my_input"]');
let elProbe = ng.probe(el);   
elProbe._debugContext.component.value = 'new-value';
elProbe.injector.get(ng.coreTokens.ApplicationRef).tick()

You can also take a look at integration tests

https://github.com/angular/angular/blob/master/packages/forms/test/template_integration_spec.ts#L28-L30

I think this way we check how our component works with NgModel and don't call direct API on inner NgModel directive.

Without calling change detection we can also the following code to simulate user action

let valueAccessor = elProbe.injector.get(elProbe.providerTokens
                       .find(x =>x.name === 'DefaultValueAccessor'));

el.value = 'some';
valueAccessor.onChange('some');

But it looks very strange

yurzui
  • 205,937
  • 32
  • 433
  • 399
0

Copy/paste spin-off from the accepted answer to fill multiple fields at once:

[
  { selector: "#initials", val: "JJA" },
  { selector: "#lastName", val: "Steenbruggehoek" },
  { selector: "#email", val: "john.doe@example.org" },
].forEach(item => {
    const el = document.querySelector(item.selector);
    if (!el) { console.warn(item.selector, 'not found'); return; }
    const probe = ng.probe(el);
    const ref = probe.providerTokens.find(p => p.name === 'NgControl');
    const directive = probe.injector.get(ref);
    directive.control.setValue(item.val);
});

As the other answer, only works for debug builds.

Jeroen
  • 60,696
  • 40
  • 206
  • 339