【问题标题】:Listen to className changes on an element replaced by ng-content监听被 ng-content 替换的元素的 className 变化
【发布时间】:2022-08-02 19:13:33
【问题描述】:

我需要听取被<ng-content> 替换的元素的类名更改。我尝试了很多方法,我发现的唯一方法是使用setInterval,我认为这不是一个好习惯。假设我将在<app-child> 组件内注入一个input 元素

@Component({
  selector: \'app-parent\',
  template: `
    <app-child>
     <input type=\"text\">
    </app-child>
  `
})
export class ParentComponent implements OnInit {
  ngOnInit() { }
}

并且每当inputclass 属性发生变化时,我都想在child.component.ts 内部做一些事情:

@Component({
  selector: \'app-child\',
  template: `<ng-content select=\"input\"></ng-content>`
})
export class ChildComponent implements OnInit {
  @ContentChild(HTMLInputElement) input: any;

  ngOnInit() {
    setInterval(() => {
       const { className } = this.input.nativeElement;
       console.log(className);
    }, 500);
  }
}

这种方法设法检测到类更改,但setInterval 的问题是该回调将每隔500 毫秒在后台运行一次,是否有另一种方法来检测更改?

注意:我已经尝试过钩子ngAfterContentChecked,它会在检测到任何更改后自动运行,但在内部我无法访问this.input.nativeElement.className 上的最新更改,就好像该函数在值更改之前执行一样。

    标签: angular angular-changedetection angular-lifecycle-hooks


    【解决方案1】:

    您可以使用MutationObserver Api

    像这样的东西:

      ngAfterContentInit(): void {
        this.changes = new MutationObserver((mutations: MutationRecord[]) => {
          mutations.forEach((mutation: MutationRecord) => {
            // this is called twice because the old class is removed and the new added
            console.log(
              `${mutation.attributeName} changed to ${this.input.nativeElement.classList}`
            );
          });
        });
    
        this.changes.observe(this.input.nativeElement, {
          attributeFilter: ['class'],
        });
      }
    

    这是一个堆栈闪电战,它正在运行https://stackblitz.com/edit/angular-ivy-tz5q88?file=src%2Fapp%2Fchild.component.ts

    【讨论】:

    • 太好了谢谢!这对我有好处,为了防止mutations 回调的多次调用,我将使用behaviourSubject&lt;string&gt;mutations 的最后一项的classNamedebounceTime 运算符结合使用,所以它会仅在指定时间段内发出最后一次className 更改。
    【解决方案2】:

    下面是实现 debounceTime 的代码。我不知道

    this.callback = this.callback.bind(this); 
    

    是正确的分配方式。但这有效。

    import { Directive, ElementRef } from '@angular/core';
    import { isEmpty } from 'lodash';
    import { Subject, Subscription } from 'rxjs';
    import { debounceTime, first } from 'rxjs';
    
    @Directive({
      selector: '.has-validation'
    })
    export class HasValidationDirective {
      el: any;
      formElement: any;
      observer: any;
      classNameChanged: Subject<any> = new Subject();
      subscriptions:any = [];
    
      constructor(el: ElementRef) {
        this.el = el;
      }
    
      ngOnInit() {
        this.callback = this.callback.bind(this);
        const subscription = this.classNameChanged.pipe(debounceTime(100)).subscribe(() => {
          this.toggleClassname();
        });
        this.subscriptions.push(subscription);
        setTimeout(() => {
          this.toggleClassname();
        })
      }
    
      ngAfterViewInit() {
        const formElementClassNames = ['form-input', 'form-textarea', 'form-select', 'form-radio', 'form-multiselect', 'form-checkbox'];
        formElementClassNames.forEach((className) => {
          const formElement = this.el.nativeElement.querySelector('.' + className)
          if (!isEmpty(formElement)) {
            this.formElement = formElement;
          }
        })
        if (isEmpty(this.formElement)) {
          const formElementClassNamesJoined = formElementClassNames.join(' (or) ');
          console.error(`class has-validation must have a form element with class name specified as ${formElementClassNamesJoined}`)
          return;
        }
        this.observer = new MutationObserver(this.callback);
        const a = this.observer.observe(this.formElement, { attributeFilter: ['class'] });
      }
    
    
      callback(mutationList: any, observer: any) {
        // Use traditional 'for loops' for IE 11
        for (const mutation of mutationList) {
          if (mutation.type === 'childList') {
           // console.log('A child node has been added or removed.');
          }
          else if (mutation.type === 'attributes') {
            // console.log(`The ${mutation.attributeName} attribute was modified.`);
            this.classNameChanged.next(null);
    
          }
        }
      };
    
      toggleClassname() {
        const validationClassnames = ['ng-valid',
          'ng-invalid',
          'ng-pending',
          'ng-pristine',
          'ng-dirty',
          'ng-untouched',
          'ng-touched'];
    
        validationClassnames.forEach((className) => {
          this.el.nativeElement.classList.remove(className);
          if (this.formElement.classList.contains(className)) {
            this.el.nativeElement.classList.add(className);
          }
        })
      }
    
      ngOnDestroy() {
        if (this.observer) {
          this.observer.disconnect();
        }
        this.subscriptions.forEach((subscription:Subscription)=>{
          subscription.unsubscribe();
        })
      }
    
    }
    

    【讨论】:

      猜你喜欢
      • 2021-08-13
      • 1970-01-01
      • 2022-01-05
      • 1970-01-01
      • 2018-04-24
      • 1970-01-01
      • 2012-11-02
      • 1970-01-01
      • 2018-02-27
      相关资源
      最近更新 更多