【问题标题】:How to pass data between sibling components without using $scope?如何在不使用 $scope 的情况下在兄弟组件之间传递数据?
【发布时间】:2020-02-24 08:55:01
【问题描述】:

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

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

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

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

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

【问题讨论】:

  • 完全一样。为了清楚起见,我不推荐 Subash Selvaraj 的建议。相反,我建议您使用 Angular 2 组件方法并使用输入/输出方法。如果您这样做,您将能够轻松迁移到 ng2。如果你有兴趣,我可以发布一个答案。否则,您当然可以使用自定义事件,但我认为这不是方式。
  • 我使用的是controllerAs语法和vm变量,所以我不想使用$scope或$rootscope。我很想看看你如何解决这个问题。我假设很多开发人员会发现您的答案很有用。所以我很想看看子组件如何一起通信

标签: angularjs components angularjs-controlleras


【解决方案1】:

使用自定义事件来实现这一点。 您可以使用事件调度程序 $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);
});

【讨论】:

  • 我实际上不想在这个上使用 $scope 或 $rootscope。我可以使用 controllerAs 语法和 vm 变量使其工作吗?
  • @BesaNeziri 如果是这种情况,您可以使用工厂在控制器之间共享数据。检查此链接thinkster.io/a-better-way-to-learn-angularjs/services
  • 这是不好的做法。您应该尝试遵循组件文档docs.angularjs.org/guide/component 中描述的方法
  • 这实际上效果很好,并且是最少的代码量。其他开发者推荐使用emitstackoverflow.com/q/37291799/584846velesin.io/2016/05/18/…
  • 如果事件订阅不是直接在组件本身中,而是在 1 层上,这会更好地工作并创建更好的隔离组件。这样,组件本身就被隔离并且更可重用。它是事件订阅层,然后变成特定于应用程序,比组件本身更好。
【解决方案2】:

组件方法

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

所以我们基本上希望标头和主要组件与标头共享状态,以便能够更改它。我们可以使用多种方法使其工作,但最简单的是利用中间父控制器属性。所以让我们假设父控制器(或组件)定义了这个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: '&lt;',我们指定header 组件将能够读取外部内容并将其绑定为自己控制器的view 属性。使用onViewChange: '&amp;' 组件定义输出:用于通知/更新外部世界所需的任何内容的通道。 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

【讨论】:

  • 我只想再问一个问题。在列表中,我按类型排序、分页、分组和过滤。使用输入/输出方法是否是所有这些事情的好习惯。我的代码对所有的绑定和别名都很混乱
  • @BesaNeziri 您不必为所有内容引入输入/输出属性。如果它们在逻辑上属于同一事物,则可以创建单个属性,例如 Input() config,它将是 config.sortingconfig.paginationconfig.filter 等对象。所以它不那么混乱。同样对于复杂的情况,在两个组件中使用通用服务(例如 GridService)来处理这些事情也是有意义的。
  • 这很好,但是您的onViewChange 回调是否有任何理由通过{$event: {view: view}} 而不仅仅是view
  • @Chris 没有理由,这只是一种偏好。我喜欢将回调中推送的数据分组到$event中,这样我就可以在HTML中轻松区分它们并知道它来自组件的&amp;绑定。但当然没有必要。
  • @Matian2040 是的,你可以。但最好避免双向绑定。
【解决方案3】:

虽然 父组件 方法(通过属性传递数据)是一个完美的有效且良好的实现,我们可以使用 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. 确保将商店数据链接到组件范围

就像上面的例子一样 (&lt;header-component&gt;)。

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

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

希望这会有所帮助! :)

您不必使用 Angular 2 方式,因为以防万一 你有时会迁移......如果你觉得这样做是有意义的,那就去做吧。

【讨论】:

  • 非常酷! Angularjs 与 Redux 风格的解决方案。您还可以查看 ng-redux 以获得类似的开箱即用解决方案 (github.com/angular-redux/ng-redux)
  • @KfirErez 很高兴你喜欢它,谢谢!我认为这种方法可以在某种程度上记住 Redux,但后者更明确,因为它需要 dispatch 操作而不是直接修改存储(它还保留整个更改树)。
猜你喜欢
  • 2019-12-30
  • 2018-07-27
  • 1970-01-01
  • 2022-09-28
  • 2019-10-09
  • 2020-12-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多