如何使用 angular 元素定义自定义元素方法

How to define a custom element method with angular elements

我们的 Angular-Elements webcomponent 应该有两个方法 openModal()closeModal() 可以在 React 中用作自定义元素、Vue 或任何其他库来更改可见性状态。

这个简单的javascript用法应该是可行的:

<my-modal id="modal"></my-modal>
<button id="openModal" onclick="openModal()">open</button>
<button id="closeModal" onclick="closeModal()">close</button>

<script>
  const modal = document.getElementById("modal");

  function openModal() {
    modal.openModal();
  }

  function closeModal() {
    modal.closeModal();
  }
</script>

内部属性 visible 应设置为 truefalse 以便模板可以使用它。

@Component({
  selector: 'my-modal',
  template: `<p>Attribute visible: {{visible}}</p>`, 
  styles: []
})
export class MyModalComponent {

  // Without `@Input` changes to the variable within `openModal()` 
  // and `closeModal()` will not effect the components state!
  visible = false;

  // Without the `@Input()` here, angular-elements will not
  // map the `openModal()` to our custom-element.
  @Input()
  public openModal(): void {
    console.log("Open Modal")
    this.visible = true;
  }

  @Input()
  public closeModal(): void {
    console.log("Close Modal")
    this.visible = false;
  }
}

问题:

  1. 为什么我们需要装饰我们的 public 组件方法(openModal()closeModal()@Input()),即使我们没有将它们用作输入参数?
  2. 为什么我们需要用@Input装饰visible-Flag?这非常糟糕,因为我们暴露了内部状态,而且它看起来是一种有效的方法?

@Input()s 是为属性而不是函数设计的。对于您的用例,您可以在那里简单地使用 getter 和 return 函数。

至于visible状态没变:确实变了(变后用console.log查一下)。但是,angular 的更改检测未检测到更改,因为调用来自 angular 的上下文。例如,在使用 setTimeout 更改值时,您会遇到同样的问题。要解决此问题,请将 NgZone 注入您的组件,并使用 this.zone.run 让 angular 检测更改。

import { Component, Input, NgZone } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `<p>Attribute visible: {{visible}}</p>`, 
  styles: []
})
export class MyModalComponent {
  visible = false;

  // use property getters to return functions

  @Input()
  get openModal(): () => void {
    return () => {
     // make change in angular's context so changes are detected by angular
      this.zone.run(() => this.visible = true);
    }
  }

  @Input()
  get closeModal(): () => void {
    return () => {
      // make change in angular's context so changes are detected by angular
      this.zone.run(() => this.visible = false);
    }
  }

  // inject NgZone
  constructor(private zone: NgZone) { }
  
}

或者,您可能想尝试一下 OnPush 变化检测。您将使用 Subjects 和 Observables 以及一个“推送”管道(异步管道的变体),如此包中所实现的:https://github.com/rx-angular/rx-angular

// in app.module.ts
import {TemplateModule} from '@rx-angular/template';
@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    TemplateModule // import module
// ...

import { Component, Input, NgZone } from '@angular/core';
import { BehaviorSubject } from 'rxjs';

@Component({
  selector: 'app-root',
  // use observable with push pipe
  template: `<p>Attribute visible: {{visible$ | push}}</p>`,
  // use OnPush change detection
  changeDetection: ChangeDetectionStrategy.OnPush,
  styles: []
})
export class AppComponent {
  
  // use subjects/observables
  visibleSubject = new BehaviorSubject<boolean>(false);
  visible$ = this.visibleSubject.asObservable();
  
  @Input()
  get openModal(): () => void {
    return () => {
      this.visibleSubject.next(true);
    }
  }

  @Input()
  get closeModal(): () => void {
    return () => {
      this.visibleSubject.next(false);
    }
  }
}

当然,如果它像更改布尔标志一样简单,您也可以将 属性 公开为 @Input(),这样它就可以在主机应用程序中绑定。