Description
AngularJS相关问题
ng-repeat中的ng-model
问题描述:
实现一个通用表单,表单中的元素类型固定,但数量和名称不固定。由于数量和名称不固定,所以不能逐一写出表单元素,只能采用ng-repeat产生元素。代码如下。
$scope.fields = ['author', 'content', 'time'];
<div ng-repeat="field in fields">
<label>{{field}}</label>
<input ng-model="field">
</div>
很显然,这样会出现一个绑定问题,具体参见 don't bind to primitives
解决方案:
$scope.fields = [
{name: 'author', model: ''},
{name: 'content', model: ''},
{name: 'time', model: ''},
];
<div ng-repeat="field in fields">
<label>{{field.name}}</label>
<input ng-model="field.model">
</div>
var postData = {};
for(var i = 0; i < fields.length; i++){
postData[fields[i].name] = fields[i].model;
}
postData
即是构造好的可以提交的表单对象。
参考:http://stackoverflow.com/questions/13714884/difficulty-with-ng-model-ng-repeat-and-inputs
AngularJS get resource from different port
问题描述:
一个只有前端的 Web 应用,在 Service 层访问运行在另一个端口的 Server 提供 RESTful API 。代码如下。
factory('AppDataService', function($resource) {
var urlPrefix = 'http://localhost:3333/';
return $resource(urlPrefix + 'api/:path', {}, {
list: {
method: 'GET',
params: {
path: 'list'
},
isArray: true
}
})
})
然而,这样会导致端口号被截掉,因为 Angular 会将冒号后的内容作为一个参数,具体参见 $resource strips port from url
解决方案:
可以在端口号的冒号前使用双反斜线来解决这个问题
var urlPrefix = 'http://localhost\\:3333/';
跨域请求资源(HTTP 访问控制)
问题描述:
同上一个问题,一个只有前端的 Web 应用,在 Service 层访问运行在另一个端口的 Server 提供 API 。由于 跨域(不同端口),出于安全考虑,浏览器会对脚本中发起的请求进行限制。
解决方案:
可以使用跨域资源共享(Cross-Origin Resource Sharing (CORS))来解决这个问题,详细可见 HTTP访问控制 。
提供 API 的 Server 是使用 NodeJS 和 Express 搭建,通过如下代码可以实现其他域对该服务器资源的请求。
app.all('*', function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header('Access-Control-Allow-Methods',
'OPTIONS,GET,POST,PUT,DELETE');
res.header("Access-Control-Allow-Headers",
"Content-Type, Authorization, X-Requested-With");
if ('OPTIONS' == req.method){
return res.send(200);
}
next();
});
参考:http://stackoverflow.com/questions/20754489/access-control-allow-origin-and-angular-js-http
使用 angular.module().controller() 创建 Controller
问题描述:
angular.module('App.controllers', [])
.controller('LoginCtrl', function () {
$scope.property = true;
});
angular.module('App', ['App.controllers'])
.config(['$routeProvider', function ($routeProvider) {
$routeProvider.when('/', {
templateUrl: 'partials/login.html',
controller: LoginCtrl
});
}]);
这样做会产生如下错误
Uncaught ReferenceError: LoginCtrl is not defined from App
解决方案:
直接使用 LoginCtrl
,会被当成变量,而这个变量并不存在,因此报错。加引号改为 String
即可
$routeProvider.when('/', {
templateUrl: 'partials/login.html',
controller: 'LoginCtrl'
});
使用 $resource 与 RESTful 数据源交互
问题描述:
angular.module('App.services', []).
factory('myservice', function ($resource) {
...
}
这样做会产生如下错误
Error: Unknown provider: $resourceProvider <- $resource <- myservice
解决方案:
由于 $resource
并不是 AngularJS Core 的一部分,因此需要引入 angular-resource.js
文件,并载入 ngResource
模块。
angular.module('App.services', ['ngResource'])
在使用 ngCookie
等其他非 AngularJS Core 的模块时,如果报以上错误,也可使用同样方法解决。
在Controller 中引用 Angular Module Constants
问题描述:
使用一个 myApp.config
模块定义 app 的配置信息,代码放在 config.js
中
angular.module('myApp.config', [])
.constant('APP_ID','54d32639da5b64d891fe283a')
在 app.js
中引入 myApp.config
模块
angular.module('myApp', ['myApp.controllers','myApp.config'])
在 index.html
中引入 config.js
文件 ,然后在 Controller 中使用 constant
angular.module('myApp.controllers', [])
.controller('ListCtrl', ['$scope', 'myApp.config',
function ($scope, config) {
$scope.APP_ID = config;
}])
这样做会产生如下错误
Unknown provider: myApp.configProvider <- myApp.config
解决方案:
直接注入常量名字
angular.module('myApp.controllers', [])
.controller('ListCtrl', ['$scope', 'APP_ID',
function ($scope, APP_ID) {
$scope.APP_ID = APP_ID;
}])
$cookieStore 的使用
问题描述:
使用 $cookieStore 维护浏览器端用户登录信息,并进行相应跳转
解决方案:
在 index.html
中引入 angular-cookies.js
文件,并载入 ngCookies
模块
angular.module('myApp', ['ngCookies', 'myApp.controllers'])
将 $cookieStore
注入到 Controller 中
.controller('LoginCtrl', ['$scope', '$location', '$cookieStore',
function ($scope, $location, $cookieStore) {
...
}
])
使用 $cookieStore
,在登录成功后
$cookieStore.put('username', $scope.user.username);
$location.path('/list');
在退出登录后
$cookieStore.remove('username');
在其他页面
if (!$cookieStore.get('username')) {
$location.path('/login')
}
使用GET方法请求资源时附带参数(req.query和req.params区别)
问题描述:
获取某一用户(author 为 jason)发布的所有状态。
解决方案:
在 Server 端定义拦截
app.get('/api/:schemaName/list', httpAPI.list);
在 Service 定义方法
factory('AppDataService', function($resource) {
var urlPrefix = 'http://localhost:3333/';
return $resource(urlPrefix + 'api/:path1/:path2', {}, {
list: {
method: 'GET',
params: {
path1: '',
path2: 'list'
},
isArray: true
}
})
})
在 Controller 中调用
AppDataService.list({path1: 'wbs', author: 'jason'}, function (wbs) {
$scope.wbs = wbs;
})
调用时对象中的键 list({path1: 'wbs', author: 'jason'})
会与参数名 params: {path1: '', path2: 'list'}
进行匹配,如果传入了一个没有在 URL 中设置过的参数 {author: 'jason'}
,那它会以普通的查询字符串的形式被发送,因此,将会产生一个这样的请求 url
Name: /list?author=jason
Path: /api/wbs/list
在 Server 端获取参数,req.query
经过解析后返回 {author: "jason"}
对象,req.params
返回与 route 拦截匹配的参数,即 req.params.schemaName
可得到 'wbs'
。
参考:Request.query and Request.param in ExpressJS
Controller 中参数的压缩
问题描述:
由于AngularJS是通过控制器构造函数的参数名字来推断依赖服务名称的。所以如果要压缩Controller的代码,它所有的参数也同时会被压缩,这时候依赖注入系统就不能正确的识别出服务了。
.controller('LoginCtrl', function ($scope, $location, $cookieStore) {
...
}
)
解决方案:
第一种方法,把要注入的服务放到一个字符串数组(代表依赖的名字)里,数组最后一个元素是控制器的方法函数
.controller('LoginCtrl', ['$scope', '$location', '$cookieStore',
function ($scope, $location, $cookieStore) {
...
}
])
第二种方法,在控制器函数里面给$inject属性赋值一个依赖服务标识符的数组
LoginCtrl.$inject = ['$scope', '$location', '$cookieStore'];
参考:XHR和依赖注入
Mongoose相关问题
Mongoose在创建Model时对Collection的命名
问题描述:
通过Mongoose操作MongoDB,创建一个user模型,在数据库生成的集合名为users
解决方案:
使用Mongoose从创建连接到向数据库写入一条数据经历了以下步骤
// 创建数据库(相当于在使用Hibernate的时候配置数据库)
mongoose.connect("mongodb://localhost/test");
// 定义UserSchema(相当于数据库建表)
var UserSchema = mongoose.Schema({
username: String,
password: String
});
// 创建User模型(相当于构建对象和数据库表映射)
var User = mongoose.model("user", UserSchema);
// 通过User模型,创建对象
var user = new User({
username: 'admin',
password: 'admin'
});
// 通过save方法持久化对象
user.save(function (err, doc) {
if(err || !doc) {
return false;
}
return true;
});
向 MongoDB 中一个不存在的 collection 插入文档,数据库会自动创建一个 Collection ,然而为什么创建 user 模型会生成 users 集合?通过分析 Mongoose 源码可以发现,Mongoose 在模型名到数据库集合名的转换上,做了特殊处理,即使用名词复数形式。
可以通过两种办法解决此问题(即保持模型名和集合名的一致):
- 准确使用名词复数对模型命名
- 注释掉 Mongoose 中名词变复数部分代码
参考:Mongoose在创建Model时对Collection的命名策略
Mongoose __v 属性
问题描述:
使用 Mongoose,在 save()
持久化对象时,会出现一个 __v
字段
var user = new User({
username: "admin",
password: 'admin'
});
user.save() // { __v: 0, username: 'admin', password: 'admin' }
解决方案:
__v
是默认的 versionKey
,在通过 Mongoose 第一次创建一个 document 时产生。可以通过设置 versionKey
为 false
使其不可用。
new mongoose.Schema({..}, { versionKey: false });
var user = new User({
username: "admin",
password: 'admin'
});
user.save() // {username: 'admin', password: 'admin' }
参考:Mongoose __v property - hide
使用 Mongoose 做高级查询
问题描述:
从一个消息 Collection 中查询两个好友(aa和bb)之间的聊天记录
解决方案:
var options = {
$or: [
{
"from": "aa",
"to": "bb"
},
{
"from": "bb",
"to": "aa"
}
]
}
db.find(options);
参考:How do you do more advance queries with Mongoose?
http://docs.mongodb.org/manual/reference/operator/query/or/
NodeJS 相关问题
全局地使用 EventEmitter
问题描述:
在多个地方(不同文件)使用同一个 EventEmitter 对象
解决方案:
在单个文件中,EventEmitter 的使用
var events = require("events");
var emitter = new events.EventEmitter();
emitter.on('ready', function(){
console.log('Ready? Go!');
})
setTimeout(function(){
emitter.emit('ready');
},1000)
在多个地方使用同一个 EventEmitter,可以使用 module.exports
在 pubsub.js
文件中定义模块
var EventEmitter = require('events').EventEmitter;
module.exports = new EventEmitter();
在 a.js
文件中使用
var pubsub = require('./pubsub');
pubsub.on('ready', function() {
console.log('Ready? Go!');
});
在 b.js
文件中使用
var pubsub = require('./pubsub');
pubsub.emit('ready', function() {
console.log('ready');
});
参考:http://nodejs.org/api/modules.html#modules_module_exports
http://stackoverflow.com/questions/17177735/nodejs-use-eventemitter-object-globally
http://sunspot.blog.51cto.com/372554/1266428
在Node中用Q实现Promise
问题描述:
在调用增删改查接口前,需要先获取该 Collection 的 Model,获取 Model 之前,需要从 schemas 集合获取该 Model 的 Schema,而前面这两个操作都是异步的。
解决方案:
可以使用 promise 。
promise是对异步编程的一种抽象。它是一个代理对象,代表一个必须进行异步处理的函数返回的值或抛出的异常。 – Kris Kowal on JSJ
promise 对象的核心部件是它的 then 方法,它是对 promise 解包以得到异步操作结果(或异常)的函数,用这个方法从异步操作中得到返回值(履约值),或抛出的异常(拒绝的理由)。then 方法有两个可选的参数,都是 callback 函数。
Node中可以使用 Q (npm install q
) 来将 callback 变成 promise
var Q = require('q');
function getSchema(dbName, schemaName){
var deferred = Q.defer();
Db.find({'name': schemaName}, 'value', function(error, schema) {
if(error){
// rejects the promise with `error` as the reason
deferred.reject(error)
}else{
// fulfills the promise with `data` as the value
deferred.resolve(schema);
}
});
return deferred.promise;
}
function getModel(dbName, shemaName){
var deferred = Q.defer();
// the first callback is onFulfilled, the seconde is onRejected
getSchema(dbName, schemaName).then(function(schemaObj){
if(!!schemaObj){
DbSchema = new mongoose.Schema(schemaObj);
var indb = mongo.model(schemaName, DbSchema);
deferred.resolve(indb);
}else{
deferred.resolve(null);
}
}, function(err){
console.error(err);
deferred.reject(err);
});
return deferred.promise;
}
参考:在Node.js 中用 Q 实现Promise – Callbacks之外的另一种选择
使用 Forever 运行 Node 应用
问题描述:
在 node 进程因为内部错误挂掉时需要重启 node server ,在服务器端文件变更时,需要重启 node server
解决方案:
可以安装 forever 模块,使用 forever 运行 node 应用。
forever 是一个 nodejs 守护进程,能够启动,停止,重启 app 应用。forever 完全基于命令行操作,在 forever 进程之下,创建 node 的子进程,通过 monitor 监控 node 子进程的运行情况,一旦文件更新,或者进程挂掉,forever 会自动重启 node 服务器,确保应用正常运行。
安装
// forever要求安装到全局环境
npm install forever -g
启动
// -p指定根目录,-l指定输出日志,-e指定错误日志,-a表示日志使用追加模式
forever -p . -l access.log -e error.log -a start app.js
停止
// 停止所有运行的node App
forever stopall
// 停止其中一个node App
forever stop app.js
此外,也可以 使用nodemon让node自动重启