AngularJS के समान @Input के रूप में बाल घटक को कोणीय पास कॉलबैक फ़ंक्शन


227

AngularJS में ऐसे पैरामीटर और पैरामीटर हैं जहां आप एक निर्देश पर कॉलबैक पास कर सकते हैं (उदाहरण के लिए AngularJS callbacks का तरीका । क्या @Inputएक कोणीय घटक (नीचे जैसा कुछ) के लिए कॉलबैक को पास करना संभव है ? यदि नहीं, तो क्या निकटतम चीज़ होगी? AngularJS करता है?

@Component({
    selector: 'suggestion-menu',
    providers: [SuggestService],
    template: `
    <div (mousedown)="suggestionWasClicked(suggestion)">
    </div>`,
    changeDetection: ChangeDetectionStrategy.Default
})
export class SuggestionMenuComponent {
    @Input() callback: Function;

    suggestionWasClicked(clickedEntry: SomeModel): void {
        this.callback(clickedEntry, this.query);
    }
}


<suggestion-menu callback="insertSuggestion">
</suggestion-menu>

6
भविष्य के पाठकों के लिए @Inputजिस तरह से सुझाव दिया गया कि मेरे कोड को स्पैगेटी बना दिया जाए और उसे बनाए रखना आसान नहीं है .. @Outputमैं जो चाहता हूं, वह करने का एक और अधिक प्राकृतिक तरीका है। परिणामस्वरूप मैंने स्वीकार किए गए उत्तर को बदल दिया
मिखाइल माइकेलिडिस

@ इयान्स सवाल यह है कि एंगुलरजेएस के समान एंगुलर में कुछ कैसे किया जाता है? शीर्षक भ्रामक क्यों है?
मिखाइल मिखाइलिडिस

AngularJ, AngularJS से बहुत अलग है। कोणीय 2+ केवल कोणीय है।
इयान एस

1
अपना शीर्षक निश्चित किया;)
इयान एस

1
@ इयान्स थैंक्स! अब सवाल angularJs के बारे में है, हालांकि - आपके द्वारा जोड़े गए टैग के साथ।
मिखाइल मिखाइलिडिस

जवाबों:


296

मुझे लगता है कि यह एक बुरा समाधान है। यदि आप किसी फंक्शन को कंपोनेंट में पास करना चाहते हैं @Input(), तो @Output()डेकोरेटर वही है जो आप ढूंढ रहे हैं।

export class SuggestionMenuComponent {
    @Output() onSuggest: EventEmitter<any> = new EventEmitter();

    suggestionWasClicked(clickedEntry: SomeModel): void {
        this.onSuggest.emit([clickedEntry, this.query]);
    }
}

<suggestion-menu (onSuggest)="insertSuggestion($event[0],$event[1])">
</suggestion-menu>

45
सटीक होने के लिए आप फ़ंक्शन को पास नहीं कर रहे हैं, बल्कि एक श्रोता ईवेंट श्रोता को आउटपुट पर हुक कर रहे हैं। यह समझने के लिए मददगार है कि यह क्यों काम करता है।
जेन्स

13
यह एक शानदार तरीका है, लेकिन मुझे इस जवाब को पढ़ने के बाद बहुत सारे सवालों के साथ छोड़ दिया गया था। मुझे उम्मीद थी कि यह अधिक गहराई से हो सकता है या एक लिंक का वर्णन प्रदान की होता है @Outputऔर EventEmitter। तो, यहाँ रुचि रखने वालों के लिए @Output के लिए कोणीय दस्तावेज है
वेबवेंडर

9
यह एक तरफ़ा बंधन के लिए ठीक है। आप बच्चे की घटना के लिए हुकअप कर सकते हैं। लेकिन आप बच्चे को कॉलबैक फ़ंक्शन नहीं दे सकते हैं और कॉलबैक के रिटर्न वैल्यू का विश्लेषण कर सकते हैं। नीचे दिया गया उत्तर इसकी अनुमति देता है।
मुर्गा

3
मुझे उम्मीद है कि "मुझे लगता है कि एक बुरा समाधान है" होने के बजाय एक तरह से बनाम दूसरे तरीके को पसंद करने के बारे में अधिक स्पष्टीकरण की उम्मीद है।
फिदन हकाज

6
शायद 80% मामलों के लिए अच्छा है, लेकिन तब नहीं जब कोई बच्चा घटक चाहे तो कॉलबैक मौजूद हो सकता है।
जॉन फ्रीमैन

115

अपडेट करें

यह उत्तर तब प्रस्तुत किया गया था जब एंगुलर 2 अभी भी अल्फ़ा में था और कई सुविधाएँ अनुपलब्ध / अविवादित थीं। जबकि नीचे अभी भी काम करेगा, यह विधि अब पूरी तरह से पुरानी है। मैं दृढ़ता से नीचे दिए गए उत्तर की सिफारिश करता हूं ।

मूल उत्तर

हां वास्तव में यह है, हालांकि आप यह सुनिश्चित करना चाहेंगे कि यह सही ढंग से स्कूप किया गया हो। इसके लिए मैंने यह सुनिश्चित करने के लिए एक संपत्ति का उपयोग किया है कि thisइसका मतलब है कि मैं इसे क्या चाहता हूं।

@Component({
  ...
  template: '<child [myCallback]="theBoundCallback"></child>',
  directives: [ChildComponent]
})
export class ParentComponent{
  public theBoundCallback: Function;

  public ngOnInit(){
    this.theBoundCallback = this.theCallback.bind(this);
  }

  public theCallback(){
    ...
  }
}

@Component({...})
export class ChildComponent{
  //This will be bound to the ParentComponent.theCallback
  @Input()
  public myCallback: Function; 
  ...
}

1
यह काम किया! धन्यवाद! काश डॉक्यूमेंटेशन में कहीं ऐसा होता कि :)
Michail Michailidis

1
यदि आप चाहें, तो आप एक स्थैतिक विधि का उपयोग कर सकते हैं, लेकिन तब आपके पास घटक के किसी भी उदाहरण का उपयोग नहीं होगा। तो शायद आपका उपयोग मामला नहीं है। लेकिन हाँ, आपको यह गुजरना होगाParent -> Child
SnareChops

3
बहुत बढ़िया जवाब! हालांकि आम तौर पर मैं बंधन का नाम नहीं बदलता हूं। में ngOnInitमैं बस का उपयोग करेंगे: this.theCallback = this.theCallback.bind(this)और फिर आप के theCallbackबजाय के साथ पारित कर सकते हैं theBoundCallback
ज़ैक

1
@MichailMichailidis हां, मैं आपके समाधान से सहमत हूं और लोगों को बेहतर तरीके से ले जाने के लिए एक नोट के साथ मेरे जवाब को अपडेट किया है। इस पर नजर रखने के लिए धन्यवाद।
SnareChops 5

7
@Output और EventEmitter एक तरह से बाइंडिंग के लिए ठीक हैं। आप बच्चे के ईवेंट पर हुकअप कर सकते हैं, लेकिन आप बच्चे को कॉलबैक फ़ंक्शन पास नहीं कर सकते हैं और कॉलबैक के रिटर्न वैल्यू का विश्लेषण कर सकते हैं। यह उत्तर यह अनुमति देता है।
बदमाश

31

जवाब के लिए एक विकल्प SnareChops दिया।

आप एक ही प्रभाव के लिए अपने टेम्पलेट में .bind (यह) का उपयोग कर सकते हैं। यह उतना साफ नहीं हो सकता है लेकिन यह कुछ लाइनों को बचाता है। मैं वर्तमान में कोणीय 2.4.0 पर हूं

@Component({
  ...
  template: '<child [myCallback]="theCallback.bind(this)"></child>',
  directives: [ChildComponent]
})
export class ParentComponent {

  public theCallback(){
    ...
  }
}

@Component({...})
export class ChildComponent{
  //This will be bound to the ParentComponent.theCallback
  @Input()
  public myCallback: Function; 
  ...
}

2
जैसा कि अन्य लोगों ने टिप्पणी की है कि टेम्पलेट में बाइंड (यह) कहीं भी प्रलेखित नहीं है, इसलिए यह भविष्य में अपूरणीय / असमर्थित हो सकता है। इसके अलावा फिर @Inputसे कोड स्पेगेटी बनने और @Outputअधिक प्राकृतिक /
असंगत

1
जब आप टेम्पलेट में बाइंड () करते हैं, तो कोणीय हर परिवर्तन का पता लगाने के लिए इस अभिव्यक्ति का पुनर्मूल्यांकन करता है। अन्य समाधान - टेम्पलेट के बाहर बांधने - कम संक्षिप्त है, लेकिन यह इस समस्या नहीं है।
क्रिस

प्रश्न: जब कर रहे हैं। (यह), आप बाइंडिंग विधि है। बच्चे या माता-पिता के साथ वापस जाएं। मुझे लगता है कि यह बच्चे के साथ है। लेकिन बात यह है कि जब बाइंड को बुलाया जा रहा है, तो यह हमेशा बच्चे को बुला रहा है, इसलिए यह बाइंड जरूरी नहीं है कि मैं सही हूं।
क्रिस जेड

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

29

कुछ मामलों में, आपको पैरेंट घटक द्वारा किए जाने वाले व्यावसायिक तर्क की आवश्यकता हो सकती है। नीचे दिए गए उदाहरण में हमारे पास एक बच्चा घटक है जो मूल घटक द्वारा प्रदान किए गए तर्क के आधार पर तालिका पंक्ति प्रदान करता है:

@Component({
  ...
  template: '<table-component [getRowColor]="getColor"></table-component>',
  directives: [TableComponent]
})
export class ParentComponent {

 // Pay attention on the way this function is declared. Using fat arrow (=>) declaration 
 // we can 'fixate' the context of `getColor` function
 // so that it is bound to ParentComponent as if .bind(this) was used.
 getColor = (row: Row) => {
    return this.fancyColorService.getUserFavoriteColor(row);
 }

}

@Component({...})
export class TableComponent{
  // This will be bound to the ParentComponent.getColor. 
  // I found this way of declaration a bit safer and convenient than just raw Function declaration
  @Input('getRowColor') getRowColor: (row: Row) => Color;

  renderRow(){
    ....
    // Notice that `getRowColor` function holds parent's context because of a fat arrow function used in the parent
    const color = this.getRowColor(row);
    renderRow(row, color);
  }
}

इसलिए, मैं यहां 2 चीजें प्रदर्शित करना चाहता था:

  1. सही संदर्भ रखने के लिए .bind (यह) के बजाय फैट एरो (=>) कार्य करता है;
  2. चाइल्ड कंपोनेंट में कॉलबैक फ़ंक्शन के प्रकारों की घोषणा।

1
वसा तीर के उपयोग के लिए महान विवरण.bind(this)
19MG पर TYMG

6
टिप: सुनिश्चित करें कि डाल [getRowColor]="getColor"और नहीं; [getRowColor]="getColor()";-)
सिमोन_विवर

अच्छा लगा। यही वह है जिसकी तलाश में मैं हूं। सरल और प्रभावी।
BrainSlugs83

7

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

मूल मोडल में मोडल को बंद करने का कार्य होता है। यह माता-पिता लॉगिन चाइल्ड कंपोनेंट के करीब का कार्य करता है।

import { Component} from '@angular/core';
import { LoginFormComponent } from './login-form.component'

@Component({
  selector: 'my-modal',
  template: `<modal #modal>
      <login-form (onClose)="onClose($event)" ></login-form>
    </modal>`
})
export class ParentModalComponent {
  modal: {...};

  onClose() {
    this.modal.close();
  }
}

चाइल्ड लॉगिन कंपोनेंट लॉगिन फॉर्म सबमिट करने के बाद, यह पैरेंट के कॉलबैक फंक्शन का उपयोग करके पैरेंट मोडल को बंद कर देता है

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

@Component({
  selector: 'login-form',
  template: `<form (ngSubmit)="onSubmit()" #loginForm="ngForm">
      <button type="submit">Submit</button>
    </form>`
})
export class ChildLoginComponent {
  @Output() onClose = new EventEmitter();
  submitted = false;

  onSubmit() {
    this.onClose.emit();
    this.submitted = true;
  }
}

7

जवाब का एक विकल्प मैक्स फाहल ने दिया।

आप कॉलबैक फ़ंक्शन को मूल घटक में एक तीर फ़ंक्शन के रूप में परिभाषित कर सकते हैं ताकि आपको इसे बांधने की आवश्यकता न हो।

@Component({
  ...
  // unlike this, template: '<child [myCallback]="theCallback.bind(this)"></child>',
  template: '<child [myCallback]="theCallback"></child>',
  directives: [ChildComponent]
})
export class ParentComponent {

   // unlike this, public theCallback(){
   public theCallback = () => {
    ...
  }
}

@Component({...})
export class ChildComponent{
  //This will be bound to the ParentComponent.theCallback
  @Input()
  public myCallback: Function; 
  ...
}


5

तर्क के साथ पासिंग विधि, टेम्पलेट के अंदर .bind का उपयोग करना

@Component({
  ...
  template: '<child [action]="foo.bind(this, 'someArgument')"></child>',
  ...
})
export class ParentComponent {
  public foo(someParameter: string){
    ...
  }
}

@Component({...})
export class ChildComponent{

  @Input()
  public action: Function; 

  ...
}

क्या आपका उत्तर अनिवार्य रूप से इस प्रकार नहीं है: stackoverflow.com/a/42131227/986160 ?
मिखाइल मिखाइलिडिस

इस टिप्पणी का उत्तर देते हुए stackoverflow.com/questions/35328652/…
शोग गुड़

0

अवलोकन पैटर्न का उपयोग करें। आप इनपुट पैरामीटर में ऑब्जर्वेबल वैल्यू (सब्जेक्ट नहीं) डाल सकते हैं और पेरेंट कंपोनेंट से इसे मैनेज कर सकते हैं। आपको कॉलबैक फ़ंक्शन की आवश्यकता नहीं है।

उदाहरण देखें: https://stackoverflow.com/a/49662611/4604351


क्या आप इसे काम के उदाहरण से स्पष्ट कर सकते हैं?
मिखाइल माइकेलिडिस

0

एक और विकल्प।

ओपी ने कॉलबैक का उपयोग करने का तरीका पूछा। इस मामले में वह विशेष रूप से एक फ़ंक्शन का उल्लेख कर रहे थे जो एक घटना की प्रक्रिया करता है (उनके उदाहरण में: एक क्लिक घटना), जिसे @serginho से स्वीकृत उत्तर के रूप में माना जाएगा: के साथ @Outputऔर EventEmitter

हालाँकि, कॉलबैक और ईवेंट के बीच अंतर होता है: कॉलबैक से आपका चाइल्ड कंपोनेंट अभिभावक से कुछ फीडबैक या जानकारी प्राप्त कर सकता है, लेकिन एक ईवेंट केवल यह सूचित कर सकता है कि कोई प्रतिक्रिया की अपेक्षा के बिना कुछ हुआ।

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

उदाहरण

मान लीजिए कि आपके पास एक सामान्य घटक है जो तत्वों की एक सूची {आईडी, नाम} पर काम करता है जिसे आप अपने सभी डेटाबेस तालिकाओं के साथ उपयोग करना चाहते हैं जिनके पास ये क्षेत्र हैं। यह घटक चाहिए:

  • तत्वों (पेज) की एक श्रृंखला को पुनः प्राप्त करें और उन्हें एक सूची में दिखाएं
  • एक तत्व को हटाने की अनुमति दें
  • सूचित करें कि एक तत्व पर क्लिक किया गया था, इसलिए अभिभावक कुछ कार्रवाई कर सकते हैं।
  • तत्वों के अगले पृष्ठ को पुनः प्राप्त करने की अनुमति दें।

बाल घटक

सामान्य बंधन का उपयोग करके हमें 1 @Input()और 3 @Output()मापदंडों की आवश्यकता होगी (लेकिन माता-पिता से किसी भी प्रतिक्रिया के बिना)। पूर्व। <list-ctrl [items]="list" (itemClicked)="click($event)" (itemRemoved)="removeItem($event)" (loadNextPage)="load($event)" ...>, लेकिन एक इंटरफ़ेस बनाने के लिए हमें केवल एक की आवश्यकता होगी @Input():

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

export interface IdName{
  id: number;
  name: string;
}

export interface IListComponentCallback<T extends IdName> {
    getList(page: number, limit: number): Promise< T[] >;
    removeItem(item: T): Promise<boolean>;
    click(item: T): void;
}

@Component({
    selector: 'list-ctrl',
    template: `
      <button class="item" (click)="loadMore()">Load page {{page+1}}</button>
      <div class="item" *ngFor="let item of list">
          <button (click)="onDel(item)">DEL</button>
          <div (click)="onClick(item)">
            Id: {{item.id}}, Name: "{{item.name}}"
          </div>
      </div>
    `,
    styles: [`
      .item{ margin: -1px .25rem 0; border: 1px solid #888; padding: .5rem; width: 100%; cursor:pointer; }
      .item > button{ float: right; }
      button.item{margin:.25rem;}
    `]
})
export class ListComponent implements OnInit {
    @Input() callback: IListComponentCallback<IdName>; // <-- CALLBACK
    list: IdName[];
    page = -1; 
    limit = 10;

    async ngOnInit() {
      this.loadMore();
    }
    onClick(item: IdName) {
      this.callback.click(item);   
    }
    async onDel(item: IdName){ 
        if(await this.callback.removeItem(item)) {
          const i = this.list.findIndex(i=>i.id == item.id);
          this.list.splice(i, 1);
        }
    }
    async loadMore(){
      this.page++;
      this.list = await this.callback.getList(this.page, this.limit); 
    }
}

जनक घटक

अब हम सूची घटक का उपयोग अभिभावक में कर सकते हैं।

import { Component } from "@angular/core";
import { SuggestionService } from "./suggestion.service";
import { IdName, IListComponentCallback } from "./list.component";

type Suggestion = IdName;

@Component({
  selector: "my-app",
  template: `
    <list-ctrl class="left" [callback]="this"></list-ctrl>
    <div class="right" *ngIf="msg">{{ msg }}<br/><pre>{{item|json}}</pre></div>
  `,
  styles:[`
    .left{ width: 50%; }
    .left,.right{ color: blue; display: inline-block; vertical-align: top}
    .right{max-width:50%;overflow-x:scroll;padding-left:1rem}
  `]
})
export class ParentComponent implements IListComponentCallback<Suggestion> {
  msg: string;
  item: Suggestion;

  constructor(private suggApi: SuggestionService) {}

  getList(page: number, limit: number): Promise<Suggestion[]> {
    return this.suggApi.getSuggestions(page, limit);
  }
  removeItem(item: Suggestion): Promise<boolean> {
    return this.suggApi.removeSuggestion(item.id)
      .then(() => {
        this.showMessage('removed', item);
        return true;
      })
      .catch(() => false);
  }
  click(item: Suggestion): void {
    this.showMessage('clicked', item);
  }
  private showMessage(msg: string, item: Suggestion) {
    this.item = item;
    this.msg = 'last ' + msg;
  }
}

ध्यान दें कि कॉलबैक ऑब्जेक्ट के रूप में <list-ctrl>प्राप्त this(मूल घटक)। एक अतिरिक्त लाभ यह है कि यह मूल उदाहरण भेजने के लिए आवश्यक नहीं है, यह एक सेवा या कोई भी वस्तु हो सकती है जो इंटरफ़ेस का उपयोग करता है यदि आपका उपयोग मामला अनुमति देता है।

पूरा उदाहरण इस स्टैकब्लिट्ज पर है


-3

वर्तमान उत्तर को सरल बनाया जा सकता है ...

@Component({
  ...
  template: '<child [myCallback]="theCallback"></child>',
  directives: [ChildComponent]
})
export class ParentComponent{
  public theCallback(){
    ...
  }
}

@Component({...})
export class ChildComponent{
  //This will be bound to the ParentComponent.theCallback
  @Input()
  public myCallback: Function; 
  ...
}

तो स्पष्ट रूप से बांधने की कोई जरूरत नहीं है?
मिखाइल मिखाइलिडिस

3
.bind(this)तब बिना thisकॉलबैक के अंदर होगा windowजो आपके उपयोग के मामले पर निर्भर करता है। हालाँकि अगर आपके पास thisकॉलबैक है, तो .bind(this)यह आवश्यक है। यदि आप नहीं तो यह सरलीकृत संस्करण जाने का रास्ता है।
SnareChops

3
मैं सलाह देता हूं कि हमेशा कॉलबैक को घटक के साथ बांधें, क्योंकि अंततः आप thisकॉलबैक फ़ंक्शन के अंदर उपयोग करेंगे । यह सिर्फ त्रुटि प्रवण है।
एलेक्जेंडर जुंगेस

यह एक कोणीय 2 प्रतिपदार्थ का एक उदाहरण है।
Serginho

यह एक विरोधी पैटर्न होना जरूरी नहीं है। ऐसे मामले हैं जहां आप बिल्कुल यही चाहते हैं। यह असामान्य नहीं है कि घटक एचओडब्ल्यू को कुछ ऐसा करने के लिए कहना चाहता है जो दृश्य के बारे में नहीं है। यह समझ में आता है और मुझे नहीं लगता कि इस जवाब से इतनी नफरत क्यों हो रही है।
लज़ार लुजुबेनोविक
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.