Angularjs 1.3 中的$parse實(shí)例代碼
這次我們來看一下angular的Sandboxing Angular Expressions。關(guān)于內(nèi)置方法的,核心有兩塊:Lexer和Parser。其中大家對(duì)$parse可能更了解一點(diǎn)。好了不多廢話,先看Lexer的內(nèi)部結(jié)構(gòu):
1.Lexer
//構(gòu)造函數(shù)
var Lexer = function(options) {
this.options = options;
};
//原型
Lexer.prototype = {
constructor: Lexer,
lex: function(){},
is: function(){},
peek: function(){ /* 返回表達(dá)式的下一個(gè)位置的數(shù)據(jù),如果沒有則返回false */ },
isNumber: function(){ /* 判斷當(dāng)前表達(dá)式是否是一個(gè)數(shù)字 */ },
isWhitespace: function(){/* 判斷當(dāng)前表達(dá)式是否是空格符 */},
isIdent: function(){/* 判斷當(dāng)前表達(dá)式是否是英文字符(包含_和$) */},
isExpOperator: function(){/* 判斷當(dāng)時(shí)表達(dá)式是否是-,+還是數(shù)字 */},
throwError: function(){ /* 拋出異常 */},
readNumber: function(){ /* 讀取數(shù)字 */},
readIdent: function(){ /* 讀取字符 */},
readString: function(){ /*讀取攜帶''或""的字符串*/ }
};
這里指出一點(diǎn),因?yàn)槭潜磉_(dá)式。所以類似"123"這類的東西,在Lexer看來應(yīng)該算是數(shù)字而非字符串。表達(dá)式中的字符串必須使用單引號(hào)或者雙引號(hào)來標(biāo)識(shí)。Lexer的核心邏輯在lex方法中:
lex: function(text) {
this.text = text;
this.index = 0;
this.tokens = [];
while (this.index < this.text.length) {
var ch = this.text.charAt(this.index);
if (ch === '"' || ch === "'") {
/* 嘗試判斷是否是字符串 */
this.readString(ch);
} else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) {
/* 嘗試判斷是否是數(shù)字 */
this.readNumber();
} else if (this.isIdent(ch)) {
/* 嘗試判斷是否是字母 */
this.readIdent();
} else if (this.is(ch, '(){}[].,;:?')) {
/* 判斷是否是(){}[].,;:? */
this.tokens.push({index: this.index, text: ch});
this.index++;
} else if (this.isWhitespace(ch)) {
/* 判斷是否是空白符 */
this.index++;
} else {
/* 嘗試匹配操作運(yùn)算 */
var ch2 = ch + this.peek();
var ch3 = ch2 + this.peek(2);
var op1 = OPERATORS[ch];
var op2 = OPERATORS[ch2];
var op3 = OPERATORS[ch3];
if (op1 || op2 || op3) {
var token = op3 ? ch3 : (op2 ? ch2 : ch);
this.tokens.push({index: this.index, text: token, operator: true});
this.index += token.length;
} else {
this.throwError('Unexpected next character ', this.index, this.index + 1);
}
}
}
return this.tokens;
}
主要看一下匹配操作運(yùn)算。這里源碼中會(huì)調(diào)用OPERATORS??匆幌翺PERATORS:
var OPERATORS = extend(createMap(), {
'+':function(self, locals, a, b) {
a=a(self, locals); b=b(self, locals);
if (isDefined(a)) {
if (isDefined(b)) {
return a + b;
}
return a;
}
return isDefined(b) ? b : undefined;},
'-':function(self, locals, a, b) {
a=a(self, locals); b=b(self, locals);
return (isDefined(a) ? a : 0) - (isDefined(b) ? b : 0);
},
'*':function(self, locals, a, b) {return a(self, locals) * b(self, locals);},
'/':function(self, locals, a, b) {return a(self, locals) / b(self, locals);},
'%':function(self, locals, a, b) {return a(self, locals) % b(self, locals);},
'===':function(self, locals, a, b) {return a(self, locals) === b(self, locals);},
'!==':function(self, locals, a, b) {return a(self, locals) !== b(self, locals);},
'==':function(self, locals, a, b) {return a(self, locals) == b(self, locals);},
'!=':function(self, locals, a, b) {return a(self, locals) != b(self, locals);},
'<':function(self, locals, a, b) {return a(self, locals) < b(self, locals);},
'>':function(self, locals, a, b) {return a(self, locals) > b(self, locals);},
'<=':function(self, locals, a, b) {return a(self, locals) <= b(self, locals);},
'>=':function(self, locals, a, b) {return a(self, locals) >= b(self, locals);},
'&&':function(self, locals, a, b) {return a(self, locals) && b(self, locals);},
'||':function(self, locals, a, b) {return a(self, locals) || b(self, locals);},
'!':function(self, locals, a) {return !a(self, locals);},
//Tokenized as operators but parsed as assignment/filters
'=':true,
'|':true
});
可以看到OPERATORS實(shí)際上存儲(chǔ)的是操作符和操作符函數(shù)的鍵值對(duì)。根據(jù)操作符返回對(duì)應(yīng)的操作符函數(shù)。我們看一下調(diào)用例子:
var _l = new Lexer({});
var a = _l.lex("a = a + 1");
console.log(a);
結(jié)合之前的lex方法,我們來回顧下代碼執(zhí)行過程:
1.index指向'a'是一個(gè)字母。匹配isIdent成功。將生成的token存入tokens中
2.index指向空格符,匹配isWhitespace成功,同上
3.index指向=,匹配操作運(yùn)算符成功,同上
4.index指向空格符,匹配isWhitespace成功,同上
5.index指向'a'是一個(gè)字母。匹配isIdent成功。同上
7.index指向+,匹配操作運(yùn)算符成功,同上
8.index指向空格符,匹配isWhitespace成功,同上
9.index指向1,匹配數(shù)字成功,同上
以上則是"a = a + 1"的代碼執(zhí)行過程。9步執(zhí)行結(jié)束之后,跳出while循環(huán)。剛才我們看到了,每次匹配成功,源碼會(huì)生成一個(gè)token。因?yàn)槠ヅ漕愋偷牟煌?,生成出來的token的鍵值對(duì)略有不同:
number:{
index: start,
text: number,
constant: true,
value: Number(number)
},
string: {
index: start,
text: rawString,
constant: true,
value: string
},
ident: {
index: start,
text: this.text.slice(start, this.index),
identifier: true /* 字符表示 */
},
'(){}[].,;:?': {
index: this.index,
text: ch
},
"操作符": {
index: this.index,
text: token,
operator: true
}
//text是表達(dá)式,而value才是實(shí)際的值
number和string其實(shí)都有相對(duì)應(yīng)的真實(shí)值,意味著如果我們表達(dá)式是2e2,那number生成的token的值value就應(yīng)該是200。到此我們通過lexer類獲得了一個(gè)具有token值得數(shù)組。從外部看,實(shí)際上Lexer是將我們輸入的表達(dá)式解析成了token json??梢岳斫鉃樯闪吮磉_(dá)式的語法樹(AST)。但是目前來看,我們依舊還沒有能獲得我們定義表達(dá)式的結(jié)果。那就需要用到parser了。
2.Parser
先看一下Parser的內(nèi)部結(jié)構(gòu):
//構(gòu)造函數(shù)
var Parser = function(lexer, $filter, options) {
this.lexer = lexer;
this.$filter = $filter;
this.options = options;
};
//原型
Parser.prototype = {
constructor: Parser,
parse: function(){},
primary: function(){},
throwError: function(){ /* 語法拋錯(cuò) */},
peekToken: function(){},
peek: function(){/*返回tokens中的第一個(gè)成員對(duì)象 */},
peekAhead: function(){ /* 返回tokens中指定成員對(duì)象,否則返回false */},
expect: function(){ /* 取出tokens中第一個(gè)對(duì)象,否則返回false */ },
consume: function(){ /* 取出第一個(gè),底層調(diào)用expect */ },
unaryFn: function(){ /* 一元操作 */},
binaryFn: function(){ /* 二元操作 */},
identifier: function(){},
constant: function(){},
statements: function(){},
filterChain: function(){},
filter: function(){},
expression: function(){},
assignment: function(){},
ternary: function(){},
logicalOR: function(){ /* 邏輯或 */},
logicalAND: function(){ /* 邏輯與 */ },
equality: function(){ /* 等于 */ },
relational: function(){ /* 比較關(guān)系 */ },
additive: function(){ /* 加法,減法 */ },
multiplicative: function(){ /* 乘法,除法,求余 */ },
unary: function(){ /* 一元 */ },
fieldAccess: function(){},
objectIndex: function(){},
functionCall: function(){},
arrayDeclaration: function(){},
object: function(){}
}
Parser的入口方法是parse,內(nèi)部執(zhí)行了statements方法。來看下statements:
statements: function() {
var statements = [];
while (true) {
if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']'))
statements.push(this.filterChain());
if (!this.expect(';')) {
// optimize for the common case where there is only one statement.
// TODO(size): maybe we should not support multiple statements?
return (statements.length === 1)
? statements[0]
: function $parseStatements(self, locals) {
var value;
for (var i = 0, ii = statements.length; i < ii; i++) {
value = statements[i](self, locals);
}
return value;
};
}
}
}
這里我們將tokens理解為表達(dá)式,實(shí)際上它就是經(jīng)過表達(dá)式通過lexer轉(zhuǎn)換過來的。statements中。如果表達(dá)式不以},),;,]開頭,將會(huì)執(zhí)行filterChain方法。當(dāng)tokens檢索完成之后,最后返回了一個(gè)$parseStatements方法。其實(shí)Parser中很多方法都返回了類似的對(duì)象,意味著返回的內(nèi)容將需要執(zhí)行后才能得到結(jié)果。
看一下filterChain:
filterChain: function() {
/* 針對(duì)angular語法的filter */
var left = this.expression();
var token;
while ((token = this.expect('|'))) {
left = this.filter(left);
}
return left;
}
其中filterChain是針對(duì)angular表達(dá)式獨(dú)有的"|"filter寫法設(shè)計(jì)的。我們先繞過這塊,進(jìn)入expression
expression: function() {
return this.assignment();
}
再看assignment:
assignment: function() {
var left = this.ternary();
var right;
var token;
if ((token = this.expect('='))) {
if (!left.assign) {
this.throwError('implies assignment but [' +
this.text.substring(0, token.index) + '] can not be assigned to', token);
}
right = this.ternary();
return extend(function $parseAssignment(scope, locals) {
return left.assign(scope, right(scope, locals), locals);
}, {
inputs: [left, right]
});
}
return left;
}
我們看到了ternary方法。這是一個(gè)解析三目操作的方法。與此同時(shí),assignment將表達(dá)式以=劃分成left和right兩塊。并且兩塊都嘗試執(zhí)行ternary。
ternary: function() {
var left = this.logicalOR();
var middle;
var token;
if ((token = this.expect('?'))) {
middle = this.assignment();
if (this.consume(':')) {
var right = this.assignment();
return extend(function $parseTernary(self, locals) {
return left(self, locals) ? middle(self, locals) : right(self, locals);
}, {
constant: left.constant && middle.constant && right.constant
});
}
}
return left;
}
在解析三目運(yùn)算之前,又根據(jù)?將表達(dá)式劃分成left和right兩塊。左側(cè)再去嘗試執(zhí)行l(wèi)ogicalOR,實(shí)際上這是一個(gè)邏輯與的解析,按照這個(gè)執(zhí)行流程,我們一下有了思路。這有點(diǎn)類似我們一般寫三目時(shí)。代碼的執(zhí)行情況,比如: 2 > 2 ? 1 : 0。如果把這個(gè)當(dāng)成表達(dá)式,那根據(jù)?劃分left和right,left就應(yīng)該是2 > 2,right應(yīng)該就是 1: 0。然后嘗試在left看是否有邏輯或的操作。也就是,Parser里面的方法調(diào)用的嵌套級(jí)數(shù)越深,其方法的優(yōu)先級(jí)則越高。好,那我們一口氣看看這個(gè)最高的優(yōu)先級(jí)在哪?
logicalOR -> logicalAND -> equality -> relational -> additive -> multiplicative -> unary
好吧,嵌套級(jí)數(shù)確實(shí)有點(diǎn)多。那么我們看下unary。
unary: function() {
var token;
if (this.expect('+')) {
return this.primary();
} else if ((token = this.expect('-'))) {
return this.binaryFn(Parser.ZERO, token.text, this.unary());
} else if ((token = this.expect('!'))) {
return this.unaryFn(token.text, this.unary());
} else {
return this.primary();
}
}
這邊需要看兩個(gè)主要的方法,一個(gè)是binaryFn和primay。如果判斷是-,則必須通過binaryFn去添加函數(shù)??聪耣inaryFn
binaryFn: function(left, op, right, isBranching) {
var fn = OPERATORS[op];
return extend(function $parseBinaryFn(self, locals) {
return fn(self, locals, left, right);
}, {
constant: left.constant && right.constant,
inputs: !isBranching && [left, right]
});
}
其中OPERATORS是之前聊Lexer也用到過,它根據(jù)操作符存儲(chǔ)相應(yīng)的操作函數(shù)??匆幌耭n(self, locals, left, right)。而我們隨便取OPERATORS中的一個(gè)例子:
'-':function(self, locals, a, b) {
a=a(self, locals); b=b(self, locals);
return (isDefined(a) ? a : 0) - (isDefined(b) ? b : 0);
}
其中a和b就是left和right,他們其實(shí)都是返回的跟之前類似的$parseStatements方法。默認(rèn)存儲(chǔ)著token中的value。經(jīng)過事先解析好的四則運(yùn)算來生成最終答案。其實(shí)這就是Parser的基本功能。至于嵌套,我們可以把它理解為js的操作符的優(yōu)先級(jí)。這樣就一目了然了。至于primay方法。塔刷選{ ( 對(duì)象做進(jìn)一步的解析過程。
Parser的代碼并不復(fù)雜,只是函數(shù)方法間調(diào)用密切,讓我們?cè)倏匆粋€(gè)例子:
var _l = new Lexer({});
var _p = new Parser(_l);
var a = _p.parse("1 + 1 + 2");
console.log(a()); //4
我們看下1+1+2生成的token是什么樣的:
[
{"index":0,"text":"1","constant":true,"value":1},{"index":2,"text":"+","operator":true},{"index":4,"text":"1","constant":true,"value":1},{"index":6,"text":"+","operator":true},{"index":8,"text":"2","constant":true,"value":2}
]
Parser根據(jù)lexer生成的tokens嘗試解析。tokens每一個(gè)成員都會(huì)生成一個(gè)函數(shù),其先后執(zhí)行邏輯按照用戶輸入的1+1+2的順序執(zhí)行。注意像1和2這類constants為true的token,parser會(huì)通過constant生成需要的函數(shù)$parseConstant,也就是說1+1+2中的兩個(gè)1和一個(gè)2都是返回$parseConstant函數(shù),通過$parseBinaryFn管理加法邏輯。
constant: function() {
var value = this.consume().value;
return extend(function $parseConstant() {
return value; //這個(gè)函數(shù)執(zhí)行之后,就是將value值返回。
}, {
constant: true,
literal: true
});
},
binaryFn: function(left, op, right, isBranching) {
var fn = OPERATORS[op];//加法邏輯
return extend(function $parseBinaryFn(self, locals) {
return fn(self, locals, left, right);//left和right分別表示生成的對(duì)應(yīng)函數(shù)
}, {
constant: left.constant && right.constant,
inputs: !isBranching && [left, right]
});
}
那我們demo中的a應(yīng)該返回什么函數(shù)呢?當(dāng)然是$parseBinaryFn。其中的left和right分別是1+1的$parseBinaryFn,right就是2的$parseConstant。
再來一個(gè)例子:
var _l = new Lexer({});
var _p = new Parser(_l);
var a = _p.parse('{"name": "hello"}');
console.log(a);
這邊我們傳入一個(gè)json,理論上我們執(zhí)行完a函數(shù),應(yīng)該返回一個(gè){name: "hello"}的對(duì)象。它調(diào)用了Parser中的object
object: function() {
var keys = [], valueFns = [];
if (this.peekToken().text !== '}') {
do {
if (this.peek('}')) {
// Support trailing commas per ES5.1.
break;
}
var token = this.consume();
if (token.constant) {
//把key取出來
keys.push(token.value);
} else if (token.identifier) {
keys.push(token.text);
} else {
this.throwError("invalid key", token);
}
this.consume(':');
//冒號(hào)之后,則是值,將值存在valueFns中
valueFns.push(this.expression());
//根據(jù)逗號(hào)去迭代下一個(gè)
} while (this.expect(','));
}
this.consume('}');
return extend(function $parseObjectLiteral(self, locals) {
var object = {};
for (var i = 0, ii = valueFns.length; i < ii; i++) {
object[keys[i]] = valueFns[i](self, locals);
}
return object;
}, {
literal: true,
constant: valueFns.every(isConstant),
inputs: valueFns
});
}
比方我們的例子{"name": "hello"},object會(huì)將name存在keys中,hello則會(huì)生成$parseConstant函數(shù)存在valueFns中,最終返回$parseObjectLiternal函數(shù)。
下一個(gè)例子:
var a = _p.parse('{"name": "hello"}["name"]');
這個(gè)跟上一個(gè)例子的差別在于后面嘗試去讀取name的值,這邊則調(diào)用parser中的objectIndex方法。
objectIndex: function(obj) {
var expression = this.text;
var indexFn = this.expression();
this.consume(']');
return extend(function $parseObjectIndex(self, locals) {
var o = obj(self, locals), //parseObjectLiteral,實(shí)際就是obj
i = indexFn(self, locals), //$parseConstant,這里就是name
v;
ensureSafeMemberName(i, expression);
if (!o) return undefined;
v = ensureSafeObject(o[i], expression);
return v;
}, {
assign: function(self, value, locals) {
var key = ensureSafeMemberName(indexFn(self, locals), expression);
// prevent overwriting of Function.constructor which would break ensureSafeObject check
var o = ensureSafeObject(obj(self, locals), expression);
if (!o) obj.assign(self, o = {}, locals);
return o[key] = value;
}
});
}
很簡單吧,obj[xx]和obj.x類似。大家自行閱讀,我們?cè)倏匆粋€(gè)函數(shù)調(diào)用的demo
var _l = new Lexer({});
var _p = new Parser(_l, '', {});
var demo = {
"test": function(){
alert("welcome");
}
};
var a = _p.parse('test()');
console.log(a(demo));
我們傳入一個(gè)test的調(diào)用。這邊調(diào)用了parser中的functionCall方法和identifier方法
identifier: function() {
var id = this.consume().text;
//Continue reading each `.identifier` unless it is a method invocation
while (this.peek('.') && this.peekAhead(1).identifier && !this.peekAhead(2, '(')) {
id += this.consume().text + this.consume().text;
}
return getterFn(id, this.options, this.text);
}
看一下getterFn方法
...
forEach(pathKeys, function(key, index) {
ensureSafeMemberName(key, fullExp);
var lookupJs = (index
// we simply dereference 's' on any .dot notation
? 's'
// but if we are first then we check locals first, and if so read it first
: '((l&&l.hasOwnProperty("' + key + '"))?l:s)') + '.' + key;
if (expensiveChecks || isPossiblyDangerousMemberName(key)) {
lookupJs = 'eso(' + lookupJs + ', fe)';
needsEnsureSafeObject = true;
}
code += 'if(s == null) return undefined;\n' +
's=' + lookupJs + ';\n';
});
code += 'return s;';
/* jshint -W054 */
var evaledFnGetter = new Function('s', 'l', 'eso', 'fe', code); // s=scope, l=locals, eso=ensureSafeObject
/* jshint +W054 */
evaledFnGetter.toString = valueFn(code);
...
這是通過字符串創(chuàng)建一個(gè)匿名函數(shù)的方法。我們看下demo的test生成了一個(gè)什么匿名函數(shù):
function('s', 'l', 'eso', 'fe'){
if(s == null) return undefined;
s=((l&&l.hasOwnProperty("test"))?l:s).test;
return s;
}
這個(gè)匿名函數(shù)的意思,需要傳入一個(gè)上下文,匿名函數(shù)通過查找上下文中是否有test屬性,如果沒有傳上下文則直接返回未定義。這也就是為什么我們?cè)谏珊玫腶函數(shù)在執(zhí)行它時(shí)需要傳入demo對(duì)象的原因。最后補(bǔ)一個(gè)functionCall
functionCall: function(fnGetter, contextGetter) {
var argsFn = [];
if (this.peekToken().text !== ')') {
/* 確認(rèn)調(diào)用時(shí)有入?yún)?*/
do {
//形參存入argsFn
argsFn.push(this.expression());
} while (this.expect(','));
}
this.consume(')');
var expressionText = this.text;
// we can safely reuse the array across invocations
var args = argsFn.length ? [] : null;
return function $parseFunctionCall(scope, locals) {
var context = contextGetter ? contextGetter(scope, locals) : isDefined(contextGetter) ? undefined : scope;
//或者之前創(chuàng)建生成的匿名函數(shù)
var fn = fnGetter(scope, locals, context) || noop;
if (args) {
var i = argsFn.length;
while (i--) {
args[i] = ensureSafeObject(argsFn[i](scope, locals), expressionText);
}
}
ensureSafeObject(context, expressionText);
ensureSafeFunction(fn, expressionText);
// IE doesn't have apply for some native functions
//執(zhí)行匿名函數(shù)的時(shí)候需要傳入上下文
var v = fn.apply
? fn.apply(context, args)
: fn(args[0], args[1], args[2], args[3], args[4]);
if (args) {
// Free-up the memory (arguments of the last function call).
args.length = 0;
}
return ensureSafeObject(v, expressionText);
};
}
下面我們看一下$ParseProvider,這是一個(gè)基于Lex和Parser函數(shù)的angular內(nèi)置provider。它對(duì)scope的api提供了基礎(chǔ)支持。
...
return function $parse(exp, interceptorFn, expensiveChecks) {
var parsedExpression, oneTime, cacheKey;
switch (typeof exp) {
case 'string':
cacheKey = exp = exp.trim();
var cache = (expensiveChecks ? cacheExpensive : cacheDefault);
parsedExpression = cache[cacheKey];
if (!parsedExpression) {
if (exp.charAt(0) === ':' && exp.charAt(1) === ':') {
oneTime = true;
exp = exp.substring(2);
}
var parseOptions = expensiveChecks ? $parseOptionsExpensive : $parseOptions;
//調(diào)用lexer和parser
var lexer = new Lexer(parseOptions);
var parser = new Parser(lexer, $filter, parseOptions);
parsedExpression = parser.parse(exp);
//添加$$watchDelegate,為scope部分提供支持
if (parsedExpression.constant) {
parsedExpression.$$watchDelegate = constantWatchDelegate;
} else if (oneTime) {
//oneTime is not part of the exp passed to the Parser so we may have to
//wrap the parsedExpression before adding a $$watchDelegate
parsedExpression = wrapSharedExpression(parsedExpression);
parsedExpression.$$watchDelegate = parsedExpression.literal ?
oneTimeLiteralWatchDelegate : oneTimeWatchDelegate;
} else if (parsedExpression.inputs) {
parsedExpression.$$watchDelegate = inputsWatchDelegate;
}
//做相關(guān)緩存
cache[cacheKey] = parsedExpression;
}
return addInterceptor(parsedExpression, interceptorFn);
case 'function':
return addInterceptor(exp, interceptorFn);
default:
return addInterceptor(noop, interceptorFn);
}
};
總結(jié):Lexer和Parser的實(shí)現(xiàn)確實(shí)讓我大開眼界。通過這兩個(gè)函數(shù),實(shí)現(xiàn)了angular自己的語法解析器。邏輯部分還是相對(duì)復(fù)雜
以上所述是小編給大家介紹的Angularjs 1.3 中的$parse實(shí)例代碼,希望對(duì)大家有所幫助,如果大家有任何疑問請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
Angularjs的ng-repeat中去除重復(fù)數(shù)據(jù)的方法
這篇文章主要介紹了Angularjs的ng-repeat中去除重復(fù)數(shù)據(jù)的方法,涉及AngularJS針對(duì)重復(fù)數(shù)據(jù)的遍歷與過濾技巧,需要的朋友可以參考下2016-08-08
AngularJS常見過濾器用法實(shí)例總結(jié)
這篇文章主要介紹了AngularJS常見過濾器用法,結(jié)合實(shí)例形式總結(jié)分析了AngularJS大小寫過濾器、貨幣過濾器、日期過濾器、limitTo過濾器、orderBy過濾器及自定義過濾器使用方法,需要的朋友可以參考下2017-07-07
angular2 ng2-file-upload上傳示例代碼
這篇文章主要介紹了angular2 ng2-file-upload上傳示例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-08-08
Angularjs在360兼容模式下取數(shù)據(jù)緩存問題的解決辦法
這篇文章主要為大家詳細(xì)介紹了Angularjs在360兼容模式下取數(shù)據(jù)緩存問題的解決辦法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06
ios設(shè)備中angularjs無法改變頁面title的解決方法
今天小編就為大家分享一篇ios設(shè)備中angularjs無法改變頁面title的解決方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-09-09
用AngularJS的指令實(shí)現(xiàn)tabs切換效果
這篇文章介紹的是寫一個(gè)通過指令嵌套實(shí)現(xiàn)tabs功能的指令模塊,這也是我在一個(gè)項(xiàng)目中應(yīng)用到的,現(xiàn)在分享給大家,有需要的可以參考借鑒。2016-08-08
Angular學(xué)習(xí)筆記之集成三方UI框架、控件的示例
這篇文章主要介紹了Angular學(xué)習(xí)筆記之集成三方UI框架、控件的示例,詳細(xì)的介紹了Material UI、Ag-grid等框架,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-03-03

