0

I've been trying to create a simple directive in Angular 5 but hit walls. I recently discovered that adding my code inside a setTimeout function makes it work as I expect. In this case I expect the form to appear populated with "yolo"

I don't completely understand why. I understand that it has to do with the way Angular bootstraps but I don't understand why it is that way - and especially why code in the constructor is discarded (After all, then what's the point of the constructor?)

Please find a simplified copy of the code in question below:

With setTimeout

Without setTimeout

@Directive({
  selector: '[formControlName][phone]',
  host: {
    '(ngModelChange)': 'onInputChange($event)'
  }
})
export class PhoneMask {

  constructor(public model: NgControl) {
    // with setTimeout
    setTimeout(() => {  
      this.model.valueAccessor.writeValue('yolo');
    });

    // without setTimeout
    // this.model.valueAccessor.writeValue('yolo');

  }

}

@Component({
  selector: 'my-app',
  providers: [],
  template: `
  <form [formGroup]="form">
    <input type="text" phone formControlName="phone"> 
  </form>
  `,
  directives: [PhoneMask]
})
export class App {
  constructor(fb:FormBuilder) {
    this.form=fb.group({
      phone:['']
    })
  }
}

@NgModule({
  imports: [ BrowserModule, FormsModule, ReactiveFormsModule ],
  declarations: [ App, PhoneMask ],
  bootstrap: [ App ]
})
export class AppModule {}
CodyBugstein
  • 21,984
  • 61
  • 207
  • 363

2 Answers2

5

When you put code in a setTimeout you are effectively taking it out of the current call stack, once the whole call stack is executed the event loop will pick your code and put it on top of a new call stack and the execution will start. You may come around the term tick, well thats a tick, when the call stack is done. In your case, setTimeout is basically 0ms but the code is removed from the current stack, so everything else completes, then your code runs, modifies the property and angular change detector detects the change.

bulforce
  • 991
  • 1
  • 6
  • 11
  • Thanks for the explanation. Why isn't the code being in the constructor enough though? Why doesn't code in the constructor end up being displayed in the view? – CodyBugstein Mar 09 '18 at 14:54
  • I dont feel qualified to answer about angular internals, but I would suggest that you should take a look at what is actually ran in the browser rather than expecting the synthetic syntax of typescript to behave as the oop that you are used to. Typescript is a transpiler that emits the actual javascript code that is run in the browser, as such certain things like classes, constructors in the classical meaning does not exists. – bulforce Mar 09 '18 at 23:02
1

At the time of constructing your directive, Angular doesn’t check for changes in the directive’s (or rather its host component’s) properties. While moving the relevant code to setTimeout works, it’s much better (ie. not a hacky workaround) to do such things in eg. ngOnInit:

// PhoneMask implements OnInit
ngOnInit() {
  this.model.valueAccessor.writeValue('yolo')
}

Worth reading: https://stackoverflow.com/a/35763811/7178441


Update: @csmckelvey explained in comments why the setTimeout trick actually works:

Because it causes the code to be delayed until after the constructor has been executed.

gsc
  • 1,559
  • 1
  • 13
  • 17
  • Thanks for the suggestion. However my question was about why setTimeout works – CodyBugstein Mar 08 '18 at 20:42
  • 1
    Because it causes the code to be delayed until after the constructor has been executed. – takendarkk Mar 08 '18 at 20:44
  • @csmckelvey why is it not good enough for it to be in the constructor? What is the point of the constructor then? – CodyBugstein Mar 08 '18 at 21:03
  • 1
    Because in the constructor the object hasn't yet been created so you can't modify it at that point - the constructor is for setting up initial values, not changing them. – takendarkk Mar 08 '18 at 21:22