ホームページ > 記事 > ウェブフロントエンド > Nodejs は Angular を使用してシングルページ アプリケーションを作成します
今回はAngularを使ってNodeでシングルページアプリケーションを作成する方法を紹介します。 ルート ディレクトリに新しい app_client を作成して、単一のページに関連するコードを具体的に配置します。静的に設定することを忘れないでください:
app.use(express.static(path.join(dirname, 'app_client')))
Angular routing
SPA アプリケーションでは、ページ間の切り替え時に毎回リクエストがバックグラウンドに送信されるわけではありません。このセクションではルーティングをクライアントに移動しますが、マスター ページ (layout.jade) は保持し、他のビューは Angular で実装されます。これを行うには、まずコントローラーに新しい angularApp メソッドを作成します。
module.exports.angularApp = function (req, res) { res.render('layout', { title: 'ReadingClub' }); };
ルーティングを設定します
router.get('/', ctrlOthers.angularApp);
残りの Express ルートは冗長なので、削除またはコメントアウトできます。ページのリロードを回避するために、Angular のデフォルトのアプローチでは、URL に # 記号を追加します。 # 記号は通常、ページ上のポイントを見つけるためのアンカーとして使用され、Angular はアプリケーション内のポイントにアクセスするために使用されます。たとえば、Express では、about ページにアクセスします:
/about
Angular では、URL は
/#/about
になります。ただし、# 記号は削除することもできます。これについては、後で説明します。次のセクション。
古いバージョンの Angular ライブラリにはルーティング モジュールが含まれていますが、現在は外部の依存関係ファイルとなっており、自分でメンテナンスできます。したがって、最初にダウンロードしてプロジェクトに追加する必要があります。 https://code.angularjs.org/1.2.19/
angular-route.min.js と angular-route.min.js.map をダウンロードし、app_client の下に app.js を作成します
script(src='/angular/angular.min.js') script(src='/lib/angular-route.min.js') script(src='/app.js')を追加します
ルーティングを使用する前にモジュールの依存関係を設定する必要があります。ルーティングのファイル名は angular-route ですが、実際のモジュール名は ngRoute であることに注意してください。 app_client/app.js の下:
angular.module('readApp', ['ngRoute']);
ngRoute モジュールは、構成関数を渡すために使用できる $routeProvider オブジェクトを生成します。これは、ルートを定義する場所です:
function config($routeProvider) { $routeProvider .when('/', {}) .otherwise({ redirectTo: '/' }); } angular .module('readApp') .config(['$routeProvider', config]);
前の $http、$scope、サービスと現在の内容を確認してください$routeProvider が
function パラメーターに表示されると、Angular は自動的にインスタンスを取得します。これは Angular の 依存性注入 メカニズムであり、config メソッドがルートを定義します。現時点では、このルートはあまり機能しませんが、構文は非常に直感的であり、URL が「/」の場合は、ホームページにアクセスするときに何も実行されません。そして別のURLでアクセスするとトップページにジャンプします。次に、このルートにいくつかの作業を実行させます。 角度ビュー
まず、ホームページにいくつかのファイルを配置するために、app_client フォルダーの下にホーム フォルダーを作成します。しかし、現時点ではホームページはまだ jade ビューなので、html に変換する必要があります。そのため、まず home.view.html を作成します:
<p class="row" > <p class="col-md-9 page" > <p class="row topictype"><a href="/" class="label label-info">全部</a><a href="/">读书</a><a href="/">书评</a><a href="/">求书</a><a href="/">求索</a></p> <p class="row topiclist" data-ng-repeat='topic in data'> <img data-ng-src='{{topic.img}}'><span class="count"><i class="coment">{{topic.commentCount}}</i><i>/</i><i>{{topic.visitedCount}}</i></span> <span class="label label-info">{{topic.type}}</span><a href="/">{{topic.title}}</a> <span class="pull-right">{{topic.createdOn}}</span><a href="/" class="pull-right author">{{topic.author}}</a> </p> </p> <p class="col-md-3"> <p class="userinfo"> <p>{{user.userName}}</p> </p> </p></p>
データがまだないため、この HTML フラグメントは何も行いません。次のステップは、ホームページにアクセスするときにこのビューをロードするように Angular モジュールに指示することです。これは、templateUrl を使用して実行されます。ただし、これは、Asp と同様に、テンプレート アドレスを提供するだけです。 .Net MVC?
@RenderBodyタグは、jade の
ブロックコンテンツ です。これには、ngRoute モジュールのディレクティブ ng-view を使用する必要があります。マークされた要素は、ビューを切り替えるためのコンテナとして Angular によって使用されます。ブロックのコンテンツの上に追加することもできます: function config($routeProvider) {
$routeProvider
.when('/', { templateUrl: 'home/home.view.html'
})
.otherwise({ redirectTo: '/' });
}
Controller ルーティングとビューでは、コントローラーも必要です。まず、
を使用します。前のセクションを読んだ後、この部分はおなじみです。
#bodycontent.container p(ng-view) block content
angular .module('readApp') .controller('homeCtrl', homeCtrl);
ルートを再度変更します: function homeCtrl($scope) {
$scope.data = topics;
$scope.user = {
userName: "stoneniqiu",
};
}
この時点で、ページにアクセスするとデータが出てきます。 したがって、Asp.net MVC、Express、または Angular のいずれであっても、MVC モードの考え方は同じであり、リクエストは最初にルーターに到達し、コントローラーがそれを転送する責任があります。データを取得してからビューをレンダリングします。
前のセクションとの違いは、ng-controller ディレクティブがページでは使用されず、ルーティングで指定されることです。
Angular提供了一个创建视图模型的方法来绑定数据,这样就不用每次直接修改$scope 对象,保持$scope 干净。
function config($routeProvider) { $routeProvider .when('/', { templateUrl: 'home/home.view.html', controller: 'homeCtrl', controllerAs: 'vm' }) .otherwise({ redirectTo: '/' }); }
红色代码表示启用controllerAs语法,对应的视图模型名称是vm。这个时候Angular会将控制器中的this绑定到$scope上,而this又是一个上下文敏感的对象,所以先定义一个变量指向this。controller方法修改如下
function homeCtrl() { var vm = this; vm.data = topics; vm.user = { userName: "stoneniqiu", }; }
注意我们已经拿掉了$scope参数。然后再修改下视图,加上前缀vm
<p class="row" > <p class="col-md-9 page" > <p class="row topictype"><a href="/" class="label label-info">全部</a><a href="/">读书</a><a href="/">书评</a><a href="/">求书</a><a href="/">求索</a></p> <p class="error">{{ vm.message }}</p> <p class="row topiclist" data-ng-repeat='topic in vm.data'> <img data-ng-src='{{topic.img}}'><span class="count"><i class="coment">{{topic.commentCount}}</i><i>/</i><i>{{topic.visitedCount}}</i></span> <span class="label label-info">{{topic.type}}</span><a href="/">{{topic.title}}</a> <span class="pull-right">{{topic.createdOn}}</span><a href="/" class="pull-right author">{{topic.author}}</a> </p> </p> <p class="col-md-3"> <p class="userinfo"> <p>{{vm.user.userName}}</p> </p> </p></p>
service:
因为服务是给全局调用的,而不是只服务于home,所以再在app_clinet下新建一个目录:common/services文件夹,并创建一个ReadData.service.js :
angular .module('readApp') .service('topicData', topicData);function topicData ($http) { return $http.get('/api/topics'); };
直接拿来上一节的代码。注意function写法, 最好用function fool()的方式,而不要var fool=function() 前者和后者的区别是前者的声明会置顶。而后者必须写在调用语句的前面,不然就是undefined。修改layout
script(src='/app.js') script(src='/home/home.controller.js') script(src='/common/services/ReadData.service.js')
相应的home.controller.js 改动:
function homeCtrl(topicData) { var vm = this; vm.message = "loading..."; topicData.success(function (data) { console.log(data); vm.message = data.length > 0 ? "" : "暂无数据"; vm.data = data; }).error(function (e) { console.log(e); vm.message = "Sorry, something's gone wrong "; }); vm.user = { userName: "stoneniqiu", }; }
这个时候页面已经出来了,但是日期格式不友好。接下来添加过滤器和指令
filter&directive
在common文件夹创建一个filters目录,并创建一个formatDate.filter.js文件,同上一节一样
angular .module('readApp') .filter('formatDate', formatDate);function formatDate() { return function (dateStr) { var date = new Date(dateStr); var d = date.getDate(); var monthNames = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"]; var m = monthNames[date.getMonth()]; var y = date.getFullYear(); var output = y + '/' + m + '/' + d; return output; }; };
然后在common文件夹下新建一个directive文件夹,再在directive目录下新建一个ratingStars目录。ratingStars指令会在多个地方使用,它包含一个js文件和一个html文件,将上一节的模板文件复制过来,并命名为:ratingStars.template.html。然后新建一个ratingStars.directive.js文件,拷贝之前的指令代码,并改造两处。
angular .module('readApp') .directive('ratingStars', ratingStars);function ratingStars () {return { restrict: 'EA',scope: { thisRating : '=rating'},templateUrl: '/common/directive/ratingStars/ratingStars.template.html'}; }
EA表示指令作用的范围,E表示元素(element),A表示属性(attribute),A是默认值。还C表示样式名(class),M表示注释(comment), 最佳实践还是EA。更多知识可以参考这篇博客 Angular指令详解
因为还没有创建booksController,先用topic.commentCount来测试ratingStars指令,并记得在layout下添加引用。
<p class="row topiclist" data-ng-repeat='topic in vm.data'> <img data-ng-src='{{topic.img}}'><span class="count"><i class="coment">{{topic.commentCount}}</i><i>/</i><i>{{topic.visitedCount}}</i></span> <small rating-stars rating="topic.commentCount"></small> <span class="label label-info">{{topic.type}}</span><a href="/">{{topic.title}}</a> <span class="pull-right">{{topic.createdOn | formatDate}}</span><a href="/" class="pull-right author">{{topic.author}}</a> </p>
这个时候效果已经出来了。
有哪些优化?
这一节和上一节相比,展现的内容基本没有变化,但组织代码的结构变得更清晰好维护了,但还是不够好,比如layout里面我们增加了过多的js引用。这也是很烦的事情。所以我们可以做一些优化:
第一点,在团队开发的时候要尽量减少全局变量,不然容易混淆和替换,最简单的办法就是用匿名函数包裹起来:
(function() { //....})();
被包裹的内容会在全局作用域下隐藏起来。而且在这个Angular应用也不需要通过全局作用域关联,因为模块之间都是通过angular.module('readApp', ['ngRoute'])连接的。controller、service、directive这些js都可以处理一下。
我们可以让js最小化,但有一个问题,在controller中的依赖注入会受影响。因为JavaScript在最小化的时候,会将一些变量替换成a,b,c
function homeCtrl ($scope, topicData, otherData)
会变成:
function homeCtrl(a,b,c){
这样依赖注入就会失效。这个时候怎么办呢,就要用到$inject ,$inject作用在方法名称后面,等于是声明当前方法有哪些依赖项。
homeCtrl.$inject = ['$scope', 'topicData', 'otherData'];function homeCtrl ($scope, topicData, otherData) {
$inject数组中的名字是不会在最小化的时候被替换掉的。但记住顺序要和方法的调用顺序一致。
topicData.$inject = ['$http'];function topicData ($http) { return $http.get('/api/topics'); };
做好了这个准备,接下来就可以最小化了
在layout中我们引用了好几个js,这样很烦,可以使用UglifyJS 去最小化JavaScript文件。 UglifyJS 能将Angular应用的源文件合并成一个文件然后压缩,而我们只需在layout中引用它的输出文件即可。
安装:
然后在根目录/app.js中引用
var uglifyJs = require("uglifyjs");var fs = require('fs');
接下来有三步
1.列出需要合并的文件
2.调用uglifyJs 来合并并压缩文件。
3.然后保存在Public目录下。
在/app.js下var一个appClientFiles数组,包含要压缩的对象。然后调用uglifyjs.minify方法压缩,然后写入public/angular/readApp.min.js
var appClientFiles = [ 'app_client/app.js', 'app_client/home/home.controller.js', 'app_client/common/services/ReadData.service.js', 'app_client/common/filters/formatDate.filter.js', 'app_client/common/directive/ratingStars/ratingStars.directive.js'];var uglified = uglifyJs.minify(appClientFiles, { compress : false }); fs.writeFile('public/angular/readApp.min.js', uglified.code, function (err) { if (err) { console.log(err); } else { console.log('脚本生产并保存成功: readApp.min.js'); } });
最后修改layout:
script(src='/angular/readApp.min.js') //script(src='/app.js') //script(src='/home/home.controller.js') //script(src='/common/services/ReadData.service.js') //script(src='/common/filters/formatDate.filter.js') //script(src='/common/directive/ratingStars/ratingStars.directive.js')
这里选择注释而不是删掉,为了便于后面的调试。但如果用nodemon启动,它会一直在重启。因为生产文件的时候触发了nodemon重启,如此循环。所以这里需要一个配置文件告诉nodemon忽略掉这个文件的改变。在根目录下新增一个文件nodemon.json
{ "verbose": true, "ignore": ["public//angular/readApp.min.js"] }
这样就得到了一个min.js 。原本5个文件是5kb,换成min之后是2kb。所以这个优化还是很明显的。
相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!
推荐阅读:
以上がNodejs は Angular を使用してシングルページ アプリケーションを作成しますの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。