什么是Angular?

从Angular的Github官网上可以看出创造者对该框架的简单定义:HTML enhanced for web apps.即Angular主要是针对Web应用,对HTML标签进行了增强。


使用Angular的好处?

Angular的使用在我看来和JavaEE的有些理念挺相似,什么依赖注入,控制反转,双向绑定等概念。那么使用Angular主要运用在哪些Web应用上呢?它给Web前端带来的好处又有哪些呢? 从Github上看使用Angular可以给开发者的带来的优势有以下几点:

  • 可以生成一个个模板引擎
  • UI表单的双向绑定
  • 实现依赖注入、控制反转
  • 解决异步回调

运用场合:经常CRUD的Web应用 AngularJS的语法及概念在这就不一一细说了,因为里面涉及到的东西太多,去官网看API和网上教程就行。想了解一个框架的本身,不在于可以用它实现什么,而在于它是怎么实现的。

下面的内容主要围绕"怎么实现"及"项目实战"开讲。


怎么实现

Angular核心原理

  • Angular启动过程分析
  • Provider与Injector执行过程
  • 指令的执行过程
  • $scope与双向数据绑定执行过程

Angular启动过程分析

源码解析Angular启动过程分析步骤:1.自执行加载完整个angular.js暴露全局变量angular

(function(window,document){
    ...
    var angular = window.angular || (window.angular = {});
    ...
})(window,document);

2.是否存在angular对象

if (window.angular.bootstrap) {
  console.log('WARNING: Tried to load angular more than once.')
  return
}

两种启动方式:

//自动启动:ng-app
<html ng-app="moduleName">
  //code
</html>

//手动启动:
<script>
  angular.element(document).ready(function () {
    angular.bootstrap(document, ['moduleName'])
  })
</script>

3.绑定jQuery

bindJQuery()

该方法判断用户是否自己导入jQuery,如果没有就导入jQlite:

function bindJQuery() {
  var jQuery = window.jQuery
  if (jQuery && jQuery.fn.on) {
    jqLite = jQuery
  } else {
    jqLite = JQLite
  }
  angular.element = jqLite
}

4.注入angularAPI

publishExternalAPI(angular)

该方法给全局变量angular扩展方法及属性,构建模块加载器,注入内置provider注册器和ng指令:

function publishExternalAPI(angular) {
  extend(angular, {
    'bootstrap': bootstrap,
    'copy': copy,
    'extend': extend,
    'merge': merge,
    'equals': equals,
    'element': jqLite,
    'forEach': forEach,
    'injector': createInjector,
    'noop': noop,
    'bind': bind,
    'toJson': toJson,
    'fromJson': fromJson,
    'identity': identity,
    'isUndefined': isUndefined,
    'isDefined': isDefined,
    'isString': isString,
    'isFunction': isFunction,
    'isObject': isObject,
    'isNumber': isNumber,
    'isElement': isElement,
    'isArray': isArray,
    'version': version,
    'isDate': isDate,
    'lowercase': lowercase,
    'uppercase': uppercase,
    'callbacks': {counter: 0},
    'getTestability': getTestability,
    '$$minErr': minErr,
    '$$csp': csp,
    'reloadWithDebugInfo': reloadWithDebugInfo
  });
  angularModule = setupModuleLoader(window);
};

5.初始化

jqLite(document).ready(function () {
  angularInit(document, bootstrap)
})

该方法查找ng-app,如果找到执行bootstrap方法,没找到执行手动启动的bootstrap方法

function angularInit(element, bootstrap) {
  if (appElement) {
    //ng-app
    config.strictDi = getNgAttribute(appElement, 'strict-di') !== null
    bootstrap(appElement, module ? [module] : [], config)
  }
}

bootstrap方法创建注册器,开始编译

function bootstrap(element, modules, config){
    ...
    var injector = createInjector(modules, config.strictDi);
    injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
       function bootstrapApply(scope, element, compile, injector) {
        scope.$apply(function() {
          element.data('$injector', injector);
          compile(element)(scope);
        });
      }]
    );
    ...
})

源码解析第5步之Provider与Injector执行过程

看源码的地方搜createInjector:

function createInjector(modulesToLoad, strictDi) {
    function provider(name, provider_) {
        ...
    }
    function factory(name, factoryFn, enforce) {
        return provider(name,{$get: ...});
    }
    function service(name, constructor) {
        return factory(name, ['$injector',...]);
    }
    function value(name, val) {
        return factory(name, valueFn(val), false);
    }
    function constant(name, value) {
       ...
    }
    function decorator(serviceName, decorFn) {
        ...
    };
    return {
      invoke: invoke,
      instantiate: instantiate,
      get: getService,
      annotate: createInjector.$$annotate,
      has: function(name) {return ...}
    };
  }
});

Provider目的是让接口和实现分离。 进行注注入的有:provider、factory、service、constant、value 上面方法核心都是provider实现的,只是参数不同,从左到右灵活性越来越差。

接受注入的有:controller、config、module、run、filter等

注入的方式:

var app=angular.module('demo',[]);
// 推断型注入
app.controller('ctrl',function($scope){
    //code
});
//声明式注入
var ctrl=function(){//code};
ctrl.$inject=['$scope'];
app.controller('ctrl',ctrl);
//内联式注入
app.controller('ctrl',['$scope',function($scope){
    //code
}]);

内置Inject注册器有:

$provide.provider({
  $anchorScroll: $AnchorScrollProvider,
  $animate: $AnimateProvider,
  $$animateQueue: $$CoreAnimateQueueProvider,
  $$AnimateRunner: $$CoreAnimateRunnerProvider,
  $browser: $BrowserProvider,
  $cacheFactory: $CacheFactoryProvider,
  $controller: $ControllerProvider,
  $document: $DocumentProvider,
  $exceptionHandler: $ExceptionHandlerProvider,
  $filter: $FilterProvider,
  $interpolate: $InterpolateProvider,
  $interval: $IntervalProvider,
  $http: $HttpProvider,
  $httpParamSerializer: $HttpParamSerializerProvider,
  $httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider,
  $httpBackend: $HttpBackendProvider,
  $location: $LocationProvider,
  $log: $LogProvider,
  $parse: $ParseProvider,
  $rootScope: $RootScopeProvider,
  $q: $QProvider,
  $$q: $$QProvider,
  $sce: $SceProvider,
  $sceDelegate: $SceDelegateProvider,
  $sniffer: $SnifferProvider,
  $templateCache: $TemplateCacheProvider,
  $templateRequest: $TemplateRequestProvider,
  $$testability: $$TestabilityProvider,
  $timeout: $TimeoutProvider,
  $window: $WindowProvider,
  $$rAF: $$RAFProvider,
  $$jqLite: $$jqLiteProvider,
  $$HashMap: $$HashMapProvider,
  $$cookieReader: $$CookieReaderProvider,
})

源码解析第5步之指令的执行过程

看源码的地方搜compile:

function compile($compileNodes,transcludeFn,maxPriority,ignoreDirective,previousCompileContext){
    compile.$$addScopeClass($compileNodes);
    var compositeLinkFn=compileNodes(...);
    return function publicLinkFn(scope,cloneConnectFn,options){
        ...
    });
};

指令的compile与link:

var app = angular.module('demo', [])
app.directive('Hello', function () {
  return {
    restrict: 'EA',
    template: '<div>Hello</div>',
    replace: true,
    //一般不使用comile,使用link
    link: function (scope, element, attrs, controller) {
      //el的获取设置attrs、scope或注册事件
    },
    compile: function (element, attrs, transclude) {
      //code
      return function (scope, element, attrs, controller) {
        //...
      }
    },
  }
})

compile指令作用是对指令的模板进行转换; link指令作用是在模型和视图之间建立关联,元素上的注册监听事件等;

内置指令有:

directive({
  a: htmlAnchorDirective,
  input: inputDirective,
  textarea: inputDirective,
  form: formDirective,
  script: scriptDirective,
  select: selectDirective,
  style: styleDirective,
  option: optionDirective,
  ngBind: ngBindDirective,
  ngBindHtml: ngBindHtmlDirective,
  ngBindTemplate: ngBindTemplateDirective,
  ngClass: ngClassDirective,
  ngClassEven: ngClassEvenDirective,
  ngClassOdd: ngClassOddDirective,
  ngCloak: ngCloakDirective,
  ngController: ngControllerDirective,
  ngForm: ngFormDirective,
  ngHide: ngHideDirective,
  ngIf: ngIfDirective,
  ngInclude: ngIncludeDirective,
  ngInit: ngInitDirective,
  ngNonBindable: ngNonBindableDirective,
  ngPluralize: ngPluralizeDirective,
  ngRepeat: ngRepeatDirective,
  ngShow: ngShowDirective,
  ngStyle: ngStyleDirective,
  ngSwitch: ngSwitchDirective,
  ngSwitchWhen: ngSwitchWhenDirective,
  ngSwitchDefault: ngSwitchDefaultDirective,
  ngOptions: ngOptionsDirective,
  ngTransclude: ngTranscludeDirective,
  ngModel: ngModelDirective,
  ngList: ngListDirective,
  ngChange: ngChangeDirective,
  pattern: patternDirective,
  ngPattern: patternDirective,
  required: requiredDirective,
  ngRequired: requiredDirective,
  minlength: minlengthDirective,
  ngMinlength: minlengthDirective,
  maxlength: maxlengthDirective,
  ngMaxlength: maxlengthDirective,
  ngValue: ngValueDirective,
  ngModelOptions: ngModelOptionsDirective,
})

$scope与双向数据绑定执行过程

看源码的地方搜scope:

function Scope() {
  this.$id = nextUid();
  this.$$phase = ... = null;
  this.$root = this;
  this.$$destroyed = false;
  this.$$listeners = {};
  this.$$listenerCount = {};
  this.$$watchersCount = 0;
  this.$$isolateBindings = null;
}
Scope.prototype = {
    constructor: Scope,
    $new: function(isolate, parent){...},
    $watch: function(watchExp, listener,...) {},
    $watchGroup: function(watchExp, listener) {},
    $watchCollection: function(obj, listener) {},
    $digest: function() {},
    $destroy: function() {},
    $eval: function(expr, locals) {},
    $evalAsync: function(expr, locals) {},
    $$postDigest: function(fn) {},
    $apply: function(expr) {},
    $applyAsync: function(expr) {},
    $on: function(name, listener) {},
    $emit: function(name, args) {},
    $broadcast: function(name, args) {}
};
var $rootScope = new Scope();
...
compile.$$addScopeClass($compileNodes);
...

双向数据绑定:一维结构(表单)、二维结构(表格)、Tree型结构(建议不用双向绑定)


项目实战

在使用Angular开始应用的时候,需要明确一些步骤:

  1. 界面原型设计
  2. 搭建目录结构
  3. 选择UI框架编写UI
  4. 编写Controller
  5. 编写Service
  6. 编写Filter
  7. 测试

有空在细说每个步骤的内容,So Sorry!