如何在不使用 $scope 的情况下在兄弟组件之间传递数据?

How to pass data between sibling components without using $scope?

我正在以这种方式制作一个包含 3 个 child 组件的组件:

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

主要组件包含英雄列表。 header 组件包含两个按钮,用于将主组件上的视图切换为列表或网格视图。

我现在遇到的问题是将数据从 header-component 传递到主要组件。因此,当我单击网格按钮时,主要内容的视图应更改为网格视图,行视图也是如此。

如何在 angular 1.5 中的 child 组件之间传递数据?

使用自定义事件来实现这一点。 您可以使用事件调度程序在您的应用程序中传递消息 $emit(name, args); or $broadcast(name, args); 您可以使用 $on(name, listener);

方法监听此事件

希望对您有所帮助

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

示例: 您可以通过 header-component

通知如下所示的更改
$rootScope.$emit("menu-changed", "list");

并且您可以监听 main-component 指令中的变化,例如

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

组件方法

我建议您使用 Angular 2 组件方法并使用 inputs/outputs 方法。如果这样做,您将能够轻松地迁移到 Angular 2,因为组件在概念上是相同的(只是语法不同)。所以这是你做的方式。

所以我们基本上希望 header 和主要组件与 header 共享状态,以便能够更改它。我们可以使用多种方法使其工作,但最简单的是使用中间 parent 控制器 属性。所以让我们假设 parent 控制器(或组件)定义了这个 view 属性 你想被 header (可以读取和修改)和主要(可以读取)组件使用.

Header组件:输入和输出。

下面是简单的 header 组件:

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

这里最重要的部分是绑定。使用 view: '<' 我们指定 header 组件将能够读取外部内容并将其绑定为自己控制器的 view 属性。使用 onViewChange: '&' 组件定义的输出:notifying/updating 外部世界的通道及其所需。 Header 组件会通过这个通道推送一些数据,但是它不知道 parent 组件会用它做什么,它不应该关心。

所以这意味着 header 控制器可以像

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

主要成分:输入.

主要组件比较简单,只需要定义它接受的输入:

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

Parent 查看

最后全部连接在一起:

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

看看并玩简单的演示。

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>

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


这是一篇博客 post 我写了涵盖 component-based 设计的详细信息:http://dfsq.info/site/read/angular-components-communication

尽管 父组件 方法(通过属性传递数据)是一个完美有效且很好的实现,我们可以用更简单的方法实现同样的事情使用 store 工厂的方式。

基本上,数据由 Store 保存,它在两个组件范围内都被引用,从而在状态更改时启用 UI 的反应式更新。

示例:

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

然后,在你的组件中你应该有这样的东西:

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

如果您想查看工作示例,check out this CodePen

这样做您还可以启用 2 个或 N 个组件之间的通信。您只需:

  1. 注入商店依赖
  2. 确保 link 将数据存储到组件范围

如上例 (<header-component>)。

在现实世界中,典型的应用程序需要管理大量数据,因此以某种方式在逻辑上拆分数据域更有意义。按照相同的方法您可以添加更多商店工厂。例如,要管理当前登录的用户信息和外部资源(即目录),您可以构建一个 UserStore 和一个 CatalogStore -- 或者 UserModelCatalogModel这些实体也是集中诸如与后端通信、添加自定义业务逻辑等内容的好地方。数据管理将由 Store 家工厂全权负责。

请记住,我们正在改变商店数据。虽然这种方法非常简单明了,但它可能无法很好地扩展,因为会产生 side effects. If you want something more advanced (immutability, pure functions, single state tree, etc.) check out Redux, or if you finally want to switch to Angular 2 take a look at ngrx/store.

希望这对您有所帮助! :)

You don't have to do it the Angular 2 way because just in case you would migrate sometimes... Do it if it make sense for you to do it.