SPA UI-router

------------------------------------------------------------------------------------javascript

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

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]java

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

Technical approaches

 There are various techniques(有幾種技術方法) available that enable the browser to retain a single page even when the application requires server communication.(後面的幾種都是啥啊,根本木有看懂)github

  1. JavaScript frameworks    Web browser JavaScript frameworks, such as AngularJS, Ember.js, Meteor.js, ExtJS and React have adopted SPA principles.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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.
  7. Server architecture
  8. Thick stateful server architecture
  9. 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.web

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模式)
express

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:

  1. Call $state.go(). High-level convenience method. Learn More
  2. Click a link containing the ui-sref directive. Learn More
  3. 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.transitionTointernally 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), If true 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), If true 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), If true will broadcast $stateChangeStart and $stateChangeSuccess events.
  • reload v0.2.5 Boolean (default false), If true 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-srefdirective'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.

相關文章
相關標籤/搜索