MEANの初体验 - Angular 1.x with ES6 & Webpack

不要觉得这样的搭配很怪 在我想这么写之前已经有好几篇这样的教程粗线了:

所以呐 我在写 [ PlotIt ] 的时候就是参考以上教程慢慢琢磨出来的…

Web Components

期初的前端开发思想中就已经产生分离的概念 即 结构层(HTML) / 表现层(CSS) / 行为层(JavaScript)

在前端进化之路上 开发工程师们觉得单单这样还是不够HOLD住大型项目的复杂程度

因此 分离思想进一步升级 变成了Web组件化

不知道组件化的自行谷歌或者看看 [ A Guide to Web Components ]

好了 其实 Angular 1.x with ES6 & Webpack 这样的搭配就是为了迎合 Web Components 的思想

Directive / Controller / Service / Route

目录结构

-- /components
    -- /slide
        -- index.html
        -- index.css
        -- index.js
    -- /others
         -- ...
         
-- /views
    -- index.html
    -- ...
-- ...
-- main.js
-- main.service.js
-- main.route.js

传统的 Angular (最常用的)基础结构由 Directive / Controller / Service / Route 组成

组件内部 index.js 将会包含该组件的 Directive(模板 + 组件交互) 和 Controller(组件方法)

那么 公用的 Service 和 Route 会集中起来分别写在 main.service.jsmain.route.js

  • Service 存放公用方法
  • Route 将配置路由 导入 /views 内的模板

最后由 main.js 作为入口 由 Webpack 进行组织所有依赖打包成 bundle.js

Component

Directive

Directive 包含了两部分 [ 加载 template ] 和 [ 组件初始化配置&组件间交互处理 ]

  • template 加载 html 文件

    html 中可以写一些 ng指令 调用内部属性和方法

    另外 html 中也可以获取到其他组件

    在使用时可直接 用组件中 controllerAs: 'name' 定义的 name 调用其组件

  • link (scope, element, attrs) 初始化&交互方法

    • scope

    此 scope 非 $scope

    scope 传入的其实是当前整个 angular app 的作用域 在里面可以获取到当前所有组件的属性和方法

    所以适合写组件间的交互

    • element

    传入当前组件的 DOM 节点 可以添加一些监听事件

    但建议 一般情况下直接在 html 写 ng 指令比较好

    • attrs

    组件调用时传入的 attr 可用来初始化一些配置需求

    • $watch

    获取或者监听组件属性的时候会使用到 $watch

    建议写在 link 中 而不是在 Controller

    坑点就是 $watch 方法是挂在 $scope 上面的

    而每个组件的 $scope 都需要 $inject ( Controller 部分会写 )

    避开此坑的方法如下:

// 导入组件模板 html
import template from './index.html';

// 导入组件样式 css
import './index.css';

// Slide Directive
let slideTpl = () => {
    return {
        template: template,
        controller: 'slideCtrl',
        controllerAs: 'slide',
        bindToController: true,
        restrict: 'E',
        link: (scope, element, attrs) => {
          // watch something
          var self = scope.slide;
          $scope = self.$scope;

          $scope.$watch('attr', self.doSomething);

          // do other things..
        }
    }
};
Controller

在 ES6 的书写格式里 所有 angular 自带的模块方法和属性 都需要通过 $inject 的方式注入到 Controller 里

注意按需注入 以下只是举例…

class slideCtrl {
    constructor($scope, $location, $route, $http, $rootScope, Service) {
        // angular 自带方法
        this.$scope = $scope;
        this.$location = $location;
        this.$route = $route;
        this.$rootScope = $rootScope;
        this.$http = $http;

        // 自行注册的 Service
        this.Service = Service;
        ...
    }
    
    innerFunction() {
    }
}

slideCtrl.$inject = ['$scope', '$location', '$http', '$route', '$rootScope', 'Service'];
Export

最后 组件是包含 tpl 和 controller 两部分作为输出

其实 两部分分开成独立文件再输出也是可以的 只不过为了方便简洁 都放在了 index.js

export default {
    tpl: slideTpl,
    controller: slideCtrl
};

Service

抽出公用方法放入 Service 中 调用方法 详见 index.js 中的 Controller 和 main.js

export default class Service {
    constructor($http) {
        this.$http = $http;
        // ...
    }
}

// whatever you need, just inject
Service.$inject = ['$http'];

Route

需要依赖安装 angular-route & angular-ui-router

做这一步其实是为了构建 [ SPA ] 即根据路由的不同 局部刷新页面 而不用整个页面做刷新

详细使用方法在此 [ 学习 ui-router - 状态嵌套和视图嵌套 ]

routing.$inject = ['$stateProvider', '$urlRouterProvider', '$locationProvider'];

export default function routing($stateProvider, $urlRouterProvider, $locationProvider) {
  
    $locationProvider.html5Mode(true);
  
    // config HOME url
    $urlRouterProvider.when('', '/home');
    $urlRouterProvider.when('/', '/home');

    // config other url
    $urlRouterProvider.when('/other', '/other');
  
    // config state
    $stateProvider
        .state('state', {
            url: '/home',
            template: require('./views/index.html')
        })
        .state('otherState', {
            url: '/other',
            template: require('./views/other.html')
        });
}
views/
  • index.html

    主页入口 在 Main 中介绍

  • other.html

    相当于局部页面 是直接作为模块加入到 index.html 中的

    可直接按需加载 directive 标签 如 <slide></slide>

Main

index.html

<!DOCTYPE html>
<!-- 加载 angular -->
<html ng-app="app" lang="zh-Has">
<head>
    ...
</head>
<body>
    <!-- ui-view 由 main.route.js 配置 -->
    <div ui-view></div>
    <!-- Webpack 将所有依赖打包成 bundle.js -->
    <script src="scripts/bundle.js"></script>
</body>
</html>

main.js

Webpack 主入口

将 Directive / Controller / Service / Route 集成起来组成 app

导入当前目录下的 js 文件 需要写成 ‘./’ + 文件名

否则 webpack 会首先去 node_modules 下查找

// import from npm
import angular from 'angular';
import uiRouter from 'angular-ui-router';
import ngRoute from 'angular-route';

// import from local file
import Route from './main.route';
import Service from './main.service';

// import main css
import '../somewhere/main.css';

// import component
import slide from './components/slide';
import others from './components/others'; 

// config angualr module info
const MODULE_NAME = 'app';
const app = angular.module(MODULE_NAME, [uiRouter, ngRoute]);

// add route
app.config(Route);

// add service
app.service('Service', Service);

// add directive
app
  .directive('slide', slide.tpl),
  .directive('others', others.tpl);

// add controller
app
  .controller('slideCtrl', slide.controller)
  .controller('othersCtrl', others.controller);
  
// final export 
export default MODULE_NAME;

END

Angular 1.x with ES6 & Webpack 大致已经记录完毕 ~

ES6 可以看看 [ ES6 Study Notes ]

Webpack 可以看看 [ Webpack 入门指迷 ]

Webpack 和 React 是一对好基友 大多数 Webpack教程 是和 React 绑在一起写的