NGXS的组件事件

开发人员通常将 @Output 装饰器与 EventEmitter 结合使用。 任何Angular开发人员都可以看到以下代码:

@Output() search = new EventEmitter<string>();

秘密是@Output可以修饰任何“可观察”的属性。 Angular编译器为Angular本身发出了必要的信息,说:“嘿,请订阅 search 类属性,并在可观察对象发出时随时调度 CustomEvent

假设我们是A团队的一员。 我们开发了使用NGXS的自定义元素,并且希望将此组件提供给团队B。团队B对NGXS一无所知,他们不能使用我们的API。 我们的元素只是一个黑盒,它只通过@Output公开数据。

我们开发了一个 app-email-list 自定义元素,该元素会发出messagesLoadedDOM事件,并将数据提供给团队B进行分析。 给出以下代码:

@Component({
  selector: 'app-email-list',
  template: `
    <app-message *ngFor="let message of messages$ | async" [message]="message"></app-message>
    <app-button (click)="refresh()">Refresh messages</app-button>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class EmailListComponent {
  @Select(MessagesState.getMessages) messages$: Observable<Message[]>;

  @Output() messagesLoaded = new EventEmitter<Message[]>();

  constructor(private store: Store) {}

  refresh(): void {
    this.store.dispatch(new LoadMessages()).subscribe(() => {
      const messages = this.store.selectSnapshot(MessagesState.getMessages);
      this.messagesLoaded.emit(messages);
    });
  }
}

上面的代码非常简单,仅用于演示! 如您所见,每当用户单击“刷新消息”按钮时,我们都会调度 LoadMessages 操作。 在 LoadMessages 动作处理程序完成其异步工作之后,我们发出 messagesLoaded 事件。 让我们更具声明性:

@Component({
  selector: 'app-email-list',
  template: `
    <app-message *ngFor="let message of messages$ | async" [message]="message"></app-message>
    <app-button>Refresh messages</app-button>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class EmailListComponent {
  @Select(MessagesState.getMessages) messages$: Observable<Message[]>;

  @ViewChild(ButtonComponent, { static: true }) button: ButtonComponent;

  @Output() messagesLoaded = this.button.click.pipe(
    switchMap(() => this.store.dispatch(new LoadMessages())),
    map(() => this.store.selectSnapshot(MessagesState.getMessages))
  );

  constructor(private store: Store) {}
}

假设 ButtonComponent.click 是一个 EventEmitter。我们以更具声明性和反应性的方式做到了。 因此,当用户单击 app-button 时,我们的 switchMap 将产生下一个 store.dispatch 订阅,并从上一个中取消。接下来,我们使用 map 运算符,它会将流的值从状态映射到Message[]数组。

现在,让我们与A和B团队一起消除这个想法。 由于我们的存储(store)是真相的唯一来源,因此我们可以侦听应用程序任何部分的任何操作。 DOM事件可以很方便地与Actions流一起使用。 假设我们有一个组件,每次加载不同类型的书籍时都会发出 booksLoaded 事件:

// books.state.ts
const enum Genre {
  Novel,
  Detective,
  Horror
}

export class LoadBooks {
  static readonly type = '[Books] Load books';
  constructor(public genre: Genre) {}
}

export class BooksState {
  static getBooks(genre: Genre) {
    return createSelector(
      [BooksState],
      (books: Book[]) => books.filter(book => book.genre === genre)
    );
  }
}

// books.component.ts
export class BooksComponent {
  @Output() booksLoaded = this.actions$.pipe(
    ofActionSuccessful(LoadBooks),
    map((action: LoadBooks) => this.store.selectSnapshot(BooksState.getBooks(action.genre)))
  );

  constructor(private store: Store, private actions$: Actions) {}
}

这可能会大大减少您的代码业务逻辑,并以更具声明性和反应性的方式来实现。

Last updated

Was this helpful?