How to transfer data between separate components without using $ scope?

I create a component that contains 3 child components in this way:

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

The main component contains a list of heroes. The header component contains two buttons that are supposed to switch the main component view to a list or grid view.

Now the problem is transferring data from the header component to the main component. Therefore, when I click the grid button, the main content view should change to the grid view, the same for the row view.

How can I pass data between child components in angular 1.5?

+46
angularjs components angularjs-controlleras
Mar 16 '16 at 11:09
source share
3 answers

Component approach

I suggest you align with Angular 2's approach and use I / O. If you do, you can easily switch to Angular 2 because the components will be conceptually identical (with a difference in syntax only). So, here is how you do it.

Thus, we want the title and the main components to share part of the state with the title in order to be able to change it. There are several approaches that we can use to make it work, but the simplest is to use the property of the intermediate parent controller. Therefore, let it be assumed that the parent controller (or component) defines this view property, which you want to use as a header (can read and modify), and the main (can read) components.

Header component : input and output.

Here's what a simple header component looks like:

 .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: '&' } }) 

The most important part here is the bindings. In view: '<' we indicate that the header component will be able to read external something and bind it as a view property of its own controller. With onViewChange: '&' components, outputs are defined: a channel for notifying / updating the outside world with what it needs. The header component will push some data through this channel, but it does not know which one will work with the parent component, and this should not worry.

Thus, this means that the header controller can be used as

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

Main component : input.

The main component is simpler, it only needs to determine the input it accepts:

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

Parent view

And finally, they are all related to each other:

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

Take a look and play with a simple demo.

 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> 

Demo: http://plnkr.co/edit/ODuY5Mp9HhbqA31G4w3t?p=info




Here is a blog post that I wrote, covering component design in detail: http://dfsq.info/site/read/angular-components-communication

+76
Mar 16 '16 at 11:59
source share

Despite the fact that the approach with the parent component (passing data through attributes) is an ideal and reliable implementation , we can achieve the same thing in a simpler way using the factory repository.

In principle, the data is stored by Store , which are referenced in the area of ​​both components, which allows you to respond to user interface updates when the state changes.

Example:

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

Then in your components you should have something like:

 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> ... ` 

If you want to see a working example, check this .png code .

For this, you can also enable communication between 2 or N components . You just need to:

  • introduce storage dependency
  • make sure you bind the storage data to the area of ​​your component.

as in the above example ( <header-component> ).

In the real world, a typical application needs to manage a lot of data, so it makes sense to logically separate data areas in some way. Following the same approach , you can add more store factories . For example, to manage the current registered user information and an external resource (i.e., the Catalog), you can build a UserStore plus a CatalogStore - alternatively UserModel and CatalogModel ; these organizations will also be good places to centralize things like communicating with internal content, adding custom business logic, etc. Data management will ultimately be the responsibility of the Store factories.

Keep in mind that we mutate storage data . Although this approach is dead simple and straightforward, it may not scale well because it will create side effects . If you want something more advanced (immutability, pure functions, a tree with one state, etc.), go to Redux , or if you finally want to go to Angular 2, look at ngrx / store .

Hope this helps! :)

You do not need to do this Angular 2, because just in case you sometimes migrated ... Do it if it makes sense for you to do it.

+11
Oct 25 '16 at 13:56 on
source share

Use special events to achieve this. You can send a message through your application using the event managers $emit(name, args); or $broadcast(name, args); $emit(name, args); or $broadcast(name, args); And you can listen to these events using the $ on (name, listenener) method;

Hope this helps

Ref: https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$emit

Example: you can notify of a change, as shown below, from your header component

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

And you can listen to changes in the directive of the main component, for example

 $rootScope.$on("menu-changed", function(evt, arg){ console.log(arg); }); 
+4
Mar 16 '16 at 11:25
source share



All Articles