डेलिगेशन: एंगुलर या ऑब्जर्वेबल इन एंगुलर


244

मैं कोणीय में एक प्रतिनिधिमंडल पैटर्न की तरह कुछ लागू करने की कोशिश कर रहा हूं। जब उपयोगकर्ता एक पर क्लिक करता है nav-item, तो मैं एक फ़ंक्शन को कॉल करना चाहूंगा जो तब एक घटना का उत्सर्जन करता है जिसे बदले में किसी अन्य घटक द्वारा घटना के लिए सुनना चाहिए।

यहाँ परिदृश्य है: मेरे पास एक Navigationघटक है:

import {Component, Output, EventEmitter} from 'angular2/core';

@Component({
    // other properties left out for brevity
    events : ['navchange'], 
    template:`
      <div class="nav-item" (click)="selectedNavItem(1)"></div>
    `
})

export class Navigation {

    @Output() navchange: EventEmitter<number> = new EventEmitter();

    selectedNavItem(item: number) {
        console.log('selected nav item ' + item);
        this.navchange.emit(item)
    }

}

यहाँ अवलोकन घटक है:

export class ObservingComponent {

  // How do I observe the event ? 
  // <----------Observe/Register Event ?-------->

  public selectedNavItem(item: number) {
    console.log('item index changed!');
  }

}

महत्वपूर्ण प्रश्न यह है कि मैं अवलोकन करने वाले घटक को प्रश्न में घटना का अवलोकन कैसे करूँ?


जवाबों:


450

अद्यतन 2016-06-27: वेधशालाओं का उपयोग करने के बजाय, या तो उपयोग करें

  • जैसा कि एक टिप्पणी में @Abdulrahman द्वारा अनुशंसित व्यवहार व्यवहार, या
  • ReplaySubject, जैसा कि @Jason Goemaat ने एक टिप्पणी में सुझाया है

एक सब्जेक्ट एक ऑब्जर्वेबल (इसलिए हम subscribe()इसे कर सकते हैं) और एक ऑब्जर्वर दोनों हैं (इसलिए हम next()इसे नए मूल्य का अनुकरण करने के लिए कह सकते हैं )। हम इस सुविधा का फायदा उठाते हैं। एक विषय कई पर्यवेक्षकों के लिए मूल्यों को बहुस्त्र्पीय होने की अनुमति देता है। हम इस सुविधा का उपयोग नहीं करते हैं (हमारे पास केवल एक पर्यवेक्षक है)।

व्यवहार विषय विषय का एक प्रकार है। इसमें "वर्तमान मूल्य" की धारणा है। हम इसका फायदा उठाते हैं: जब भी हम एक ऑब्जर्विंगकंपोनेंट बनाते हैं, तो यह स्वचालित रूप से व्यवहार सर्वर से वर्तमान नेविगेशन आइटम मूल्य प्राप्त करता है।

नीचे दिए गए कोड और प्लंकर BehaviorSubject का उपयोग करते हैं।

ReplaySubject Subject का एक और संस्करण है। यदि आप वास्तव में उत्पादन होने तक इंतजार करना चाहते हैं, तो उपयोग करें ReplaySubject(1)। जबकि BehaviorSubject को एक प्रारंभिक मूल्य (जो तुरंत प्रदान किया जाएगा) की आवश्यकता होती है, ReplaySubject नहीं करता है। ReplaySubject हमेशा सबसे हाल का मूल्य प्रदान करेगा, लेकिन चूंकि इसमें एक आवश्यक प्रारंभिक मूल्य नहीं है, इसलिए सेवा पहले मूल्य को वापस करने से पहले कुछ async ऑपरेशन कर सकती है। यह अभी भी सबसे हाल के मूल्य के साथ बाद में कॉल पर तुरंत आग लगा देगा। यदि आप केवल एक मूल्य चाहते हैं, first()तो सदस्यता पर उपयोग करें । यदि आप उपयोग करते हैं तो आपको सदस्यता समाप्त करने की आवश्यकता नहीं है first()

import {Injectable}      from '@angular/core'
import {BehaviorSubject} from 'rxjs/BehaviorSubject';

@Injectable()
export class NavService {
  // Observable navItem source
  private _navItemSource = new BehaviorSubject<number>(0);
  // Observable navItem stream
  navItem$ = this._navItemSource.asObservable();
  // service command
  changeNav(number) {
    this._navItemSource.next(number);
  }
}
import {Component}    from '@angular/core';
import {NavService}   from './nav.service';
import {Subscription} from 'rxjs/Subscription';

@Component({
  selector: 'obs-comp',
  template: `obs component, item: {{item}}`
})
export class ObservingComponent {
  item: number;
  subscription:Subscription;
  constructor(private _navService:NavService) {}
  ngOnInit() {
    this.subscription = this._navService.navItem$
       .subscribe(item => this.item = item)
  }
  ngOnDestroy() {
    // prevent memory leak when component is destroyed
    this.subscription.unsubscribe();
  }
}
@Component({
  selector: 'my-nav',
  template:`
    <div class="nav-item" (click)="selectedNavItem(1)">nav 1 (click me)</div>
    <div class="nav-item" (click)="selectedNavItem(2)">nav 2 (click me)</div>`
})
export class Navigation {
  item = 1;
  constructor(private _navService:NavService) {}
  selectedNavItem(item: number) {
    console.log('selected nav item ' + item);
    this._navService.changeNav(item);
  }
}

Plunker


मूल उत्तर जो एक ऑब्जर्वेबल का उपयोग करता है: (इसमें एक व्यवहार कोड का उपयोग करने की तुलना में अधिक कोड और तर्क की आवश्यकता होती है, इसलिए मैं इसकी अनुशंसा नहीं करता, लेकिन यह शिक्षाप्रद हो सकता है)

इसलिए, यहां एक कार्यान्वयन है जो एक EventEmitter के बजाय एक ऑब्जर्वेबल का उपयोग करता है । मेरे EventEmitter कार्यान्वयन के विपरीत, यह कार्यान्वयन वर्तमान navItemमें सेवा में चयनित स्टोर को भी संग्रहीत करता है , ताकि जब एक अवलोकन घटक बनाया जाता है, तो यह API कॉल के माध्यम से वर्तमान मूल्य को पुनः प्राप्त कर सकता है navItem(), और फिर navChange$वेधशाला के माध्यम से परिवर्तनों के बारे में सूचित किया जा सकता है ।

import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/share';
import {Observer} from 'rxjs/Observer';

export class NavService {
  private _navItem = 0;
  navChange$: Observable<number>;
  private _observer: Observer;
  constructor() {
    this.navChange$ = new Observable(observer =>
      this._observer = observer).share();
    // share() allows multiple subscribers
  }
  changeNav(number) {
    this._navItem = number;
    this._observer.next(number);
  }
  navItem() {
    return this._navItem;
  }
}

@Component({
  selector: 'obs-comp',
  template: `obs component, item: {{item}}`
})
export class ObservingComponent {
  item: number;
  subscription: any;
  constructor(private _navService:NavService) {}
  ngOnInit() {
    this.item = this._navService.navItem();
    this.subscription = this._navService.navChange$.subscribe(
      item => this.selectedNavItem(item));
  }
  selectedNavItem(item: number) {
    this.item = item;
  }
  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}

@Component({
  selector: 'my-nav',
  template:`
    <div class="nav-item" (click)="selectedNavItem(1)">nav 1 (click me)</div>
    <div class="nav-item" (click)="selectedNavItem(2)">nav 2 (click me)</div>
  `,
})
export class Navigation {
  item:number;
  constructor(private _navService:NavService) {}
  selectedNavItem(item: number) {
    console.log('selected nav item ' + item);
    this._navService.changeNav(item);
  }
}

Plunker


घटक इंटरएक्शन कुकबुक उदाहरण भी देखें , जो Subjectवेधशालाओं के अतिरिक्त उपयोग करता है । हालांकि उदाहरण "माता-पिता और बच्चों के संचार," एक ही तकनीक असंबंधित घटकों के लिए लागू है।


3
यह एंगुलर 2 मुद्दों पर टिप्पणियों में बार-बार उल्लेख किया गया था कि यह EventEmitterआउटपुट को छोड़कर कहीं भी उपयोग करने के लिए हतोत्साहित है । वे वर्तमान में इस अभ्यास को प्रोत्साहित नहीं करने के लिए ट्यूटोरियल (सर्वर संचार AFAIR) को फिर से लिख रहे हैं।
गुंटर ज़ोचबॉएर

2
क्या कोई तरीका है जो सेवा को इनिशियलाइज़ करता है और ऊपर के सैंपल कोड में नेविगेशन कंपोनेंट के भीतर से पहली घटना को फायर करता है? समस्या यह है कि _observerसेवा ऑब्जेक्ट को कम से कम शुरू में ngOnInit()नेविगेशन घटक के नाम से नहीं जाना जाता है।
कॉमफ्रीक

4
क्या मैं ऑब्जर्वेबल के बजाय बिहेवियरसुबजेक्ट का उपयोग करने का सुझाव दे सकता हूं । यह करीब है EventEmitterक्योंकि यह "हॉट" है, जिसका अर्थ है कि यह "पहले से ही साझा" है, यह वर्तमान मूल्य को बचाने के लिए डिज़ाइन किया गया है और अंत में यह ऑब्जर्वेबल और ऑब्जर्वर दोनों को लागू करता है जो आपको कम से कम पांच लाइनों के कोड और दो संपत्तियों को
बचाएगा

2
@PankajParkar, "कैसे करता है परिवर्तन का पता लगाने वाला इंजन जानता है कि परिवर्तन ऑब्जर्वेबल ऑब्जेक्ट पर हुआ है" - मैंने अपनी टिप्पणी प्रतिक्रिया को हटा दिया। मैंने हाल ही में सीखा है कि कोणीय बंदर-पैच नहीं करता है subscribe(), इसलिए यह पता नहीं लगा सकता है कि कब एक अवलोकन योग्य परिवर्तन होता है। आम तौर पर, कुछ एस्किंट घटना होती है जो फायर करती है (मेरे नमूना कोड में, यह बटन क्लिक इवेंट है), और संबंधित कॉलबैक अगले () को एक अवलोकन योग्य पर कॉल करेगा। लेकिन परिवर्तन का पता लगाने के लिए async घटना के कारण चलता है, न कि देखने योग्य परिवर्तन के कारण। गुंटर की टिप्पणियाँ भी देखें: stackoverflow.com/a/36846501/215945
मार्क राजकोक

9
यदि आप तब तक इंतजार करना चाहते हैं जब तक कि मूल्य वास्तव में उत्पन्न न हो जाए तब आप उपयोग कर सकते हैं ReplaySubject(1)। एक BehaviorSubjectप्रारंभिक मूल्य की आवश्यकता होती है जिसे तुरंत उपलब्ध कराया जाएगा। ReplaySubject(1)हमेशा सबसे हाल ही में मूल्य प्रदान करेगा, लेकिन एक प्रारंभिक मूल्य की आवश्यकता है ताकि सेवा यह की पहली मूल्य लौटने से पहले कुछ async आपरेशन कर सकते हैं नहीं है, लेकिन अभी भी अंतिम मान के साथ आगामी कॉल पर तुरंत आग। यदि आप केवल एक मूल्य में रुचि रखते हैं, तो आप first()सदस्यता पर उपयोग कर सकते हैं और अंत में सदस्यता समाप्त करने की आवश्यकता नहीं है क्योंकि यह पूरा हो जाएगा।
जेसन गोएमाट

33

ब्रेकिंग न्यूज़: मैंने एक और उत्तर जोड़ा है जो एक EventEmitter के बजाय एक ऑब्जर्वेबल का उपयोग करता है। मैं इस एक पर जवाब देने की सलाह देता हूं। और वास्तव में, किसी सेवा में EventEmitter का उपयोग करना बुरा व्यवहार है


मूल उत्तर: (यह मत करो)

EventEmitter को एक सेवा में रखें, जो अवलोकन करने वाले को इस आयोजन को सीधे सदस्यता (और सदस्यता समाप्त करने) की अनुमति देता है :

import {EventEmitter} from 'angular2/core';

export class NavService {
  navchange: EventEmitter<number> = new EventEmitter();
  constructor() {}
  emit(number) {
    this.navchange.emit(number);
  }
  subscribe(component, callback) {
    // set 'this' to component when callback is called
    return this.navchange.subscribe(data => call.callback(component, data));
  }
}

@Component({
  selector: 'obs-comp',
  template: 'obs component, index: {{index}}'
})
export class ObservingComponent {
  item: number;
  subscription: any;
  constructor(private navService:NavService) {
   this.subscription = this.navService.subscribe(this, this.selectedNavItem);
  }
  selectedNavItem(item: number) {
    console.log('item index changed!', item);
    this.item = item;
  }
  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}

@Component({
  selector: 'my-nav',
  template:`
    <div class="nav-item" (click)="selectedNavItem(1)">item 1 (click me)</div>
  `,
})
export class Navigation {
  constructor(private navService:NavService) {}
  selectedNavItem(item: number) {
    console.log('selected nav item ' + item);
    this.navService.emit(item);
  }
}

यदि आप कोशिश करते हैं Plunker, तो कुछ चीजें हैं जो मुझे इस दृष्टिकोण के बारे में पसंद नहीं हैं:

  • जब यह नष्ट हो जाता है तो अवलोकन करने की जरूरत है
  • हमें घटक को पास करना होगा subscribe()ताकि thisकॉलबैक कहे जाने पर उचित सेट हो जाए

अद्यतन: एक विकल्प जो दूसरी गोली को हल करता है, वह है सीधे navchangeअवलोकनकर्ता को सीधे EventEmitter संपत्ति की सदस्यता देना :

constructor(private navService:NavService) {
   this.subscription = this.navService.navchange.subscribe(data =>
     this.selectedNavItem(data));
}

यदि हम सीधे सदस्यता लेते हैं, तो हमें subscribe()NavService पर विधि की आवश्यकता नहीं होगी ।

NavService को थोड़ा और अतिक्रमित करने के लिए, आप एक getNavChangeEmitter()तरीका और जोड़ सकते हैं :

getNavChangeEmitter() { return this.navchange; }  // in NavService

constructor(private navService:NavService) {  // in ObservingComponent
   this.subscription = this.navService.getNavChangeEmitter().subscribe(data =>
     this.selectedNavItem(data));
}

मैं इस समाधान को श्री ज़ौबी द्वारा प्रदान किए गए उत्तर के लिए पसंद करता हूं, लेकिन मैं इस समाधान का प्रशंसक नहीं हूं, ईमानदार होने के लिए। मुझे विनाश के बारे में बताने की परवाह नहीं है, लेकिन मुझे इस तथ्य से नफरत है कि हमें इस कार्यक्रम की सदस्यता के लिए घटक को पास करना होगा ...
the_critic

मैंने वास्तव में इस बारे में सोचा और इस समाधान के साथ जाने का फैसला किया। मैं थोड़ा क्लीनर समाधान करना पसंद करूंगा, लेकिन मुझे यकीन नहीं है कि यह संभव है (या मैं शायद ऐसा कुछ नहीं पा रहा हूं जो अधिक सुंदर है जो मैं कह सकता हूं)।
द क्रिटिक

वास्तव में दूसरी बुलेट समस्या यह है कि इसके बजाय फ़ंक्शन का संदर्भ दिया जा रहा है। ठीक करने के लिए: this.subscription = this.navService.subscribe(() => this.selectedNavItem());और सदस्यता पर:return this.navchange.subscribe(callback);
एन्द्र वेर्लंग

1

यदि कोई प्रोग्रामिंग की अधिक प्रतिक्रियाशील उन्मुख शैली का पालन करना चाहता है, तो निश्चित रूप से "सब कुछ एक धारा है" की अवधारणा तस्वीर में आती है और इसलिए, इन धाराओं से निपटने के लिए वेधशालाओं का उपयोग जितनी बार संभव हो सके।


1

आप या तो उपयोग कर सकते हैं:

  1. व्यवहार विषय:

BehaviorSubject एक प्रकार का विषय है, एक विषय एक विशेष प्रकार का अवलोकन है जो अवलोकन योग्य और पर्यवेक्षक के रूप में कार्य कर सकता है आप किसी भी अन्य अवलोकन योग्य और सदस्यता पर संदेशों की सदस्यता ले सकते हैं, यह स्रोत द्वारा अवलोकन किए गए विषय का अंतिम मान देता है:

लाभ: घटकों के बीच डेटा पास करने के लिए माता-पिता के संबंध जैसे कोई संबंध नहीं।

एनएवी सेवा

import {Injectable}      from '@angular/core'
import {BehaviorSubject} from 'rxjs/BehaviorSubject';

@Injectable()
export class NavService {
  private navSubject$ = new BehaviorSubject<number>(0);

  constructor() {  }

  // Event New Item Clicked
  navItemClicked(navItem: number) {
    this.navSubject$.next(number);
  }

 // Allowing Observer component to subscribe emitted data only
  getNavItemClicked$() {
   return this.navSubject$.asObservable();
  }
}

नेविगेशन घटक

@Component({
  selector: 'navbar-list',
  template:`
    <ul>
      <li><a (click)="navItemClicked(1)">Item-1 Clicked</a></li>
      <li><a (click)="navItemClicked(2)">Item-2 Clicked</a></li>
      <li><a (click)="navItemClicked(3)">Item-3 Clicked</a></li>
      <li><a (click)="navItemClicked(4)">Item-4 Clicked</a></li>
    </ul>
})
export class Navigation {
  constructor(private navService:NavService) {}
  navItemClicked(item: number) {
    this.navService.navItemClicked(item);
  }
}

OBSERVING घटक

@Component({
  selector: 'obs-comp',
  template: `obs component, item: {{item}}`
})
export class ObservingComponent {
  item: number;
  itemClickedSubcription:any

  constructor(private navService:NavService) {}
  ngOnInit() {

    this.itemClickedSubcription = this.navService
                                      .getNavItemClicked$
                                      .subscribe(
                                        item => this.selectedNavItem(item)
                                       );
  }
  selectedNavItem(item: number) {
    this.item = item;
  }

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

दूसरा दृष्टिकोण है Event Delegation in upward direction child -> parent

  1. @Input और @Output डेकोरेटर पैरेंट डेटा का उपयोग चाइल्ड कंपोनेंट और चाइल्ड पेरेंट कंपोनेंट को सूचित करते हुए

उदा। @ आशीष शर्मा द्वारा दिया गया उत्तर।


0

आपको ओब्जर्विंगकंपोनेंट के टेम्प्लेट में नेविगेशन घटक का उपयोग करने की आवश्यकता है ( नेविगेशन घटक के लिए चयनकर्ता को जोड़ने के लिए मत भूलना .. पूर्व के लिए नेविगेशन-घटक)

<navigation-component (navchange)='onNavGhange($event)'></navigation-component>

और ऑब्जर्विंगकंपोनेंट में onNavGhange () को लागू करें

onNavGhange(event) {
  console.log(event);
}

अंतिम बात .. आपको @Componennt में ईवेंट्स विशेषता की आवश्यकता नहीं है

events : ['navchange'], 

यह केवल अंतर्निहित घटक के लिए किसी ईवेंट को हुक करता है। यही मैं करने की कोशिश नहीं कर रहा हूं। मैं बस (^ navchange) (कार्यवाहक घटना बुदबुदाहट के लिए) की तरह कुछ कह सकता था, nav-itemलेकिन मैं सिर्फ एक घटना का अनुकरण करना चाहता हूं जो दूसरों को देख सकते हैं।
the_critic

आप navchange.toRx ()। subscribe () का उपयोग कर सकते हैं .. लेकिन आपको अवलोकनकंप्लेंट पर navchange पर एक संदर्भ की आवश्यकता होगी
Mourad Zouabi

0

आप ऊपर वर्णित के रूप में BehaviourSubject का उपयोग कर सकते हैं या एक और तरीका है:

आप इस तरह से EventEmitter को संभाल सकते हैं: पहले एक चयनकर्ता जोड़ें

import {Component, Output, EventEmitter} from 'angular2/core';

@Component({
// other properties left out for brevity
selector: 'app-nav-component', //declaring selector
template:`
  <div class="nav-item" (click)="selectedNavItem(1)"></div>
`
 })

 export class Navigation {

@Output() navchange: EventEmitter<number> = new EventEmitter();

selectedNavItem(item: number) {
    console.log('selected nav item ' + item);
    this.navchange.emit(item)
}

}

अब आप इस घटना को संभाल सकते हैं जैसे मान लें कि observer.component.html ऑब्जर्वर घटक का दृश्य है

<app-nav-component (navchange)="recieveIdFromNav($event)"></app-nav-component>

इसके बाद ऑब्जर्विंगकंपोनेंट.इन में

export class ObservingComponent {

 //method to recieve the value from nav component

 public recieveIdFromNav(id: number) {
   console.log('here is the id sent from nav component ', id);
 }

 }

-2

मैं इस मामले के लिए एक और समाधान बाहर पाया प्रतिक्रियाशील न तो सेवाओं का उपयोग किए बिना। मैं वास्तव में rxjx एपीआई से प्यार करता हूं, लेकिन मुझे लगता है कि यह एक async और / या जटिल फ़ंक्शन को हल करते समय सबसे अच्छा होता है। इस तरह से इसका उपयोग करना, इसकी बहुत हद तक मुझसे अधिक हो गया।

मुझे लगता है कि आप एक प्रसारण के लिए देख रहे हैं। बस कि। और मुझे यह पता चला:

<app>
  <app-nav (selectedTab)="onSelectedTab($event)"></app-nav>
       // This component bellow wants to know when a tab is selected
       // broadcast here is a property of app component
  <app-interested [broadcast]="broadcast"></app-interested>
</app>

 @Component class App {
   broadcast: EventEmitter<tab>;

   constructor() {
     this.broadcast = new EventEmitter<tab>();
   }

   onSelectedTab(tab) {
     this.broadcast.emit(tab)
   }    
 }

 @Component class AppInterestedComponent implements OnInit {
   broadcast: EventEmitter<Tab>();

   doSomethingWhenTab(tab){ 
      ...
    }     

   ngOnInit() {
     this.broadcast.subscribe((tab) => this.doSomethingWhenTab(tab))
   }
 }

यह एक पूर्ण कार्य उदाहरण है: https://plnkr.co/edit/xGVuFBOpk2GP0pRBImsE


1
सबसे अच्छा जवाब देखो, यह सदस्यता विधि का भी उपयोग करता है .. वास्तव में आजकल मैं घटकों के साथ इस संचार समस्या को हल करने के लिए Redux या कुछ अन्य राज्य नियंत्रण का उपयोग करने की सिफारिश करूंगा। यह किसी भी अन्य समाधान की तुलना में बहुत बेहतर है, हालांकि यह अतिरिक्त जटिलता को जोड़ता है। या तो कोणीय 2 घटक ईवेंट हैंडलर सिंटैक्स का उपयोग करना या सदस्यता विधि का स्पष्ट रूप से उपयोग करना अवधारणा समान है। मेरे अंतिम विचार हैं यदि आप उस समस्या के लिए एक निश्चित समाधान चाहते हैं Redux का उपयोग करें, अन्यथा इवेंट एमिटर के साथ सेवाओं का उपयोग करें।
निकोलस मार्कासिनी ऑगस्टो

सदस्यता तब तक मान्य है जब तक कोणीय इस तथ्य को दूर नहीं करता कि यह अवलोकन योग्य है। .subscribe () का उपयोग सर्वोत्तम उत्तर में किया जाता है, लेकिन उस विशेष वस्तु पर नहीं।
पोर्शेई
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.