web-dev-qa-db-fra.com

Communication d'événements de parent à enfant dans les composants AngularJS

dans le nouveau projet sur lequel je travaille, j'ai commencé à utiliser les composants au lieu de directives.

cependant, j'ai rencontré un problème où je ne pouvais pas trouver de solution concrète standard.

Il est facile de notifier un événement d'enfant à parent, vous pouvez le trouver sur mon plunkr ci-dessous, mais quelle est la bonne façon de notifier un événement de parent à enfant?

Angular2 semble résoudre ce problème en utilisant quelque chose comme ceci: https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#parent-to-child-local-var Mais je ne pense pas qu'il soit possible de définir un "pointeur" sur le composant enfant, comme dans l'exemple de #timer

Afin de maintenir une conversion facile possible en Angular2, je souhaite éviter:

  • événement émetteur (émission et diffusion à partir des portées)
  • en utilisant l'exigence de l'enfant (et ensuite ajouter un rappel au parent..UGLY)
  • en utilisant une liaison unidirectionnelle, en injectant l'étendue dans l'enfant puis en "surveillant" cette propriété .. PLUS UGLY

Exemple de code: 

var app = angular.module('plunker', []);

app.controller('RootController', function() {
});

app.component('parentComponent', {
  template: `
    <h3>Parent component</h3>
    <a class="btn btn-default btn-sm" ng-click="$ctrl.click()">Notify Child</a>
    <span data-ng-bind="$ctrl.childMessage"></span>
    <child-component on-change="$ctrl.notifiedFromChild(count)"></child-component>
  `,
  controller: function() {
    var ctrl = this;
    ctrl.notifiedFromChild = function(count){
      ctrl.childMessage = "From child " + count;
    }
    ctrl.click = function(){
    }
  },
  bindings: {
  }
});

app.component('childComponent', {
  template: `
    <h4>Child component</h4>
    <a class="btn btn-default btn-sm" ng-click="$ctrl.click()">Notify Parent</a>
  `,
  controller: function() {
    var ctrl = this;
    ctrl.counter = 0;
    ctrl.click = function(){
        ctrl.onChange({ count: ++ctrl.counter });
    }
  },
  bindings: {
    onChange: '&'
  }
});

Vous pouvez trouver un exemple ici:

http://plnkr.co/edit/SCK8XlyoYCRceCP7q2Rn?p=preview

C'est une solution possible que j'ai créée

http://plnkr.co/edit/OfANmt4zLyPG2SZyVNLr?p=preview

où l'enfant a besoin du parent, et ensuite l'enfant définit une référence parent à l'enfant ... maintenant le parent peut utiliser l'enfant ... moche mais c'est comme l'exemple angular2 ci-dessus

20
Luca Trazzi

Communication d'événements de parent à enfant dans les composants AngularJS

Publier la directive $ API à l'aide de la liaison d'expression

Pour permettre aux composants parents de communiquer des événements à un composant enfant, demandez à l'enfant de publier une API:

<grid-component grid-on-init="$ctrl.gridApi=$API; $ctrl.someFn($API)">
</grid-component>    

JS

app.component('gridComponent', {
  //Create API binding
  bindings: {gridOnInit: "&"},
  template: `
    <h4>Grid component</h4>
    <p> Save count = {{$ctrl.count}}</p>
  `,
  controller: function() {
    var ctrl = this;
    this.$onInit = function() {
        ctrl.count = 0;
        ctrl.api = {};
        //Publish save function
        ctrl.api.save = save;
        //Invoke Expression with $API as local
        ctrl.gridOnInit({$API: ctrl.api});
    };
    function save(){
      console.log("saved!");
      ctrl.count++;
    }
  }
});

L'exemple ci-dessus appelle l'expression angulaire définie par l'attribut grid-on-init avec son API exposée en tant que $API. L'avantage de cette approche est que le parent peut réagir à l'initialisation de l'enfant en transmettant une fonction au composant enfant avec l'expression angulaire.

De la documentation:

Le hachage d'objet de portée 'isolate' définit un ensemble de propriétés de portée locales dérivées d'attributs de l'élément de la directive. Ces propriétés locales sont utiles pour créer des alias pour les valeurs des modèles. Les clés dans le hachage d'objet mappent le nom de la propriété sur l'étendue d'isolement; les valeurs définissent le lien entre la propriété et la portée parent, via des attributs correspondants sur l'élément de la directive:

  • & ou &attr - fournit un moyen d'exécuter une expression dans le contexte de la portée parent. Si aucun nom d'attr n'est spécifié, le nom d'attribut est supposé être le même que le nom local. Avec <my-component my-attr="count = count + value"> et l'étendue de la définition de la portée d'isolement: { localFn:'&myAttr' }, la propriété de portée d'isolement localFn pointera vers un encapsuleur de fonctions pour le count = count + value expression. Il est souvent souhaitable de transmettre des données de la portée isolée via une expression à la portée parent. Cela peut être fait en transmettant une mappe de noms de variables locales et de valeurs dans le wrapper d'expression fn. Par exemple, si l'expression est increment($amount), nous pouvons spécifier la valeur en appelant la variable localFn sous la forme localFn({$amount: 22}).

- Directive complète API AngularJS - Champ d'application

Par convention, je recommande de préfixer les variables locales avec $ pour les distinguer des variables parent.


Utiliser alternativement la liaison bidirectionnelle

REMARQUE: Pour faciliter la transition vers Angular 2+, évitez l’utilisation de la liaison bidirectionnelle =. Utilisez plutôt la liaison à sens unique < et la liaison d'expression &. Pour plus d'informations, voir AngularJS Guide de l'utilisateur - Comprendre les composants .

Pour permettre aux composants parents de communiquer des événements à un composant enfant, demandez à l'enfant de publier une API:

<grid-component api="$ctrl.gridApi"></grid-component>

Dans l'exemple ci-dessus, le grid-component utilise des liaisons pour publier son API sur l'étendue parent à l'aide de l'attribut api.

app.component('gridComponent', {
  //Create API binding
  bindings: {api: "="},
  template: `
    <h4>Grid component</h4>
    <p> Save count = {{$ctrl.count}}</p>
  `,
  controller: function() {
    var ctrl = this;
    this.$onInit = function() {
        ctrl.count = 0;
        ctrl.api = {};
        //Publish save function
        ctrl.api.save = save;
    };
    function save(){
      console.log("saved!");
      ctrl.count++;
    }
  }
});

Le composant parent peut ensuite appeler la fonction enfant save à l'aide de l'API publiée:

ctrl.click = function(){
  console.log("Search clicked");
  ctrl.gridApi.save();
}

La DEMO sur PLNKR .

28
georgeawg

Voici un moyen facile: http://morrisdev.com/2017/03/triggering-events-in-a-child-component-in-angular/

en gros, vous ajoutez une variable liée appelée "commande" (ou ce que vous voulez) et utilisez les $ onChanges pour faire attention aux changements de cette variable et déclencher tout événement qu'il dit déclencher manuellement. 

Personnellement, j'aime bien mettre toutes mes variables dans un objet appelé "paramètres" et l'envoyer à tous mes composants. Cependant, une modification d'une valeur dans un objet NE déclenche PAS l'événement $ onChanges. Vous devez donc lui indiquer de déclencher l'événement avec une variable plate.

Je dirais que ce n'est pas la "bonne" façon de le faire, mais c'est certainement beaucoup plus facile à programmer, à comprendre, et beaucoup plus facile à convertir au format A2 plus tard.

3
Daniel Morris

J'ai affronté la même question. Que pensez-vous de cette approche: utiliser l’héritage via require au lieu de la liaison bidirectionnelle?

http://plnkr.co/edit/fD1qho3eoLoEnlvMzzbw?p=preview

var app = angular.module('plunker', []);

    app.controller('RootController', function() {
    });

    app.component('filterComponent', {
      template: `
        <h3>Filter component</h3>
        <a class="btn btn-default btn-sm" ng-click="$ctrl.click()">Search</a>
        <span data-ng-bind="$ctrl.childMessage"></span>

        <grid-component api="$ctrl.gridApi"></grid-component>
      `,
      controller: function() {
        var ctrl = this;

        ctrl.click = function(){
          console.log("Search clicked");
          ctrl.gridApi.save();
        };
      }
    });

    app.component('gridComponent', {
      require: {parent:'^^filterComponent'},
      bindings: {api: "<"},
      template: `
        <h4>Grid component</h4>
        <p> Save count = {{$ctrl.count}}
      `,
      controller: function() {
        var ctrl = this;



        this.$onInit = function() {
            ctrl.count = 0;
            ctrl.api = {};
            ctrl.api.save = save;

            ctrl.parent.gridApi = ctrl.api;
        };
        function save(){
          console.log("saved!");
          ctrl.count++;
        }
      }
    });

Ou nous pouvons définir la méthode de définition pour le parent afin de la rendre plus explicite.

http://plnkr.co/edit/jmETwGt32BIn3Tl0yDzY?p=preview

var app = angular.module('plunker', []);

app.controller('RootController', function() {
});

app.component('filterComponent', {
  template: `
    <h3>Filter component</h3>
    <a class="btn btn-default btn-sm" ng-click="$ctrl.click()">Search</a>
    <span data-ng-bind="$ctrl.childMessage"></span>

    <grid-component pass-api="$ctrl.setGridApi(api)"></grid-component>
  `,
  controller: function() {
    var ctrl = this;

    var gridApi = {};

    ctrl.setGridApi = function(api){
      gridApi = api;
    };

    ctrl.click = function(){
      console.log("Search clicked");
      gridApi.save();
    };
  }
});

app.component('gridComponent', {
  bindings: {
    passApi:'&'
  },
  template: `
    <h4>Grid component</h4>
    <p> Save count = {{$ctrl.count}}
  `,
  controller: function() {
    var ctrl = this;

    this.$onInit = function() {
        ctrl.count = 0;
        ctrl.api = {};
        ctrl.api.save = save;

        ctrl.passApi({api: ctrl.api});
    };
    function save(){
      console.log("saved!");
      ctrl.count++;
    }
  }
});
1
ViES