【问题标题】:How to correctly interact with a view which is managed from another controller?如何正确与另一个控制器管理的视图交互?
【发布时间】:2015-09-20 21:43:54
【问题描述】:

这个问题可能看起来太模糊了,但我想不出更好的方式来描述这个想法,所以我会尝试详细解释一下。

我将 MasterController 附加到我的 SPA 应用程序的 <html> 标签。此 MasterController 包含用于控制以下 UI 元素的所有逻辑和模型:

  • 页面标题(<title>标签)
  • 显示当前页面标题的子标题(如客户、订单、设置等)
  • 当前登录用户名
  • 一些常用的操作按钮,将用于系统中的所有页面。具体来说,这些按钮是Show filtersExport data to ExcelAdd new record

虽然此列表中的前两项可以通过检测当前 ui-router 状态(通过其$stateChangeSuccess 事件)来管理,但后两项(用户名和按钮)有些问题,尤其是按钮。

我可以使用$broadcast 管理按钮操作,因此每个控制器都可以收到任何按钮的点击通知。但这里棘手的部分是按钮可能需要以不同的组合 - 一页可能需要所有按钮,而另一页可能不需要。

假设,ui-router 加载了一些 CustomersController。此时 MasterController 接收到$stateChangeSuccess 事件并默认隐藏所有按钮。

但是现在,CustomersController 如何告诉 MasterController CustomersController 从一开始就需要两个特定按钮?

理论上,我可以使用 CustomersController 中的$emit 向 MasterController 发送事件,但不知何故感觉很难看。事件是用于事件,而不是用于发送诸如“嘿,MasterController,如果您在范围内的某个地方,请显示以下按钮吗?”之类的请求。

当然,我可能是错的,也许有一些方法可以使用 Angular 事件系统以干净的方式管理这个场景。

我想到的是,也许在 $stateChangeSuccess 事件中,我可以以某种方式检测当前是否有任何侦听器用于我的按钮单击事件,然后我可以隐藏没有附加任何侦听器的按钮,但我不知道该怎么做,我不确定它是否会按预期工作 - 当 ui-router 使用另一个控制器重新创建视图时是否会分离旧的侦听器。

【问题讨论】:

    标签: javascript angularjs events view controller


    【解决方案1】:

    如果你只是嵌套控制器,它们对应的作用域实际上使用了原型继承。因此,您可以在 MasterController 中定义一个函数 $scope.configureButtons,然后从嵌套的 CustomerController 中的 $scope 调用此函数。

    如果控制器没有嵌套,您可能需要求助于$rootScope.$broadcast 来设置您的按钮。

    【讨论】:

    • 这很棘手,因为我使用的是controller as 语法。我已经在 SO 上看到了针对此类情况的一些解决方法,但它们似乎都扰乱了从 MasterController 扩展 CustomerController 或其 $scope 的 OOP 想法。这样的继承就好比说CustomerController是MasterController的一个特例,这是一个谎言,因为它们的关系是“容器->包含”,而不是“一般实现->更具体的实现”。但我想我将不得不牺牲 OOP 理想的功能:)
    • 在我看来,保守的 OOP 范式根本不适用于 Angular,并且在处理它时会妨碍你。 Angular 有自己处理 DRY 原则的方法,即将 UX 功能封装到指令中,将数据功能封装到服务中。控制器只是介于两者之间的粘合剂,如果您想充分利用 Angular,您需要非常自律,并将控制器专门用作您的视图模型。同样,根据我的经验,如果你试图遵守这些原则,你会发现自己站在自己的道路上的频率要少得多。 :)
    • 我以我的经验说过,因为当我第一次进入 Angular 时,我在确定最佳实践时遇到了很多困难,并且在我的第一个项目中,只有一个视图,最终以舒适的方式完成了几乎所有事情单个控制器,这对我来说很好,直到我需要扩展该项目并添加另外两个视图。这变得非常痛苦,因为我经常不遵守 DRY,忘记在重复的代码上复制粘贴更新等。然后我花时间构建了应用程序,自从我习惯了这种风格并对此感到非常满意。
    【解决方案2】:

    为什么不简单地为每个视图使用不同的控制器呢?也许可以概括一点 CustomerController 并为您需要的每个按钮组合扩展它(专门化它)。对我来说,使用 $stateChangeSuccess 就像避免多态一样。

    【讨论】:

    • 从某个基础扩展控制器是个好主意,但只有一个问题——视图不能被继承。我必须为由 ui-router 管理的每个视图复制按钮的 HTML 代码(或使用一些复制模板导入机制)。但也许有一些方法可以告诉 ui-router 在将新创建的控制器附加到 HTML 元素时保持现有部分完整并绑定到范围?然后我可以添加一次按钮模板并仅重新加载特定于控制器的视图部分。不过,看起来很棘手,因此我暂时将按钮留在了标题中。
    【解决方案3】:

    今天我根据@Diego Castaño Chillarón 的回答得到了一个棘手的想法。我想 - 但是是否可以使用 ui-router 来交换现有视图的控制器,并且它还会重新绑定 $scope 吗?我还能更换已加载视图的内部吗?

    事实证明这是可行的!现在我不必从主控控制公共视图片段,我也不需要继承或复制它们 - 我只需通过 ui-router 将控制器切换到所需的。

    像这样:

    $stateProvider
        .state("customers", {
                url: "^/customers",
                views: {
                    "controller": {
                        controller: "CustomerController as cntrlr"
                    },                                    
                    "page@customers": // <- this is important, absolute name required for ui-router to find nested view
                    {
                        templateUrl: "customers"
                    }
                }
            }) // other routes follow in the same manner
    

    我的 HTML 看起来像这样:

    <div id="routes-root" ui-view="controller">
    
      <div id="content-header-buttons">
          <button type="button" ng-click="master.toggleFilter()">Filter data</button>
          <button type="button" ng-click="cntrlr.exportClicked()">Export</button>
          <button type="button" ng-click="cntrlr.createNewClicked()">Create</button>
       </div>
        <div id="view-content" ui-view="page"></div>
    </div>
    

    如您所见,我让主控制器只控制过滤器块的可见性,这不会改变。

    但控制器本身附加到 #routes-root 元素,保留内部内容,并且 ui-router(或 Angular)足够智能,可以将 $scope 和 cntrlr 变量附加到加载的控制器。然后我将内部视图加载到#view-content,它也被附​​加到已经加载的控制器上。

    【讨论】:

      猜你喜欢
      • 2011-05-18
      • 1970-01-01
      • 1970-01-01
      • 2023-03-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多