Angular-UI 路由器:嵌套视图不工作

Angular-UI Router: Nested Views Not Working

构建多步骤表单(“向导”)。最初关注 this tutorial,效果很好,但现在我正在尝试对其进行调整,以便将第一步嵌入主页而不是单独的状态。无论我尝试什么,我都无法创建可行的 ui-sref 路径。我总是得到:

Could not resolve '.where' from state 'home'

Could not resolve 'wizard.where' from state 'home'

Could not resolve 'wizard.where@' from state 'home'

…即使 wizard.where@<div ui-view="wizard.where@"></div> 中工作正常。正确的语法是什么?

相关文件如下:

home.js(完整保留评论,以便您可以看到我正在尝试的各种方法):

var wizard = {
  url: '/home/wizard',
  controller: 'VendorsCtrl',
  templateUrl: 'vendors/wizard.tpl.html'
};

angular.module( 'myApp.home', [
  'ui.router',
  'ui.bootstrap',
  'myApp.modal',
  'angularMoment'
])

.config(function config( $stateProvider, $urlRouterProvider ) {
  $stateProvider
    .state( 'home', {
      url: '/home',
      views: {
        "main": {
          controller: 'HomeCtrl',
          templateUrl: 'home/home.tpl.html'
        },
        "jumbotron": {
          controller: 'HomeCtrl',
          templateUrl: 'home/welcome.tpl.html'
        },
        "wizard": wizard,
        "wizard.where": {
          url: '/home/wizard/where',
          controller: 'VendorsCtrl',
          templateUrl: 'vendors/wizard-where.tpl.html',
          parent: wizard
        },
        "wizard.what": {
          url: '/home/wizard/what',
          controller: 'VendorsCtrl',
          templateUrl: 'vendors/wizard-what.tpl.html',
          parent: wizard
        },
        "wizard.when": {
          url: '/home/wizard/when',
          controller: 'VendorsCtrl',
          templateUrl: 'vendors/wizard-when.tpl.html',
          parent: wizard
        },
      },
      data: { pageTitle: 'Home' }
    })

    // route to show our basic form (/wizard)
    // .state('wizard', {
    //   url: '/wizard',
    //   views: {
    //     "main": {
    //       controller: 'VendorsCtrl',
    //       templateUrl: 'vendors/wizard.tpl.html'
    //     }
    //   },
    //   abstract: true,
    //   //data: { pageTitle: 'Vendor Search' }
    // })

    // nested states 
    // each of these sections will have their own view
    // url will be nested (/wizard/where)
    // .state('wizard.where', {
    //   url: '/where',
    //   templateUrl: 'vendors/wizard-where.tpl.html'
    // })

    // url will be /wizard/when
    // .state('wizard.when', {
    //   url: '/when',
    //   templateUrl: 'vendors/wizard-when.tpl.html'
    // })

    // url will be /wizard/vendor-types
    // .state('wizard.what', {
    //   url: '/what',
    //   templateUrl: 'vendors/wizard-what.tpl.html'
    // })
    ;

    // catch all route
    // send users to the form page 
    $urlRouterProvider.otherwise('/home/wizard/where');
})

wizard.tpl.html:

<div class="jumbotron vendate-wizard" ng-controller="VendorsCtrl as vendorsCtrl">
  <header class="page-title">
    <h1>{{ pageTitle }}</h1>
    <p>Answer the following three questions to search available vendors. All answers can be changed later.</p>

    <!-- the links to our nested states using relative paths -->
    <!-- add the active class if the state matches our ui-sref -->
    <div id="status-buttons" class="text-center">
      <a ui-sref-active="active" ui-sref="wizard.where@"><span>1</span> Where</a>
      <a ui-sref-active="active" ui-sref="wizard.what@"><span>2</span> What</a>
      <a ui-sref-active="active" ui-sref="wizard.when@"><span>3</span> When</a>
    </div>
  </header>

  <!-- use ng-submit to catch the form submission and use our Angular function -->
  <form id="signup-form" ng-submit="processForm()">

    <!-- our nested state views will be injected here -->
    <div id="form-views" ui-view="wizard.where@"></div>
  </form>
</div>

wizard.where.tpl.html:

<div class="form-group">
  <label class="h2" for="where">Where Is Your Wedding?</label>
  <p id="vendor-where-description">If left blank, vendors in all available locations will be shown.</p>
  <div class="input-group-lg">
    <input id="where" ng-model="formData.where" class="form-control" type="text" placeholder="Boston, MA" aria-describedby="vendor-where-description" />
  </div>
</div>

<ul class="list-inline">
  <li>
    <a ui-sref="wizard.what@" class="btn btn-block btn-primary">
      Next <span class="fa fa-arrow-right"></span>
    </a>
  </li>
</ul>

我创造了working plunker here

注意:您应该阅读更多有关状态嵌套和命名视图的内容。因为当前状态和视图定义根本就是错误的。

首先,我们不应该将ONE状态定义与许多views: {}一起使用。但是我们应该将它们拆分为真实状态。层次结构将有 三个级别

一级——超级根国

.state( 'home', {
  url: '/home',
  views: {
    "main": {
      controller: 'HomeCtrl',
      templateUrl: 'home/home.tpl.html'
    },
  }
})

第二关-wizzard,检查现在我们更改url。我们将从 parent (home)

继承它的第一部分
.state("wizard", {
  parent: 'home',
  //url: '/home/wizard',
  url: '/wizard',
  controller: 'VendorsCtrl',
  templateUrl: 'vendors/wizard.tpl.html'
})

第三层——所有where, what, when now也将继承url。他们不必定义 parent,因为它是他们名字的一部分

.state( "wizard.where",  {
      //url: '/home/wizard/where',
      url: '/where',
      controller: 'VendorsCtrl',
      templateUrl: 'vendors/wizard-where.tpl.html',
      //parent: wizard
})
.state( "wizard.what",  {
      //url: '/home/wizard/what',
      url: '/what',
      controller: 'VendorsCtrl',
      templateUrl: 'vendors/wizard-what.tpl.html',
      //parent: wizard
})
.state( "wizard.when",  {
      //url: '/home/wizard/when',
      url: '/when',
      controller: 'VendorsCtrl',
      templateUrl: 'vendors/wizard-when.tpl.html',
      //parent: wizard
})

Parent wizzard 现在必须包含未命名的视图目标 ui-view=""

<div ui-view=""></div>

当前 wizard.tpl.html 包含:

<!-- our nested state views will be injected here -->
<div id="form-views" ui-view="wizard.where@"></div>

应该避免符号@,因为它可以用于绝对视图命名——但在状态定义内部。所以,可行的是 ui-view="someName

<!-- our nested state views will be injected here -->
<div id="form-views" ui-view="someName"></div>

现在,这些是home.tpl

的(in example here)条查看内容
<div>
  <h1>HOME</h1>

  <div ui-view=""></div>
</div>

wizzard.tpl

<div>
  <h2>WIZZARD</h2>

  <div ui-view=""></div>
</div>

所以,我们在 homewizard 状态中有未命名的视图目标,这非常方便,因为我们可以使用光状态定义,没有 views : {} object。如果我们没有 multi-views.

,那总是首选

这意味着,这个状态定义将正确地注入到上面的模板中:

// no views - search in parent for a ui-view=""
...
.state( "wizard.when",  {
      url: '/when',
      controller: 'VendorsCtrl',
      templateUrl: 'vendors/wizard-when.tpl.html',
})
...

查看文档:

View Names - Relative vs. Absolute Names

Behind the scenes, every view gets assigned an absolute name that follows a scheme of viewname@statename, where viewname is the name used in the view directive and state name is the state's absolute name, e.g. contact.item. You can also choose to write your view names in the absolute syntax.

For example, the previous example could also be written as:

.state('report',{
    views: {
      'filters@': { },
      'tabledata@': { },
      'graph@': { }
    }
})

Notice that the view names are now specified as absolute names, as opposed to the relative name. It is targeting the 'filters', 'tabledata', and 'graph' views located in the root unnamed template. Since it's unnamed, there is nothing following the '@'. The root unnamed template is your index.html.

从状态调用状态

当我们想在where state导航到什么时候,我们可以使用directiv ui-sref,但是它必须包含state name,而不是视图命名约定

// instead of this
<a ui-sref="wizard.what@"
we need this
<a ui-sref="wizard.what"

原因是,在这个三级层次结构中,我们只使用 parent 和 child 名称 (不是大 parent 'home'),隐藏在状态定义中。因为我们使用了这个:

.state("wizard", {
  parent: 'home',

Parent 只是一个 parent,不是州名的一部分。这在这种情况下很好 (我们需要 root/grand parent 来建立一些通用的东西,但子状态不需要它的名称)

查看文档:

ui-sref

A directive that binds a link (<a> tag) to a state. If the state has an associated URL, the directive will automatically generate & update the href attribute via the $state.href() method. Clicking the link will trigger a state transition with optional parameters.
...

You can specify options to pass to $state.go() using the ui-sref-opts attribute. Options are restricted to location, inherit, and reload.

ui-sref - string - 'stateName' can be any valid absolute or relative state

[S]tep one is embedded on the homepage rather than being a separate state

您应该将每个 ui 视图视为一个状态,但将 wizard.where 声明为 default/index state

请注意,本教程使用 $urlRouterProvider 使 form/profile 成为默认状态。

// catch all route
// send users to the form page 
$urlRouterProvider.otherwise('/form/profile');

但是,通过这种方式,/form 最终会变成 /form/profile

但是,您可以使用 minor modification:

创建一个空的 URL 状态
// route to show our basic form (/form)
.state('form', {
    url: '/form',
    templateUrl: 'form.html',
    controller: 'formController',
    abstract: true //<-- Declare parent as an abstract state. 
})

// nested states 
// each of these sections will have their own view
// url will be nested (/form)
.state('form.profile', {
    url: '', //<-- Empty string for "profile" state to override the /form abstract state
    templateUrl: 'form-profile.html'
})

// catch all route
// send users to the form page
$urlRouterProvider.otherwise('/form'); //<-- Default state is empty

@radim-köhler 还提供了对 UI-路由器和状态定义的深刻见解。