web-dev-qa-db-fra.com

Comment passer des données entre composants frères sans utiliser $ scope?

Je crée un composant qui contient 3 composants enfants de cette manière:

<header-component>
<side-component>
<main-component>

Le composant principal contient la liste des héros. Le composant en-tête contient deux boutons censés faire basculer la vue du composant principal vers la vue liste ou grille.

Le problème que j'ai maintenant est de transmettre des données du composant en-tête au composant principal. Ainsi, lorsque je clique sur le bouton de la grille, la vue du contenu principal doit être remplacée par la vue de la grille, comme pour la vue des lignes.

Comment les données peuvent-elles être transmises entre les composants enfants dans angular 1.5?

49
Beslinda N.

Approche par composants

Je vous suggérerais d'aligner l'approche Angular à deux composants et d'utiliser entrées / sorties . Si vous le faites, vous pourrez pour migrer facilement vers Angular 2, car les composants seront identiques sur le plan conceptuel (avec une différence de syntaxe uniquement). Voici comment procéder.

Nous voulons donc essentiellement que l’en-tête et les principaux composants partagent l’état avec l’en-tête pour pouvoir le changer. Plusieurs méthodes peuvent être utilisées pour le faire fonctionner, mais la plus simple consiste à utiliser la propriété de contrôleur parent intermédiaire. Supposons donc que le contrôleur parent (ou le composant) définisse cette propriété view que vous voulez utiliser à la fois par les composants header (lecture et modification) et main (lecture possible).

Composant d'en-tête : entrée et sortie.

Voici à quoi pourrait ressembler un composant d'en-tête simple:

.component('headerComponent', {
  template: `
    <h3>Header component</h3>
    <a ng-class="{'btn-primary': $ctrl.view === 'list'}" ng-click="$ctrl.setView('list')">List</a>
    <a ng-class="{'btn-primary': $ctrl.view === 'table'}" ng-click="$ctrl.setView('table')">Table</a>
  `,
  controller: function() {
    this.setView = function(view) {
      this.view = view
      this.onViewChange({$event: {view: view}})
    }
  },
  bindings: {
    view: '<',
    onViewChange: '&'
  }
})

La partie la plus importante ici est la liaison. Avec view: '<' nous spécifions que le composant header pourra lire quelque chose extérieur et le lier en tant que propriété view de son propre contrôleur. Avec onViewChange: '&' sorties définies de composants: le canal pour notifier/mettre à jour le monde extérieur avec tout ce dont il a besoin. Le composant d'en-tête transmettra certaines données via ce canal, mais il ne sait pas quel composant parent en fera et ne devrait pas s'en soucier.

Donc, cela signifie que header contrôleur peut être utilisé quelque chose comme

<header-component view="root.view" on-view-change="root.view = $event.view"></header-component> 

Composante principale : entrée.

Le composant principal est plus simple, il suffit de définir les entrées qu'il accepte:

.component('mainComponent', {
  template: `
    <h4>Main component</h4>
    Main view: {{ $ctrl.view }}
  `,
  bindings: {
    view: '<'
  }
})

Vue parent

Et finalement, tout est câblé ensemble:

<header-component view="root.view" on-view-change="root.view = $event.view"></header-component>
<main-component view="root.view"></main-component>

Jetez un coup d'œil et jouez avec une simple démo.

angular.module('demo', [])

.controller('RootController', function() {
  this.view = 'table'
})

.component('headerComponent', {
  template: `
    <h3>Header component</h3>
    <a class="btn btn-default btn-sm" ng-class="{'btn-primary': $ctrl.view === 'list'}" ng-click="$ctrl.setView('list')">List</a>
    <a class="btn btn-default btn-sm" ng-class="{'btn-primary': $ctrl.view === 'table'}" ng-click="$ctrl.setView('table')">Table</a>
  `,
  controller: function() {
    this.setView = function(view) {
      this.view = view
      this.onViewChange({$event: {view: view}})
    }
  },
  bindings: {
    view: '<',
    onViewChange: '&'
  }
})

.component('mainComponent', {
  template: `
    <h4>Main component</h4>
    Main view: {{ $ctrl.view }}
  `,
  bindings: {
    view: '<'
  }
})
<script src="https://code.angularjs.org/1.5.0/angular.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.css" />

<div class="container" ng-app="demo" ng-controller="RootController as root">
  
    <pre>Root view: {{ root.view }}</pre>
    
    <header-component view="root.view" on-view-change="root.view = $event.view"></header-component>
    <main-component view="root.view"></main-component>
    
</div>

Démo: http://plnkr.co/edit/ODuY5Mp9HhbqA31G4w3t?p=info


Voici un article de blog que j'ai écrit et qui couvre en détail la conception à base de composants: http://dfsq.info/site/read/angular-components-communication

83
dfsq

Bien que l’approche composant parent (transmission de données via des attributs) soit une implémentation parfaite, valide mais bonne, nous pouvons réaliser la même chose d’une manière plus simple en utilisant a store fabrique.

Fondamentalement, les données sont conservées par Store, qui est référencé dans la portée des deux composants, ce qui permet des mises à jour réactives de l'interface utilisateur lorsque l'état change.

Exemple:

angular
    .module('YourApp')
    // declare the "Store" or whatever name that make sense
    // for you to call it (Model, State, etc.)
    .factory('Store', () => {
        // hold a local copy of the state, setting its defaults
        const state = {
            data: {
              heroes: [],
              viewType: 'grid'
            }
        };
        // expose basic getter and setter methods
        return {
            get() {
                return state.data;
            },
            set(data) {
                Object.assign(state.data, data);
            },
        };
    });

Ensuite, dans vos composants, vous devriez avoir quelque chose comme:

angular
    .module('YourApp')
    .component('headerComponent', {
        // inject the Store dependency
        controller(Store) {
            // get the store reference and bind it to the scope:
            // now, every change made to the store data will
            // automatically update your component UI
            this.state = Store.get();

            // ... your code
        },
        template: `
            <div ng-show="$ctrl.state.viewType === 'grid'">...</div>
            <div ng-show="$ctrl.state.viewType === 'row'">...</div>
            ...
        `
    })
    .component('mainComponent', {
        // same here, we need to inject the Store
        controller(Store) {
            // callback for the switch view button
            this.switchViewType = (type) => {
                // change the Store data:
                // no need to notify or anything
                Store.set({ viewType: type });
            };

            // ... your code
        },
        template: `
            <button ng-click="$ctrl.switchViewType('grid')">Switch to grid</button>
            <button ng-click="$ctrl.switchViewType('row')">Switch to row</button>
            ...
        `

Si vous voulez voir un exemple de travail, consultez ce CodePen .

Ce faisant, vous pouvez également activer la communication entre 2 ou N composants . Il suffit juste de:

  1. injecter la dépendance du magasin
  2. assurez-vous de lier les données du magasin à la portée de votre composant

comme dans l'exemple ci-dessus (<header-component>).

Dans le monde réel, une application typique doit gérer un grand nombre de données. Il est donc plus logique de diviser logiquement les domaines de données. En suivant la même approche , vous pouvez ajouter plusieurs usines de magasin . Par exemple, pour gérer les informations de l'utilisateur actuellement connecté et une ressource externe (catalogue), vous pouvez créer un UserStore plus un CatalogStore - alternativement UserModel et CatalogModel; Ces entités seraient également de bons endroits pour centraliser des éléments tels que la communication avec le back-end, l’ajout d’une logique métier personnalisée, etc.. La gestion des données sera alors sous la seule responsabilité des usines Store.

N'oubliez pas que nous modifions les données du magasin . Bien que cette approche soit simple et claire, elle pourrait ne pas évoluer correctement car elle produira effets secondaires . Si vous voulez quelque chose de plus avancé (immuabilité, fonctions pures, arbre d'état unique, etc.), vérifiez Redux , ou si vous voulez finalement basculer sur Angular 2 take un coup d'oeil à ngrx/store .

J'espère que cela t'aides! :)

Vous n'êtes pas obligé de le faire de la manière Angular 2 parce que juste au cas où vous migreriez parfois ... Faites-le s'il est logique pour vous fais le.

15
Andrea Puddu

Utilisez des événements personnalisés pour y parvenir. vous pouvez faire passer des messages dans votre application à l'aide de répartiteurs d'événements $emit(name, args); or $broadcast(name, args);. Vous pouvez également écouter ces événements à l'aide de la méthode $ on (nom, écouteur);

J'espère que ça aide

Réf.: https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$emit

Exemple: vous pouvez notifier le changement comme ci-dessous à partir de votre composant d'en-tête

$rootScope.$emit("menu-changed", "list");

Et vous pouvez écouter le changement de votre directive de composant principal comme

$rootScope.$on("menu-changed", function(evt, arg){
  console.log(arg);
});
6
Subash Selvaraj