Start开发中遇到的问题及解决办法

AngularJS相关问题

ng-repeat中的ng-model

问题描述

实现一个通用表单,表单中的元素类型固定,但数量和名称不固定。由于数量和名称不固定,所以不能逐一写出表单元素,只能采用ng-repeat产生元素。代码如下。

1
$scope.fields = ['author', 'content', 'time'];
1
2
3
4
<div ng-repeat="field in fields">
<label>{{field}}</label>
<input ng-model="field">
</div>

很显然,这样会出现一个绑定问题,具体参见 don’t bind to primitives

解决方案

1
2
3
4
5
$scope.fields = [
{name: 'author', model: ''},
{name: 'content', model: ''},
{name: 'time', model: ''},
];
1
2
3
4
<div ng-repeat="field in fields">
<label>{{field.name}}</label>
<input ng-model="field.model">
</div>
1
2
3
4
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 。代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
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

解决方案

可以在端口号的冒号前使用双反斜线来解决这个问题

1
var urlPrefix = 'http://localhost\\:3333/';

参考:http://stackoverflow.com/questions/19909611/why-the-port-number-is-cut-when-angular-app-call-rest-service

跨域请求资源(HTTP 访问控制)

问题描述

同上一个问题,一个只有前端的 Web 应用,在 Service 层访问运行在另一个端口的 Server 提供 API 。由于 跨域(不同端口),出于安全考虑,浏览器会对脚本中发起的请求进行限制。

解决方案

可以使用跨域资源共享(Cross-Origin Resource Sharing (CORS))来解决这个问题,详细可见 HTTP访问控制

提供 API 的 Server 是使用 NodeJS 和 Express 搭建,通过如下代码可以实现其他域对该服务器资源的请求。

1
2
3
4
5
6
7
8
9
10
11
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

问题描述

1
2
3
4
5
6
7
8
9
10
11
12
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
});
}]);

这样做会产生如下错误

1
Uncaught ReferenceError: LoginCtrl is not defined from App

解决方案

直接使用 LoginCtrl,会被当成变量,而这个变量并不存在,因此报错。加引号改为 String 即可

1
2
3
4
$routeProvider.when('/', {
templateUrl: 'partials/login.html',
controller: 'LoginCtrl'
});

使用 $resource 与 RESTful 数据源交互

问题描述

1
2
3
4
angular.module('App.services', []).
factory('myservice', function ($resource) {
...
}

这样做会产生如下错误

1
Error: Unknown provider: $resourceProvider <- $resource <- myservice

解决方案

由于 $resource 并不是 AngularJS Core 的一部分,因此需要引入 angular-resource.js 文件,并载入 ngResource 模块。

1
angular.module('App.services', ['ngResource'])

在使用 ngCookie 等其他非 AngularJS Core 的模块时,如果报以上错误,也可使用同样方法解决。

在Controller 中引用 Angular Module Constants

问题描述

使用一个 myApp.config 模块定义 app 的配置信息,代码放在 config.js

1
2
angular.module('myApp.config', [])
.constant('APP_ID','54d32639da5b64d891fe283a')

app.js 中引入 myApp.config 模块

1
angular.module('myApp', ['myApp.controllers','myApp.config'])

index.html 中引入 config.js 文件 ,然后在 Controller 中使用 constant

1
2
3
4
5
angular.module('myApp.controllers', [])
.controller('ListCtrl', ['$scope', 'myApp.config',
function ($scope, config) {
$scope.APP_ID = config;
}])

这样做会产生如下错误

1
Unknown provider: myApp.configProvider <- myApp.config

解决方案

直接注入常量名字

1
2
3
4
5
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 模块

1
angular.module('myApp', ['ngCookies', 'myApp.controllers'])

$cookieStore 注入到 Controller 中

1
2
3
4
5
.controller('LoginCtrl', ['$scope', '$location', '$cookieStore', 
function ($scope, $location, $cookieStore) {
...
}
])

使用 $cookieStore ,在登录成功后

1
2
$cookieStore.put('username', $scope.user.username);
$location.path('/list');

在退出登录后

1
$cookieStore.remove('username');

在其他页面

1
2
3
if (!$cookieStore.get('username')) {
$location.path('/login')
}

使用GET方法请求资源时附带参数(req.query和req.params区别)

问题描述

获取某一用户(author 为 jason)发布的所有状态。

解决方案

在 Server 端定义拦截

1
app.get('/api/:schemaName/list', httpAPI.list);

在 Service 定义方法

1
2
3
4
5
6
7
8
9
10
11
12
13
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 中调用

1
2
3
AppDataService.list({path1: 'wbs', author: 'jason'}, function (wbs) {
$scope.wbs = wbs;
})

调用时对象中的键 list({path1: 'wbs', author: 'jason'}) 会与参数名 params: {path1: '', path2: 'list'} 进行匹配,如果传入了一个没有在 URL 中设置过的参数 {author: 'jason'} ,那它会以普通的查询字符串的形式被发送,因此,将会产生一个这样的请求 url

1
2
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的代码,它所有的参数也同时会被压缩,这时候依赖注入系统就不能正确的识别出服务了。

1
2
3
4
.controller('LoginCtrl', function ($scope, $location, $cookieStore) {
...
}
)

解决方案

第一种方法,把要注入的服务放到一个字符串数组(代表依赖的名字)里,数组最后一个元素是控制器的方法函数

1
2
3
4
5
.controller('LoginCtrl', ['$scope', '$location', '$cookieStore', 
function ($scope, $location, $cookieStore) {
...
}
])

第二种方法,在控制器函数里面给$inject属性赋值一个依赖服务标识符的数组

1
LoginCtrl.$inject = ['$scope', '$location', '$cookieStore'];

参考:XHR和依赖注入

Mongoose相关问题

Mongoose在创建Model时对Collection的命名

问题描述

通过Mongoose操作MongoDB,创建一个user模型,在数据库生成的集合名为users

解决方案

使用Mongoose从创建连接到向数据库写入一条数据经历了以下步骤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 创建数据库(相当于在使用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 字段

1
2
3
4
5
var user = new User({
username: "admin",
password: 'admin'
});
user.save() // { __v: 0, username: 'admin', password: 'admin' }

解决方案

__v 是默认的 versionKey ,在通过 Mongoose 第一次创建一个 document 时产生。可以通过设置 versionKeyfalse 使其不可用。

1
2
3
4
5
6
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)之间的聊天记录

解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
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 的使用

1
2
3
4
5
6
7
8
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 文件中定义模块

1
2
var EventEmitter = require('events').EventEmitter;
module.exports = new EventEmitter();

a.js 文件中使用

1
2
3
4
var pubsub = require('./pubsub');
pubsub.on('ready', function() {
console.log('Ready? Go!');
});

b.js 文件中使用

1
2
3
4
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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 服务器,确保应用正常运行。

安装

1
2
// forever要求安装到全局环境
npm install forever -g

启动

1
2
// -p指定根目录,-l指定输出日志,-e指定错误日志,-a表示日志使用追加模式
forever -p . -l access.log -e error.log -a start app.js

停止

1
2
3
4
// 停止所有运行的node App
forever stopall
// 停止其中一个node App
forever stop app.js

此外,也可以 使用nodemon让node自动重启

参考:Nodejs服务器管理模块forever
使用forever运行nodejs应用

坚持原创技术分享,您的支持将鼓励我继续创作!