AngularJS 相關問題學習 借鑑

[SignalR2] AngularJS 應用

因爲手頭有一個AngularJS項目,而其中消息模塊就是採用SignalR來推送。AngularJS就像天生須要SignalR同樣,由於他們都須要很快。其實更在乎的是WebSocket在AngularJS的應用,而當前只是一個小試驗,但效果然的很是好。這一篇文章,咱們圍繞AngularJS中如何應用SignalR,因此你看到的更多會是JavaScript腳本。javascript

項目是以ASP.NET API做爲服務端、OAuth2票據令牌認證、AngularJS作爲前端、中間所有采用JSON做爲數據傳輸。css

依賴jQuery.SignalR.js

這是必須的,而SignalR客戶端腳本庫又是以插件的形式,因此無論將來如何更新,都不擔憂腳本的變更而倒置AngularJS的改動,要知道AngularJS項目的JS腳本管理要控制得很是嚴謹。html

Hub代理腳本

其實我在SignalR JavaScript 客戶端已經很是詳細的描述過關於若是建立代理腳本,但有個缺點就是他是全局式的,而對於AngularJS而言不建議使用這種全局式的,因此咱們須要手動的建立代理腳本部分;而且把它看成一個 factory,這樣對於多個Controller是能夠共享數據的。前端

這裏有個github的項目,徹底能夠知足咱們的需求,很是簡單,我把代理貼出來:java

angular.module('SignalR', []) .constant('$', $) .factory('Hub', ['$', function ($) { //This will allow same connection to be used for all Hubs //It also keeps connection as singleton. var globalConnections = []; function initNewConnection(options) { var connection = null; if (options && options.rootPath) { connection = $.hubConnection(options.rootPath, { useDefaultPath: false }); } else { connection = $.hubConnection(); } connection.logging = (options && options.logging ? true : false); return connection; } function getConnection(options) { var useSharedConnection = !(options && options.useSharedConnection === false); if (useSharedConnection) { return typeof globalConnections[options.rootPath] === 'undefined' ? globalConnections[options.rootPath] = initNewConnection(options) : globalConnections[options.rootPath]; } else { return initNewConnection(options); } } return function (hubName, options) { var Hub = this; Hub.connection = getConnection(options); Hub.proxy = Hub.connection.createHubProxy(hubName); Hub.on = function (event, fn) { Hub.proxy.on(event, fn); }; Hub.invoke = function (method, args) { return Hub.proxy.invoke.apply(Hub.proxy, arguments) }; Hub.disconnect = function () { Hub.connection.stop(); }; Hub.connect = function () { return Hub.connection.start(options.transport ? { transport: options.transport } : null); }; if (options && options.listeners) { angular.forEach(options.listeners, function (fn, event) { Hub.on(event, fn); }); } if (options && options.methods) { angular.forEach(options.methods, function (method) { Hub[method] = function () { var args = $.makeArray(arguments); args.unshift(method); return Hub.invoke.apply(Hub, args); }; }); } if (options && options.queryParams) { Hub.connection.qs = options.queryParams; } if (options && options.errorHandler) { Hub.connection.error(options.errorHandler); } //Adding additional property of promise allows to access it in rest of the application. Hub.promise = Hub.connect(); return Hub; }; }]); 

使用時先把 SignalR 模塊依賴到你的應用中,而後再須要的地方建立 var hub = new Hub('hubname',options);,就能夠了。git

其中 options 會有一些參數:angularjs

  • listeners 服務端調用客戶端方法。
  • methods 客戶端調用服務端方法。
  • rootPath 服務器地址。
  • queryParams 同 connection.qs
  • errorHandler 同 connection.error
  • logging 是否啓用日誌,這裏的日誌是指 console.log,若是是chrome,則F12就能夠查看到了。
  • useSharedConnection 是否共享一個鏈接,當有兩個不一樣的服務器地址時,仍是會按兩個鏈接的。
  • transport 指定使用哪個方法進行數據傳輸,好比:'longPolling' 或 ['webSockets', 'longPolling']

固然以上是針對大部分項目來設計的,個人項目裏面根據這個作法,作了更適合自身項目的修改,但基本上如同上面同樣。github

票據字符串

在建立鏈接時把會票據字符串加入 qs中,可能別的項目不是採用OAuth2票據令牌認證,可是做爲ASP.NET API項目這種認證方式是無可挑剔。web

服務端驗證票據

這一點,請詳見另外一篇文章認證和受權,這裏就再也不重複。ajax

一個完整的示例

一、首先建立一個factory的Hub代理類

app.constant('$', $).factory('hub', ['$', '$localStorage', function ($, $localStorage) { var authData = $localStorage.authorizationData; //This will allow same connection to be used for all Hubs //It also keeps connection as singleton. var globalConnection = null; function initNewConnection(options) { var connection = null; if (options && options.rootPath) { connection = $.hubConnection(options.rootPath, { useDefaultPath: false, qs: { Bearer: authData.token } }); } else { connection = $.hubConnection(); } connection.logging = (options && options.logging ? true : false); return connection; } function getConnection(options) { var useSharedConnection = !(options && options.useSharedConnection === false); if (useSharedConnection) { return globalConnection === null ? globalConnection = initNewConnection(options) : globalConnection; } else { return initNewConnection(options); } } return function (hubName, options) { var Hub = this; Hub.connection = getConnection(options); Hub.proxy = Hub.connection.createHubProxy(hubName); Hub.on = function (event, fn) { Hub.proxy.on(event, fn); }; Hub.invoke = function (method, args) { return Hub.proxy.invoke.apply(Hub.proxy, arguments) }; Hub.disconnect = function () { Hub.connection.stop(); }; Hub.connect = function () { Hub.connection.start(); }; if (options && options.listeners) { angular.forEach(options.listeners, function (fn, event) { Hub.on(event, fn); }); } if (options && options.methods) { angular.forEach(options.methods, function (method) { Hub[method] = function () { var args = $.makeArray(arguments); args.unshift(method); return Hub.invoke.apply(Hub, args); }; }); } if (options && options.errorHandler) { Hub.connection.error(options.errorHandler); } //Adding additional property of promise allows to access it in rest of the application. Hub.promise = Hub.connection.start(); return Hub; }; }]); 

因爲票據字符串是放在LocalStorage當中,因此我注入了 $localStorage 來獲取個人 token,並把他放在 qs 中。其餘並無什麼不同。

二、Controller層使用

首先我須要將我建立的factory注入到我須要的使用的Controller中,好比:

app.controller('DemoCtrl', [ 'Hub', function(Hub) { var msgHub = new Hub('msgHub', { logging: true, // 啓用日誌 rootPath: 'app', // 服務器地址 listeners: { // 服務端調用客戶端方法 get: function (r) { console.log(r); } }, methods: ['echo'], // 客戶端調用服務端方法。 errorHandler: function (error) { // 異常處理 console.error(error); } }); }]); 

總結

這裏惟一咱們要注意的就是你所採用的登陸認證的方式,由於這裏個人確吃了一些虧,因此務必要清楚。

查看 SignalR系列文章

AngularJS優化:bindonce 不是萬能藥,用時需謹慎

看過一次對 Misko Hevery 的專訪,有說到限制2000個對象綁定,可讓頁面刷新時間減小到5ms如下。而所謂的刷新時間是指 dirty-checking,由於一次 dirty-checking 時間將會影響 DOM 更新時間。dirty-checking 核心就是對全部 $watch 進行檢查。而 bindonce 是爲了減小這種沒必要要的 $watch

什麼是 Watch

$watch就是監視數據的變化。這裏的數據多是指令或$scope

好比,如下建立兩個$watch,一個文本框、一個名字輸出:

<p>用戶名:<input type="text" ng-model="name" /></p> <p>個人用戶名:{{name}}</p> 

以上,當我在文本框輸入字母 a 時,會當即啓動一個檢查(前期也會有不少工做,好比綁定keydown事件,用於觸發事件,這裏再也不說太多,建議看源代碼),會檢查到 name 發生變化;這個過程叫dirty-checking,而對全部的 $watch 都檢查一遍後,纔會將主動權交給瀏覽器作DOM上的變化(即輸出部分)。

試想當咱們經過一個 ng-repeat 循環N條記錄,而每一行有10個綁定對象,那意味者會有 N*10 個 $watch 等着咱們。每每在一個複雜的頁面,這種行綁定對象會多得多。

取消 $watch

對於像 ng-repeat,都是由官方提供的,限制是沒法限制,因此咱們只能在綁定的時候使用傳統的方式。而 bindonce 就是這樣子的,好比:bo-text 至關於binder.element.text(value);

注意:bo-text 是以一個指令出現,那麼咱們須要注意一個問題了,指令是在DOM被加載徹底後開始執行,這裏的執行順序很是重要。當你的數據源已經加載完成,此時 bo-text 指令出現,他才能獲取到數據,不然永遠都是空的。所以這裏得出第一個優化規則:

bindonce只能在數據存在時有效

因此對於像表單數據,正常咱們都是從 $http.get 來獲取,而DOM早就加載完成,因此這個時候再去使用 bindonce 已經沒有意義了。

而像SPA不少時候咱們會有一些共享的數據,好比版本號、APP名稱、用戶信息等等,這些信息若是你可以保證在 DOM 加載前就已經存在,那麼就能夠大膽去使用 bindonce

bindonce 更適合數據列表

$watch 最大的泛濫是數據列表,如我前所說它是N*M在增加。而咱們又是經過 ng-repeat 來循環數據,ng-repeat 自己就是一個指令,因此自己會有一個 $watch 來監視數據變化,換句話說咱們能夠保證 規則一 原則,因此咱們大可大膽在 ng-repeat 下去掉咱們不須要監視的數據。

因此這裏得出第二個優化規則:

ng-repeat下使用bindonce是安全的

在優化以前,我有幾點建議:

  1. 絕對不要過早優化,由於NG已經夠快的了。
  2. SPA應用加載順序很是重要。
  3. bindonce適用於數據列表。
  4. 性能的限制因素是人,NG真的很是快。

AngularJS中的value和constant

Values

當咱們須要一組數據,好比:當前用戶登陸信息或APP的一些配置,而這組數據又但願在 controller/service 使用它,那麼它就是作這個事的。

value 支持string、number、date-time、array、object、function,好比:

var app = angular.module('myApp',[]); app.value('user', { name: 'asdf', role: 'admin' }); 

而當咱們須要使用時,就像這樣:

app.controller('MyCtrl', ['$scope', 'user', function($scope, user) { $scope.name = user.name; }]); 

Angular 會自動將 user 注入進來,同時咱們能夠隨時修改數據,好比:

user.role = 'normal'; 

這樣子其餘再注入 user 時role的值變成normal。

Constants

value 與 constant 之間的差別只有兩點:

  1. 可注入類型,對於前者只容許在 service 或 controller,然後者還包括模塊配置函數。
  2. constant 是常量,沒法被修改。

AngularJS在controller延遲加載自定義指令

有個幾百KB的地址庫,原則上我可使用 http 按需加載數據來減小文件大小。可我就是太變態,由於地址的修改頻率很是很是低,因此要一開始就把玩意兒加載放在那不使用,就不是一個滋味。這即是變態之緣由一。

個人需求裏面有一個訂單詳情頁,但詳情頁能夠直接修改地址,問題來了,詳情頁打開頻率很是高,而修改頻率極低【注意是極低】,因此我但願當我對其進行修改操做時,再延遲加載指令。其變態之緣由二。

依賴

ocLazyLoad

簡單點說就是將所須要的文件加載完畢後,並返回一個 promise,這樣咱們能夠跟路由的resolve一塊兒,達到一個完成的延遲加載 css/js etc.

同時,ocLazyLoad 提供一個指令,能夠直接在 view 中延遲加載某個指令,也會從新編譯該指令內的HTML元素 $compile(content)($scope)

<div oc-lazy-load="{name: 'TestModule', files: ['js/testModule.js', 'partials/lazyLoadTemplate.html']}"> // Use a directive from TestModule <test-directive></test-directive> </div> 

看上去問題已經能夠解決了,可老是那麼不如意,ngIf 會打亂因爲 DOM 被預編譯且被緩存,那就蛋疼了。

即便從新編譯也沒用。

好,既然是變態作法,那天然不可以依賴 ocLazyLoad 提供的最方便免費午飯,須要一點點改進。

一個解決辦法

大概分幾步:

  1. 包裹一個 div,方便於只對該元素內進行從新編譯,不然會出現循環編譯。
  2. 調用 $ocLazyLoad.load 延遲加載所須要的文件。
  3. 已經加載過的,還須要從新編譯,由於對於 ocLazyLoad 而言會保留本身的 module 信息。

那麼,如下就是大概的一個方案了:

<div ng-init="cityLoad('#citySelect')"> <city-select id="citySelect" ng-model="city.dis"></city-select> </div> 
var citySelectParam = { name: 'angular.city.select', files: ['angular-city-select/angular-city-select.min.js', 'angular-city-select/angular-city-select.min.css'] }; $scope.cityLoad = function (eleId) { try { angular.module(citySelectParam .name); $compile(angular.element(eleId))($scope); } catch (err) { $ocLazyLoad.load(citySelectParam ).then(function () { $compile(angular.element(eleId))($scope); }); } } 

好了,大概就是這樣子吧。但願您也有這麼個變態作法。

弄懂AngularJS select的數據綁定ngOptions

雖然項目已經進行一大半了,可是 ngOptions 還真的一直保留着困惑,其實第一眼看我怎麼都感受像是 Linq 通常,因此弄懂他纔可以真正活用。而官方文檔也只是把各類方式羅列一下,也沒有示例,因此仍是認認真真的學習一遍。

那麼,我須要開始假設有如下數據,因爲數組和對象數據源有些不一樣,如下是我假定的兩種數據源。完整的示例

$scope.users = [ { name: 'cipchk', gender: '男', age: 25 }, { name: '卡色', gender: '男', age: 18 }, { name: '外賣歷來沒遇到美女', gender: '女', age: 18 }, { name: '今晚不知道要吃什麼', gender: '女', age: 18 }, { name: '其實.NET很好', gender: '女', age: 18 }, { name: 'fhw r', gender: '女', age: 18 } ]; // 至關於c# Dictionary<int, string> $scope.product = { 1: 'KFC', 2: '麥當蘇', 3: '魚丸', 4: '星巴克', 5: '錯了,星巴克放在前面' }; 

1、label for value in arraylabel for (key , value) in object

<select ng-model="first.user" ng-options="item.name for item in users" class="form-control"></select> <select ng-model="first.product" ng-options="value for (key, value) in product" class="form-control"></select> 
  1. value等同於源碼中的item(key, value),至關於c#中 foreach(var item in object) 同樣,即每次獲取一個元素放入item中。
  2. label等同於源碼中的item.namevalue,至關於 <option>item.name</option> 同樣。

執行結果:
1

2、 as 關鍵詞

<select ng-model="first.user" ng-options="item.gender as item.name for item in users" class="form-control"></select> <select ng-model="first.product" ng-options="key as value for (key, value) in product" class="form-control"></select> 

直白點講就是將 as 前面的變量賦於 ng-model

執行結果:
2

3、 group by 關鍵詞

<select ng-model="third.user" ng-options="item.name group by item.gender for item in users" class="form-control"></select> <select ng-model="third.product" ng-options="key as value group by key for (key, value) in product" class="form-control"></select> 
  1. group by xx位置必須是 for 關鍵詞以後。
  2. 分組實際是生成一個 <optgroup label="男"></optgroup> 標籤。

4、完整表達式

因此的關鍵詞已經所有說到了,這裏我來弄一個完整的表達式,其意:對數據源users,以年齡分組,下拉列表文本是姓名、值爲年齡。

<select ng-model="full.user" ng-options="item.age as item.name group by item.gender for item in users" class="form-control"></select> 

其實把表達式拆分開來講,回過頭看文章,好像並無什麼特別,也至關容易理解,只不過剛開始在結合文檔時很暈。

附錄:

  1. 在線示例代碼
  2. 官網文檔

以上。

AngularJS如何下載Excel文件

在AngularJS中要下載一個Excel文件到底有多難呢?

最簡單的方法

這固然是放一個 a 連接元素在頁面搞定。

<a href="/path/file.xlsx" target="_blank">下載文件</a> 

可若是咱們涉及到一些身份驗證,並且又是經過 Cookie,瀏覽器會很怪的一併發送到服務端,是否是一切都很好呢?

若是……

像上面說的若是我須要自定義請求頭,例如:OWIN等身份驗證的狀況下,怎麼辦呢?

問題

也許咱們能夠很是簡單的經過 ajax 發送一個 get 請求,並填寫相應 headers,好比:

$http.get({ url: '/path/file.xlsx', method: 'get', headers: { Authorization: 'Bearer pTVhzRZgA6yW-fp8c5vcxzBxr6vuIBYQrlo0ASIVxgkfN6' } }).success(function (data) { // 怎麼保存? }); 

有一個辦法就是咱們能夠經過 HTML5 的 a 元素,指定一段 Base64 數據編碼,咱們能夠生成一個 a 連接,而後點擊下載。

這種方式在個人實驗中,發現對於 Excel 支持很差,對於大一點的文件,下載回來都是沒法打開。

Blob

Blob 存儲的是二進制,實則就是一個 JavaScript 下的一個 File 對象,目前被大部分流行瀏覽器所支持。

我這裏還找到一個 FileServer.js 是對 Blob 保存的具體實現。

如下是我結合 FileServer.js 寫的一個AngularJS指令,好了,廢話很少說:

App.directive('downFile', ['$http',function ($http) { return { restrict: 'A', scope: true, link: function (scope, element, attr) { var ele = $(element); ele.on('click', function (e) { ele.prop('disabled', true); e.preventDefault(); $http({ url: attr.downFile, method: 'get', responseType: 'arraybuffer' }).success(function (data, status, headers) { ele.prop('disabled', false); var type; switch (attr.downFileType) { case 'xlsx': type = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; break; } if (!type) throw '無效類型'; saveAs(new Blob([data], { type: type }), decodeURI(headers()["x-filename"])); // 中文亂碼 }).error(function (data, status) { alert(data); ele.prop('disabled', false); }); }); } }; }]); 

相對於 View 的具體實現:

<button down-file="/order/export/{{item.id}}" down-file-type="xlsx" class="btn btn-green btn-sm">導出</button> 

如下是 ASP.NET API 的具體實現:

HttpResponseMessage response = new HttpResponseMessage(); response.StatusCode = HttpStatusCode.OK; response.Content = new ByteArrayContent(pck.GetAsByteArray()); response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment"); response.Content.Headers.Add("x-filename", System.Web.HttpUtility.UrlEncode(item.title, System.Text.Encoding.UTF8) + ".xlsx"); // 中文亂碼 return response; 

中文文件名亂碼問題

文件名爲中文時獲取到的 x-filename 會是亂碼,因此須要進行編碼,在示例中已經標識鳥。

寫文章時比較倉促,因此示例中只對 Excel 進行轉化,能夠根據需求加入各類文件格式類型。

以上,但願幫助到各位。

相關文章
相關標籤/搜索