Home > Software engineering >  Angular cannot loop a list of ElementRef
Angular cannot loop a list of ElementRef

Time:12-21

I have a list of ElementRef for all my inputs but when I try to add listeners to them it makes like textInputs is empty but it's not.


@ViewChildren('text_input') textInputs!: QueryList<ElementRef>;

ngAfterViewInit(): void {
    this.initTextInputsListeners();
}

private initTextInputsListeners() {
    this.textInputs.forEach(input => {
      const inputElement = input.nativeElement;
      const parentNode = inputElement.parentNode;
      inputElement.addEventListener('focus', () => {
        parentNode.classList.add('user-input-active')
      });
      inputElement.addEventListener('focusout', () => {
        if (inputElement.value === '') parentNode.classList.remove('user-input-active');
      });
    });
  }
<div  [formGroup]="userInputForm" (ngSubmit)="onSendMessage()">
      <div >
        <label for="message">Send message</label>
        <input id="message"  type="text" name="message" #text_input formControlName="message" (keyup.enter)="onSendMessage()">
        <button  type="submit" value="" (click)="onSendMessage()">
          <svg width="32" height="32" viewBox="0 0 24 24">
            <path
              d="m3.4 20.4l17.45-7.48a1 1 0 0 0 0-1.84L3.4 3.6a.993.993 0 0 0-1.39.91L2 9.12c0 .5.37.93.87.99L17 12L2.87 13.88c-.5.07-.87.5-.87 1l.01 4.61c0 .71.73 1.2 1.39.91z"/>
          </svg>
        </button>
      </div>
    </div>

CodePudding user response:

your code looks like work. So the problem is that when you execute the initTextInputsListeners textInputs have not all the inputs

So To be sure, you can subscribe to this.textInputs.changes

  ngAfterViewInit(): void {
    this.textInputs.changes.pipe(startWith(null)).subscribe((_) => {
      this.initTextInputsListeners();
    });
  }

There're another approach to achieve you want that it's use directives

You can use a directive applied to your inputs like

@Directive({
  selector: '[specialfocus]'
})
export class AddClassParentDirective {
  @Input('specialfocus') 
  @HostListener('focus') addClass(){
    this.el.nativeElement.parentNode.classList.add(this.class)
  }
  @HostListener('blur') removeClass(){
    this.el.nativeElement.parentNode.classList.remove(this.class)
  }
  constructor(private el:ElementRef) { }
}

And use as

<div >
  <label for="message">Send message</label>
  <input [specialfocus]  type="text" />
</div>

Or use a directive applied to the div (see that in this case I use as selector '.user-input-box2', so each div that has a class "user-input-box2" is really a UserInputBoxDirective

@Directive({
  selector: '.user-input-box2'
})
export class UserInputBoxDirective implements AfterViewInit,OnDestroy {
  focus:boolean=false;
  subscription:any=null
  @HostBinding('class.user-input-active') get _(){
    return this.focus?true:null
  }
  @ContentChild(HTMLInputElement) input:HTMLInputElement
  constructor(private el:ElementRef) { }
  ngAfterViewInit()
  {
    const inputs=this.el.nativeElement.getElementsByTagName('input')
    if (inputs && inputs.length)
    this.subscription=merge(fromEvent(inputs[0],'focus').pipe(map(_=>true)),
                            fromEvent(inputs[0],'blur').pipe(map(_=>false)))
                            .subscribe(res=>{
                              this.focus=res
                            })
  }
  ngOnDestroy()
  {
    this.subscription && this.subscription.unsubscribe
  }
}

You use like

<div >
  <label for="message">Send message</label>
  <input  type="text" />
</div>

You has the three approach (your's and this about directives) in this stackblitz

CodePudding user response:

Easiest solution would be to not use JS for this at all. This is standard CSS stuff that has fairly good support in modern browsers:

Use focus-within to determine if there is a focus active in a container.

Second way to do this with CSS is to use the :has() css pesude-class, but this has poorer css support. If you only want to activate this rule if the input is selected, you can modify the HTML markup so the focus-within only triggers if the input is in focus.

In the example below I color the text pink and fill the SVG with blue if focus is inside the .user-input-box element

.user-input-box:focus-within {
  color: pink
}

.user-input-box:focus-within svg {
  fill: blue
}
<div >
  <label for="message">Send message</label>
  <input id="message"  type="text" name="message" #text_input formControlName="message">
  <button  type="submit" value="">
          <svg width="32" height="32" viewBox="0 0 24 24">
            <path
              d="m3.4 20.4l17.45-7.48a1 1 0 0 0 0-1.84L3.4 3.6a.993.993 0 0 0-1.39.91L2 9.12c0 .5.37.93.87.99L17 12L2.87 13.88c-.5.07-.87.5-.87 1l.01 4.61c0 .71.73 1.2 1.39.91z"/>
          </svg>
        </button>
</div>

  • Related