Controller & Attributes
1. Controller
1.1 配置使用
{ controller: 'MyController',// 配置当前directive需要的controller require: '^myDir',// 通过配置require来获取该directive的controller link: function (scope,el,attrs,ctrls) { // ... ctrls就是require获取的ctrls } }
1.2 源码
// 由于配置了templateUrl的directive进入异步阶段后会重新执行修改后的directive, // 所以不需要在这作ctrl统计 if (!directive.templateUrl && directive.controller) { directiveValue = directive.controller; controllerDirectives = controllerDirectives || {}; assertNoDuplicate("'" + directiveName + "' controller",controllerDirectives[directiveName],directive,$compileNode); controllerDirectives[directiveName] = directive; }
收集是在compile阶段,但是真正的执行是在link阶段nodeLinkFn中,如下
if (controllerDirectives) { forEach(controllerDirectives,function(directive) { // locals提供了ctrl必要的scope,attrs等参数供ctrl中注入调用 var locals = { $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope,$element: $element,$attrs: attrs,$transclude: transcludeFn },controllerInstance; controller = directive.controller; if (controller == '@') { controller = attrs[directive.name]; } // 调用$controller服务生成ctrlInstance controllerInstance = $controller(controller,locals); // 缓存directive -> ctrl,为后续require作准备 elementControllers[directive.name] = controllerInstance; if (!hasElementTranscludeDirective) { $element.data('$' + directive.name + 'Controller',controllerInstance); } // 处理配置了controllerAs,绑定到scope上 if (directive.controllerAs) { locals.$scope[directive.controllerAs] = controllerInstance; } }); }
1.3. require
require定义在directive的配置中,源码处理逻辑:
1). 在applyDirectivesToNode的addLinkFns中赋值给linkFn
pre.require = directive.require; // ... post.require = directive.require;
2). 在nodeLinkFn中正式使用刚才保存的require,通过getControllers()获取ctrls注入到linkFn的参数中
linkFn(linkFn.isolateScope ? isolateScope : scope,$element,linkFn.require && getControllers(linkFn.directiveName,linkFn.require,elementControllers),transcludeFn);
3). 详细见getControllers逻辑
function getControllers(directiveName,require,elementControllers) { var value,retrievalMethod = 'data',optional = false; if (isString(require)) { // 判断是否^(可从上层获取),及是否?(option可选) while((value = require.charAt(0)) == '^' || value == '?') { require = require.substr(1); if (value == '^') { retrievalMethod = 'inheritedData'; } optional = optional || value == '?'; } value = null; // 先本层获取ctrl if (elementControllers && retrievalMethod === 'data') { value = elementControllers[require]; } // 没有需要的ctrl,就从上层获取 value = value || $element[retrievalMethod]('$' + require + 'Controller'); // 还是没获取到并且是非可选就抛错 if (!value && !optional) { throw $compileMinErr('ctreq',"Controller '{0}',required by directive '{1}',can't be found!",directiveName); } return value; } else if (isArray(require)) { value = []; // array循环调用 forEach(require,function(require) { value.push(getControllers(directiveName,elementControllers)); }); } return value; }
2. Attributes
对attrs的处理分为收集监听及注入$attrs
2.1 收集attrs
ng在收集directive时顺便收集了attrs,所以都是在collectDirectives()中
// 对所有的attribute进行循环 for (var attr,name,nName,ngAttrName,value,isNgAttr,nAttrs = node.attributes,j = 0,jj = nAttrs && nAttrs.length; j < jj; j++) { var attrStartName = false; var attrEndName = false; attr = nAttrs[j]; if (!msie || msie >= 8 || attr.specified) { name = attr.name; value = trim(attr.value); // directiveNormalize 用于 xx-abc-d -> xxAbcD 转换 ngAttrName = directiveNormalize(name); // 判断是否ngAttr开头的属性 if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) { // snake_case 是 directiveNormalize 反操作 name = snake_case(ngAttrName.substr(6),'-'); } var directiveNName = ngAttrName.replace(/(Start|End)$/,''); if (ngAttrName === directiveNName + 'Start') { attrStartName = name; attrEndName = name.substr(0,name.length - 5) + 'end'; name = name.substr(0,name.length - 6); } nName = directiveNormalize(name.toLowerCase()); // 缓存映射,paAttr -> pa-attr attrsMap[nName] = name; if (isNgAttr || !attrs.hasOwnProperty(nName)) { attrs[nName] = value; // 判断是否可以配置boolean的属性 if (getBooleanAttrName(node,nName)) { attrs[nName] = true; } } addAttrInterpolateDirective(node,directives,nName); addDirective(directives,'A',maxPriority,ignoreDirective,attrStartName,attrEndName); } }
这里有个地方,何为可以配置为boolean的属性呢?
function getBooleanAttrName(element,name) { var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()]; return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr; } // BOOLEAN_ATTR > multiple,selected,checked,disabled,readOnly,required,open // BOOLEAN_ELEMENTS > input,select,option,textarea,button,form,details
2.2 $attrs源码
主要分析如何实现监听
$observe: function(key,fn) { var attrs = this,$$observers = (attrs.$$observers || (attrs.$$observers = {})),listeners = ($$observers[key] || ($$observers[key] = [])); listeners.push(fn); // 使用$evalAsync为了初始化时也能触发执行fn $rootScope.$evalAsync(function() { if (!listeners.$$inter) { fn(attrs[key]); } }); // 跟$watch类似返回removeObserve方法,调用后移除监听 return function() { arrayRemove(listeners,fn); }; }
$set: function(key,writeAttr,attrName) { // ...省略 var $$observers = this.$$observers; $$observers && forEach($$observers[observer],function(fn) { try { fn(value); } catch (e) { $exceptionHandler(e); } }); }
3. $controller
ng中很大一部分逻辑都是写在controller中,下面对ctrl的注册及使用作分析
3.1 注册ctrl
module.controller('ctrlName',function () { // ... });
该controller方法是$controllerProvider中的register提供,具体怎么连接起来的后续在inject中作分析
function $ControllerProvider() { var controllers = {}; this.register = function(name,constructor) { assertNotHasOwnProperty(name,'controller'); if (isObject(name)) { extend(controllers,name); } else { controllers[name] = constructor; } }; // ... 省略 }
其实就是将name -> fn的映射保存下来
3.2 实例化ctrl
由于本质上也是个service所以定义provider就是$get,如下:
this.$get = ['$injector','$window',function($injector,$window) { return function(expression,locals) { var instance,match,constructor,identifier; if(isString(expression)) { // 检验是否符合 ctrl as aCtrl 这样的特性 match = expression.match(CNTRL_REG),constructor = match[1],identifier = match[3]; // 查找的顺序是已注册的controllers中 > $scope > window全局 expression = controllers.hasOwnProperty(constructor) ? controllers[constructor] : getter(locals.$scope,true) || getter($window,true); // 判断expression是否为空 assertArgFn(expression,true); } // 实例化ctrl,由于需要注入arg所以需要用$injector进行实例化 instance = $injector.instantiate(expression,locals,constructor); if (identifier) { if (!(locals && typeof locals.$scope === 'object')) { throw minErr('$controller')('noscp',"Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.",constructor || expression.name,identifier); } locals.$scope[identifier] = instance; } return instance; }; }];原文链接:https://www.f2er.com/angularjs/149038.html