【问题标题】:Angular how to get button element on form submitAngular如何在表单提交上获取按钮元素
【发布时间】:2019-09-18 09:29:47
【问题描述】:

我有一个页面,上面有多个表单。每个表单上都有许多按钮。我想在按下按钮后在按钮上实现加载微调器。当我使用正常的点击事件时,我可以传入按钮:

HTML

<button #cancelButton class="button-with-icon" type="button" *ngIf="hasCancel" mat-raised-button color="warn" [disabled]="isLoading" (click)="clickEvent(cancelButton)">
    <mat-spinner *ngIf="isLoading" style="display: inline-block;" diameter="24"></mat-spinner>
    Cancel
</button>

TS

clickEvent(button: MatButton) {
    console.log(button);
}

在这种情况下,按钮元素被传递,您可以访问它以向按钮添加加载类。

但是,如果您使用提交按钮尝试相同的操作,它会显示为undefined

HTML

<form (ngSubmit)="save(saveButton)">
    <button #saveButton class="button-with-icon" type="submit" *ngIf="hasSave" mat-raised-button color="primary" [disabled]="isLoading">
        <mat-spinner *ngIf="isLoading" style="display: inline-block;" diameter="24"></mat-spinner>
        Save
    </button>
</form>

TS

save(button: MatButton) {
    console.log(button);
}

在这种情况下,按钮是undefined,因为按钮上的*ngIf 阻止了它进入表单的范围。我可以删除 *ngIf 并隐藏按钮,但这会将按钮留在 DOM 上,我不想这样做。

这里是堆栈闪电战:https://stackblitz.com/edit/angular-zh7jcw-mrqaok?file=app%2Fform-field-overview-example.html

我尝试向保存按钮添加一个额外的点击事件以将按钮设置为加载,但点击事件首先触发并将按钮设置为禁用,然后阻止调用提交事件。

我查看了提交事件,但看不到任何链接回单击按钮的内容。

有什么建议吗?

【问题讨论】:

  • 首先...为什么需要像那样操作 dom(添加加载),有什么特殊原因吗?
  • 保存和取消函数将异步调用到 Web 服务器。我想在处理异步调用时禁用按钮并在按钮上显示一个旋转图标。我正在制作一个动态表单组件。每个表单上可以有多个表单和多个按钮。因此,通过固定变量跟踪哪个按钮正在加载似乎比仅向按钮的 dom 中添加 loading 类效率低。
  • 好的,我明白了。有些人只是在简单的布尔标志就足够的情况下这样做,但我在这种情况下很好地理解了你的理由:)
  • @Curtis 我不确定您的效率问题是什么。关键是 Angular 通过将状态存储在组件而不是 DOM 中来做到这一点。它的代码更少,但性能却不低。

标签: javascript angular


【解决方案1】:

tl;dr这是我的实际解决方案:https://stackblitz.com/edit/angular-zh7jcw-ythpdb


编辑:正如我刚刚注意到的,@AJT_82 很好地解释了 OP 的原始问题与参考元素。所以我现在删除了我之前的答案。


但是我会建议以下解决方案,它更适合关注点分离以角度方式做事,即引入两个新成员@ 987654325@和submitActive,您将能够更灵活地处理表单的不同状态。

您可以在这里看到它的实际效果:https://stackblitz.com/edit/angular-zh7jcw-ythpdb

export class FormFieldOverviewExample {
  cancelActive = false;
  submitActive = false;

  // this is for submit event
  saveEvent(event: Event) {
    // additional logic can be added
    // if(!this.cancelActive) return;

    this.submitActive = true;
    this.cancelActive = false;
  }

  // this is for cancel event
  cancelEvent(event: Event) {
    // additional logic can be added
    // if(!this.submitActive) return;

    this.submitActive = true;
    this.cancelActive = false;
  }

}
<form (ngSubmit)="saveEvent($event)">
  <button  class="button-with-icon"  type="button"  *ngIf="hasCancel"  mat-raised-button color="warn"  [disabled]="cancelActive"  (click)="cancelEvent($event)">
    <mat-spinner *ngIf="cancelActive" class="spinner" diameter="15"></mat-spinner>
    Cancel
  </button>
  <br><br>
  <button class="button-with-icon" type="submit" *ngIf="hasSave" mat-raised-button color="primary" [disabled]="submitActive">
    <mat-spinner *ngIf="submitActive" class="spinner" diameter="15"></mat-spinner>
    Save
  </button>
</form>

这样你也会有更多的游乐场,例如:

<button type="submit" [disabled]="submitActive || cancelActive">...</button>

如果您出于某种原因想要这样做,您可以简单地禁用 submit 按钮而不显示加载动画...

此外,假设您的表单中有一个&lt;input type="text" /&gt; 元素。用户切换到该元素(通过单击或按 Tab 键),完成编辑后按 Enter 键,在这种情况下,浏览器也会自动提交表单;所以这会使你现有的代码结构变得更加复杂。

【讨论】:

  • 正如@AJT_82 所提到的,元素引用的这个范围仅出现在结构指令中。您应该更新答案以包含该信息。这是一个示例,如果它不包含*ngIf,您可以获得兄弟姐妹的参考我还没有看到这个文档......你有链接吗?这是一个示例,显示添加 *ngIf 使元素引用在该节点之外不可用:。 stackblitz.com/edit/angular-zh7jcw-u3eakq
  • 这需要每个按钮的固定变量。不是我所追求的。虽然我的演示确实有固定变量:hasCancelhasSave。实际上,我的代码中没有这些变量。该组件将动态生成 API 告诉它的按钮和表单。
  • @Curtis 感谢您提供信息。由于您提到的此规范是您的问题的一部分并且似乎很重要,因此我会将其包含在问题描述中,以便其他人知道它。不过,我很高兴你解决了你的问题。
  • 嘿@JuanMendes,感谢您的提示!老实说,我昨天很懒惰,没有阅读任何这些答案。相反,我写了我自己的。无论如何,我只是相应地更新了我的答案。对于您的问题:我没有链接,我只是根据我的经验知道它。 :)
  • @JuanMendes 这可能更容易阅读,但它有开销并且不灵活。
【解决方案2】:

不要在表单上使用ngSubmit 事件处理程序,而是在保存按钮上使用click 事件处理程序。将类型保留为submit,这样当用户在输入控件上使用Enter 键提交表单时它仍然有效。

<form #formElement="ngForm">
    ...
    <button #saveButton (click)="save(saveButton, formElement)" type="submit" ...>
        <mat-spinner ...></mat-spinner>
        Save
    </button>
</form>

您还可以通过使用对象来跟踪哪个按钮处于加载状态,从而避免将loading 类添加到DOM。工作示例here。这也允许您拥有许多按钮,而不必为每个按钮创建一个固定变量。

class FormFieldOverviewExample {
  
  hasCancel = true;
  hasSave = true;
  isLoading = {};

  saveEvent(event, buttonName, form) {
    console.log(buttonName);
    if(form.valid) {
      this.isLoading[buttonName]=true;
      // ...
    } else {
      this.isLoading[buttonName]=false;
      console.warn("Form not submitted because it contains errors");
    }
  }

  clickEvent(event, buttonName) {
    console.log(buttonName);
    this.isLoading[buttonName]=true;
    // ...
  }
}
form { display: none; }
<form #formElement="ngForm">

  ...

  <button (click)="clickEvent($event, 'cancelButton')" [disabled]="isLoading['cancelButton']" *ngIf="hasCancel" type="button" ...>
    <mat-spinner *ngIf="isLoading['cancelButton']" ...></mat-spinner>
    Cancel
  </button> 
  
  <br><br>
  
  <button (click)="saveEvent($event, 'saveButton', formElement)"  [disabled]="isLoading['saveButton']" *ngIf="hasSave" type="submit" ...>
    <mat-spinner *ngIf="isLoading['saveButton']" ...></mat-spinner>
    Save
  </button>
</form>

【讨论】:

  • OP 的方法有什么问题?在不了解问题的情况下提出解决方法是有问题的。
  • 另外,如果用户使用 enter 提交表单,这将不起作用...我知道,这里不是这种情况,但您应该始终妥善处理提交
  • 我之前试过这个,我好像记得提交有问题。但我又试了一次......我似乎找不到任何问题,所以也许上次我只是有错字或错误或其他什么。 stackblitz.com/edit/angular-zh7jcw-mwklob?file=app/… .. 也可以通过 Enter 按钮提交表单...不回答问题,但它看起来可以解决我的问题。谢谢。
【解决方案3】:

这实际上与提交函数无关,而是与角度和结构指令有关。当您应用结构指令(ngForngIf...)时,Angular 的作用是创建一个 ng-template 节点。这意味着,在结构指令范围内定义的模板引用变量在该节点中可用。

那么你在这里拥有什么:

<button #saveButton *ngIf="hasSave">
  Save
</button>

表示saveButton 模板引用仅在为按钮创建的ng-template 内部可用,因此在提交函数中不可用。

我们在这里拥有什么:

<button #cancelButton *ngIf="hasCancel" (click)="clickEvent(cancelButton)">
  Cancel
</button>

是点击事件在按钮上,所以cancelButton模板引用变量可用于该事件。

使用hidden 而不是ngIf 就像其他答案中建议的那样,解决了这个问题,因为我们只是......好吧,隐藏元素。

【讨论】:

  • * 仅在该节点和子节点中可用,但在父节点中不可用
  • 我还没有看到这个文档...你有链接吗?这是一个示例,显示添加 *ngIf 使元素引用在该节点之外不可用:。 stackblitz.com/edit/angular-zh7jcw-u3eakq
  • 很抱歉在我的原始帖子中没有说清楚,但我确实知道这项工作。我只是不想将元素隐藏在 DOM 上,我一直在寻找*ngIf 仍然存在的解决方案。
  • @Curtis 我解释了为什么会这样,您需要使用 ngIf 做一些另一种解决方法,例如使用 hidden,但是当您的代码位于您的问题中时......结构指令就可以了这个,你无法控制它。但很高兴您找到了适合您的解决方案:)
  • @JuanMendes 是的,这就是角度和结构指令的工作方式。文档出奇地难找,当我寻找它时……虽然我发现了一篇文章:weblogs.thinktecture.com/thomas/2017/05/…
【解决方案4】:

更新我现在了解您的具体问题。问题是按钮上的*ngIf。它打破了参考。将其更改为[hidden],它应该可以工作。这是更新的 StackBlitz:https://stackblitz.com/edit/angular-zh7jcw-pcuuyv

至于为什么会发生,我认为这很好地描述了这个问题:https://stackoverflow.com/a/36651625/64279

原答案:

我什至不会把它传进去。只需在你的课堂上添加这个:

 @ViewChild('saveButton') button;

然后你可以在你的保存方法中引用它:

save() {
    console.log(this.saveButton);
}

但我还要补充一点,从插值调用函数不是一个好主意,因为它会导致性能问题。相反,您应该订阅一个设置属性的事件,然后在您的视图中引用该属性。这样,该函数仅在事件触发时调用,而不是每次渲染页面时调用。

【讨论】:

  • 这个问题是组件上可能有许多保存按钮。因为页面上可以有多个表单。即使我使用了 ViewChildren,我仍然需要知道提交了哪个表单才能确定单击了哪个保存按钮。
  • 又一个“尝试这种其他方法,即使您当前的方法应该有效”。
  • @Curtis,查看我的更新。我在 StackBlitz 上对它进行了测试,它可以工作。
  • @JuanMendes,我迫不及待想看看你的贡献。
  • @adam0101 在更新中很好的调查
【解决方案5】:

对于您的情况,您不应将元素传递给函数,然后在其上操作类,而应简单地维护一个用于加载状态的变量。在您的组件中,将其初始化为

loading: boolean = false;

并在您的模板中,将此类绑定为

[class.loading]="loading"

在您的方法中,首先将此属性设置为 true,然后在异步调用完成后设置为 false。同样使用相同的变量将禁用属性设置为

[disabled]="loading"

【讨论】:

  • 我正在制作一个动态组件,它可以有多个表单和每个表单上的多个按钮。因此,通过固定变量跟踪哪个按钮正在加载似乎比仅仅向按钮的 dom 添加加载类效率低。如果我这样做,我需要制作一个加载变量矩阵,并且必须继续遍历该矩阵以检查每个按钮的状态。
猜你喜欢
  • 2017-08-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-21
  • 2023-04-10
相关资源
最近更新 更多