How to add "Active" class when input field is not empty
Asked Answered
W

6

12

I'm trying to create an Angular 4 Directive that will add class="active" on the label when the textfield is not empty

<div class="md-form">
    <input appMdbInputInit type="text" name="" id="FirstName" 
         class="form-control"   formControlName="firstName">
    <label  for="FirstName" >First Name</label>
 </div>

Result that I expect when textfield is not empty

 <div class="md-form">
    <input  appMdbInputInit  type="text" name="" id="FirstName" 
         class="form-control"   formControlName="firstName">
    <label class="Active"  for="FirstName" >First Name</label>
 </div>

How can I do that ?

Thanks a lot

Wellman answered 28/6, 2017 at 19:1 Comment(0)
D
8

You can create directive and inject FormControlName instance to listen to value changes. And add or remove label active class when value changes.

Directive

import { Directive, OnInit, OnDestroy, ElementRef } from '@angular/core';
import { FormControlName } from '@angular/forms';

import { Subscription } from 'rxjs/Subscription';

@Directive({
    selector: '[setLabelActive]'
})
export class SetLabelActiveDirective implements OnDestroy {

    valueSub: Subscription; 

    constructor(
        private el: ElementRef,
        private formControlName: FormControlName // Inject FormControlName
    ) {

    }

    ngOnInit() {
        // Listen value changes
        this.valueSub = this.formControlName.valueChanges.subscribe(value => {

            // Get label
            const inputId = this.el.nativeElement.getAttribute('id'),
                label = document.querySelector(`label[for="${inputId}"]`);

            // Toggle `active` class
            if (label) {
                label.classList.toggle('active', value);
            }
        });
    }

    ngOnDestroy() {
        // Unlisten value changes
        this.valueSub.unsubscribe();
    }

}

Usage

<form [formGroup]="myForm">

    <div>
        <input setLabelActive type="text" id="FirstName" formControlName="firstName">
        <label for="FirstName">First Name</label>
    </div>

</form>
Deryl answered 21/9, 2017 at 20:33 Comment(1)
this answer helped me recently and i wanted to add, if you have an input and a label after, you can add the class onto the input and style the label with input.active + labelOvercloud
T
13

You don't need any custom directive to do that. Example:

  <form [formGroup]="group">
    <input appMdbInputInit type="text" name="" id="FirstName" 
       class="form-control" formControlName="firstName">
    <label [class.Active]="group.get('firstName').value.length" for="FirstName">First Name</label>
  </form>

Demo: http://plnkr.co/edit/SUmIVCaWnJzjU7j0XHwj?p=preview

Tymes answered 28/6, 2017 at 19:39 Comment(0)
D
8

You can create directive and inject FormControlName instance to listen to value changes. And add or remove label active class when value changes.

Directive

import { Directive, OnInit, OnDestroy, ElementRef } from '@angular/core';
import { FormControlName } from '@angular/forms';

import { Subscription } from 'rxjs/Subscription';

@Directive({
    selector: '[setLabelActive]'
})
export class SetLabelActiveDirective implements OnDestroy {

    valueSub: Subscription; 

    constructor(
        private el: ElementRef,
        private formControlName: FormControlName // Inject FormControlName
    ) {

    }

    ngOnInit() {
        // Listen value changes
        this.valueSub = this.formControlName.valueChanges.subscribe(value => {

            // Get label
            const inputId = this.el.nativeElement.getAttribute('id'),
                label = document.querySelector(`label[for="${inputId}"]`);

            // Toggle `active` class
            if (label) {
                label.classList.toggle('active', value);
            }
        });
    }

    ngOnDestroy() {
        // Unlisten value changes
        this.valueSub.unsubscribe();
    }

}

Usage

<form [formGroup]="myForm">

    <div>
        <input setLabelActive type="text" id="FirstName" formControlName="firstName">
        <label for="FirstName">First Name</label>
    </div>

</form>
Deryl answered 21/9, 2017 at 20:33 Comment(1)
this answer helped me recently and i wanted to add, if you have an input and a label after, you can add the class onto the input and style the label with input.active + labelOvercloud
S
8

The easiest way would be with a template reference variable (see my other answer for a way to do this with a directive):

  1. Define a template reference variable on the input element with #myInput property

    <input type="text" #myInput>
    
  2. Define a conditional class property binding with [class.active]="myInput.value" with the condition being the value of the #myInput element:

    <label [class.active]="myInput.value"  for="FirstName" >First Name</label>
    
  3. Define a (keyup) or (change) mock event handler on the element to trigger Angular change detection.

    • "change" event would activate the class on element blur event, whereas (keyup) would enable it on key up. it's your choice which one to choose according to your need:

there you go!


complete example code:

 <div class="md-form">
    <input type="text" #myInput (keyup)="true"
         class="form-control"   formControlName="firstName">
    <label [class.active]="myInput.value"  for="FirstName" >First Name</label>
 </div>
Sharpe answered 23/9, 2017 at 10:28 Comment(1)
This answer helped me. Thank you!Demesne
S
4

There is actually a simpler, built-in Angular directive [class.className] (see my previous answer), but if you insist of accomplishing this with a directive, this is how I would do it :

This is your directive, makeactive.directive.ts (don't forget to add it to your app.module.ts file as a declaration):

import {Directive, ElementRef, Input, Renderer2} from '@angular/core';

@Directive({
    selector: '[makeActive]'
})
export class ActiveDirective
{
    input;

    @Input() set makeActive(input) {
        this.input = input;
    }

    ngDoCheck() {
        let fn = (this.input && this.input.value) ? 'addClass' : 'removeClass';
        this.renderer[fn](this.el.nativeElement, 'active');
    }

    constructor(private el: ElementRef, private renderer: Renderer2) {}
}
  • in your template app.component.html, you need to add the directive property to the element that you want to apply the class to (the <label> element):

    <label [makeActive]="myInput">First Name</label>

  • Then you need to declare a template reference variable on the text input:

    <input type="text" #myInput>

  • You also need to register it's "keyup" or "change" event to trigger Angular change detection (either on blur or on key up, as in my previous answer):

    <input type="text" #myInput (keyup)="true">

Complete template code:

<div class="md-form">
  <input type="text" #myInput (keyup)="true">
  <label [makeActive]="myInput">First Name</label>
</div>
Sharpe answered 23/9, 2017 at 11:47 Comment(0)
I
3

You can use ngClass for this one created plunker.

<form [formGroup]="group">
<input appMdbInputInit type="text" name="" id="FirstName" 
   class="form-control" formControlName="firstName">
<label [ngClass]="{'active': group.get('firstName').value.length}" for="FirstName">First Name</label>

Ivories answered 20/9, 2017 at 3:7 Comment(0)
M
2

As others mentioned here you don't really need separate directive for such small thing, but if you want it that way... ok, here it is.

UPDATE

Actually, it can be done even simpler. I've updated the code.

app.module.ts

import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';

import {AppComponent} from './app.component';
import {AppMdbInputInitDirective} from './app-mdb-input-init.directive';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';

@NgModule({
    declarations: [
        AppComponent,
        AppMdbInputInitDirective
    ],
    imports: [
        BrowserModule,
        FormsModule,
        ReactiveFormsModule
    ],
    providers: [],
    bootstrap: [AppComponent]
})
export class AppModule {
}

app.component.ts

import {Component} from '@angular/core';
import {FormBuilder, FormGroup} from '@angular/forms';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/map';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent {
    formGroup: FormGroup;
    activeFlag$: Observable<boolean>;
    constructor(fb: FormBuilder) {
        this.formGroup = fb.group({
            firstName: fb.control('')
        });
        this.activeFlag$ = this.formGroup.get('firstName').valueChanges.map(v => !!v);
    }
}

app.component.css

.active {
    color: red;
}

app.component.html

<div class="md-form" [formGroup]="formGroup">
    <input type="text" name="" id="FirstName" class="form-control" formControlName="firstName">
    <label for="FirstName" [appMdbInputInit]="activeFlag$ | async" labelClassName="active">First Name</label>
</div>

And, finally, most interesting part - app-mdb-input-init.directive.ts

import {Directive, ElementRef, Input, Renderer2} from '@angular/core';

@Directive({
    selector: '[appMdbInputInit]'
})
export class AppMdbInputInitDirective {

    @Input()
    labelClassName: string;

    @Input()
    set appMdbInputInit(val: boolean) {
        if (val) {
            this.renderer.addClass(this.elementRef.nativeElement, this.labelClassName);
        } else {
            this.renderer.removeClass(this.elementRef.nativeElement, this.labelClassName);
        }
    }

    constructor(private elementRef: ElementRef, private renderer: Renderer2) {
    }

}
Midtown answered 21/9, 2017 at 21:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.