AngularJS服務及注入--Provider


Provider簡介

在AngularJS中,app中的大多數對象通過injector服務初始化和連接在一起。

Injector創建兩種類型的對象,service對象和特別對象。

Service對象由開發者自定義api。

特別對象則遵照AngularJS框架特定的api,這些對象包括:controller, directive, filter or animation

 

最詳細最全面的是Provider,其他四種(Value, Factory, Service and Constant)只是在Provider“”之上包裝了一下而已 

 

為了使injector知道怎么樣創建和鏈接這些對象,你需要recipe的注冊表。每一個recipe都有一個對象的id和怎樣創建對象的說明。

當AngularJS使用給定的app module啟動app時,angularjs會創建一個新的injector實例,injector會依次把核心module的“配方”加入“配方”注冊表,並且創建app module和他的依賴。當需要為app創建對象時,就會去查找“配方”注冊表。

 

Value 

代碼中可能多個地方需要訪問一個共享的字符串,我們先用Value“配方”來實現這種情境。

我們來創建一個非常簡單的service,該service提供一個用於遠程api鑒權的id字符串,可以這么寫:

<!DOCTYPE html> <html ng-app="myApp"> <head> <meta charset="uft-8"/> <title></title> </head> <script src="script/angular.min.js"></script> <body ng-controller="DemoController as demo"> Client ID: {{demo.clientId}} </body> <script> var myApp = angular.module('myApp', []); myApp.value('clientId', 'a12345654321x'); myApp.controller('DemoController', ['clientId', function DemoController(clientId) { this.clientId = clientId; }]); </script> </html>

 

我們創建了一個名字叫myApp的module,並且指定該module的定義包含一個構建clientId服務的recipe,這個例子中service僅僅是一個字符串。

這個例子中我們使用value“配方”定義了一個值供DemoController需要一個clientId的service時調用。

factory

Value“配方”雖然簡單,但是缺少我們創建service時需要的很多重要的功能,Factory“配方”就強大很多,有以下功能:

  • 可以通過依賴使用其他service
  • service初始化
  • 延遲初始化

Factory“配方”可以使用0個或者多個參數創建service,這些參數可以是依賴的其他service。函數的返回值是一個通過“配方”創建的service實例。

在angularjs中所有的service都是單例的,這意味着injector為了創建對象只使用每個一次,然后injector把service的引用緩存起來,以便將來可以調用。

示例:

<!DOCTYPE html> <html ng-app="myApp"> <head> <meta charset="uft-8"/> <title></title> </head> <script src="script/angular.min.js"></script> <body ng-controller="DemoController as demo"> Client ID: {{demo.clientId}} </body> <script> var myApp = angular.module('myApp', []); //myApp.value('clientId', 'a12345654321x'); myApp.factory("clientId",function(){ return "a12345654321x"; }); myApp.controller('DemoController', ['clientId', function DemoController(clientId) { this.clientId = clientId; }]); </script> </html>

如果僅僅是返回一個token這么簡單,那么value“配方”則更合適,因為寫起來簡單也容易明白。

但是我們希望創建一個復雜點的service來計算token做遠程api的身份驗證,這個token叫做apiToken,通過clientId的值和瀏覽器本地存儲中的secret值計算得出。

以上代碼可以看到如何依賴clientId服務通過Factory“配方”創建apiToken服務。

factory的函數名最好使用<serviceId>Factory形式命名,例如apiTokenFactory,當然函數名是可以不要的,但是為了調試方便還是最好寫上。

同Value”配方“一樣,Factory”配方“可以創建任意類型的服務,無論是原始類型,對象,函數還是自定義的類型。

service“配方”

javascript開發者經常喜歡寫面向對象的自定義類型,現在讓我們通過unicornLauncher服務把一個unicorn發射到太空,unicornLauncher是一個自定義類型的實例。

function UnicornLauncher(apiToken) { this.launchedCount = 0; this.launch = function() { // 使用apiToken訪問遠程api ... this.launchedCount++; } }

 

現在我們准備發射獨角獸,注意到UnicornLauncher依賴於apiToken,我們通過Factory”配方“來滿足這個依賴:

myApp.factory('unicornLauncher', ["apiToken", function(apiToken) { return new UnicornLauncher(apiToken); }]);

 

然而這個例子使用service“配方”更為合適

Service”配方“可以像Value”配方“和Factory“配方”一樣生產service,但是它可以通過new操作符調用對象的構造函數。構造函數的參數可以通過Service“配方中”的依賴項加入。

Service“配方”的設計模式叫做構造器注入(constructor injection)。

既然我已經有了UnicornLauncher多多構造器,我們可以把Factory”配方”替換為Service“配方”。

myApp.service('unicornLauncher', ["apiToken", UnicornLauncher]);

Provider“配方”

Provider“配方”是核心“配方”,其他所有的“配方”僅僅是在它里面加了點糖。它是最多能力最詳細的“配方”,但是對於大多數service來說,不是所有的能力都有用。

Provider“配方”定義了一個實現了$get方法的自定義類型。$get方法是一個Factory函數,很像Factory“配方”定義的Factory函數一樣。事實上,如果你定義了一個Factory“配方”,框架會創建一個空的Provider類型,其中的$get方法就會指向你定義的Factory方法。

在app啟動時,我們需要一些配合時,這個時候才會需要Provider披露的API。通常是一些可以重用的service。

默認情況下,發射器發射unicorn到太空是沒有任何防護的,但是有些行星上大氣層太厚了,在unicorn開始它的太空旅行之前,我們需要為它包裹tinfoil,否則她沒通過大氣層時會摩擦燃燒。配置代碼如下:

myApp.provider('unicornLauncher', function UnicornLauncherProvider() { var useTinfoilShielding = false; this.useTinfoilShielding = function(value) { useTinfoilShielding = !!value; }; this.$get = ["apiToken", function unicornLauncherFactory(apiToken) { // 我們假設UnicornLauncher構造函數已更改,可以接收useTinfoilShielding參數 return new UnicornLauncher(apiToken, useTinfoilShielding); }]; });

 

在程序啟動時,config函數中調用unicornLauncherProvider的方式如下:

myApp.config(["unicornLauncherProvider", function(unicornLauncherProvider) { unicornLauncherProvider.useTinfoilShielding(true); }]);

 

unicornLauncherProvider是被注入到config方法中的,這里的injector和常規的實例injector不同,它只負責provider的初始化和連接。

在app啟動之時,在創建service之前,它會配置和實例化所有的provider,我們把這個叫做app生命周期的配置階段,在這個階段,service還不可使用,因為他們還未創建。

一旦配置階段完成,那么所有的provider變為不可用,創建service的進程開始啟動,這個階段被叫做app生命周期的運行階段。

Constant“配方”

我們剛剛了解了如何區分app生命周期的配置階段和運行階段,還有怎么樣通過config函數配置app。因為配置函數在配置階段執行,這時service都是不可以用的,它甚至無法訪問通過Value“配方”創建的簡單的值對象。

有一些簡單的值,比如url的前綴,沒有任何依賴和配置,常常需要配置階段和運行階段都可以方便的訪問, 
Constant“配方”的作用就在這了。

假設我們的unicornLauncher服務需要使用它的所在行星的名字來標記,行星的名字可以在配置階段提供。行星的名字對於每個app是特定的,運行階段會在各種各樣的controller中使用。我們可以這樣這樣定義行星的名字作為一個常量:

myApp.constant('planetName', 'Greasy Giant');
  • 1

配置unicornLauncherProvider如下:

myApp.config(['unicornLauncherProvider', 'planetName', function(unicornLauncherProvider, planetName) { unicornLauncherProvider.useTinfoilShielding(true); unicornLauncherProvider.stampText(planetName); }]);

Constant“配方”和Value“配方”一樣在配置階段是可用的,可用用於controller和模板:

myApp.controller('DemoController', ["clientId", "planetName", function DemoController(clientId, planetName) { this.clientId = clientId; this.planetName = planetName; }]);
<html ng-app="myApp"> <body ng-controller="DemoController as demo"> Client ID: {{demo.clientId}} <br> Planet Name: {{demo.planetName}} </body> </html>

特殊用途的object

之前提到有些特殊用途的對象和service是不同的,這些對象作為插件來擴展angularjs框架,需要實現angularjs指定的接口,這些接口是Controller, Directive, Filter 和 Animation。

這些特殊對象除controller外,其余的在底層其實是由Factory“配方”創建的。

在下面的例子中,我們演示如何通過Directive的api創建一個簡單的組件,這個組件依賴於我們之前定義的planetName常量,並顯示這個常量:“Planet Name: Greasy Giant”

由於Directive是通過Factory“配方”注冊的,我們采用Factory相同的語法:

myApp.directive('myPlanet', ['planetName', function myPlanetDirectiveFactory(planetName) { // directive definition object return { restrict: 'E', scope: {}, link: function($scope, $element) { $element.text('Planet: ' + planetName); } } }]);

然后使用這個組件:

<html ng-app="myApp"> <body> <my-planet></my-planet> </body> </html>

使用Factory“配方”也可以定義filter和animation,但是controller有點特殊。你能創建controller作為一個自定義類型,它的依賴可以從它的構造函數中傳入,這個構造器又被module用來注冊controller。我們來看下我之前寫的DemoController:

myApp.controller('DemoController', ['clientId', function DemoController(clientId) { this.clientId = clientId; }]);

每次app需要DemoController實例的時候,就好調用它的構造器進行初始化。因此和service不一樣,controller不是單例的。構造器可以注入其他service。

總結

讓我們來總結一下:

  • injector使用“配方”創建兩種對象:service和特殊用途對象
  • 有五種“配方”可以創建對象:Value, Factory, Service, Provider, Constant.
  • Factory和Service是使用最多的“配方”。他們之間唯一的區別是:Service“配方”更適合自定義類型的對象,而Factory“配方”更適合原生對象和函數。
  • Provider“配方”是最核心的“配方”,其他“配方”只是在它上面加點糖。
  • Provider“配方”是最復雜的“配方”,除非你需要全局配置的代碼,而這代碼你還想不斷復用。
  • 所有的特殊對象除了controller都是使用Factory“配方”創建的
Features / Recipe type Factory Service Value Constant Provider
能否依賴與其他對象 yes yes no no yes
能否友好的注入到其他對象 no yes yes* yes* no
創建的對象配置階段是否可用 no no no yes yes**
能否創建函數 yes yes yes yes yes
能否創建原生類型 yes no yes yes yes

* 需要使用new操作符付出預先初始化的代價

** service在配置階段不可用,但是provider對象在配置階段可用


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM