Angular UI Router - 无父级子状态
Angular UI Router - 无父级子状态
我认为我的目标无法在AngularUI Router中实现-但我还是在这里提出来,以防有人可以证明我是错的,或者有替代解决方案或解决相同问题的解决方法。\n目标\n我想显示模态窗口,这些窗口可以从应用程序的任何位置打开,但可以更改URL。特别是,我想更改URL,以便当浏览器/设备的返回按钮被按下时,模态窗口关闭(即应用程序将返回到用户正在使用的任何父状态)。这样的模态窗口可以在使用应用程序的任何时候由用户打开(例如,从应用程序的主菜单栏中可以访问的帮助窗口)。\n我真的不想将模态状态复制粘贴为每个可能的父状态的子状态(即为每个用户配置文件/搜索结果/主页等注册帮助状态作为子状态)。如果应用程序中只有一个这样的模态窗口,那么这样做可能是可以接受的方法-但是,当您开始在应用程序中引入几个全局可访问的模态子状态时,多个子状态注册开始成为一个真正的问题。\n为了更清楚地说明,这里是一个用户故事:\n
- \n
- 用户正在查看一些搜索结果(他们无限滚动了几页的结果)。
- 有一个操作他们想执行,但他们不确定如何实现,所以他们点击应用程序标题中的帮助图标。
- 打开一个模态对话框,位于他们正在查看的搜索结果上方。
- 他们通过帮助文档进行搜索,并找出他们需要做什么。
- 他们按下设备的返回按钮。
- 模态对话框关闭,显示他们之前查看的状态,不会丢失任何上下文。
- 用户完成任务,对自己感到非常满意-而不是因为愚蠢的用户体验设计而对应用程序开发人员感到恼火。
\n
\n
\n
\n
\n
\n
\n
\n在上述故事中,我唯一能想到的使返回事件关闭模态对话框的方法是将模态对话框与AngularUI Router的状态转换绑定。用户将从搜索结果状态(URL:/search-results
)转到帮助状态(URL:/search-results?help
)-但是,在另一种情况下,他们可能从用户配置文件状态(URL:/profile/123
)转到帮助状态(URL:/profile/123?help
)。关键在于,帮助没有直接注册为搜索结果和配置文件状态的子状态,而是以一种独立的方式作为一种类型的孤立状态,可以应用于任何父状态。\n替代目标\n这不是我首选的解决方案。如果可以使浏览器/设备的返回按钮关闭模态对话框而不更改URL,则可以使这些模态对话框独立于AngularUI Router工作-但我不喜欢这种方法,这意味着对不同类型的视图采用不一致的开发方法(而且谁知道,也许在将来我们会决定其中一个模态窗口应该成为自己的一流状态,这将需要从一种方法改变到另一种方法-这是不可取的)。我还认为这是一种不可靠的方法,因为处理返回事件并不是一件简单的事情,根据我的经验。\n实际上,这对许多情况非常有用(例如,用户可以单击返回按钮关闭子菜单或上下文菜单),我只是认为这不是一种技术可行的解决方案-但随时可以证明我是错的。;-)\n注释\n
- \n
- 我知道可以打开模态子状态-事实上,我已经在将子状态明确绑定到特定父状态的情况下实施了这一点。
- 这是一个以手机为主要用途的应用程序。这意味着返回按钮是一个基本重要的考虑因素-对于手机用户来说,使用返回按钮关闭或取消对话框是正常行为,我绝对不想让我的应用程序用户在已经习惯使用返回按钮的情况下训练他们点击关闭按钮。
- 对不起,我没有代码尝试可以呈现-我不知道如何使其工作,甚至不知道从何开始-我的研究也没有解决问题(也许我用错误的术语搜索?)。
\n
\n
\n
\n提前感谢您提供的任何帮助!\n编辑\n1. 更新了用户故事解释,以包括具体的URL/状态示例,以提高清晰度。
在Angular UI Router中,出现了一个问题,即如何实现没有父状态的子状态。以下是这个问题出现的原因和解决方法。
在这个讨论中,某些情况下了在他的Angular应用程序中使用一个单一的状态作为整个应用程序的父状态。他通过以下代码定义了这个根状态:
$stateProvider .state('app', { url: '/', templateUrl: '/scripts/app/app/views/app.html', controller: 'appController', resolve: { //在这里解析任何应用程序的全局数据 } });
然后他可以将模态框作为这个状态的子状态。这样,当模态框关闭时,他可以始终返回到这个路由,回到应用程序的默认状态。
这种做法的另一个好处是,您可以使用该路由的视图作为布局,放置任何从页面到页面都不会改变的标记(头部,侧栏等)。
然后有人提出了一个问题,即如果他有一个根状态(url: '/'),而他的模态框是根状态的子状态(url: 'my-modal'),但他希望从用户个人资料状态(url: 'profile/{uid}')查看模态框,是否可以在"/profile/123/my-modal"(个人资料状态和模态框状态)的URL上显示模态框层在个人资料页面上方?
对此,另一个人意识到了问题,认识到之前的回答并没有解决这个问题。他表示需要进一步思考。
这个问题的出现的原因是在Angular UI Router中无法直接实现没有父状态的子状态。然而,可以通过重新设计状态和URL结构来解决这个问题。例如,在这种情况下,可以将模态框状态设计为个人资料状态的子状态,而不是根状态的子状态。这样,就可以通过访问"/profile/123/my-modal"的URL来显示模态框层在个人资料页面上方。通过重新设计状态和URL结构,可以在Angular UI Router中解决没有父状态的子状态的问题。
Angular UI Router - Parentless Child States这个问题的出现的原因是在UI Router中,当需要创建一个没有父级状态的子状态时,会遇到一些困难。解决这个问题的方法是创建一个名为hashRouter的服务,通过管理查询数据的序列化和反序列化以及监控$locationChangeSuccess事件来处理URL的外部更改。
hashRouter服务的代码如下:
hashRouter.$inject = [ '$rootScope', '$location' ]; function hashRouter($rootScope, $location) { var service = this, hashString = $location.hash(), hash = fromHashString(hashString); $rootScope.$on('$locationChangeSuccess', function (e, newUrl) { var newHashString = getHashSection(newUrl); if (newHashString != hashString) { var newHash = fromHashString(newHashString); service.hash(newHash.name, newHash.params); } }); service.hash = function (name, params) { var oldHash = hash, oldHashString = hashString; hash = { name: name || '', params: params || {} }; hashString = toHashString(hash); if (hashString !== oldHashString) { var oldHashExists = oldHashString.length > 0; if (oldHashExists) { $rootScope.$broadcast('hashRouteRemoved', oldHash); } if (hashString.length > 0) { $rootScope.$broadcast('hashRouteAdded', hash); } $location.hash(hashString); if (oldHashExists) { $location.replace(); } } }; return service; function toHashString(data) { var newHashString = ''; var name = data.name; if (!!name) { newHashString += encodeURIComponent(name); } var params = data.params; if (!!params) { var paramList = []; for (var prop in params) { var key = encodeURIComponent(prop), value = params.hasOwnProperty(prop) ? encodeURIComponent(params[prop].toString()) : ''; paramList.push(key + '=' + value); } if (paramList.length > 0) { newHashString += ':' + paramList.join('&'); } } return newHashString; } function fromHashString(urlHash) { var parsedHash = { name: '', params: {} }; if (!!urlHash && urlHash.length > 0) { if (urlHash.indexOf(':') !== -1) { var hashSegments = urlHash.split(':'); parsedHash.name = decodeURIComponent(hashSegments[0]); var querySegments = hashSegments[1].split('&'); for (var i = 0; i < querySegments.length; i++) { var pair = querySegments[i].split('='); parsedHash.params[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]) || null; } } else { parsedHash.name = decodeURIComponent(urlHash); } } return parsedHash; } function getHashSection(url) { if (url.indexOf('#') === -1 || (url.indexOf('#!') !== -1 && url.indexOf('#') === url.lastIndexOf('#'))) { return ''; } var urlSegments = url.split('#'); return urlSegments[urlSegments.length - 1]; } } angular.module('myApp').service('hashRouter', hashRouter);
这个服务的一些要注意的事项有:
1. 我自己编写了序列化/反序列化函数,它们并不完善,所以使用时请自己承担风险,或者用更合适的替代品。
2. 这依赖于在非html5模式下使用hash-bang的部分(#!而不是#)。
3. 如果你要操作序列化/反序列化功能,请非常小心:我发现自己陷入了一些无限循环的情况,导致浏览器崩溃。所以一定要进行充分的测试!
4. 你仍然需要在打开/关闭使用该服务的对话框/菜单等时调用该服务,并根据需要监听hashRouteAdded和hashRouteRemoved事件。
5. 我构建了这个系统仅支持一个视图一次 - 如果需要多个视图,则需要自定义代码(虽然我想它可以轻松支持嵌套视图)。
希望这个解决方案能够为其他有类似需求的人节省一些时间。