50

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

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

主要组件包含英雄列表。标题组件包含两个按钮,假设将主组件上的视图切换到列表或网格视图。

我现在遇到的问题是将数据从标头组件传递到主组件。因此,当我单击网格按钮时,主要内容上的视图应更改为网格视图,与行视图相同。

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

4

3 回答 3

87

组件方法

我建议您与 Angular 2 组件方法保持一致并使用输入/输出方法。如果这样做,您将能够轻松迁移到 Angular 2,因为组件在概念上是相同的(仅在语法上有所不同)。所以这就是你这样做的方式。

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

标头组件:输入和输出。

以下是简单的标头组件的外观:

.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: '&'组件定义的输出:用于通知/更新外部世界所需的任何内容的通道。Header 组件会通过这个通道推送一些数据,但是它不知道父组件会用它做什么,它应该不在乎。

所以这意味着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: '<'
  }
})

父视图

最后,这一切都连接在一起:

<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


这是我写的一篇博客文章,详细介绍了基于组件的设计:http: //dfsq.info/site/read/angular-components-communication

于 2016-03-16T11:59:26.883 回答
16

尽管父组件方法(通过属性传递数据)是一个完美有效且良好的实现,但我们可以使用存储工厂以更简单的方式实现相同的目标。

基本上,数据由 保存,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>
            ...
        `

如果您想查看一个工作示例,请查看此 CodePen

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

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

就像上面的例子一样(<header-component>)。

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

请记住,我们正在改变存储数据。虽然这种方法非常简单明了,但它可能无法很好地扩展,因为会产生副作用。如果您想要更高级的东西(不变性、纯函数、单状态树等),请查看Redux,或者如果您最终想切换到 Angular 2,请查看ngrx/store

希望这可以帮助!:)

您不必以 Angular 2 的方式执行此操作,因为以防万一 您有时会迁移……如果您这样做有意义,请执行此操作。

于 2016-10-25T13:56:10.857 回答
6

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

希望能帮助到你

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

示例:您可以从标题组件通知如下更改

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

你可以监听你的 main-component 指令的变化,比如

$rootScope.$on("menu-changed", function(evt, arg){
  console.log(arg);
});
于 2016-03-16T11:25:41.013 回答