AngularJS 방식과 유사한 @Input으로 자식 구성 요소에 각도 패스 콜백 함수


227

AngularJS와는 (지시문에 예를 들어 콜백을 전달할 수 및 매개 변수가 콜백 AngularJS와 방법을 . 그것이로 콜백을 전달할 수 있는가 @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내 코드를 spagetti로 만들고 유지 관리하기 쉽지 않은 방법을 제안했습니다 @Output. 그 결과 내가 허용 대답을 변경
미하일 Michailidis

@ IanS 질문은 AngularJS와 비슷한 Angular에서 어떻게 수행되는지에 관한 것입니까? 제목이 왜 오해의 소지가 있습니까?
Michail Michailidis

Angular는 AngularJS와 매우 다릅니다. Angular 2+는 단지 Angular입니다.
Ian S

1
타이틀 수정;)
Ian S

1
@IanS 감사합니다! 이제 질문은 angularJs에 관한 것입니다.하지만 태그를 추가했습니다.
Michail Michailidis 10

답변:


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
정확히 말하면 함수를 전달하는 것이 아니라 리스너 이벤트 리스너를 출력에 연결하는 것입니다. 왜 작동하는지 이해하는 데 도움이됩니다.
Jens

13
이것은 훌륭한 방법이지만이 답변을 읽은 후에 많은 질문이 남았습니다. 나는 더 깊이있는 일 또는 링크가 설명 제공 할 것이다 기대했다 @Output하고 EventEmitter. 그래서 여기 관심있는 사람들을 위한 @Output에 대한 Angular 문서가 있습니다.
WebWanderer

9
단방향 바인딩에는 좋습니다. 자녀의 이벤트에 연결할 수 있습니다. 그러나 자식에게 콜백 함수를 전달할 수 없으며 콜백의 반환 값을 분석 할 수 있습니다. 아래 답변이 가능합니다.
rook

3
나는 "그것이 나쁜 해결책이라고 생각하는 것"대신에 한 가지 방법보다 다른 방법을 선호하는 이유에 대해 더 많은 설명을 기대할 것입니다.
Fidan Hakaj

6
아마도 80 %의 경우에는 좋지만 하위 구성 요소가 콜백 존재 여부에 따라 시각화를 원하는 경우에는 적합하지 않습니다.
John Freeman

115

최신 정보

이 답변은 Angular 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
이것은 효과가 있었다! 감사! 나는 내용은 해당 어딘가에 :) 있었으면 좋겠다
미하일 Michailidis을

1
원하는 경우 정적 메서드를 사용할 수 있지만 구성 요소의 인스턴스 멤버에 액세스 할 수 없습니다. 아마도 유스 케이스가 아닐 것입니다. 하지만 그렇습니다.Parent -> Child
SnareChops

3
좋은 대답입니다! 나는 일반적으로 바인딩 할 때 함수의 이름을 바꾸지 않습니다. 에서 ngOnInit그냥 사용 this.theCallback = this.theCallback.bind(this)하고 다음 theCallback대신에 전달할 수 있습니다 theBoundCallback.
Zack

1
@MichailMichailidis 예, 귀하의 솔루션에 동의하며 사람들을 더 나은 방법으로 이끌 수 있도록 메모로 답변을 업데이트했습니다. 이것을 주시 해 주셔서 감사합니다.
SnareChops

7
@Output 및 EventEmitter는 단방향 바인딩에 적합합니다. 자식 이벤트에 연결할 수는 있지만 자식에게 콜백 함수를 전달할 수 없으며 콜백의 반환 값을 분석 할 수 있습니다. 이 대답은 가능합니다.
rook

31

SnareChops가 제공 한 답변에 대한 대안.

템플릿에서 .bind (this)를 사용하여 동일한 효과를 얻을 수 있습니다. 깨끗하지는 않지만 몇 줄을 절약합니다. 나는 현재 각도 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
다른 사람들이 템플릿에서 bind (this)를 주석 처리 했으므로 문서화되지 않으므로 향후 더 이상 사용되지 않거나 지원되지 않을 수 있습니다. 또한 다시 @Input코드가 스파게티가되게하고 @Output보다 자연스럽고
복잡한

1
템플릿에 bind ()를 배치하면 Angular는 변경 감지 시마다이 표현식을 다시 평가합니다. 템플릿 외부에서 바인드를 수행하는 다른 솔루션은 덜 간결하지만이 문제는 없습니다.
Chris

질문 : .bind (this)를 할 때 theCallBack 메소드를 자식 또는 부모와 바인딩합니까? 아이와 함께있는 것 같아요. 그러나 문제는 바인딩이 호출 될 때 항상 호출하는 자식 이므로이 올바른 경우 바인딩이 필요하지 않은 것입니다.
ChrisZ

부모 구성 요소와 바인딩됩니다. 이것이 수행되는 이유는 theCallBack ()이 호출 될 때, 아마도 내부에서 무언가를하고 싶을 것이고, "this"가 부모 컴포넌트가 아닌 경우 컨텍스트에서 벗어날 수 있고, 따라서 자신의 메소드와 변수에 도달 할 수 없기 때문입니다. 더 이상
Max Fahl 2019

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);
  }
}

그래서 여기에 두 가지를 보여주고 싶었습니다.

  1. 올바른 컨텍스트를 유지하기 위해 .bind (this) 대신 팻 화살표 (=>)가 작동합니다.
  2. 자식 컴포넌트에서 콜백 함수의 타입 세이프 선언.

1
.bind(this)
TYMG의

6
사용법 팁 : ;-) 를 넣지 [getRowColor]="getColor"말고[getRowColor]="getColor()"
Simon_Weaver

좋은. 이것이 바로 내가 찾던 것입니다. 간단하고 효과적입니다.
BrainSlugs83

7

예를 들어, 로그인 모달 창을 사용하고 있습니다. 여기서 모달 창은 부모이고 로그인 양식은 자식이며 로그인 버튼은 모달 부모의 닫기 기능을 다시 호출합니다.

부모 모달에는 모달을 닫는 기능이 있습니다. 이 부모는 close 함수를 로그인 자식 구성 요소에 전달합니다.

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

Max Fahl의 답변에 대한 대안.

부모 컴포넌트에서 콜백 함수를 화살표 함수로 정의하여 바인딩 할 필요가 없습니다.

@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 ?
Michail Michailidis 2016 년



0

다른 대안.

OP는 콜백을 사용하는 방법을 물었습니다. 이 경우 그는 이벤트 (예 : 클릭 이벤트)를 처리하는 함수를 구체적으로 언급 한 것으로 @serginho의 제안 된 답변 : with @Output및 으로 처리됩니다 EventEmitter.

그러나 콜백과 이벤트에는 차이가 있습니다. 콜백을 사용하면 자식 구성 요소가 부모로부터 피드백이나 정보를 검색 할 수 있지만 이벤트는 피드백을 예상하지 않고 어떤 일이 발생했음을 알릴 수 있습니다.

피드백이 필요한 사용 사례가 있습니다 (예 : 구성 요소가 처리해야하는 색상 또는 요소 목록을 가져옵니다. 일부 답변에서 제안한대로 바운드 함수를 사용하거나 인터페이스를 사용할 수 있습니다 (항상 선호합니다).

이러한 필드가있는 모든 데이터베이스 테이블에 사용하려는 {id, name} 요소 목록에서 작동하는 일반 구성 요소가 있다고 가정하십시오. 이 구성 요소는 다음을 수행해야합니다.

  • 다양한 요소 (페이지)를 검색하여 목록에 표시
  • 요소를 제거 할 수있다
  • 요소를 클릭했음을 알리면 부모가 조치를 취할 수 있습니다.
  • 요소의 다음 페이지를 검색 할 수 있습니다.

자식 구성 요소

일반 바인딩을 사용하면 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콜백 목적으로 (상위 성분). 추가 이점 중 하나는 상위 인스턴스를 전송할 필요가 없다는 것입니다. 사용 사례에서 허용하는 경우 인터페이스를 구현하는 서비스 또는 객체 일 수 있습니다.

완전한 예제는 이 stackblitz에 있습니다.


-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; 
  ...
}

따라서 명시 적으로 바인딩 할 필요가 없습니까?
Michail Michailidis 2014 년

3
포함하지 않는 .bind(this)다음 this콜백의 내부 될 것 window없이 사용 사례에 따라하지 않을 수있다. 그러나 this콜백 .bind(this)이 있으면 필요합니다. 그렇지 않으면이 단순화 된 버전이 좋습니다.
SnareChops

3
this콜백 함수 내부에서 사용하기 때문에 항상 콜백을 구성 요소와 바인딩하는 것이 좋습니다 . 오류가 발생하기 쉽습니다.
Alexandre Junges

이것이 Angular 2 반 패턴의 예입니다.
Serginho

안티 패턴 일 필요는 없습니다. 정확히 원하는 경우가 있습니다. 구성 요소에 뷰에 관한 것이 아닌 것을 수행하는 방법을 알려 주려는 것은 드문 일이 아닙니다. 말이 되네요. 왜이 대답이 그렇게 미워지고 있는지 모르겠습니다.
Lazar Ljubenović
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.