3

I feel like I must be missing something here and would like to find out if there is a better way of doing this.

I've got a directive that basically adds a class to the parent element of a input field. On load I need to check if the input field has a value, you'd think that would be simple...

I've used a few different options now which include, ngOnInit, AfterContentInit and AfterViewInit. But for the below I'm still on AfterContentInit:

ngAfterContentInit() {
  console.log(this.el.nativeElement); // Element Object "Value is not empty if you look inside the object."
  console.log(this.el.nativeElement.value); // = ''

  setTimeout(() => {
    console.log(this.el.nativeElement.value); // = 'The Value!'
  }, 1000);

  this.activeStateCheck(this.el.nativeElement);
}

activeStateCheck(element) {
  if (element.value !== '') {
    element.parentElement.classList.add('active');
    return true;
  }
}

I should point out that all options give the same results.

The first console logs out an empty string and the second one logs out the actual string. In this case it is 'Grand Tour'.

My question is how do I do this without using a setTimeout?

It appears to have to wait for the data to load on the page before it you can log the value. However I was under the impression that OnInit was meant to wait for this to be finished?

The reason why I'd like to find a better solution is because if the page takes longer than a second to load then the console log will output an empty string, therefore not a complete solution.

This is an example of the directive being called. Notice the appPlaceholder attribute:

<fieldset class="form__placeholder">
    <input type="text" 
            id="lastName" 
            formControlName="lastName" 
            appPlaceholder>
    <label for="lastName"
           class="form__placeholder--label">
      Lastname:
    </label>
</fieldset>
ata
  • 3,398
  • 5
  • 20
  • 31
MaxwellLynn
  • 880
  • 5
  • 23
  • 46
  • I'm not sure why you're trying to access the native element's value. Use either a reactive form or template form and set the value of the input within the component ts file. showing your other component's code and the html would be helpful – Kevin Feb 14 '18 at 15:23
  • I've added the html to the question. Just about to add the activeStateCheck as well. – MaxwellLynn Feb 14 '18 at 15:25
  • Since you're usinf formControlName I'll assume you're using a reactive form.. You can subscribe to value changes on your input by doing this.[enterFormName].get('lastName').valueChanges.subscribe(value => { here you call your active state check function }); – Kevin Feb 14 '18 at 15:32
  • Sorry Kevin, I don't understand. Are you meant to put this code in the directive? Also how do I go about passing the formName to the directive? – MaxwellLynn Feb 15 '18 at 11:15

4 Answers4

4

From what you've told us, I'm not sure you need a custom directive. If you truly want to add the class "active" to the parent of a non-empty input, you can simply do

<div [ngClass]={'active': groupForm.get('name').value !== ''}>
  <input formControlName="lastName">
</div>

You can even omit the !== '' as an empty string is falsy.

If you absolutely need a directive for some other reason, you can treat formControlName as an input to your directive, @Input() formControlName;, but if you do that, you'll also need to pass the formGroup in. Changing parent behavior from a child seems messy, however, expecially with direct DOM manipulation like you're doing (use HostBinding instead). I'd probably suggest pairing the parent/input combination into a new component instead.

adamdport
  • 11,687
  • 14
  • 69
  • 91
  • TypeError: path.split is not a function if I just use ngClass. And if I use NgClass.active I get this error. – MaxwellLynn Feb 19 '18 at 17:20
  • Can't bind to 'ngClass.active' since it isn't a known property of 'fieldset'. – MaxwellLynn Feb 19 '18 at 17:20
  • @MaxwellLynn looks like they changed how ngclass works since I've read the docs. It's `[class.active]`, not `[ngClass.active]`. [Read more here](https://angular.io/guide/template-syntax#binding-targets) – adamdport Feb 19 '18 at 20:35
  • Ok this was the solution but can you update your code to the this please as I had to alter it slightly. [ngClass]={'active': groupForm.get('name').value} – MaxwellLynn Feb 20 '18 at 09:42
1

When you said the page take longer than 1 second to load, does it mean it loads data from service to fill in the form?

You can try wrapping your form with ng-container and only render the form if the data loading is completed.

Something like

<ng-container *ngIf="finishLoading">

    // ......

    <fieldset class="form__placeholder">
        <input type="text" 
                id="lastName" 
                formControlName="lastName" 
                appPlaceholder>
        <label for="lastName"
               class="form__placeholder--label">
          Lastname:
        </label>
    </fieldset>

    // ......

</ng-container>

and in your component class set finishLoading to true after the data load is completed

this.finishLoading = true;

Hope this helps.

0

You can use form valueChanges like so:

this.myForm.get('lastName').valueChanges.subscribe(val => {
     console.log(val);
     this.activeStateCheck(val);
});  

You can use this anywhere in your component, OnInt, or AfterViewInit.

Check this article for more details:

https://alligator.io/angular/reactive-forms-valuechanges/

Nadhir Falta
  • 5,047
  • 2
  • 21
  • 43
0

To be sure when the value arrives, you must subscribe to the HTMLInputElement input event emitter. With this, you would never have race conditions or so. I think it will fit well for your purposes.

ngOnInit() {  
  fromEvent(this.el.nativeElement, 'input').subscribe((event) => activeStateCheck(event.target.value);
}

activeStateCheck(value) {
  if (value !== '') {
    this.el.element.parentElement.classList.add('active');
    return true;
  }
}

Hope this helps.