我正在尝试编写指令以修改按钮以在服务器操作正在进行时显示微调按钮。
<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函数调用它吗?
答案 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()函数中)
答案 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>