Sự kiện toàn cầu ở góc


224

Không có tương đương $scope.emit()hoặc $scope.broadcast()trong Angular?

Tôi biết EventEmitterchức năng, nhưng theo như tôi hiểu thì sẽ chỉ phát ra một sự kiện cho phần tử HTML gốc.

Nếu tôi cần liên lạc giữa các fx. anh chị em hoặc giữa một thành phần trong thư mục gốc của DOM và một phần tử được lồng sâu vài cấp?


2
Tôi đã có một câu hỏi tương tự liên quan đến việc tạo một thành phần hộp thoại có thể được truy cập từ bất kỳ điểm nào trong dom: stackoverflow.com/questions/34572539/ , Về cơ bản, một giải pháp là đưa một trình phát sự kiện vào một dịch vụ
brando

1
Dưới đây là triển khai của tôi về một dịch vụ như vậy bằng cách sử dụng RXJS cho phép nhận giá trị cuối cùng thứ n khi đăng ký. stackoverflow.com/questions/46027693/
Mạnh

Câu trả lời:


385

Không có tương đương với $scope.emit()hoặc $scope.broadcast()từ AngularJS. EventEuctor bên trong một thành phần đến gần, nhưng như bạn đã đề cập, nó sẽ chỉ phát ra một sự kiện cho thành phần cha mẹ ngay lập tức.

Trong Angular, có những lựa chọn thay thế khác mà tôi sẽ cố gắng giải thích bên dưới.

Các ràng buộc @Input () cho phép mô hình ứng dụng được kết nối trong biểu đồ đối tượng được định hướng (từ gốc đến lá). Hành vi mặc định của chiến lược phát hiện thay đổi của một thành phần là tuyên truyền tất cả các thay đổi cho một mô hình ứng dụng cho tất cả các ràng buộc từ bất kỳ thành phần nào được kết nối.

Ngoài ra: Có hai loại mô hình: Xem mô hình và Mô hình ứng dụng. Một mô hình ứng dụng được kết nối thông qua các ràng buộc @Input (). Mô hình khung nhìn chỉ là một thuộc tính thành phần (không được trang trí bằng @Input ()) được ràng buộc trong mẫu của thành phần.

Để trả lời câu hỏi của bạn:

Nếu tôi cần liên lạc giữa các thành phần anh chị em thì sao?

  1. Mô hình ứng dụng được chia sẻ : Anh chị em có thể giao tiếp thông qua mô hình ứng dụng được chia sẻ (giống như góc 1). Ví dụ: khi một anh chị em thực hiện thay đổi cho một mô hình, anh chị em khác có các ràng buộc với cùng một mô hình sẽ được cập nhật tự động.

  2. Sự kiện thành phần : Các thành phần con có thể phát ra một sự kiện đến thành phần cha mẹ bằng cách sử dụng các ràng buộc @Output (). Thành phần cha mẹ có thể xử lý sự kiện và thao tác mô hình ứng dụng hoặc mô hình khung nhìn của chính nó. Các thay đổi đối với Mô hình ứng dụng được tự động truyền tới tất cả các thành phần liên kết trực tiếp hoặc gián tiếp với cùng một mô hình.

  3. Sự kiện dịch vụ : Các thành phần có thể đăng ký các sự kiện dịch vụ. Ví dụ: hai thành phần anh chị em có thể đăng ký vào cùng một sự kiện dịch vụ và phản hồi bằng cách sửa đổi các mô hình tương ứng của chúng. Thêm về điều này dưới đây.

Làm thế nào tôi có thể giao tiếp giữa một thành phần Root và một thành phần lồng nhau sâu vài cấp?

  1. Mô hình ứng dụng được chia sẻ : Mô hình ứng dụng có thể được chuyển từ thành phần Root xuống các thành phần phụ được lồng sâu thông qua các ràng buộc @Input (). Thay đổi đối với một mô hình từ bất kỳ thành phần nào sẽ tự động truyền tới tất cả các thành phần có chung mô hình.
  2. Sự kiện dịch vụ : Bạn cũng có thể di chuyển EventEuctor sang dịch vụ chia sẻ, cho phép mọi thành phần tiêm dịch vụ và đăng ký sự kiện. Theo cách đó, một thành phần Root có thể gọi một phương thức dịch vụ (thường làm biến đổi mô hình), từ đó phát ra một sự kiện. Một số lớp xuống, một thành phần lớn cũng đã tiêm dịch vụ và đăng ký vào cùng một sự kiện, có thể xử lý nó. Bất kỳ trình xử lý sự kiện nào thay đổi Mô hình ứng dụng được chia sẻ, sẽ tự động truyền tới tất cả các thành phần phụ thuộc vào nó. Đây có lẽ là tương đương gần nhất với $scope.broadcast()từ Angular 1. Phần tiếp theo mô tả ý tưởng này chi tiết hơn.

Ví dụ về Dịch vụ quan sát được sử dụng Sự kiện dịch vụ để tuyên truyền các thay đổi

Dưới đây là một ví dụ về một dịch vụ quan sát được sử dụng các sự kiện dịch vụ để tuyên truyền các thay đổi. Khi một TodoItem được thêm vào, dịch vụ sẽ phát ra một sự kiện thông báo cho các thuê bao thành phần của nó.

export class TodoItem {
    constructor(public name: string, public done: boolean) {
    }
}
export class TodoService {
    public itemAdded$: EventEmitter<TodoItem>;
    private todoList: TodoItem[] = [];

    constructor() {
        this.itemAdded$ = new EventEmitter();
    }

    public list(): TodoItem[] {
        return this.todoList;
    }

    public add(item: TodoItem): void {
        this.todoList.push(item);
        this.itemAdded$.emit(item);
    }
}

Đây là cách một thành phần gốc sẽ đăng ký sự kiện:

export class RootComponent {
    private addedItem: TodoItem;
    constructor(todoService: TodoService) {
        todoService.itemAdded$.subscribe(item => this.onItemAdded(item));
    }

    private onItemAdded(item: TodoItem): void {
        // do something with added item
        this.addedItem = item;
    }
}

Một thành phần con được lồng sâu vài cấp sẽ đăng ký vào sự kiện theo cùng một cách:

export class GrandChildComponent {
    private addedItem: TodoItem;
    constructor(todoService: TodoService) {
        todoService.itemAdded$.subscribe(item => this.onItemAdded(item));
    }

    private onItemAdded(item: TodoItem): void {
        // do something with added item
        this.addedItem = item;
    }
}

Đây là thành phần gọi dịch vụ để kích hoạt sự kiện (nó có thể nằm ở bất kỳ đâu trong cây thành phần):

@Component({
    selector: 'todo-list',
    template: `
         <ul>
            <li *ngFor="#item of model"> {{ item.name }}
            </li>
         </ul>
        <br />
        Add Item <input type="text" #txt /> <button (click)="add(txt.value); txt.value='';">Add</button>
    `
})
export class TriggeringComponent{
    private model: TodoItem[];

    constructor(private todoService: TodoService) {
        this.model = todoService.list();
    }

    add(value: string) {
        this.todoService.add(new TodoItem(value, false));
    }
}

Tham khảo: Phát hiện thay đổi trong góc


27
Bây giờ tôi đã thấy dấu $ trong một vài bài đăng cho một Người theo dõi hoặc Sự kiện có thể quan sát - ví dụ : itemAdded$. Đó có phải là một quy ước RxJS hay cái gì đó không? Trường hợp nào này đến từ đâu?
Mark Rajcok

1
Câu trả lời tốt đẹp. Bạn đã nói, "Các thay đổi đối với Mô hình ứng dụng được tự động truyền tới tất cả các thành phần liên kết trực tiếp hoặc gián tiếp với cùng một mô hình." Tôi có linh cảm rằng nó không hoạt động theo cách này (nhưng tôi không chắc chắn). Bài đăng trên blog khác của Savkin đưa ra ví dụ về thành phần thay đổi thuộc streettính của mô hình ứng dụng, nhưng vì Angular 2 thực hiện phát hiện thay đổi theo danh tính / tham chiếu, nên không có thay đổi nào được truyền bá ( onChangesvì không được gọi), vì tham chiếu mô hình ứng dụng đã không thay đổi ( tiếp ...)
Mark Rajcok

10
Bạn có thể muốn cập nhật câu trả lời của mình để sử dụng Đài quan sát thay vì Trình phát sự kiện trong dịch vụ. Xem stackoverflow.com/a/35568924/215945stackoverflow.com/questions/36076700
Mark Rajcok

2
Đúng, $ hậu tố là một quy ước RxJS được phổ biến bởi Cycle.js. cycl.js.org/,
jody tate

4
Bạn không nên đăng ký thủ công vào một sự kiện. Nó có thể không phải là một quan sát trong bản phát hành cuối cùng! Xem điều này: bennadel.com/blog/ Kẻ
NetProvoke

49

Đoạn mã sau đây là một ví dụ về sự thay thế cho $ scope.emit () hoặc $ scope.broadcast () trong Angular 2 bằng cách sử dụng dịch vụ chia sẻ để xử lý các sự kiện.

import {Injectable} from 'angular2/core';
import * as Rx from 'rxjs/Rx';

@Injectable()
export class EventsService {
    constructor() {
        this.listeners = {};
        this.eventsSubject = new Rx.Subject();

        this.events = Rx.Observable.from(this.eventsSubject);

        this.events.subscribe(
            ({name, args}) => {
                if (this.listeners[name]) {
                    for (let listener of this.listeners[name]) {
                        listener(...args);
                    }
                }
            });
    }

    on(name, listener) {
        if (!this.listeners[name]) {
            this.listeners[name] = [];
        }

        this.listeners[name].push(listener);
    }

    off(name, listener) {
        this.listeners[name] = this.listeners[name].filter(x => x != listener);
    }

    broadcast(name, ...args) {
        this.eventsSubject.next({
            name,
            args
        });
    }
}

Ví dụ sử dụng:

Phát sóng:

function handleHttpError(error) {
    this.eventsService.broadcast('http-error', error);
    return ( Rx.Observable.throw(error) );
}

Thính giả:

import {Inject, Injectable} from "angular2/core";
import {EventsService}      from './events.service';

@Injectable()
export class HttpErrorHandler {
    constructor(eventsService) {
        this.eventsService = eventsService;
    }

    static get parameters() {
        return [new Inject(EventsService)];
    }

    init() {
        this.eventsService.on('http-error', function(error) {
            console.group("HttpErrorHandler");
            console.log(error.status, "status code detected.");
            console.dir(error);
            console.groupEnd();
        });
    }
}

Nó có thể hỗ trợ nhiều đối số:

this.eventsService.broadcast('something', "Am I a?", "Should be b", "C?");

this.eventsService.on('something', function (a, b, c) {
   console.log(a, b, c);
});

Cái này làm gì tham số get tĩnh () {return [new Meth (EventsService)]; }
Beanwah

Trong ví dụ này tôi đang sử dụng Ionic 2 Framework. Phương thức tham số tĩnh được gọi khi phương thức constructor được gọi và được sử dụng để cung cấp các phụ thuộc cho hàm tạo. Giải thích tại đây stackoverflow.com/questions/35919593/ từ
jim.taylor.1974

1
Làm rất tốt Đơn giản và cung cấp một hệ thống thông báo dễ dàng thích ứng cho toàn bộ ứng dụng, không chỉ một lần.
Mike M

Tôi chỉ tạo ra dịch vụ tương tự với sự hỗ trợ ký tự đại diện. Hy vọng nó giúp. github.com/govorov/ng-radio
Stanislav E. Govorov

2
Tuyệt vời, đã sử dụng nó nhưng đã thêm chức năng tắt nếu còn quan tâm: off(name, listener) { this.listeners[name] = this.listeners[name].filter(x => x != listener); }
LVDM

16

Tôi đang sử dụng dịch vụ tin nhắn bao bọc rxjs Subject( TypeScript )

Ví dụ về Plunker: Dịch vụ tin nhắn

import { Injectable } from '@angular/core';
import { Subject } from 'rxjs/Subject';
import { Subscription } from 'rxjs/Subscription';
import 'rxjs/add/operator/filter'
import 'rxjs/add/operator/map'

interface Message {
  type: string;
  payload: any;
}

type MessageCallback = (payload: any) => void;

@Injectable()
export class MessageService {
  private handler = new Subject<Message>();

  broadcast(type: string, payload: any) {
    this.handler.next({ type, payload });
  }

  subscribe(type: string, callback: MessageCallback): Subscription {
    return this.handler
      .filter(message => message.type === type)
      .map(message => message.payload)
      .subscribe(callback);
  }
}

Các thành phần có thể đăng ký và phát sóng các sự kiện (người gửi):

import { Component, OnDestroy } from '@angular/core'
import { MessageService } from './message.service'
import { Subscription } from 'rxjs/Subscription'

@Component({
  selector: 'sender',
  template: ...
})
export class SenderComponent implements OnDestroy {
  private subscription: Subscription;
  private messages = [];
  private messageNum = 0;
  private name = 'sender'

  constructor(private messageService: MessageService) {
    this.subscription = messageService.subscribe(this.name, (payload) => {
      this.messages.push(payload);
    });
  }

  send() {
    let payload = {
      text: `Message ${++this.messageNum}`,
      respondEvent: this.name
    }
    this.messageService.broadcast('receiver', payload);
  }

  clear() {
    this.messages = [];
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}

(người nhận)

import { Component, OnDestroy } from '@angular/core'
import { MessageService } from './message.service'
import { Subscription } from 'rxjs/Subscription'

@Component({
  selector: 'receiver',
  template: ...
})
export class ReceiverComponent implements OnDestroy {
  private subscription: Subscription;
  private messages = [];

  constructor(private messageService: MessageService) {
    this.subscription = messageService.subscribe('receiver', (payload) => {
      this.messages.push(payload);
    });
  }

  send(message: {text: string, respondEvent: string}) {
    this.messageService.broadcast(message.respondEvent, message.text);
  }

  clear() {
    this.messages = [];
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}

Các subscribephương pháp MessageServicelợi nhuận một rxjs Subscriptionđối tượng, có thể được hủy đăng ký khỏi như vậy:

import { Subscription } from 'rxjs/Subscription';
...
export class SomeListener {
  subscription: Subscription;

  constructor(private messageService: MessageService) {
    this.subscription = messageService.subscribe('someMessage', (payload) => {
      console.log(payload);
      this.subscription.unsubscribe();
    });
  }
}

Cũng xem câu trả lời này: https://stackoverflow.com/a/36782616/1861779

Ví dụ về Plunker: Dịch vụ tin nhắn


2
rất có giá trị Cảm ơn câu trả lời. Tôi vừa phát hiện ra rằng bạn không thể giao tiếp với hai thành phần trong hai mô-đun khác nhau bằng cách này. Để đạt được mục tiêu, tôi đã phải đăng ký MessageService ở cấp app.module bằng cách thêm các nhà cung cấp ở đó. Bất kỳ cách nào đây là một cách thực sự mát mẻ.
Rukshan Dangalla

Đây là tất cả mong muốn lỗi thời. đặc biệt là plunker không tải bất kỳ tài nguyên nào thành công. tất cả đều có 500 mã lỗi.
tatsu

Tôi nhận đượcProperty 'filter' does not exist on type 'Subject<EventMessage>'.
vẽ

@Drew, trên các phiên bản mới hơn của RxJS sử dụng this.handler.pipe(filter(...)). Xem các nhà khai thác cho vay .
t.888

1
@ t.888 cảm ơn, tôi đã tìm ra rồi. Chức năng đăng ký được cập nhật trông giống nhưreturn this.handler.pipe( filter(message => message.type === type), map(message => message.payload) ).subscribe(callback);
vẽ

12

KHÔNG sử dụng EventEuctor cho giao tiếp dịch vụ của bạn.

Bạn nên sử dụng một trong những loại có thể quan sát được. Cá nhân tôi thích BehaviorSubject.

Ví dụ đơn giản:

Bạn có thể vượt qua trạng thái ban đầu, ở đây tôi chuyển null

let topic = new BehaviorSubject (null);

Khi bạn muốn cập nhật chủ đề

chủ đề.next (myObject)

Quan sát từ bất kỳ dịch vụ hoặc thành phần nào và hành động khi nhận được cập nhật mới.

chủ đề.subscribe (this.YOURMETHOD);

Đây là nhiều thông tin hơn. .


1
bạn có thể giải thích những lý do cho quyết định thiết kế này?
mtraut

@mtraut liên kết đó cũng có một lời giải thích toàn diện.
Danial Kalbasi

để được giải thích chi tiết hơn về cách sử dụng BehaviourSubject, vui lòng đọc bài viết này blog.cloudboost.io/iêu
rafalkasa

Eaxctly những gì tôi cần. Đẹp và đơn giản :)
Thấp


2

Cách ưa thích của tôi là sử dụng chủ đề hành vi hoặc trình phát sự kiện (gần như giống nhau) trong dịch vụ của tôi để kiểm soát tất cả thành phần phụ của tôi.

Sử dụng cli góc, chạy ng gs để tạo một dịch vụ mới, sau đó sử dụng BehaviorSubject hoặc EventEuctor

export Class myService {
#all the stuff that must exist

myString: string[] = [];
contactChange : BehaviorSubject<string[]> = new BehaviorSubject(this.myString);

   getContacts(newContacts) {
     // get your data from a webservices & when you done simply next the value 
    this.contactChange.next(newContacts);
   }
}

Khi bạn làm điều đó, mọi thành phần sử dụng dịch vụ của bạn với tư cách là nhà cung cấp sẽ nhận thức được sự thay đổi. Chỉ cần đăng ký kết quả như bạn làm với eventEuctor;)

export Class myComp {
#all the stuff that exists like @Component + constructor using (private myService: myService)

this.myService.contactChange.subscribe((contacts) => {
     this.contactList += contacts; //run everytime next is called
  }
}

1

Tôi đã tạo một mẫu pub-sub ở đây:

http://www.syntaxsuccess.com/viewarticle/pub-sub-in-angular-2.0

Ý tưởng là sử dụng các Đối tượng RxJ để kết nối một Người quan sát và và Đài quan sát như một giải pháp chung để phát ra và đăng ký các sự kiện tùy chỉnh. Trong mẫu của tôi, tôi sử dụng một đối tượng khách hàng cho mục đích demo

this.pubSubService.Stream.emit(customer);

this.pubSubService.Stream.subscribe(customer => this.processCustomer(customer));

Đây cũng là bản demo trực tiếp: http://www.syntaxsuccess.com/angular-2-samples/#/demo/pub-sub


1

Đây là phiên bản của tôi:

export interface IEventListenr extends OnDestroy{
    ngOnDestroy(): void
}

@Injectable()
export class EventManagerService {


    private listeners = {};
    private subject = new EventEmitter();
    private eventObserver = this.subject.asObservable();


    constructor() {

        this.eventObserver.subscribe(({name,args})=>{



             if(this.listeners[name])
             {
                 for(let listener of this.listeners[name])
                 {
                     listener.callback(args);
                 }
             }
        })

    }

    public registerEvent(eventName:string,eventListener:IEventListenr,callback:any)
    {

        if(!this.listeners[eventName])
             this.listeners[eventName] = [];

         let eventExist = false;
         for(let listener of this.listeners[eventName])
         {

             if(listener.eventListener.constructor.name==eventListener.constructor.name)
             {
                 eventExist = true;
                 break;
             }
         }

        if(!eventExist)
        {
             this.listeners[eventName].push({eventListener,callback});
        }
    }

    public unregisterEvent(eventName:string,eventListener:IEventListenr)
    {

        if(this.listeners[eventName])
        {
            for(let i = 0; i<this.listeners[eventName].length;i++)
            {

                if(this.listeners[eventName][i].eventListener.constructor.name==eventListener.constructor.name)
                {
                    this.listeners[eventName].splice(i, 1);
                    break;
                }
            }
        }


    }


    emit(name:string,...args:any[])
    {
        this.subject.next({name,args});
    }
}

sử dụng:

export class <YOURCOMPONENT> implements IEventListener{

  constructor(private eventManager: EventManagerService) {


    this.eventManager.registerEvent('EVENT_NAME',this,(args:any)=>{
       ....
    })


  }

  ngOnDestroy(): void {
    this.eventManager.unregisterEvent('closeModal',this)
  }

}

phát ra:

 this.eventManager.emit("EVENT_NAME");

0

Chúng tôi đã triển khai một lệnh ngModelChange có thể quan sát được, gửi tất cả các thay đổi mô hình thông qua một trình phát sự kiện mà bạn khởi tạo trong thành phần của riêng bạn. Bạn chỉ cần ràng buộc trình phát sự kiện của bạn với chỉ thị.

Xem: https://github.com/atomicbits/angular2-modelchangeobservable

Trong html, liên kết trình phát sự kiện của bạn (countryChanged trong ví dụ này):

<input [(ngModel)]="country.name"
       [modelChangeObservable]="countryChanged" 
       placeholder="Country"
       name="country" id="country"></input>

Trong thành phần bản thảo của bạn, hãy thực hiện một số thao tác không đồng bộ trên EventEuctor:

import ...
import {ModelChangeObservable} from './model-change-observable.directive'


@Component({
    selector: 'my-component',
    directives: [ModelChangeObservable],
    providers: [],
    templateUrl: 'my-component.html'
})

export class MyComponent {

    @Input()
    country: Country

    selectedCountries:Country[]
    countries:Country[] = <Country[]>[]
    countryChanged:EventEmitter<string> = new EventEmitter<string>()


    constructor() {

        this.countryChanged
            .filter((text:string) => text.length > 2)
            .debounceTime(300)
            .subscribe((countryName:string) => {
                let query = new RegExp(countryName, 'ig')
                this.selectedCountries = this.countries.filter((country:Country) => {
                    return query.test(country.name)
                })
            })
    }
}

0

Sự kiện dịch vụ: Các thành phần có thể đăng ký các sự kiện dịch vụ. Ví dụ: hai thành phần anh chị em có thể đăng ký vào cùng một sự kiện dịch vụ và phản hồi bằng cách sửa đổi các mô hình tương ứng của chúng. Thêm về điều này dưới đây.

Nhưng hãy chắc chắn hủy đăng ký thành phần đó khi hủy thành phần cha.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.