AngularJS - 跨多个控制器/ UI元素管理URL更改

时间:2021-12-11 18:50:19

Building an Angular application, I'm a fan of using ng-controller and custom directives to structure the app, e.g.:

构建一个Angular应用程序,我是使用ng-controller和自定义指令构建应用程序的粉丝,例如:

<nav ng-controller="NavController"></nav>
<date-picker></date-picker>

I like these patterns because they're declarative, and really easy to follow - you know exactly where to look to find the supporting logic.

我喜欢这些模式,因为它们是声明性的,并且非常容易理解 - 您确切地知道在哪里寻找支持逻辑。

On the other hand, using ng-view, or even the ui-router plugin (to my mind) abstracts away some of the functionality and makes it harder to read.

另一方面,使用ng-view,甚至ui-router插件(在我看来)抽象出一些功能并使其更难阅读。

However, my preferred pattern gives me a problem when it comes to responding to URL changes app-wide in a logical way. I would like each part of the UI to respond appropriately to URL changes, but I don't know where to put the code. I have assumed I have two options:

但是,我的首选模式在以逻辑方式响应应用程序范围内的URL更改时给我一个问题。我希望UI的每个部分都能适当地响应URL更改,但我不知道在哪里放置代码。我假设我有两个选择:

  1. Respond to $location changes individually in each controller or directive
  2. 在每个控制器或指令中单独响应$ location更改

  3. Encapsulate this in some other class or service that maintains responsibility for listening to URL changes
  4. 将此封装在其他类或服务中,该类或服务负责监听URL更改

I can't quite figure out how to do either.

我不知道怎么做。

Option one gives me complete control, but I'm unable to take advantage of ng-route - parsing parameters, etc. I'd really have to roll my own when it comes to inspecting URL changes.

选项一让我完全控制,但我无法利用ng-route - 解析参数等。在检查URL更改时,我真的必须自己动手。

With option two, I can't reference any of my controllers, because they've been declared already!

对于选项二,我无法引用任何控制器,因为它们已经被声明了!

More broadly, my concern is that not finding a solution to this right away suggests that I must be thinking about things incorrectly. Is this fundamentally wrong?

更广泛地说,我担心的是,没有立即找到解决方案表明我必须正确地思考问题。这根本错了吗?

EDIT: For the avoidance of doubt, I very much AM talking about an SPA, and more specifically, this is the sort of thing I'm trying to do:

编辑:为了避免疑问,我非常谈论SPA,更具体地说,这是我正在尝试做的事情:

<nav ng-controller="NavController"><!-- more HTML here --></nav>
<div ng-controller="VideoController"><!-- more HTML here --></div>
<div ng-controller="CommentController" ng-show="Active"><!-- more HTML here --></div>

Now, suppose I navigate to: "/video/:key/" - I want the VideoController to pick up the video key, and do what it needs to do to load a video. I want the NavController to highlight which menu item is active, etc.

现在,假设我导航到:“/ video /:key /” - 我希望VideoController获取视频密钥,并执行加载视频所需的操作。我希望NavController突出显示哪个菜单项处于活动状态等。

If I navigate to "/video/:videokey/comment" - I want the VideoController to load a video, AND the NavController to highlight which is active, AND the CommentController to load comments / appear into video, etc.

如果我导航到“/ video /:videokey / comment” - 我希望VideoController加载一个视频,并使用NavController突出显示哪个是活动的,以及CommentController加载评论/出现在视频中等等。

It's this idea I haven't got my head around - responding to a URL change in TWO controllers, each responsible for a separate part of the UI in a Single Page Application.

这个想法我没有理解 - 响应两个控制器中的URL更改,每个控制器负责单页应用程序中UI的单独部分。

6 个解决方案

#1


4  

This isn't the answer you're looking for, but it is the answer that (IMO) you need to hear.

这不是您正在寻找的答案,但它是您需要听到的答案(IMO)。

Opinions away!

You'll spend a lot of time building the SPA around your own layout and routing ideas. It wouldn't surprise me if this were possible - if you emit events on the appropriate scope and have child controllers listen for state change events, you can define different controllers that respond differently to state changes, showing and hiding their contents, and updating those contents to reflect the current object model (/things/1 to things/2 to things/2/edit or whatever).

您将花费大量时间围绕自己的布局和路线创建SPA。如果可能的话,我不会感到惊讶 - 如果您在适当的范围内发出事件并让子控制器监听状态更改事件,您可以定义不同的控制器,这些控制器对状态更改做出不同的响应,显示和隐藏其内容,并更新这些内容反映当前对象模型(/ things / 1到things / 2到things / 2 / edit或者其他)。

This is pretty much how ui-router works already, except that instead of embedding the top-level html content in the main pages it uses ui-view directives which basically handle partials and controllers.

这几乎是ui-router的工作方式,除了不是在主页面中嵌入*html内容,而是使用基本上处理局部和控制器的ui-view指令。

At the end of the day, doing this, you'll have reproduced about 50% of what ui-router currently does. If you're lucky, it will work as well as ui-router. If you're not, it'll be buggy in funny ways.

在一天结束时,这样做,你将复制ui-router目前所做的大约50%。如果你很幸运,它将和ui-router一样好用。如果你不是,它会以有趣的方式出错。

What you'll have is a huuuuuge single page application which will be very difficult to debug. If you have everything in a single top-level html page with no included templates, you won't easily be able to separate concerns or decompose behaviors. Some other developer wouldn't be able to follow the work, and subtle bugs with unterminated tags may be harder to find. Plus: all that time fighting the framework could be spent working on getting your application to work.

你将拥有一个huuuuuge单页面应用程序,它将很难调试。如果您在一个没有包含模板的*html页面中拥有所有内容,则您将无法轻松分离关注点或分解行为。其他一些开发人员无法跟进这项工作,而使用未终结标签的细微错误可能更难找到。另外:与框架作斗争的所有时间都可以用于使应用程序正常工作。

#2


2  

As you suggest using ngRoute, you could just inject the $rootParams-service into your controllers and get all the current params. If you also need the current controller selected by ngRoute, you should inject the $route-service as well. See the example in the ngRoute documentation for more details on how to use these services - or provide a (minimal) version of your angular code to give you an example based on your use case.

正如您建议使用ngRoute,您可以将$ rootParams-service注入控制器并获取所有当前的参数。如果你还需要ngRoute选择的当前控制器,你也应该注入$ route-service。有关如何使用这些服务的更多详细信息,请参阅ngRoute文档中的示例 - 或提供角度代码的(最小)版本,以根据您的用例提供示例。

BTW: If you also have different views and/or layouts (for example not only video) in your SPA, you could use ngRoute's ng-view directive to set up a viewport for all these different layouts. In that way each layout can have it's own template and a layout specific controller that can reuse generic controllers and directives (for example for comments). Again I need more code to give an meaningful example for that.

顺便说一句:如果SPA中也有不同的视图和/或布局(例如不仅仅是视频),您可以使用ngRoute的ng-view指令为所有这些不同的布局设置视口。这样,每个布局都可以拥有自己的模板和特定于布局的控制器,可以重用通用控制器和指令(例如用于注释)。我再次需要更多代码来为此提供一个有意义的示例。

#3


1  

For what you want to do, I see three options.

对于你想做的事,我看到三个选择。

  1. Use $rootScope to tie your controllers together; keep your URL variables here. When they change, your controllers can react accordingly. You could use $broadcast and $emit here for message passing, or you could just watch a $rootScope variable.
  2. 使用$ rootScope将控制器连接在一起;在这里保留您的URL变量。当他们改变时,您的控制器可以做出相应的反你可以使用$ broadcast和$ emit来传递消息,或者你可以只看$ rootScope变量。

  3. Encapsulate your routing in a publish/subscribe service. The service would handle your routes, while your controllers would subscrible to any published changes. This could be elegant, depending on the complexity of the behavior you want.
  4. 将您的路由封装在发布/订阅服务中。该服务将处理您的路由,而您的控制器将订阅任何已发布的更改。这可能很优雅,具体取决于您想要的行为的复杂程度。

  5. Use nested controllers. The outer controller would handle your routing; the inner controllers, your views.
  6. 使用嵌套控制器。外部控制器将处理您的路由;内部控制者,你的意见。

It sounds as though you want to avoid putting routing logic in each controller - which is good - but you'll have to find some way to specify the behavior you want. If I were going to try to roll this from scratch, I think I'd prefer the publish/subscribe route, myself; I prefer encapsulation in a provider for something like this (as it's modular and can be reused easily).

听起来好像你想避免在每个控制器中放置路由逻辑 - 这很好 - 但你必须找到一些方法来指定你想要的行为。如果我打算尝试从头开始,我想我更喜欢发布/订阅路线,我自己;我更喜欢在提供程序中封装这样的东西(因为它是模块化的,可以很容易地重用)。

That said, I agree with the other answers here - it'd be far easier to simply use ngRoute or uiRouter. By doing this, you can set your routes in one place (along wtih route parameters) and either use nested controllers/views, or directives. You may consider converting your Video and Coments objects to Angular directives, actually.

也就是说,我同意这里的其他答案 - 简单地使用ngRoute或uiRouter要容易得多。通过这样做,您可以在一个地方(沿着路由参数)设置路由,并使用嵌套的控制器/视图或指令。实际上,您可以考虑将Video和Coments对象转换为Angular指令。

#4


0  

Try angular-ui router. It can be library you need. Project on Github: https://github.com/angular-ui/ui-router

尝试angular-ui路由器。它可以是您需要的图书馆。 Github项目:https://github.com/angular-ui/ui-router

#5


0  

I guess you are not talking about SPA, Single Page Application.

我想你不是在谈论SPA,单页应用程序。

So question is,

所以问题是,

Do you want to use the same $routeProvider for many pages and provide the right controller to the current page path?

你想为许多页面使用相同的$ routeProvider并为当前页面路径提供正确的控制器吗?

Sadly, the url path and angularjs route path is different, and you cannot use url path to AngularJS route path.

遗憾的是,url路径和angularjs路径路径是不同的,并且您不能使用URL路径到AngularJS路径。

To get around, this is my idea.
We can determine angularjs route path from the absolute pate.

为了解决这个问题,这是我的想法。我们可以从绝对头部确定angularjs路径路径。

  1. In your app, get the current path using $window.location.href or $window.location.pathname

    在您的应用程序中,使用$ window.location.href或$ window.location.pathname获取当前路径

  2. Use that path inside your otherwise block.

    在你的块中使用该路径。

Here is the code and demo: http://plnkr.co/edit/OS38E38J11FeDS1hyHde?p=preview

以下是代码和演示:http://plnkr.co/edit/OS38E38J11FeDS1hyHde?p = preview

  angular.module('myApp', ['ngRoute'])
    .controller('MainController', function($scope) {
     $scope.pageName = "main page";
   })
   .controller('Page1Controller', function($scope) {
     $scope.pageName = "page1";
   })
   .controller('Page2Controller', function($scope) {
     $scope.pageName = "page2";
   })
  .config(function($routeProvider, $windowProvider) {
    var otherwiseFunc = function() {
      var absPath = $windowProvider.$get().location.href;
      console.log('absPath', absPath);

      var redirectTo =  absPath.match(/path1/) ? '/page1' :
        absPath.match(/path2/) ? '/page2' :
        '/main';
      return {
        redirectTo: redirectTo
      }
    };

    $routeProvider
    .when('/main',  {templateUrl: 'partial.html',controller: 'MainController'})
    .when('/page1', {templateUrl: 'partial.html',controller: 'Page1Controller' })
    .when('/page2', {templateUrl: 'partial.html',controller: 'Page2Controller' })
    .otherwise(
      otherwiseFunc()
    );
  });

#6


0  

Probably the easiest way which will cover everything you need is creating a service for handling URLs.

可能最简单的方法是覆盖您需要的一切,即创建一个处理URL的服务。

The service would return a set of parameters relevant for each controller that depends on the URL. Example, it would have a property Video.id to denote a video id, Nav.id to denote the nav link, etc.

该服务将返回一组与每个控制器相关的参数,这些参数取决于URL。例如,它将具有一个属性Video.id来表示视频ID,Nav.id来表示导航链接等。

The service itself would be very simple - fetching the URL, splitting it into parameters, and the hardest part is separating what parameter is needed for which controller (to build an object following the rules from the example above).

服务本身非常简单 - 获取URL,将其拆分为参数,最难的部分是分离哪个控制器所需的参数(根据上述示例中的规则构建对象)。


An example URL: /video/mySupercoolVideo/comment

示例网址:/ video / mySupercoolVideo / comment

The service returns something like:

该服务返回如下内容:

{
    video: "mySupercoolVideo",
    nav: "comment",
    comments: true
    // other url-related data here
}

This is a very simple and basic example (nav and comments seem redundant, and I didn't expand any of the top-level properties, so no stuff like video.id, etc), obviously you can extend/optimize this a lot.

这是一个非常简单和基本的例子(导航和注释似乎是多余的,我没有扩展任何*属性,因此没有像video.id这样的东西),显然你可以扩展/优化这个。

Each controller would check for the URL state at the beginning and work according to the response.

每个控制器都会在开始时检查URL状态,并根据响应进行操作。

If you wish, you can also use a wrapper-controller for this, so instead of returning the same data for each controller, you would just populate one $rootScope variable with that data.

如果您愿意,您也可以使用包装器控制器,因此您不必为每个控制器返回相同的数据,而只需使用该数据填充一个$ rootScope变量。

#1


4  

This isn't the answer you're looking for, but it is the answer that (IMO) you need to hear.

这不是您正在寻找的答案,但它是您需要听到的答案(IMO)。

Opinions away!

You'll spend a lot of time building the SPA around your own layout and routing ideas. It wouldn't surprise me if this were possible - if you emit events on the appropriate scope and have child controllers listen for state change events, you can define different controllers that respond differently to state changes, showing and hiding their contents, and updating those contents to reflect the current object model (/things/1 to things/2 to things/2/edit or whatever).

您将花费大量时间围绕自己的布局和路线创建SPA。如果可能的话,我不会感到惊讶 - 如果您在适当的范围内发出事件并让子控制器监听状态更改事件,您可以定义不同的控制器,这些控制器对状态更改做出不同的响应,显示和隐藏其内容,并更新这些内容反映当前对象模型(/ things / 1到things / 2到things / 2 / edit或者其他)。

This is pretty much how ui-router works already, except that instead of embedding the top-level html content in the main pages it uses ui-view directives which basically handle partials and controllers.

这几乎是ui-router的工作方式,除了不是在主页面中嵌入*html内容,而是使用基本上处理局部和控制器的ui-view指令。

At the end of the day, doing this, you'll have reproduced about 50% of what ui-router currently does. If you're lucky, it will work as well as ui-router. If you're not, it'll be buggy in funny ways.

在一天结束时,这样做,你将复制ui-router目前所做的大约50%。如果你很幸运,它将和ui-router一样好用。如果你不是,它会以有趣的方式出错。

What you'll have is a huuuuuge single page application which will be very difficult to debug. If you have everything in a single top-level html page with no included templates, you won't easily be able to separate concerns or decompose behaviors. Some other developer wouldn't be able to follow the work, and subtle bugs with unterminated tags may be harder to find. Plus: all that time fighting the framework could be spent working on getting your application to work.

你将拥有一个huuuuuge单页面应用程序,它将很难调试。如果您在一个没有包含模板的*html页面中拥有所有内容,则您将无法轻松分离关注点或分解行为。其他一些开发人员无法跟进这项工作,而使用未终结标签的细微错误可能更难找到。另外:与框架作斗争的所有时间都可以用于使应用程序正常工作。

#2


2  

As you suggest using ngRoute, you could just inject the $rootParams-service into your controllers and get all the current params. If you also need the current controller selected by ngRoute, you should inject the $route-service as well. See the example in the ngRoute documentation for more details on how to use these services - or provide a (minimal) version of your angular code to give you an example based on your use case.

正如您建议使用ngRoute,您可以将$ rootParams-service注入控制器并获取所有当前的参数。如果你还需要ngRoute选择的当前控制器,你也应该注入$ route-service。有关如何使用这些服务的更多详细信息,请参阅ngRoute文档中的示例 - 或提供角度代码的(最小)版本,以根据您的用例提供示例。

BTW: If you also have different views and/or layouts (for example not only video) in your SPA, you could use ngRoute's ng-view directive to set up a viewport for all these different layouts. In that way each layout can have it's own template and a layout specific controller that can reuse generic controllers and directives (for example for comments). Again I need more code to give an meaningful example for that.

顺便说一句:如果SPA中也有不同的视图和/或布局(例如不仅仅是视频),您可以使用ngRoute的ng-view指令为所有这些不同的布局设置视口。这样,每个布局都可以拥有自己的模板和特定于布局的控制器,可以重用通用控制器和指令(例如用于注释)。我再次需要更多代码来为此提供一个有意义的示例。

#3


1  

For what you want to do, I see three options.

对于你想做的事,我看到三个选择。

  1. Use $rootScope to tie your controllers together; keep your URL variables here. When they change, your controllers can react accordingly. You could use $broadcast and $emit here for message passing, or you could just watch a $rootScope variable.
  2. 使用$ rootScope将控制器连接在一起;在这里保留您的URL变量。当他们改变时,您的控制器可以做出相应的反你可以使用$ broadcast和$ emit来传递消息,或者你可以只看$ rootScope变量。

  3. Encapsulate your routing in a publish/subscribe service. The service would handle your routes, while your controllers would subscrible to any published changes. This could be elegant, depending on the complexity of the behavior you want.
  4. 将您的路由封装在发布/订阅服务中。该服务将处理您的路由,而您的控制器将订阅任何已发布的更改。这可能很优雅,具体取决于您想要的行为的复杂程度。

  5. Use nested controllers. The outer controller would handle your routing; the inner controllers, your views.
  6. 使用嵌套控制器。外部控制器将处理您的路由;内部控制者,你的意见。

It sounds as though you want to avoid putting routing logic in each controller - which is good - but you'll have to find some way to specify the behavior you want. If I were going to try to roll this from scratch, I think I'd prefer the publish/subscribe route, myself; I prefer encapsulation in a provider for something like this (as it's modular and can be reused easily).

听起来好像你想避免在每个控制器中放置路由逻辑 - 这很好 - 但你必须找到一些方法来指定你想要的行为。如果我打算尝试从头开始,我想我更喜欢发布/订阅路线,我自己;我更喜欢在提供程序中封装这样的东西(因为它是模块化的,可以很容易地重用)。

That said, I agree with the other answers here - it'd be far easier to simply use ngRoute or uiRouter. By doing this, you can set your routes in one place (along wtih route parameters) and either use nested controllers/views, or directives. You may consider converting your Video and Coments objects to Angular directives, actually.

也就是说,我同意这里的其他答案 - 简单地使用ngRoute或uiRouter要容易得多。通过这样做,您可以在一个地方(沿着路由参数)设置路由,并使用嵌套的控制器/视图或指令。实际上,您可以考虑将Video和Coments对象转换为Angular指令。

#4


0  

Try angular-ui router. It can be library you need. Project on Github: https://github.com/angular-ui/ui-router

尝试angular-ui路由器。它可以是您需要的图书馆。 Github项目:https://github.com/angular-ui/ui-router

#5


0  

I guess you are not talking about SPA, Single Page Application.

我想你不是在谈论SPA,单页应用程序。

So question is,

所以问题是,

Do you want to use the same $routeProvider for many pages and provide the right controller to the current page path?

你想为许多页面使用相同的$ routeProvider并为当前页面路径提供正确的控制器吗?

Sadly, the url path and angularjs route path is different, and you cannot use url path to AngularJS route path.

遗憾的是,url路径和angularjs路径路径是不同的,并且您不能使用URL路径到AngularJS路径。

To get around, this is my idea.
We can determine angularjs route path from the absolute pate.

为了解决这个问题,这是我的想法。我们可以从绝对头部确定angularjs路径路径。

  1. In your app, get the current path using $window.location.href or $window.location.pathname

    在您的应用程序中,使用$ window.location.href或$ window.location.pathname获取当前路径

  2. Use that path inside your otherwise block.

    在你的块中使用该路径。

Here is the code and demo: http://plnkr.co/edit/OS38E38J11FeDS1hyHde?p=preview

以下是代码和演示:http://plnkr.co/edit/OS38E38J11FeDS1hyHde?p = preview

  angular.module('myApp', ['ngRoute'])
    .controller('MainController', function($scope) {
     $scope.pageName = "main page";
   })
   .controller('Page1Controller', function($scope) {
     $scope.pageName = "page1";
   })
   .controller('Page2Controller', function($scope) {
     $scope.pageName = "page2";
   })
  .config(function($routeProvider, $windowProvider) {
    var otherwiseFunc = function() {
      var absPath = $windowProvider.$get().location.href;
      console.log('absPath', absPath);

      var redirectTo =  absPath.match(/path1/) ? '/page1' :
        absPath.match(/path2/) ? '/page2' :
        '/main';
      return {
        redirectTo: redirectTo
      }
    };

    $routeProvider
    .when('/main',  {templateUrl: 'partial.html',controller: 'MainController'})
    .when('/page1', {templateUrl: 'partial.html',controller: 'Page1Controller' })
    .when('/page2', {templateUrl: 'partial.html',controller: 'Page2Controller' })
    .otherwise(
      otherwiseFunc()
    );
  });

#6


0  

Probably the easiest way which will cover everything you need is creating a service for handling URLs.

可能最简单的方法是覆盖您需要的一切,即创建一个处理URL的服务。

The service would return a set of parameters relevant for each controller that depends on the URL. Example, it would have a property Video.id to denote a video id, Nav.id to denote the nav link, etc.

该服务将返回一组与每个控制器相关的参数,这些参数取决于URL。例如,它将具有一个属性Video.id来表示视频ID,Nav.id来表示导航链接等。

The service itself would be very simple - fetching the URL, splitting it into parameters, and the hardest part is separating what parameter is needed for which controller (to build an object following the rules from the example above).

服务本身非常简单 - 获取URL,将其拆分为参数,最难的部分是分离哪个控制器所需的参数(根据上述示例中的规则构建对象)。


An example URL: /video/mySupercoolVideo/comment

示例网址:/ video / mySupercoolVideo / comment

The service returns something like:

该服务返回如下内容:

{
    video: "mySupercoolVideo",
    nav: "comment",
    comments: true
    // other url-related data here
}

This is a very simple and basic example (nav and comments seem redundant, and I didn't expand any of the top-level properties, so no stuff like video.id, etc), obviously you can extend/optimize this a lot.

这是一个非常简单和基本的例子(导航和注释似乎是多余的,我没有扩展任何*属性,因此没有像video.id这样的东西),显然你可以扩展/优化这个。

Each controller would check for the URL state at the beginning and work according to the response.

每个控制器都会在开始时检查URL状态,并根据响应进行操作。

If you wish, you can also use a wrapper-controller for this, so instead of returning the same data for each controller, you would just populate one $rootScope variable with that data.

如果您愿意,您也可以使用包装器控制器,因此您不必为每个控制器返回相同的数据,而只需使用该数据填充一个$ rootScope变量。