首页 > 代码库 > SPA UI-router
SPA UI-router
------------------------------------------------------------------------------------
SPA
SPA(单页面应用):A single-page application (SPA) is a web application or web site that fits on a single web page with the goal of providing a user experience similar to that of a desktop application(单页面应用的目标是提供和桌面应用相类似的用户体验). In an SPA, either all necessary code – HTML, JavaScript, and CSS – is retrieved with a single page load, or the appropriate resources are dynamically loaded and added to the page as necessary, usually in response to user actions. The page does not reload at any point in the process(在这个过程中,页面并不重新加载), nor does control transfer to another page, although the location hash can be used to provide the perception and navigability of separate logical pages in the application, as can the HTML5 pushState()
API. Interaction with the single page application often involves dynamic communication with the web server behind the scenes.
History
The origins of the term single-page application are unclear(起源并不清楚), though the concept was discussed at least as early as 2003. Stuart (stunix) Morris wrote the Self-Contained website at slashdotslash.com with the same goals and functions in April 2002 and later the same year, Lucas Birdeau, Kevin Hakman, Michael Peachey and Evan Yeh described a single page application implementation in the US patent 8,136,109.[5]
Modern browsers that can parse HTML5 allow developers to shift the user interface (UI) and application logic from web servers to the client(把UI和应用逻辑从服务器端移动到客户端). Mature open-source libraries are available that support the building of an SPA, reducing the amount of javascript the developer has to write.
Technical approaches
There are various techniques(有几种技术方法) available that enable the browser to retain a single page even when the application requires server communication.(后面的几种都是啥啊,根本木有看懂)
- JavaScript frameworks Web browser JavaScript frameworks, such as AngularJS, Ember.js, Meteor.js, ExtJS and React have adopted SPA principles.
- Ajax. The most prominent technique currently being used is Ajax.Predominantly using the XMLHttpRequest object from JavaScript, other Ajax approaches include using IFRAME or script HTML elements. Popular libraries like jQuery, which normalize Ajax behavior across browsers from different manufacturers, have further popularized the Ajax technique.
- Websockets WebSockets are a bidirectional stateful real-time client-server communication technology part of the HTML5 specification, superior to Ajax in terms of performance and simplicity.
- Server-sent events Server-sent events (SSEs) is a technique whereby servers can initiate data transmission to browser clients. Once an initial connection has been established, an event stream remains open until closed by the client. SSEs are sent over traditional HTTP and have a variety of features that WebSockets lack by design such as automatic reconnection, event IDs, and the ability to send arbitrary events.
- Browser plugins Although this method is outdated, asynchronous calls to the server may also be achieved using browser plug-in technologies such as Silverlight, Flash, or Java applets.
- Data transport (XML, JSON and Ajax) Requests to the server typically result in either raw data (e.g., XML or JSON), or new HTML being returned. In the case where HTML is returned by the server, JavaScript on the client updates a partial area of the DOM (Document Object Model).[11] When raw data is returned, often a client-side JavaScript XML / (XSL) process (and in the case of JSON a template) is used to translate the raw data into HTML, which is then used to update a partial area of the DOM.
- Server architecture
- Thick stateful server architecture
- Thick stateless server architecture
Running locally(在本地运行)
Some SPAs may be executed from a local file using the file URI scheme. This gives users the ability to download the SPA from a server and run the file from a local storage device, without depending on server connectivity. If such an SPA wants to store and update data, it must use browser-based Web Storage.(如果SPA想要存储和更新数据,则必须使用浏览器的Web Storage) These applications benefit from advances available with HTML5.
Challenges with the SPA model
Because the SPA is an evolution away from the stateless page-redraw model that browsers were originally designed for, some new challenges have emerged.(因为浏览器最初设计的是“无状态的”,即redraw整个页面,而单页面应用是与这不一样的进化) Each of these problems has an effective solution with:
- Client-side JavaScript libraries addressing various issues.
- Server-side web frameworks that specialize in the SPA model.
- The evolution of browsers and the HTML5 specification designed for the SPA model.
Search engine optimization
Because of the lack of JavaScript execution on crawlers of some popular Web search engines, SEO (Search engine optimization) has historically presented a problem for public facing websites wishing to adopt the SPA model.(由于网络爬虫对js的“不够友好”,因此在seo问题上,传统的面向公众的网站并不希望采用SPA模式)
Google currently crawls URLs containing hash fragments starting with #!
.(Google爬虫可以爬取hash部分中以“!#”开头的片段) This allows the use of hash fragments within the single URL of an SPA. Special behavior must be implemented by the SPA site to allow extraction of relevant metadata by the search engine‘s crawler. For search engines that do not support(不支持这一机制的,hash部分仍旧会被忽略掉) this URL hash scheme, the hashed URLs of the SPA remain invisible.
Alternatively, applications may render the first page load on the server and subsequent page updates on the client. This is traditionally difficult, because the rendering code might need to be written in a different language or framework on the server and in the client. Using logic-less templates, cross-compiling from one language to another, or using the same language on the server and the client may help to increase the amount of code that can be shared.
Because SEO compatibility is not trivial in SPAs, it‘s worth noting that SPAs are commonly not used in a context(SPAs并不经常被使用的场景) where search engine indexing is either a requirement, or desirable. Use cases include applications that surface private data hidden behind an authentication system. In the cases where these applications are consumer products, often a classic ‘page redraw‘ model is used for the applications landing page and marketing site, which provides enough meta data for the application to appear as a hit in a search engine query. Blogs, support forums, and other traditional page redraw artifacts often sit around the SPA that can seed search engines with relevant terms.
Another approach used by server-centric web frameworks like the Java-based ItsNat is to render any hypertext in the server using the same language and templating technology. In this approach, the server knows with precision the DOM state in the client, any big or small page update required is generated in the server, and transported by Ajax, the exact JavaScript code to bring the client page to the new state executing DOM methods. Developers can decide which page states must be crawlable by web spiders for SEO and be able to generate the required state at load time generating plain HTML instead of JavaScript. In the case of the ItsNat framework, this is automatic because ItsNat keeps the client DOM tree in the server as a Java W3C DOM tree; rendering of this DOM tree in the server generates plain HTML at load time and JavaScript DOM actions for Ajax requests. This duality is very important for SEO because developers can build with the same Java code and pure HTML-based templating the desired DOM state in server; at page load time, conventional HTML is generated by ItsNat making this DOM state SEO-compatible. As of version 1.3, ItsNat provides a new stateless mode, and the client DOM is not kept on the server because, with the stateless mode client, DOM state is partially or fully reconstructed on the server when processing any Ajax request based on required data sent by the client informing the server of the current DOM state; the stateless mode may be also SEO-compatible because SEO compatibility happens at load time of the initial page unaffected by stateful or stateless modes.
There are a couple of workarounds to make it look as though the web site is crawlable. Both involve creating separate HTML pages that mirror the content of the SPA. The server could create an HTML-based version of the site and deliver that to crawlers, or it‘s possible to use a headless browser such as PhantomJS to run the JavaScript application and output the resulting HTML.
Both of these do require quite a bit of effort, and can end up giving a maintenance headache for the large complex sites. There are also potential SEO pitfalls. If server-generated HTML is deemed to be too different from the SPA content, then the site will be penalized. Running PhantomJS to output the HTML can slow down the response speed of the pages, which is something for which search engines – Google in particular – downgrade the rankings.[22]
Client/Server code partitioning(前后端分离)
One way to increase the amount of code that can be shared between servers and clients is to use a logic-less template language like Mustache or Handlebars. Such templates can be rendered from different host languages, such as Ruby on the server and JavaScript in the client. However, merely sharing templates typically requires duplication of business logic used to choose the correct templates and populate them with data. Rendering from templates may have negative performance effects when only updating a small portion of the page—such as the value of a text input within a large template. Replacing an entire template(更新整个模板) might also disturb a user‘s selection or cursor position, where updating only the changed value might not. To avoid these problems, applications can use UI data bindings or granular DOM manipulation to only update the appropriate parts of the page instead of re-rendering entire templates.(为了避免这些问题,应用可以使用UI数据绑定或“粒度DOM操作技术”只去更新需要被更改的部分,而不是更新整个模板)
Browser history(浏览器历史问题)
With an SPA being, by definition, ‘a single page‘, the model breaks the browser‘s design for page history navigation using the Forward/Back buttons. This presents a usability impediment when a user presses the back button, expecting the previous screen state within the SPA, but instead the application‘s single page unloads and the previous page in the browser‘s history is presented.
浏览器设计的目的 是通过前进/后退按钮来实现网页历史导航,而按照“单”页面应用的定义,SPA模式破环了这种设计的目的。
有两种方法来解决这个问题,如下:
参考资料: hashchange、pushState
1 The traditional solution for SPAs has been to change the browser URL‘s hash fragment identifier in accord with the current screen state. This can be achieved with JavaScript, and causes URL history events to be built up within the browser. As long as the SPA is capable of resurrecting the same screen state from information contained within the URL hash, the expected back button behavior is retained.
传统的方式是利用hashchange,锚点改变
2 To further address this issue, the HTML5 specification has introduced pushState and replaceState providing programmatic access to the actual URL and browser history.
HTML5新特性pushState replaceState
Analytics(数据分析)
Analytics tools such as Google Analytics rely heavily upon entire new pages(数据分析很大程度上依赖于整个新页面的加载) loading in the browser, initiated by a new page load. SPAs do not work this way.
After the first page load, all subsequent page and content changes are handled internally by the application, which should simply call a function to update the analytics package. Failing to call said function, the browser never triggers a new page load, nothing gets added to the browser history, and the analytics package has no idea who is doing what on the site(分析工具不知道是谁正在网站上做些什么).
Adding page loads to an SPA
It is possible to add page load events to an SPA(把page load事件增加到SPA里面) using the HTML5 history API; this will help integrate analytics. The difficulty comes in managing this and ensuring that everything is being tracked accurately – this involves checking for missing reports and double entries. Some frameworks provide open source analytics integrations addressing most of the major analytics providers. Developers can integrate them into the application and make sure that everything is working correctly, but there is no need to do everything from scratch.
Speed of initial load
Single Page Applications have a slower first page loa(有较慢的首页加载速度) than server-based applications. This is because the first load has to bring down the framework and the application code before rendering the required view as HTML in the browser. A server-based application just has to push out the required HTML to the browser, reducing the latency and download time.
Speeding up the page load
There are some ways of speeding up the initial load of an SPA, such as a heavy approach to caching and lazy-loading modules when needed. But it‘s not possible to get away from the fact that it needs to download the framework, at least some of the application code, and will most likely hit an API for data before displaying something in the browser(避不开的是SPA需要下载框架、至少下载一些应用数据,至少在浏览器展示页面之前需要通过API加载一些数据). This is a ‘pay me now, or pay me later‘ trade-off scenario. The question of performance and wait-times remains a decision that the developer must make.
Page lifecycle
An SPA is fully loaded in the initial page load and then page regions are replaced or updated with new page fragments loaded from the server on demand. To avoid excessive downloading of unused features, an SPA will often progressively download more features as they become required, either small fragments of the page, or complete screen modules(按需加载,或是小的页面片段,或是整个页面模型).
In this way an analogy exists between "states" in an SPA and "pages" in a traditional web site. Because "state navigation" in the same page is analogous to page navigation,(状态导航和页面导航是类似的) in theory, any page-based web site could be converted to single-page replacing in the same page only the changed parts result of comparing consecutive pages in a non-SPA.
The SPA approach on the web is similar to the Single Document Interface (SDI) presentation technique popular in native desktop applications.
----------------------------------------------------------------------------------------------------------------------------------------------
UI-Router
UI-Router官网 ui-router|github-wiki
入门介绍
About
State based routing for client-side web apps/以状态为基础的客户端web apps的路由
UI-Router is a client-side router(客户端的路由) for single page web applications.
UI-Router core is framework agnostic(“中立的”,与框架无关,并不是紧密耦合的). We provide implementations for Angular 1 and Angular 2, and recently also React.
文档
The Components(UI-Router的组成部分)
- $state / $stateProvider: Manages state definitions, the current state, and state transitions(管理“状态”的定义,当前的状态,和状态的过度). This includes triggering transition-related events and callbacks, asynchronously resolving any dependencies of the target state, and updating $location to reflect the current state. For states that have URLs, a rule is automatically registered with $urlRouterProvider that performs a transition to that state.
- ui-sref directive: Equivalent to href or ng-href in
<a />
elements except the target value is a state name. Adds an appropriate href to the element according to the state associated URL. - ui-view directive: Renders views defined in the current state(当前的state的内容会被渲染在ui-view中). Essentially ui-view directives are (optionally named) placeholders that gets filled with views defined in the current state.
- $urlRouter / $urlRouterProvider: Manages a list of rules that are matched against $location whenever it changes. At the lowest level, a rule can be an arbitrary function that inspects $location and returns true if it was handled. Support is provided on top of this for RegExp rules and URL patterns that are compiled into UrlMatcher objects via $urlMatcherFactory.
- $urlMatcherFactory: Compiles URL patterns with placeholders into UrlMatcher objects. In addition to the placeholder syntax supported by $routeProvider, it also supports an extended syntax that allows a regexp to be specified for the placeholder, and has the ability to extract named parameters from the query part of the URL.
- $templateFactory: Loads templates via $http / $templateCache. Used by $state.
State Manager
1 Where does the template get inserted?
When a state is activated(当状态被激活,模板会被插入ui-view中), its templates are automatically inserted into the ui-view
of its parent state‘s template. If it‘s a top-level state—which ‘contacts‘ is because it has no parent state–then its parent template is index.html.
Right now, the ‘contacts‘ state won‘t ever be activated. So let‘s see how we can activate a state.
2 Activating a state(状态激活 有 三种方式)
There are three main ways to activate a state:
- Call
$state.go()
. High-level convenience method. Learn More - Click a link containing the
ui-sref
directive. Learn More - Navigate to the
url
associated with the state. Learn More.
2.1 $state.go(to [, toParams] [, options])
Returns a Promise(返回Promise对象) representing the state of the transition.
Convenience method for transitioning to a new state. $state.go
calls $state.transitionTo
internally but automatically sets options to { location: true, inherit: true, relative: $state.$current, notify: true }
. This allows you to easily use an absolute or relative to
path and specify only the parameters you‘d like to update (while letting unspecified parameters inherit from the current state.
to
String Absolute State Name or Relative State Path(绝对或相对的状态名字)
The name of the state that will be transitioned to or a relative state path. If the path starts with ^
or .
then it is relative, otherwise it is absolute.
如果state的路径以“^”或是“.”开头,则是相对路径;否则是绝对路径。
Some examples:
$state.go(‘contact.detail‘)
will go to the ‘contact.detail‘ state$state.go(‘^‘)
will go to a parent state.$state.go(‘^.sibling‘)
will go to a sibling state.$state.go(‘.child‘)
will go to a child state.$state.go(‘.child.grandchild‘)
will go to a grandchild state.
toParams
Object/对象
A map of the parameters that will be sent to the state, will populate $stateParams.
Any parameters that are not specified will be inherited from currently defined parameters. This allows, for example, going to a sibling state that shares parameters specified in a parent state. Parameter inheritance only works between common ancestor states, I.e. transitioning to a sibling will get you the parameters for all parents, transitioning to a child will get you all current parameters, etc.
options
Object/对象
If Object is passed, object is an options hash. The following options are supported:
location
Boolean or "replace" (default true), Iftrue
will update the url in the location bar, iffalse
will not. If string"replace"
, will update url and also replace last history record.inherit
Boolean (default true), Iftrue
will inherit url parameters from current url.relative
stateObject (default $state.$current), When transitioning with relative path (e.g ‘^‘), defines which state to be relative from.notify
Boolean (default true), Iftrue
will broadcast $stateChangeStart and $stateChangeSuccess events.reload
v0.2.5
Boolean (default false), Iftrue
will force transition even if the state or params have not changed, aka a reload of the same state. It differs from reloadOnSearch because you‘d use this when you want to force a reload when everything is the same, including search params.
Examples Diagram:
- Green = Starting State
- Yellow = Intermediary State
- Blue = Final Destination State
2.2 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. Also middle-clicking, right-clicking, and ctrl-clicking on the link will be handled natively by the browser.
Usage:
ui-sref=‘stateName‘
- Navigate to state, no params.‘stateName‘
can be any valid absolute or relative state, following the same syntax rules as$state.go()
ui-sref=‘stateName({param: value, param: value})‘
- Navigate to state, with params.
ui-sref-active
动态的添加class类
A directive working alongside ui-sref to add classes to an element when the related ui-sref
directive‘s state is active, and removing them when it is inactive. The primary use-case is to simplify the special appearance of navigation menus relying on ui-sref, by having the "active" state‘s menu button appear different, distinguishing it from the inactive menu items.
2.3 URL Routing
Most states in your application will probably have a url associated with them. URL Routing was not an afterthought to the state mechanics, but was figured into the design from the beginning(URL Routing并不是事后的想法,而是从一开始就设计好的) (all while keeping states separate from url routing)
Here‘s how you set a basic url.
$stateProvider
.state(‘contacts‘, {
url: "/contacts",
templateUrl: ‘contacts.html‘
})
Now when the user accesses index.html/contacts
then the ‘contacts‘ state would become active and the main ui-view
will be populated with the ‘contacts.html‘ partial. Alternatively, if the user were to transition to the ‘contacts‘ state via transitionTo(‘contacts‘)
then the url would be updated to index.html/contacts
URL Parameters
Basic Parameters
Often, URLs have dynamic parts(动态的部分) to them which are called parameters. There are several options for specifying parameters. A basic parameter looks like this:
$stateProvider
.state(‘contacts.detail‘, {
url: "/contacts/:contactId",
templateUrl: ‘contacts.detail.html‘,
controller: function ($stateParams) {
// If we got here from a url of /contacts/42
expect($stateParams).toBe({contactId: "42"});
}
})
Alternatively you can also use curly brackets:
// identical to previous example
url: "/contacts/{contactId}"
Examples:
‘/hello/‘
- Matches only if the path is exactly ‘/hello/‘. There is no special treatment for trailing slashes, and patterns have to match the entire path, not just a prefix.‘/user/:id‘
- Matches ‘/user/bob‘ or ‘/user/1234!!!‘ or even ‘/user/‘ but not ‘/user‘ or ‘/user/bob/details‘. The second path segment will be captured as the parameter ‘id‘.‘/user/{id}‘
- Same as(和前面的一样,只是用了花括号的语法) the previous example, but using curly brace syntax.‘/user/{id:int}‘
- The param is interpreted as Integer(整数型).
Note:
- Parameter names may contain only word characters (latin letters, digits, and underscore) and must be unique within the pattern (across both path and search parameters).
Using Parameters in Links
To create a link that passes parameters, use the state name like a function and pass it an object with parameter names as keys. The proper href
will be generated.
For example, using the above state which specified a contactId
parameter, create a link like so:
<a ui-sref="contacts.detail({contactId: id})">View Contact</a>
The value for id
can be anything in scope.
Regex Parameters(带正则规则的参数)
A bonus to using curly brackets is the ability to set a Regular Expression rule for the parameter(可以对参数使用正则表达式规则):
// will only match a contactId of one to eight number characters
url: "/contacts/{contactId:[0-9]{1,8}}"
Examples:
‘/user/{id:[^/]*}‘
- Same as‘/user/{id}‘
from the previous example.‘/user/{id:[0-9a-fA-F]{1,8}}‘
- Similar to the previous example, but only matches if the id parameter consists of 1 to 8 hex digits.‘/files/{path:.*}‘
- Matches any URL starting with ‘/files/‘ and captures the rest of the path into the parameter ‘path‘.‘/files/*path‘
- Ditto(同上). Special syntax for catch all.
Warning:
- Don‘t put capturing parentheses into your regex patterns, the UrlMatcher in ui-router adds those itself around the entire regex. You‘re effectively introducing a second capture group for the same parameter, which trips up the numbering in the child URL. You can use non-capturing groups though, i.e. (?:...) is fine.
- Regular expression can‘t include forward slashes as that‘s route segment delimiter
- Route parameters with regular expressions can‘t be optional or greedy
Query Parameters
You can also specify parameters as query parameters, following a ‘?‘:(同样,可以把参数作为查询参数来使用,后面跟着“?”即可)
url: "/contacts?myParam"
// will match to url of "/contacts?myParam=value"
If you need to have more than one, separate them with an ‘&‘:
url: "/contacts?myParam1&myParam2"
// will match to url of "/contacts?myParam1=value1&myParam2=wowcool"
Using Parameters without Specifying Them in State URLs
使用参数,与上面不一样的是参数不放在URL中
You still can specify what parameters to receive even though the parameters don‘t appear in the url. You need to add a new field params in the state and create links as specified in Using Parameters in Links
For example, you have the state.
.state(‘contacts‘, {
url: "/contacts",
params: {
param1: null
},
templateUrl: ‘contacts.html‘
})
The link you create is
<a ui-sref="contacts({param1: value1})">View Contacts</a>
Or can you pass them to $state.go() too.
$state.go(‘contacts‘, {param1: value1})
URL Routing for Nested States
Appended Routes (default)
When using url routing together with nested states the default behavior is for child states to append their url to the urls of each of its parent states.
当使用URL Routing和“嵌套路由”时,默认的是子状态会把url拼接到父状态上。
$stateProvider
.state(‘contacts‘, {
url: ‘/contacts‘,
...
})
.state(‘contacts.list‘, {
url: ‘/list‘,
...
});
So the routes would become:
- ‘contacts‘ state matches
"/contacts"
- ‘contacts.list‘ state matches
"/contacts/list"
. The urls were combined.
Absolute Routes (^)(绝对路径)
If you want to have absolute url matching, then you need to prefix your url string with a special symbol ‘^‘.
$stateProvider
.state(‘contacts‘, {
url: ‘/contacts‘,
...
})
.state(‘contacts.list‘, {
url: ‘^/list‘,
...
});
So the routes would become:
- ‘contacts‘ state matches
"/contacts"
- ‘contacts.list‘ state matches
"/list"
. The urls were not combined(路径没有拼接) because^
was used.
$stateParams Service
As you saw previously the $stateParams service is an object(对象) that will have one key per url parameter. The $stateParams is a perfect way to provide your controllers or other services with the individual parts of the navigated url.
$urlRouterProvider
$urlRouterProvider has the responsibility of watching $location(监听$location). When $location changes it runs through a list of rules one by one until a match is found. $urlRouterProvider is used behind the scenes anytime you specify a url
in a state configuration. All urls are compiled into a UrlMatcher object (see $urlMatcherFactory below).
There are several methods on $urlRouterProvider that make it useful to use directly in your module config.
SPA UI-router