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

时间:2022-02-04 10:30:51

Building a multi-step form (“wizard”). Was originally following this tutorial, which worked great, but am now trying to adapt it so step one is embedded on the homepage rather than being a separate state. No matter what I try, I can not create a ui-sref path that will work. I always get:

构建一个多步骤的表单(“向导”)。本教程非常棒,但是我现在正在尝试对它进行调整,因此第一步是嵌入到主页上,而不是作为一个独立的状态。无论我尝试什么,我都不能创建一个ui-sref路径。我总是:

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

不可能解决的。从国家“回家”

or

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

不能解决的向导。从国家“回家”

or

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

不能解决的向导。where@”状态“回家”

…even though wizard.where@ works fine in <div ui-view="wizard.where@"></div>. What is the correct syntax?

…尽管向导。@在

中工作良好。正确的语法是什么?

Here are the relevant files:

以下是相关文件:

home.js (left comments intact so you can see various methods I’m trying):

家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:

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:

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>

2 个解决方案

#1


41  

I created working plunker here

我在这里创建了工作柱塞

NOTE: You should read about state nesting and named views more. Because the current state and view definition is simply wrong.

注意:您应该阅读关于状态嵌套和命名视图的更多信息。因为当前状态和视图定义是完全错误的。

Firstly, we should not use the ONE state definition with many views: {}. But we should split them into real states. Hierarchy will have three levels

首先,我们不应该使用具有多个视图的一个状态定义:{}。但是我们应该把它们分成真正的状态。层次结构将有三个层次

The first level - super root state

第一级-超级根状态

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

The second level - wizzard, check that now we change the url. We will inherit its first part from our parent (home)

第二级- wizzard,现在检查我们是否更改了url。我们将从父母(家)那里继承第一部分

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

The third level - all where, what, when now will also inherit url. They do not have to define parent, because it is part of their names

第三层- all where, what, when now也将继承url。他们不需要定义父类,因为父类是他们名字的一部分

.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 must now contain unnamed view target ui-view=""

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

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

Current wizard.tpl.html contains this:

当前wizard.tpl。html包含:

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

The sign @ should be avoided, because it could be used for absulte view naming - BUT inside of the state defintion. So, what could work is ui-view="someName

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

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

Now, these are (in example here) view content of the home.tpl

现在,这些是home.tpl的视图内容

<div>
  <h1>HOME</h1>

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

And wizzard.tpl

和wizzard.tpl

<div>
  <h2>WIZZARD</h2>

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

So, we have unnamed view target inside of home and wizard states, That is very handy, because we can use the light state definition, without views : {} object. And that is always preferred in case we do not have multi-views.

因此,我们在home和wizard的状态中有一个未命名的视图目标,这非常方便,因为我们可以使用light状态定义,没有视图:{}对象。如果我们没有多视图,这是首选。

That means, that this state definition will properly be injected into above template:

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

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

Check the doc:

检查医生:

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.

在幕后,每个视图都被赋予一个绝对名称,该名称遵循viewname@statename方案,其中viewname是视图指令中使用的名称,而statename是状态的绝对名称,例如contact.item。您还可以选择以绝对语法编写视图名。

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.

注意,视图名现在被指定为绝对名称,而不是相对名称。它针对的是位于根未命名模板中的“过滤器”、“表数据”和“图形”视图。因为它是匿名的,所以在@后面没有任何东西。未命名的根模板是index.html。

Calling the state from state

从状态调用状态

Whe we want in where state navigate to when, we can use directiv ui-sref, but it must contain state name, not view naming convention

当我们想要在状态导航到何时的位置时,我们可以使用directiv ui-sref,但是它必须包含状态名,而不是视图命名约定

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

The reason, that in this three level hierarchy we do use only parent and child names (not grand parent 'home'), is hidden in state definition. Because we used this:

原因是,在这三层层次结构中,我们只使用父类和子名(而不是父类名),这在状态定义中是隐藏的。因为我们用这个:

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

Parent is just a parent, not part of the state name. Which is good in scenarios like this (we need the root/grand parent to establish some comon stuff, but it name is not needed for substates)

父类只是父类,而不是状态名的一部分。这在这样的场景中是很好的(我们需要root/grand parent来建立一些comon的东西,但是对于子状态不需要它的名称)

Check the doc:

检查医生:

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

将链接(< A >标记)绑定到一个状态的指令。如果状态有一个关联的URL,指令将通过$state.href()方法自动生成和更新href属性。单击该链接将触发具有可选参数的状态转换。

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

可以使用ui- sf -opts属性指定要传递到$state.go()的选项。选项仅限于位置、继承和重载。

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

ui-sref - string - 'stateName'可以是任何有效的绝对或相对状态

#2


3  

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

tep one是嵌入在主页上的,而不是作为一个独立的状态

You should treat each ui-view as a state, but declare wizard.where as the default/index state.

您应该将每个ui视图视为一个状态,但是声明向导。其中为默认/索引状态。

Note that the tutorial uses $urlRouterProvider to make form/profile the default state.

注意,本教程使用$urlRouterProvider使表单/概要文件成为默认状态。

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

In this manner, however, /form will end up as /form/profile.

然而,以这种方式,/form将以/form/profile结束。

You may, however, create an empty URL state with 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 has also provided great insight into UI-Router and state definitions.

@radim-kohler还对UI-Router和状态定义提供了非常深入的理解。

#1


41  

I created working plunker here

我在这里创建了工作柱塞

NOTE: You should read about state nesting and named views more. Because the current state and view definition is simply wrong.

注意:您应该阅读关于状态嵌套和命名视图的更多信息。因为当前状态和视图定义是完全错误的。

Firstly, we should not use the ONE state definition with many views: {}. But we should split them into real states. Hierarchy will have three levels

首先,我们不应该使用具有多个视图的一个状态定义:{}。但是我们应该把它们分成真正的状态。层次结构将有三个层次

The first level - super root state

第一级-超级根状态

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

The second level - wizzard, check that now we change the url. We will inherit its first part from our parent (home)

第二级- wizzard,现在检查我们是否更改了url。我们将从父母(家)那里继承第一部分

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

The third level - all where, what, when now will also inherit url. They do not have to define parent, because it is part of their names

第三层- all where, what, when now也将继承url。他们不需要定义父类,因为父类是他们名字的一部分

.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 must now contain unnamed view target ui-view=""

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

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

Current wizard.tpl.html contains this:

当前wizard.tpl。html包含:

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

The sign @ should be avoided, because it could be used for absulte view naming - BUT inside of the state defintion. So, what could work is ui-view="someName

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

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

Now, these are (in example here) view content of the home.tpl

现在,这些是home.tpl的视图内容

<div>
  <h1>HOME</h1>

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

And wizzard.tpl

和wizzard.tpl

<div>
  <h2>WIZZARD</h2>

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

So, we have unnamed view target inside of home and wizard states, That is very handy, because we can use the light state definition, without views : {} object. And that is always preferred in case we do not have multi-views.

因此,我们在home和wizard的状态中有一个未命名的视图目标,这非常方便,因为我们可以使用light状态定义,没有视图:{}对象。如果我们没有多视图,这是首选。

That means, that this state definition will properly be injected into above template:

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

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

Check the doc:

检查医生:

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.

在幕后,每个视图都被赋予一个绝对名称,该名称遵循viewname@statename方案,其中viewname是视图指令中使用的名称,而statename是状态的绝对名称,例如contact.item。您还可以选择以绝对语法编写视图名。

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.

注意,视图名现在被指定为绝对名称,而不是相对名称。它针对的是位于根未命名模板中的“过滤器”、“表数据”和“图形”视图。因为它是匿名的,所以在@后面没有任何东西。未命名的根模板是index.html。

Calling the state from state

从状态调用状态

Whe we want in where state navigate to when, we can use directiv ui-sref, but it must contain state name, not view naming convention

当我们想要在状态导航到何时的位置时,我们可以使用directiv ui-sref,但是它必须包含状态名,而不是视图命名约定

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

The reason, that in this three level hierarchy we do use only parent and child names (not grand parent 'home'), is hidden in state definition. Because we used this:

原因是,在这三层层次结构中,我们只使用父类和子名(而不是父类名),这在状态定义中是隐藏的。因为我们用这个:

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

Parent is just a parent, not part of the state name. Which is good in scenarios like this (we need the root/grand parent to establish some comon stuff, but it name is not needed for substates)

父类只是父类,而不是状态名的一部分。这在这样的场景中是很好的(我们需要root/grand parent来建立一些comon的东西,但是对于子状态不需要它的名称)

Check the doc:

检查医生:

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

将链接(< A >标记)绑定到一个状态的指令。如果状态有一个关联的URL,指令将通过$state.href()方法自动生成和更新href属性。单击该链接将触发具有可选参数的状态转换。

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

可以使用ui- sf -opts属性指定要传递到$state.go()的选项。选项仅限于位置、继承和重载。

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

ui-sref - string - 'stateName'可以是任何有效的绝对或相对状态

#2


3  

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

tep one是嵌入在主页上的,而不是作为一个独立的状态

You should treat each ui-view as a state, but declare wizard.where as the default/index state.

您应该将每个ui视图视为一个状态,但是声明向导。其中为默认/索引状态。

Note that the tutorial uses $urlRouterProvider to make form/profile the default state.

注意,本教程使用$urlRouterProvider使表单/概要文件成为默认状态。

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

In this manner, however, /form will end up as /form/profile.

然而,以这种方式,/form将以/form/profile结束。

You may, however, create an empty URL state with 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 has also provided great insight into UI-Router and state definitions.

@radim-kohler还对UI-Router和状态定义提供了非常深入的理解。