【问题标题】:How do I achieve multi level component/template event handling in Angular 5?如何在 Angular 5 中实现多级组件/模板事件处理?
【发布时间】:2018-09-19 03:01:42
【问题描述】:

我正在开发一个 Angular 5 项目。我有一个包含一些 HTML 元素的模板(模板 A)。我将此模板嵌入到同级模板(模板 B)中,该模板在模板 A 上具有扩展功能。模板 B 具有按钮等。

模板 A

<input type="text"></input>
<input type="text"></input>
<input type="text"></input>

模板 B

 <html>
   <templateA></templateA>
   <button (click)="onSubmit()")
 </html>

现在我的实际组件(组件 A-Z)仅链接到模板 B 作为其模板 URL(可重用)。 每个组件都可以有自己的 onSubmit() 事件,该事件实际上是从模板 B 触发的,但每个组件都有自己的定义。 这很好用。

我想要的是在模板 A 中的一个元素中有一个 (onChange) 事件,但每个组件都将在 (组件 A-Z) 中有定义。

【问题讨论】:

  • 这太宽泛了,你在问如何构建你的应用程序。听起来您需要在组件的功能之间进行更清晰的分离并概括事件输出。但这不适合 SO。
  • 你能否在最后的声明中更详细一点> What I want is to have a (onChange) event in one of the elements in Template A but each component which will have definitions in (Component A-Z).
  • 基本上,我希望模板 A 与组件 A 进行通信。而不是典型的父子通信,这将是父子子通信
  • 广播之类的吧?
  • 是的,类似于广播。如果在模板 A 上触发点击,所有扩展模板 B 的组件都应该能够对事件做出反应

标签: angular typescript angular5 angular4-forms


【解决方案1】:

正如@bryan60 所提到的,这更多是架构问题,而不是特定于应用程序特定状态的问题。

在我看来,您需要想出一个通用的方法来使用事件在多个组件之间共享数据。

这将导致一个灵活的实现,然后可以在您的应用程序的其他任何地方使用。

我在我的 Angular 应用程序中使用了这样一个基于 observables 的事件服务,它允许组件触发事件,而其他组件可以监听这些事件 -

活动服务

import { Injectable } from "@angular/core";
import * as Rx from 'rxjs/Rx';
import { Observable, Subject } from "rxjs/Rx";
import { UUID } from "./uuid-generator";

export const GLOBAL_EVENTS = {
    Test_Event: "Test_Event";
}

/**
 * 
 * 
 * @class EventListener
 */
class EventListener {

    constructor(uuid: string, func: Function) {
        if (!uuid || !func) {
            console.error("required arguments not provided!");
            return;
        }
        this._uuid = uuid;
        this.func = func;
    }

    private _uuid: string;
    public get uuid(): string {
        return this._uuid;
    }

    public func: Function;
}

@Injectable()
export class EventsService {

    private eventsSubject: Subject<any>;
    private listeners: Map<string, Array<EventListener>>;

    public constructor() {
        //initialise listeners
        this.listeners = new Map();
        //initialise event subject
        this.eventsSubject = new Rx.Subject();
        //listen to the changes in subject
        Rx.Observable.from(this.eventsSubject)
            .subscribe(({ name, args }) => {
                if (this.listeners[name]) {
                    this.listeners[name].forEach((listener: EventListener) => {
                        listener.func(...args);
                    });
                }
            });
    }

    /**
     * Listens to the event and provides a unique key for the listener
     * @method on
     * @param {string} name name of the event to be listened to.
     * @param {Function} listener listener function with one array argument
     * @return {string} returns the key (UUID) for listener function which can be used to stop listening to the event.
     * @memberof EventsService
    */
    public on(name: string, listener: Function): string {
        this.listeners[name] = this.listeners[name] || [];
        let listenerEvent: EventListener = new EventListener(UUID.generate(), listener);
        this.listeners[name].push(listenerEvent);
        return listenerEvent.uuid;
    }

    /**
     * Stops listening to the event by using the unique key for the listener
     * @method off
     * @param {string} name name of the event to be broadcasted
     * @param {string} uuid name of the event listener specific uuid to be removed
     * @param {boolean} [removeAll=false] removes all event listeners attached to specified event name
     * @returns {void} 
     * @memberof EventsService
    */
    public off(name: string, uuid?: string, removeAll: boolean = false): void {
        if (!this.listeners[name] || !this.listeners[name].length) {
            return;
        }
        if (removeAll) {
            this.listeners[name] = [];
            return;
        }
        this.listeners[name] = this.listeners[name].filter((item: EventListener, index: number) => {
            return item.uuid !== uuid;
        });
    }

    /**
     * Broadcasts the event and passes data provided in args argument as event data
     * @method broadcast
     * @param {string} name name of the event to be broadcasted.
     * @param {any} args arguments to be sent with the event.
     * @return {void} return void
     * @memberof EventsService
    */
    public broadcast(name: string, ...args): void {
        this.eventsSubject.next({ name, args });
    }
}

UUID 生成器

export class UUID {
    public static generate(): string {
        return "your_hash";
    }
}

注册活动

import { Component, OnDestroy } from "@angular/core";
import { EventsService, GLOBAL_EVENTS } from "./events.service";

@Component({
    selector: "[app-listener-component]",
    templateUrl: "./app-listener-component.html",
    styleUrls: ["./app-listener-component.scss"]
})
export class ListenerComponent {

    private eventId: string;

    public constructor(private _EventsService: EventsService) {
        this.eventId = this._EventsService.on(GLOBAL_EVENTS.Test_Event, (response: any) => {
            console.log(response);            
        });
    }

    public ngOnDestroy(): void {
        this._EventsService.off(this.eventId);
    }
}

广播消息

import { EventsService, GLOBAL_EVENTS } from "./events.service";

export class BroadcatComponent
    public constructor(private _EventsService: EventsService) {
        let data: any = {
            message: "any"
        }
        this._EventsService.broadcast(GLOBAL_EVENTS.Test_Event, data);
    }
}

要记住的事情

确保在主应用模块中添加服务,以便它在整个应用程序中开始表现为单例服务。

每个事件订阅都会返回一个 UUID 字符串,该字符串可用于删除全局范围内的事件处理程序 (additional features are in code comments)。

使用ngDestroy 删除事件处理程序将确保未使用的事件不会保留在内存中

At last but not the least, I have modified the original code so you might get compilation errors in the sample code.

我希望这会有所帮助:)

【讨论】:

  • 谢谢,会试试这个。欣赏它:)
【解决方案2】:

创建一个服务a.service.ts 喜欢:

服务

@Injectable()
export class AService {
  private subject = new Subject<any>();
  constructor() { }
    sendEvent(eventName: string) {
        this.subject.next({ event: eventName });
    }

    getEvent(): Observable<any> {
        return this.subject.asObservable();
    }
}

此服务将在组件(A-Z)中使用。每当您想触发/接收事件时,请订阅组件中的事件:

组件

eventName: any;
subscription: Subscription;

constructor(private aService: AService) {
    this.subscription = this.aService.getEvent()
          .subscribe(event=> { 
               this.eventName = event;  // Do whatever you want
               if(this.eventName == 'elemAClicked') {
                  // I guess what you want is here
               }
          });
}

ngOnDestroy() {
    // unsubscribe to ensure no memory leaks
    this.subscription.unsubscribe();
}
// method that would be used in template just suppose onClickAElem
onClickAElem() {
   this.aService.sendEvent('elemAClicked');
}

【讨论】:

  • 谢谢,会试试这个。欣赏它:)
猜你喜欢
  • 2018-07-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-15
  • 2018-07-26
  • 2020-06-17
  • 2020-09-01
相关资源
最近更新 更多