简体   繁体   中英

What is the right way to send data between modules in AngularJS?

One of the great things about angular is that you can have independent Modules that you can reuse in different places. Say that you have a module to paint, order, and do a lot of things with lists. Say that this module will be used all around your application. And finally, say that you want to populate it in different ways. Here is an example:

 angular.module('list', []).controller('listController', ListController); var app = angular.module('myapp', ['list']).controller('appController', AppController); function AppController() { this.name = "Misae"; this.fetch = function() { console.log("feching"); //change ListController list //do something else } } function ListController() { this.list = [1, 2, 3]; this.revert = function() { this.list.reverse(); } } 
 <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script> <div class="app" ng-app="myapp" ng-controller="appController as App"> <div class="filters"> Name: <input type="text" ng-model="App.name" /> <button ng-click="App.fetch()">Fetch</button> </div> <div class="list" ng-controller="listController as List"> <button ng-click="List.revert()">Revert</button> <ul> <li ng-repeat="item in List.list">{{item}}</li> </ul> </div> </div> 

Now, when you click on Fetch button, you'll send the name (and other filters and stuff) to an API using $http and so on. Then you get some data, including a list of items you want to paint. Then you want to send that list to the List module, to be painted.

It has to be this way because you'll be using the list module in diferent places and it will always paint a list and add some features like reordering and reversing it. While the filters and the API connection will change, your list behaviour will not, so there must be 2 different modules.

That said, what is the best way to send the data to the List module after fetching it? With a Service?

You should be using Angular components for this task.

You should create a module with a component that will be displaying lists and providing some actions that will modify the list and tell the parent to update the value.

var list = angular.module('listModule', []);

list.controller('listCtrl', function() {
    this.reverse = function() {
        this.items = [].concat(this.items).reverse();
        this.onUpdate({ newValue: this.items });
    };
});

list.component('list', {
  bindings: {
    items: '<',
    onUpdate: '&'
  },
  controller: 'listCtrl',
  template: '<button ng-click="$ctrl.reverse()">Revert</button><ul><li ng-repeat="item in $ctrl.items">{{ item }}</li></ul>'
});

This way when you click on "Revert" list component will reverse the array and execute the function provided in on-update attribute of HTML element.

Then you can simply set your app to be dependent on this module

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

and use list component

<list data-items="list" data-on-update="updateList(newValue)"></list>

You can see the rest of the code in the example

It is very simple. Please take a look at this small snippet. comments are added to highlight.

You can have a common module that contains all the data that needs to be shared across modules by two steps

  • adding module dependency
  • injecting corresponding provider in the respective module's controller

 angular.module('commonAppData', []).factory('AppData',function(){ var a,b,c; a=1; return{ a:a, b:b, c:c } }) angular.module('list', ['commonAppData']).controller('listController', ListController); var app = angular.module('myapp', ['list','commonAppData']).controller('appController', AppController); function AppController(AppData) { //assigning a variable AppData.a=100; this.name = "Misae"; this.fetch = function() { console.log("feching"); //change ListController list //do something else } } function ListController(AppData) { //Using the data sent by App Controller this.variableA=AppData.a; this.list = [1, 2, 3]; this.revert = function() { this.list.reverse(); } } 
 <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script> <div class="app" ng-app="myapp" ng-controller="appController as App"> <div class="filters"> Name: <input type="text" ng-model="App.name" /> <button ng-click="App.fetch()">Fetch</button> </div> <div class="list" ng-controller="listController as List"> <b>Shared variable : {{List.variableA}}</b> <br> <button ng-click="List.revert()">Revert</button> <ul> <li ng-repeat="item in List.list">{{item}}</li> </ul> </div> </div> 

There are a few ways to handle objects acress multiple controllers. Here are two.

1. Using Angulars $rootScope

You can assign an object to $rootScope which will hold up all information. This object can be passed into every controller through Angulars dependency injection. Also you can listen up to changes on your object by watching it through $watch or $emit .

Using $rootScope is an easy way, but may lead to performance issues on larger applications.

2. Using services

Angular provides a possibility to share object through services. Instead of defining your object inside of your controller, you could also do that inside of a service. Doing so you could inject that service into any controller and use it's values across your application.

function AppController(listService) {
  // reference to the injected data
}

function ListController(listService) {
  // update data
}

AngularJS provides $on , $emit , and $broadcast services for event-based communication between controllers.

So, if we want to pass data from the inner controller( listController ) to outer controller( appController ) then we have to use $emit .It dispatches an event name upwards through the scope hierarchy and notify to the registered $rootScope .

Working Plunker : https://plnkr.co/edit/szf9jHvvOPLOvQc5sQI2?p=preview

This plunker is not as per the exact requirement as i don't know the api response but this sample plunker describe the problem statement I hope it will help you.

Thanks.

It depends on the circumstance. Is there a parent child relationship? Is the relationship unknown, or you simply want to avoid having to worry about it at all?

I think this post lays it out well (it always seems to be helpful):

http://mean.expert/2016/05/21/angular-2-component-communication/

There are many ways to pass data from one module to another module and many ppl have suggested different ways.

One of the finest way and cleaner approach is using a factory instead of polluting $rootScope or using $emit or $broadcast or $controller .If you want to know more about how to use all of this visit this blog on Accessing functions of one controller from another in angular js

By simply inject the factory you have created in main module and add child modules as dependancy in main module, then inject factory in child modules to access the factory objects.

Here is a sample example on how to use factory for sharing data across the application.

Lets create a factory which can be used in entire application across all controllers to store data and access them.

Advantages with factory is you can create objects in it and intialise them any where in the controllers or we can set the defult values by intialising them in the factory itself.

Factory

app.factory('SharedData',['$http','$rootScope',function($http,$rootScope){

    var SharedData = {}; // create factory object...
    SharedData.appName ='My App';
    return SharedData;
}]);

Service

app.service('Auth', ['$http', '$q', 'SharedData', function($http, $q,SharedData) {
   this.getUser = function() {

            return $http.get('user.json')
              .then(function(response) {
                  this.user = response.data;
                  SharedData.userData = this.user; // inject in the service and create a object in factory ob ject to store user data..
                  return response.data;
              }, function(error) {
                  return $q.reject(error.data);
              });

    };

}]);

Controller

var app = angular.module("app", []);
app.controller("testController", ["$scope",'SharedData','Auth',
  function($scope,SharedData,Auth) {

    $scope.user ={};
   // do a service call via service and check the shared data which is factory object ...
   var user = Auth.getUser().then(function(res){
       console.log(SharedData);
       $scope.user = SharedData.userData;// assigning to scope.
   });


  }]);

In HTML

<body ng-app='app'>
    <div class="media-list" ng-controller="testController">

       <pre> {{user | json}}</pre>

    </div>

</body>

I think you can use the publisher-subscriber pattern implemented in $rootScope.

In the ListController you have to inject $rootScope and after that you have to subscribe to an arbitrary called event that could have the pattern _data_received

$rootScope.$on('ingredients_data_received', function(ingredients) { prepare_recipe();});

so in your AppController you have to call $rootScope.$emit once the data of that list has been received

$rootScope.$emit('ingredients_data_received', ingredients);

This is just a way to pass data, you can also push those data or the promise in a $rootScope property but this is not a good practice, or you can create your own Service that manage the data for you (remember that you are working with a frontend framework so the controller has to control the view, the business logic has to be transfered to a service, not to a crontroller).

1) If you want non persist data, one way to do this is to use service.

app.service('yourService', function() {
   this.setData = function(value) {
     this.params = value;
   }

    this.getData = function () {
        return this.params;
    }
});

Like services are kind of singleton data can be assign inside service from one module and recover into another one.

http://www.w3schools.com/angular/angular_services.asp

2) If you want persistance for your data you can play with Local Storage or indexDB. Like that each module have access to persisted data :

Sample for localStorage

window.localStorage.setItem("attribute", "value");
window.localStorage.getItem("attribute");

http://www.w3schools.com/html/html5_webstorage.asp

I like to use angular resource with a caching layer to persist data to multiple controllers using a singular method. This has a few benefits namely any controller has the same entry point to data. Keep in mind sometimes you need to fetch fresh data when navigating from one portion of the site to another so persisting http data is not always ideal.

'use strict';
(function() {
    angular.module('customModule')
        .service('BookService', BookService);

    BookService.$inject = ['$resource'];

    function BookService($resource) {
        var BookResource = $resource('/books', {id: '@id'}, {
            getBooks: {
                method: 'GET',
                cache: true
            }
        });

        return {
            getBooks: getBooks
        };

        function getBooks(params) {
            if (!params) {
                params = {};
            }
            return BookResource.getBooks(params).$promise;
        }
    }
})();

In any controller

BookService.getBooks().then(function(books) {
    //books will be cached so invoking the call will return the same set of data anywhere
});

Services in angular are used to share functionality between components. Please try to keep them simple and with a single responsibility/purpose. An idea would be creating a store service that will cache every response from the api in your application. Then you don't have to request it every time.

Hope it helps! ;)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM