在按钮中显示微调器的角度指令

时间:2018-08-13 17:16:46

标签: angular

我正在尝试编写指令以修改按钮以在服务器操作正在进行时显示微调按钮。

<button type="submit" waitingButton (click)="submit($event)">
 {{submitBtnText}}
</button>

到目前为止,我已经能够展示微调器,但是我不知道如何捕获click事件的结果以将其切换回正常状态。

这是指令:

import {
  Directive,
  ElementRef,
  HostListener,
  OnInit,
  Renderer2,
  ComponentFactoryResolver,
  ViewContainerRef
} from '@angular/core';
import { MatSpinner } from '@angular/material';


@Directive({
  selector: '[bcnWaitingButton]'
})
export class BCNWaitingButtonDirective implements OnInit {


  constructor(
    private el: ElementRef, 
    private renderer: Renderer2, 
    private componentFactoryResolver: ComponentFactoryResolver, 
    public vcRef: ViewContainerRef) {

  }

  ngOnInit(): void {
    
  }

  @HostListener('click', ['$event']) onClick(e): void {
    // Create the spinner
    const factory = this.componentFactoryResolver.resolveComponentFactory(MatSpinner);
    const componentRef = this.vcRef.createComponent(factory);
    const spinner: MatSpinner = componentRef.instance;
    
    // Configure the spinner
    spinner.strokeWidth = 3;
    spinner.diameter = 24;

    // Set the button to disabled
    this.renderer.setAttribute(this.el.nativeElement, 'disabled', 'true');
    
    // Apply new styles
    const span: ElementRef = this.el.nativeElement.querySelector('.mat-button-wrapper');
    this.renderer.setStyle(span, 'display', 'flex');
    this.renderer.setStyle(span, 'align-items', 'center');
    this.renderer.setStyle(span, 'justify-content', 'center');
    this.renderer.setStyle(spinner._elementRef.nativeElement, 'margin-top', '0px');
    this.renderer.setStyle(spinner._elementRef.nativeElement, 'margin-left', '5px');

    // Append the spinner
    this.renderer.appendChild(this.el.nativeElement.firstChild, spinner._elementRef.nativeElement);
  }
}

关于如何做到这一点的任何想法?我应该在@Input上使用Submit回调,以便从指令的onClick函数调用它吗?

3 个答案:

答案 0 :(得分:1)

这是一个完整的工作指令,用于用MatSpinner替换“材质”按钮的文本,然后在操作完成后返回到文本。它利用了前两个答案的一部分:

import {
  ComponentFactoryResolver, Directive, Input, OnChanges, OnInit, Renderer2, SimpleChanges, ViewContainerRef
} from '@angular/core';
import { ElementRef } from '@angular/core';
import { MatSpinner } from '@angular/material';

@Directive({
  selector: 'button[appShowSpinner]'
})
export class SpinnerButtonDirective implements OnInit, OnChanges {
  // tslint:disable-next-line:no-input-rename
  @Input('appShowSpinner') showSpinner: boolean;
  originalInnerText: string;
  spinner: MatSpinner;

  constructor(
    private el: ElementRef,
    private renderer: Renderer2,
    private viewContainerRef: ViewContainerRef,
    private componentFactoryResolver: ComponentFactoryResolver
  ) { }

  ngOnInit() {
    // Record the button's original text
    this.originalInnerText = this.el.nativeElement.innerText;

    // Set the button to maintain the same dimensions, even once we put the spinner inside.
    this.el.nativeElement.style.width = `${(this.el.nativeElement as HTMLElement).offsetWidth}px`;
    this.el.nativeElement.style.height = `${(this.el.nativeElement as HTMLElement).offsetHeight}px`;

    // Create the spinner
    const factory = this.componentFactoryResolver.resolveComponentFactory(MatSpinner);
    const componentRef = this.viewContainerRef.createComponent(factory);
    this.spinner = componentRef.instance;

    // Configure the spinner
    this.spinner.strokeWidth = 3;
    this.spinner.diameter = 24;

    // Hide the spinner
    this.renderer.setStyle(this.spinner._elementRef.nativeElement, 'display', 'none');

    // Apply new styles to the button content's container
    const span = this.el.nativeElement.querySelector('.mat-button-wrapper') as HTMLSpanElement;
    this.renderer.setStyle(span, 'display', 'flex');
    this.renderer.setStyle(span, 'align-items', 'center');
    this.renderer.setStyle(span, 'justify-content', 'center');
  }

  ngOnChanges(changes: SimpleChanges) {
    if (typeof(changes.showSpinner) === 'object' && !changes.showSpinner.isFirstChange()) {
      if (changes.showSpinner.currentValue === true) {
        // Clear the button's text
        const span = this.el.nativeElement.querySelector('.mat-button-wrapper') as HTMLSpanElement;
        span.innerText = '';

        // Append the spinner
        this.renderer.appendChild(this.el.nativeElement.firstChild, this.spinner._elementRef.nativeElement);

        // Show the spinner
        this.renderer.setStyle(this.spinner._elementRef.nativeElement, 'display', 'inherit');
      }

      if (changes.showSpinner.currentValue === false) {
        // Hide the spinner
        this.renderer.setStyle(this.spinner._elementRef.nativeElement, 'display', 'none');

        // Remove the spinner
        this.renderer.removeChild(this.el.nativeElement.firstChild, this.spinner._elementRef.nativeElement);

        const span = this.el.nativeElement.querySelector('.mat-button-wrapper') as HTMLSpanElement;
        span.innerText = this.originalInnerText;
      }

      this.el.nativeElement.disabled = changes.showSpinner.currentValue;
    }
  }
}

使用指令:

<button [appShowSpinner]="this.isSaving" mat-raised-button>
  Submit
</button>

答案 1 :(得分:0)

我只是举了一个可行的示例(尽管由于依赖性错误,我无法在stackblitz中使用Material Spinner),但这应该可以指导您:

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

@Directive({
  selector: '[spinnerButton]'
})
export class SpinnerButtonDirective {
@Input('spinnerButton') isWaiting: boolean;
  originalInnerText: string;
  constructor( private el: ElementRef ) { }

  ngOnInit(){
    // Save the original button text so I can restore it when waiting ends
    this.originalInnerText = this.el.nativeElement.innerText;
  }

  ngOnChanges() {
    if (this.isWaiting) {
      this.el.nativeElement.innerText = 'waiting...';
    } else {
      if (this.el.nativeElement.innerText == 'waiting...') {
        this.el.nativeElement.innerText = this.originalInnerText;
      }
    }
    this.el.nativeElement.disabled = this.isWaiting;
  }
}

在您的html中,

<button (click)="getData()" [spinnerButton]="isWaiting">Get Data</button>

您唯一要做的就是在代码中将isWaiting设置为true或false(通常在getData()函数中)

演示:https://stackblitz.com/edit/angular-rb5vmu

答案 2 :(得分:0)

最好使用内容投影。您应该创建包装器组件,该组件将为加​​载程序(具有绝对覆盖的相对容器,其中包括微调器)实现逻辑,并将按钮投影到该组件内部。这是我的实现:

import {ChangeDetectionStrategy, Component, ContentChild, ElementRef, Input, OnInit, Renderer2, RendererStyleFlags2} from '@angular/core';
import {MatButton} from '@angular/material';

@Component({
  selector: 'app-loading-button',
  templateUrl: './loading-button.component.html',
  styleUrls: ['./loading-button.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class LoadingButtonComponent implements OnInit {
  public loaderDiameter: number;
  public loaderVisible: boolean;
  /* How many percents of projected button height should be diameter of loader */
  private readonly loaderDiameterInPercents: number = 70;

  @Input() set loading(isLoading: boolean) {
    this.loaderVisible = isLoading;
    this.setChildButtonStyles(isLoading);
  }

  /* Currently works only with projected MatButton, but can be changed to any button element */
  @ContentChild(MatButton, {read: ElementRef}) projectedButton: ElementRef<HTMLButtonElement>;

  constructor(private renderer: Renderer2) {
  }

  ngOnInit() {
    /* Content queries are set before the ngAfterContentInit callback is called. https://angular.io/api/core/ContentChild#description */
    this.loaderDiameter = this.getLoaderDiameter();
  }

  private setChildButtonStyles(isLoading: boolean) {
    if (isLoading) {
      /* set projected button color as transparent*/
      this.renderer.setStyle(this.projectedButton.nativeElement, 'color', 'transparent', RendererStyleFlags2.Important);
      /* disable projected button */
      this.projectedButton.nativeElement.disabled = true;
    } else {
      this.renderer.removeStyle(this.projectedButton.nativeElement, 'color', RendererStyleFlags2.Important);
      this.projectedButton.nativeElement.disabled = false;
    }
  }

  private getLoaderDiameter(): number {
    /* get 70% of child button height */
    const childButtonHeight: number = this.projectedButton.nativeElement.offsetHeight;
    const computedHeight: number = (childButtonHeight / 100) * this.loaderDiameterInPercents;
    return Math.floor(computedHeight);
  }
}

模板:

<div class="loader-container">
  <div *ngIf="loaderVisible" class="loader-overlay">
    <mat-spinner [strokeWidth]="3" [diameter]="loaderDiameter" color="accent"></mat-spinner>
  </div>
  <ng-content></ng-content>
</div>

样式:

.loader-container {
  position: relative;
}

.loader-overlay {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 1;
  display: flex;
  justify-content: center;
  align-items: center;
  background-color: rgba(255,255,255, 0.1);
}

使用方式:

<app-loading-button [loading]="loading$ | async">
        <button mat-raised-button color="primary">Save</button>
</app-loading-button>