├── .eslintignore ├── .stylelintignore ├── src ├── assets │ ├── styles │ │ ├── .gitkeep │ │ ├── base │ │ │ ├── _reset.scss │ │ │ └── _base.scss │ │ ├── abstracts │ │ │ └── _variables.scss │ │ └── main.scss │ └── images │ │ └── AngularJS-large.png ├── common │ ├── components │ │ └── index.js │ ├── directives │ │ ├── index.js │ │ └── auto-focus.directive.js │ ├── services │ │ ├── index.js │ │ └── http.service.js │ ├── common.module.js │ └── common.config.js ├── views │ ├── list │ │ ├── list.scss │ │ ├── components │ │ │ └── news-panel │ │ │ │ ├── news-panel.html │ │ │ │ ├── news-panel.component.js │ │ │ │ └── news-panel.scss │ │ ├── list.module.js │ │ ├── list.html │ │ ├── list.component.js │ │ └── list.service.js │ └── index │ │ ├── index.html │ │ ├── components │ │ └── hello-angular │ │ │ ├── hello-angular.scss │ │ │ ├── hello-angular.html │ │ │ └── hello-angular.component.js │ │ ├── index.component.js │ │ ├── index.module.js │ │ └── index.scss ├── main.js └── router │ ├── router.module.js │ ├── router.transitions.js │ ├── router.config.js │ └── routes.js ├── .browserslistrc ├── public ├── favicon.ico └── index.html ├── .gitignore ├── environments ├── production.js └── development.js ├── jsconfig.json ├── babel.config.js ├── .stylelintrc.js ├── .eslintrc.js ├── package.json ├── mock └── db.json └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /.stylelintignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /src/assets/styles/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/common/components/index.js: -------------------------------------------------------------------------------- 1 | export default {}; 2 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not ie <= 8 4 | -------------------------------------------------------------------------------- /src/assets/styles/base/_reset.scss: -------------------------------------------------------------------------------- 1 | * { 2 | padding: 0; 3 | margin: 0; 4 | } 5 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onlymisaky/AngularJS-ES6/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/styles/abstracts/_variables.scss: -------------------------------------------------------------------------------- 1 | $distance-list: 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50; 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.log 3 | .vscode 4 | dist 5 | node_modules 6 | package-lock.json 7 | .stylelintcache 8 | -------------------------------------------------------------------------------- /src/assets/styles/main.scss: -------------------------------------------------------------------------------- 1 | @import 'abstracts/variables'; 2 | 3 | @import 'base/reset'; 4 | 5 | @import 'base/base'; 6 | -------------------------------------------------------------------------------- /src/common/directives/index.js: -------------------------------------------------------------------------------- 1 | import { autoFocus } from './auto-focus.directive'; 2 | 3 | export default { autoFocus }; 4 | -------------------------------------------------------------------------------- /src/common/services/index.js: -------------------------------------------------------------------------------- 1 | import { HttpService } from './http.service'; 2 | 3 | export default { 4 | HttpService, 5 | }; 6 | -------------------------------------------------------------------------------- /src/assets/images/AngularJS-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onlymisaky/AngularJS-ES6/HEAD/src/assets/images/AngularJS-large.png -------------------------------------------------------------------------------- /environments/production.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // NODE_ENV: "production", 3 | NAME: '线上环境', 4 | DOMAIN: 'http://news-at.zhihu.com/', 5 | }; 6 | -------------------------------------------------------------------------------- /environments/development.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // NODE_ENV: "development", 3 | NAME: '开发环境', 4 | DOMAIN: 'http://news-at.zhihu.com/', 5 | }; 6 | -------------------------------------------------------------------------------- /src/views/list/list.scss: -------------------------------------------------------------------------------- 1 | #view-list { 2 | background-color: #f9f9f9; 3 | 4 | .list { 5 | display: flex; 6 | flex-wrap: wrap; 7 | justify-content: center; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "@/*": [ 6 | "./src/*" 7 | ] 8 | } 9 | }, 10 | "include": [ 11 | "src" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /src/views/index/index.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 看看日报 7 | 8 | 9 |
10 | -------------------------------------------------------------------------------- /src/views/list/components/news-panel/news-panel.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | {{$ctrl.news.title}} 5 | 6 |
7 | -------------------------------------------------------------------------------- /src/common/directives/auto-focus.directive.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @returns {import('angular').IDirective} 3 | */ 4 | export function autoFocus() { 5 | return { 6 | restrict: 'A', 7 | link($scope, $ele) { 8 | $ele[0].focus(); 9 | }, 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /src/views/index/components/hello-angular/hello-angular.scss: -------------------------------------------------------------------------------- 1 | #hello-angular { 2 | margin-top: 80px; 3 | text-align: center; 4 | 5 | input[type="text"] { 6 | width: 290px; 7 | height: 20px; 8 | padding: 5px; 9 | font-size: 16px; 10 | outline-style: none; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/views/index/components/hello-angular/hello-angular.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |

Hello {{$ctrl.value}}!

5 | 6 |
7 | -------------------------------------------------------------------------------- /src/views/index/index.component.js: -------------------------------------------------------------------------------- 1 | import './index.scss'; 2 | import template from './index.html'; 3 | 4 | /** @type {import('angular').IComponentOptions} */ 5 | export const index = { 6 | template, 7 | controller: class { 8 | constructor() { 9 | this.listState = 'list'; 10 | } 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /src/views/index/index.module.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | 3 | import { index } from './index.component'; 4 | import { helloAngular } from './components/hello-angular/hello-angular.component'; 5 | 6 | export default angular 7 | .module('app.views.index', []) 8 | .component({ index, helloAngular }) 9 | .name; 10 | -------------------------------------------------------------------------------- /src/views/index/components/hello-angular/hello-angular.component.js: -------------------------------------------------------------------------------- 1 | import './hello-angular.scss'; 2 | import template from './hello-angular.html'; 3 | 4 | /** @type {angular.IComponentOptions} */ 5 | export const helloAngular = { 6 | template, 7 | controller: class { 8 | constructor() { 9 | this.value = 'AngularJS'; 10 | } 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /src/views/list/list.module.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | 3 | import { list } from './list.component'; 4 | import { newsPanel } from './components/news-panel/news-panel.component'; 5 | import { ListService } from './list.service'; 6 | 7 | export default angular 8 | .module('app.views.list', []) 9 | .component({ list, newsPanel }) 10 | .service({ ListService }) 11 | .name; 12 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | 3 | import { commonModule } from '@/common/common.module'; 4 | import { routerModule } from '@/router/router.module'; 5 | 6 | import '@/assets/styles/main.scss'; 7 | 8 | angular.module('app', [ 9 | commonModule, 10 | routerModule, 11 | ]); 12 | 13 | angular.element(document).ready(() => { 14 | angular.bootstrap(document, ['app']); 15 | }); 16 | -------------------------------------------------------------------------------- /src/views/list/components/news-panel/news-panel.component.js: -------------------------------------------------------------------------------- 1 | import './news-panel.scss'; 2 | import template from './news-panel.html'; 3 | 4 | /** @type {import('angular').IComponentOptions} */ 5 | export const newsPanel = { 6 | template, 7 | bindings: { 8 | news: '<', 9 | }, 10 | controller: class { 11 | constructor() { 12 | this.url = 'http://daily.zhihu.com/story/'; 13 | } 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /src/common/common.module.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | import components from './components'; 3 | import directives from './directives'; 4 | import services from './services'; 5 | import { commonConfig } from './common.config'; 6 | 7 | export const commonModule = angular 8 | .module('app.common', []) 9 | .component(components) 10 | .directive(directives) 11 | .service(services) 12 | .config(commonConfig) 13 | .name; 14 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('@babel/core').TransformOptions} */ 2 | const transformOptions = { 3 | presets: [ 4 | [ 5 | '@babel/preset-env', 6 | { 7 | modules: false, 8 | }, 9 | ], 10 | ], 11 | plugins: [ 12 | '@babel/plugin-transform-runtime', 13 | '@babel/plugin-syntax-dynamic-import', 14 | '@babel/plugin-proposal-class-properties', 15 | ], 16 | }; 17 | 18 | module.exports = transformOptions; 19 | -------------------------------------------------------------------------------- /src/router/router.module.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | import * as uiRouter from '@uirouter/angularjs'; 3 | import oclazyload from 'oclazyload'; 4 | import { routerConfig } from './router.config'; 5 | import { routeChange } from './router.transitions'; 6 | 7 | export const routerModule = angular 8 | .module('app.router', [ 9 | uiRouter.default, 10 | oclazyload, 11 | ]) 12 | .config(routerConfig) 13 | .run(routeChange) 14 | .name; 15 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | AngularJS-ES6 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/views/list/list.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 正在加载 4 |
5 | 6 |
7 | 没有数据 8 |
9 | 10 |
11 | 12 | 13 |
14 | 15 |
16 | -------------------------------------------------------------------------------- /src/views/index/index.scss: -------------------------------------------------------------------------------- 1 | #view-index { 2 | a { 3 | display: inline-block; 4 | min-width: 120px; 5 | color: #fff; 6 | background-color: #1e88e5; 7 | text-decoration: none; 8 | border-radius: 2px; 9 | padding: 10px 16px; 10 | box-shadow: 0 0 2px rgba(0, 0, 0, 0.12), 0 2px 2px rgba(0, 0, 0, 0.24); 11 | transition: all 0.3s; 12 | 13 | &:hover { 14 | box-shadow: 0 0 12px rgba(0, 0, 0, 0.12), 0 6px 6px rgba(0, 0, 0, 0.24); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/views/list/components/news-panel/news-panel.scss: -------------------------------------------------------------------------------- 1 | #news-panel { 2 | width: 260px; 3 | height: 325px; 4 | background-color: #fff; 5 | padding: 20px; 6 | border-radius: 2px; 7 | 8 | &:hover { 9 | cursor: pointer; 10 | } 11 | 12 | a { 13 | display: block; 14 | color: #428bca; 15 | text-decoration: none; 16 | 17 | img { 18 | display: block; 19 | max-width: 100%; 20 | height: 260px; 21 | } 22 | 23 | span { 24 | font-size: 16px; 25 | color: #000; 26 | line-height: 30px; 27 | display: block; 28 | background: #fff; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/router/router.transitions.js: -------------------------------------------------------------------------------- 1 | import NProgress from 'nprogress'; 2 | import 'nprogress/nprogress.css'; 3 | 4 | /** 5 | * @param {import('@uirouter/angularjs').Transition} $transitions 6 | */ 7 | export function routeChange($transitions) { 8 | $transitions.onStart({}, () => { 9 | NProgress.start(); 10 | }); 11 | 12 | $transitions.onFinish({}, () => { 13 | NProgress.done(); 14 | }); 15 | } 16 | 17 | routeChange.$inject = ['$transitions']; 18 | 19 | // https://ui-router.github.io/guide/ng1/migrate-to-1_0#state-change-events 20 | // $stateChangeStart $stateChangeCancel $stateChangeSuccess $stateChangeError $stateNotFound 21 | -------------------------------------------------------------------------------- /src/common/common.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {import('angular').IQService} $q 3 | * @returns {import('angular').IHttpInterceptor} 4 | */ 5 | function httpInterceptorFactory($q) { 6 | return { 7 | 8 | request(config) { 9 | return config; 10 | }, 11 | 12 | requestError(rejection) { 13 | return rejection; 14 | }, 15 | 16 | response(resp) { 17 | return $q.resolve(resp); 18 | }, 19 | 20 | responseError(rejection) { 21 | return rejection; 22 | }, 23 | }; 24 | } 25 | 26 | httpInterceptorFactory.$inject = ['$q']; 27 | 28 | /** 29 | * @param {import('angular').IHttpProvider} $httpProvider 30 | */ 31 | export function commonConfig($httpProvider) { 32 | $httpProvider.interceptors.push(httpInterceptorFactory); 33 | } 34 | 35 | commonConfig.$inject = [ 36 | '$httpProvider', 37 | ]; 38 | -------------------------------------------------------------------------------- /src/router/router.config.js: -------------------------------------------------------------------------------- 1 | import { routes } from './routes'; 2 | 3 | /** 4 | * @param {import('angular').ILocationProvider} $locationProvider 5 | * @param {import('@uirouter/angularjs').UrlRouterProvider} $urlRouterProvider 6 | * @param {import('@uirouter/angularjs').StateProvider} $stateProvider 7 | */ 8 | export function routerConfig($locationProvider, $urlRouterProvider, $stateProvider) { 9 | $locationProvider 10 | .hashPrefix('') // remove ! 11 | .html5Mode({ 12 | // history Api 13 | enabled: !(process.env.NODE_ENV === 'production'), 14 | requireBase: false, 15 | }); 16 | 17 | $urlRouterProvider.otherwise('/index'); 18 | 19 | routes.forEach((route) => $stateProvider.state(route)); 20 | } 21 | 22 | routerConfig.$inject = [ 23 | '$locationProvider', 24 | '$urlRouterProvider', 25 | '$stateProvider', 26 | ]; 27 | -------------------------------------------------------------------------------- /src/views/list/list.component.js: -------------------------------------------------------------------------------- 1 | import './list.scss'; 2 | import template from './list.html'; 3 | 4 | /** @type {import('angular').IComponentOptions} */ 5 | export const list = { 6 | template, 7 | controller: class { 8 | static $inject = ['$scope', '$injector', 'ListService']; 9 | 10 | /** 11 | * @param {import('angular').IScope} $scope 12 | * @param {import('angular').auto.IInjectorService} $injector 13 | * @param {import('./list.service')} ListService 14 | */ 15 | constructor($scope, $injector, ListService) { 16 | this.$scope = $scope; 17 | this.ListService = ListService; 18 | this.newsList = []; 19 | } 20 | 21 | async $onInit() { 22 | this.ListService 23 | .getNewsList() 24 | .then((news) => { 25 | this.newsList = news; 26 | }); 27 | } 28 | }, 29 | }; 30 | -------------------------------------------------------------------------------- /src/router/routes.js: -------------------------------------------------------------------------------- 1 | /** @type {Array} */ 2 | export const routes = [ 3 | { 4 | name: 'index', 5 | url: '/index', 6 | component: 'index', 7 | lazyLoad(transition) { 8 | /** @type {import('oclazyload').ILazyLoad} */ 9 | const $ocLazyLoad = transition.injector().get('$ocLazyLoad'); 10 | return import('@/views/index/index.module.js').then((ngModule) => $ocLazyLoad.load({ name: ngModule.default })); 11 | }, 12 | }, 13 | { 14 | name: 'list', 15 | url: '/list', 16 | component: 'list', 17 | lazyLoad(transition) { 18 | /** @type {import('oclazyload').ILazyLoad} */ 19 | const $ocLazyLoad = transition.injector().get('$ocLazyLoad'); 20 | return import('@/views/list/list.module.js').then((ngModule) => $ocLazyLoad.load({ name: ngModule.default })); 21 | }, 22 | }, 23 | ]; 24 | -------------------------------------------------------------------------------- /.stylelintrc.js: -------------------------------------------------------------------------------- 1 | /** @type {import('stylelint').Configuration} */ 2 | const styleLintConfig = { 3 | processors: [], 4 | extends: 'stylelint-config-standard', 5 | plugins: ['stylelint-order', 'stylelint-scss'], 6 | rules: { 7 | 'at-rule-empty-line-before': 'always', 8 | 'at-rule-name-case': 'lower', 9 | 'block-no-empty': true, 10 | // scss 语法提示 11 | // 参考 https://github.com/stylelint/stylelint/issues/3190 12 | 'at-rule-no-unknown': null, 13 | 'scss/at-rule-no-unknown': true, 14 | // css书写顺序 15 | 'order/order': [ 16 | 'declarations', 17 | 'custom-properties', 18 | 'dollar-variables', 19 | 'rules', 20 | 'at-rules', 21 | ], 22 | 'order/properties-order': [ 23 | 'position', 24 | 'z-index', 25 | // 其他样式的顺序 26 | ], 27 | // 其他规则 28 | 'no-empty-source': null, 29 | }, 30 | }; 31 | 32 | module.exports = styleLintConfig; 33 | -------------------------------------------------------------------------------- /src/views/list/list.service.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable global-require */ 2 | export class ListService { 3 | static $inject = ['HttpService']; 4 | 5 | /** 6 | * 7 | * @param {import('@/common/services/http.service')} HttpService 8 | */ 9 | constructor(HttpService) { 10 | this.HttpService = HttpService; 11 | } 12 | 13 | getNewsList() { 14 | return this.HttpService 15 | .get('/4/news/latest') 16 | .then((response) => { 17 | if (response.status === 200) { 18 | return this.formatNewsList(response.data); 19 | } 20 | return this.formatNewsList(require('../../../mock/db.json')); 21 | }) 22 | .catch(() => this.formatNewsList(require('../../../mock/db.json'))); 23 | } 24 | 25 | /** 26 | * @private 27 | * @param {Array} data 28 | */ 29 | formatNewsList(data) { 30 | const idList = []; 31 | const list = []; 32 | [...data.top_stories, ...data.stories].forEach((item) => { 33 | if (!idList.includes(item.id)) { 34 | idList.push(item.id); 35 | // eslint-disable-next-line no-param-reassign 36 | item.image = item.image || item.images[0]; 37 | list.push(item); 38 | } 39 | }); 40 | return list; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** @type {import('eslint').Linter.Config} */ 2 | const eslintConfig = { 3 | root: true, 4 | env: { 5 | browser: true, 6 | node: true, 7 | }, 8 | parser: 'babel-eslint', 9 | parserOptions: { 10 | ecmaVersion: 2020, 11 | sourceType: 'module', 12 | ecmaFeatures: { 13 | tsx: true, 14 | jsx: true, 15 | }, 16 | }, 17 | extends: ['airbnb-base'], 18 | settings: { 19 | 'import/resolver': { 20 | alias: { 21 | map: [ 22 | ['@', './src'], 23 | ], 24 | extensions: ['.js', '.jsx', '.ts', '.tsx'], 25 | }, 26 | }, 27 | 'import/extensions': ['.js', '.jsx', '.ts', '.tsx'], 28 | }, 29 | rules: { 30 | 'import/extensions': ['error', 'always', { 31 | js: 'never', 32 | jsx: 'never', 33 | ts: 'never', 34 | tsx: 'never', 35 | }], 36 | 'import/no-absolute-path': 'off', 37 | 'import/prefer-default-export': 'off', 38 | 'import/no-extraneous-dependencies': ['error', { devDependencies: true }], 39 | 'no-param-reassign': ['error', { 40 | props: true, 41 | ignorePropertyModificationsFor: [ 42 | 'Target', // 装饰器 43 | 'e', // for e.returnvalue 44 | ], 45 | }], 46 | 'max-len': 'off', 47 | 'no-console': 'warn', 48 | 'no-debugger': 'error', 49 | 'no-restricted-syntax': 'off', 50 | 'guard-for-in': 'off', 51 | 'arrow-body-style': 'off', 52 | 'class-methods-use-this': 'off', 53 | }, 54 | }; 55 | 56 | module.exports = eslintConfig; 57 | -------------------------------------------------------------------------------- /src/common/services/http.service.js: -------------------------------------------------------------------------------- 1 | export class HttpService { 2 | baseUrl = process.env.NODE_ENV === 'production' ? '' : 'api'; 3 | 4 | static $inject = ['$http']; 5 | 6 | /** 7 | * @param {import('angular').IHttpService} $http 8 | */ 9 | constructor($http) { 10 | this.$http = $http; 11 | } 12 | 13 | /** 14 | * @param {string} url 15 | * @param {import('angular').IRequestShortcutConfig} config 16 | */ 17 | get(url, config = {}) { 18 | return this.$http.get(this.baseUrl + url, config); 19 | } 20 | 21 | /** 22 | * @param {string} url 23 | * @param {any} data 24 | * @param {import('angular').IRequestShortcutConfig} config 25 | * @returns 26 | */ 27 | post(url, data, config = {}) { 28 | return this.$http.post(this.baseUrl + url, data, config); 29 | } 30 | 31 | /** 32 | * @param {string} url 33 | * @param {import('angular').IRequestShortcutConfig} config 34 | */ 35 | delete(url, config = {}) { 36 | return this.$http.delete(this.baseUrl + url, config); 37 | } 38 | 39 | /** 40 | * @param {string} url 41 | * @param {any} data 42 | * @param {import('angular').IRequestShortcutConfig} config 43 | */ 44 | put(url, data, config = {}) { 45 | return this.$http.put(this.baseUrl + url, data, config); 46 | } 47 | 48 | /** 49 | * @param {string} url 50 | * @param {any} data 51 | * @param {import('angular').IRequestShortcutConfig} config 52 | */ 53 | patch(url, data, config = {}) { 54 | return this.$http.patch(this.baseUrl + url, data, config); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/assets/styles/base/_base.scss: -------------------------------------------------------------------------------- 1 | .fl { 2 | float: left; 3 | } 4 | 5 | .fr { 6 | float: right; 7 | } 8 | 9 | .clearfix::after { 10 | content: ""; 11 | display: block; 12 | visibility: hidden; 13 | height: 0; 14 | clear: both; 15 | } 16 | 17 | .clearfix { 18 | zoom: 1; 19 | } 20 | 21 | .text-center { 22 | text-align: center; 23 | } 24 | 25 | @each $distance in $distance-list { 26 | .m-#{$distance} { 27 | margin: $distance + px !important; 28 | } 29 | .m-y-#{$distance} { 30 | margin-top: $distance + px !important; 31 | margin-bottom: $distance + px !important; 32 | } 33 | .m-x-#{$distance} { 34 | margin-left: $distance + px !important; 35 | margin-right: $distance + px !important; 36 | } 37 | .m-t-#{$distance} { 38 | margin-top: $distance + px !important; 39 | } 40 | .m-r-#{$distance} { 41 | margin-right: $distance + px !important; 42 | } 43 | .m-b-#{$distance} { 44 | margin-bottom: $distance + px !important; 45 | } 46 | .m-l-#{$distance} { 47 | margin-left: $distance + px !important; 48 | } 49 | .p-#{$distance} { 50 | padding: $distance + px !important; 51 | } 52 | .p-y-#{$distance} { 53 | padding-top: $distance + px !important; 54 | padding-bottom: $distance + px !important; 55 | } 56 | .p-x-#{$distance} { 57 | padding-left: $distance + px !important; 58 | padding-right: $distance + px !important; 59 | } 60 | .p-t-#{$distance} { 61 | padding-top: $distance + px !important; 62 | } 63 | .p-r-#{$distance} { 64 | padding-right: $distance + px !important; 65 | } 66 | .p-b-#{$distance} { 67 | padding-bottom: $distance + px !important; 68 | } 69 | .p-l-#{$distance} { 70 | padding-left: $distance + px !important; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angularjs-es6", 3 | "version": "1.0.0", 4 | "description": "", 5 | "author": "onlymisaky", 6 | "license": "ISC", 7 | "keywords": [ 8 | "angularjs", 9 | "ES6", 10 | "webpack" 11 | ], 12 | "scripts": { 13 | "dev": "webpack serve --open --mode development --progress --color --config build/webpack.dev.js", 14 | "build": "node build/build.js", 15 | "lint-js": "eslint --fix --ext .js --ext .ts src/", 16 | "lint-css": "stylelint **/*.{html,css,sass,scss,less} --fix --cache" 17 | }, 18 | "dependencies": { 19 | "@babel/runtime": "^7.12.5", 20 | "@uirouter/angularjs": "^1.0.29", 21 | "angular": "^1.8.2", 22 | "nprogress": "^0.2.0", 23 | "oclazyload": "^1.1.0" 24 | }, 25 | "devDependencies": { 26 | "@babel/core": "^7.12.10", 27 | "@babel/plugin-proposal-class-properties": "^7.12.1", 28 | "@babel/plugin-syntax-dynamic-import": "^7.8.3", 29 | "@babel/plugin-transform-runtime": "^7.12.10", 30 | "@babel/preset-env": "^7.12.11", 31 | "@types/angular": "^1.8.0", 32 | "autoprefixer": "^10.2.1", 33 | "babel-eslint": "^10.1.0", 34 | "babel-loader": "^8.2.2", 35 | "clean-webpack-plugin": "^3.0.0", 36 | "copy-webpack-plugin": "^7.0.0", 37 | "css-loader": "^5.0.1", 38 | "eslint": "^7.18.0", 39 | "eslint-config-airbnb-base": "^14.2.1", 40 | "eslint-import-resolver-alias": "^1.1.2", 41 | "eslint-plugin-import": "^2.22.1", 42 | "eslint-webpack-plugin": "^2.4.1", 43 | "file-loader": "^6.2.0", 44 | "friendly-errors-webpack-plugin": "^1.7.0", 45 | "html-loader": "^1.3.2", 46 | "html-webpack-plugin": "^4.5.1", 47 | "less": "^4.1.0", 48 | "less-loader": "^7.2.1", 49 | "mini-css-extract-plugin": "^1.3.4", 50 | "optimize-css-assets-webpack-plugin": "^5.0.4", 51 | "ora": "^5.2.0", 52 | "portfinder": "^1.0.28", 53 | "postcss-loader": "^4.1.0", 54 | "sass": "^1.32.4", 55 | "sass-loader": "^10.1.1", 56 | "style-loader": "^2.0.0", 57 | "stylelint": "^13.8.0", 58 | "stylelint-config-standard": "^20.0.0", 59 | "stylelint-order": "^4.1.0", 60 | "stylelint-scss": "^3.18.0", 61 | "stylelint-webpack-plugin": "^2.1.1", 62 | "url-loader": "^4.1.1", 63 | "webpack": "^5.15.0", 64 | "webpack-bundle-analyzer": "^4.3.0", 65 | "webpack-cli": "^4.3.1", 66 | "webpack-dev-server": "^3.11.2", 67 | "webpack-merge": "^5.7.3" 68 | }, 69 | "postcss": { 70 | "plugins": { 71 | "autoprefixer": {} 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /mock/db.json: -------------------------------------------------------------------------------- 1 | { 2 | "date": "20180403", 3 | "stories": [ 4 | { 5 | "images": [ 6 | "https:\/\/pic3.zhimg.com\/v2-b2d8efa97811f51b6bf808545fb04c12.jpg" 7 | ], 8 | "type": 0, 9 | "id": 9676858, 10 | "ga_prefix": "040315", 11 | "title": "算不上游戏迷,我也觉得电影《头号玩家》还不错" 12 | }, 13 | { 14 | "images": [ 15 | "https:\/\/pic1.zhimg.com\/v2-bb23542fdf5645229a42d5bf1028e214.jpg" 16 | ], 17 | "type": 0, 18 | "id": 9677138, 19 | "ga_prefix": "040313", 20 | "title": "比起星巴克致癌,更可怕的是活跃在朋友圈里谣言号" 21 | }, 22 | { 23 | "images": [ 24 | "https:\/\/pic1.zhimg.com\/v2-0aec038b6c1076e06af8d35af8408d98.jpg" 25 | ], 26 | "type": 0, 27 | "id": 9676927, 28 | "ga_prefix": "040312", 29 | "title": "大误 · 《头号玩家》里的隐藏细节" 30 | }, 31 | { 32 | "images": [ 33 | "https:\/\/pic2.zhimg.com\/v2-f4334b59b0922bfb4c9f6a93faa0c4ed.jpg" 34 | ], 35 | "type": 0, 36 | "id": 9677020, 37 | "ga_prefix": "040310", 38 | "title": "为什么人到中年,很少有身材苗条的?" 39 | }, 40 | { 41 | "images": [ 42 | "https:\/\/pic3.zhimg.com\/v2-fa6123c0c3e2f261a99e8367e7681182.jpg" 43 | ], 44 | "type": 0, 45 | "id": 9674718, 46 | "ga_prefix": "040309", 47 | "title": "春天一到,怎么有的树先开花再长叶,而有的却先长叶再开花?" 48 | }, 49 | { 50 | "images": [ 51 | "https:\/\/pic4.zhimg.com\/v2-f1c084ca341d396f69cb0c31535332bb.jpg" 52 | ], 53 | "type": 0, 54 | "id": 9675761, 55 | "ga_prefix": "040308", 56 | "title": "用数据告诉你,什么样的数据分析师才是企业需要的" 57 | }, 58 | { 59 | "images": [ 60 | "https:\/\/pic4.zhimg.com\/v2-db46ed6ffca222f8cea56d9681aceb8f.jpg" 61 | ], 62 | "type": 0, 63 | "id": 9676962, 64 | "ga_prefix": "040307", 65 | "title": "明明在国内,为什么 B 站、爱奇艺这些公司还要赴美 IPO?" 66 | }, 67 | { 68 | "images": [ 69 | "https:\/\/pic2.zhimg.com\/v2-53cab5878c3c23fcc1c169c2743a3695.jpg" 70 | ], 71 | "type": 0, 72 | "id": 9677039, 73 | "ga_prefix": "040307", 74 | "title": "「白银案」凶手和他的同学们" 75 | }, 76 | { 77 | "images": [ 78 | "https:\/\/pic3.zhimg.com\/v2-40b35835a2780eb2291d2fb79d6a4f76.jpg" 79 | ], 80 | "type": 0, 81 | "id": 9676818, 82 | "ga_prefix": "040306", 83 | "title": "瞎扯 · 如何正确地吐槽" 84 | } 85 | ], 86 | "top_stories": [ 87 | { 88 | "image": "https:\/\/pic4.zhimg.com\/v2-498ce8f4b9ce9886cc0066abcaf44687.jpg", 89 | "type": 0, 90 | "id": 9676858, 91 | "ga_prefix": "040315", 92 | "title": "算不上游戏迷,我也觉得电影《头号玩家》还不错" 93 | }, 94 | { 95 | "image": "https:\/\/pic2.zhimg.com\/v2-0d9f5f97429089f20ee1ce224cd79c1d.jpg", 96 | "type": 0, 97 | "id": 9677138, 98 | "ga_prefix": "040313", 99 | "title": "比起星巴克致癌,更可怕的是活跃在朋友圈里谣言号" 100 | }, 101 | { 102 | "image": "https:\/\/pic3.zhimg.com\/v2-dd0b4daf5f529b84572b89b197191ff2.jpg", 103 | "type": 0, 104 | "id": 9677039, 105 | "ga_prefix": "040307", 106 | "title": "「白银案」凶手和他的同学们" 107 | }, 108 | { 109 | "image": "https:\/\/pic1.zhimg.com\/v2-ff4d7de6674f827c905004d255236520.jpg", 110 | "type": 0, 111 | "id": 9676962, 112 | "ga_prefix": "040307", 113 | "title": "明明在国内,为什么 B 站、爱奇艺这些公司还要赴美 IPO?" 114 | }, 115 | { 116 | "image": "https:\/\/pic2.zhimg.com\/v2-768623fde965fe862190301cad11e88d.jpg", 117 | "type": 0, 118 | "id": 9677020, 119 | "ga_prefix": "040310", 120 | "title": "为什么人到中年,很少有身材苗条的?" 121 | } 122 | ] 123 | } 124 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2021-01-18: 升级了项目依赖,添加了 eslint 和 stylelint 。因为发现还有小伙伴在fork本项目,由于本项目距今已经有两年多的时间,只剩下参考意义了,如果确实对 Angularjs 非常感兴趣,建议学习 Angular ,再不济也看一下 用 TypeScript 写 Angularjs 的方式吧。 2 | 3 | --- 4 | 5 | 一个用 `AngularJS` + `es6/es7` + `webpack` 构建的种子项目。 6 | 7 | 该项目只有两个页面,目的是为了展示如何使用主流的方式开发 `AngularJS` 应用。 8 | 9 | 在线地址 10 | 11 | ## 截图 12 | 13 | ![首页](https://s1.ax1x.com/2018/04/03/CpmYex.png) 14 | 15 | ![列表页](https://s1.ax1x.com/2018/04/03/Cpmtw6.png) 16 | 17 | ## 用法 18 | 19 | 1. 下载到本地 20 | ```bash 21 | git clone https://github.com/onlymisaky/AngularJS-ES6.git 22 | ``` 23 | 24 | 2. 切换到项目目录 25 | ```bash 26 | cd AngularJS-ES6 27 | ``` 28 | 29 | 3. 安装依赖,这一步很慢,耐心等待即可,如长时间没有安装好,请尝试用 [cnpm](https://npm.taobao.org/) 安装 30 | ```bash 31 | npm i --registry=http://r.cnpmjs.org/ 32 | ``` 33 | 4. 启动服务 34 | ```bash 35 | npm run dev 36 | ``` 37 | 5. 打包 38 | ```bash 39 | npm run build 40 | ``` 41 | 6. 如果在执行 **第4** 或 **第5** 步的过程中提示 *node-sass* 错误,请使用下面的命令重新 build node-sass ,build 完成后再次使用 **第4** 或 **第5** 的命令即可 42 | ```bash 43 | npm rebuild node-sass 44 | ``` 45 | 46 | **提示** 47 | 48 | 原则上,不建议用 [cnpm](https://npm.taobao.org/) 安装依赖,因为用 cnpm 安装依赖后,再用 vscode 打开项目会导致 cpu 狂飙至 100% ,这个锅应该是 cnpm 的。 详情可以查看这两个 issues : 49 | 50 | [When using cnpm/pnpm, rg uses lots of CPU #35659](https://github.com/Microsoft/vscode/issues/35659) 51 | 52 | [Use search.followSymlinks for all searches #37000](https://github.com/Microsoft/vscode/issues/37000) 53 | 54 | 如果由于网络问题,不得不使用 cnpm ,恰巧你的开发工具可是 vscode ,那么请用下面两种方式解决: 55 | 56 | 第一种方式 : 在 `cnpm i` 完成后,打开 vscode 将**设置**中的 **search.followSymlinks** 修改为 **false** 。 57 | 58 | 第二种方式 : 在 cnpm 命令后面添加参数,即使用 `cnpm i --by=npm` 来安装。 59 | 60 | 另外也不推荐用 taobao 的源安装,他的速度还没 cnpm 的快。 61 | 62 | ## 开发日记 63 | 64 | ### 1、目录结构 65 | 66 | 项目的目录结构决定了整个项目的风格,下面是整体的目录结构图: 67 | 68 | ![项目结构](https://s1.ax1x.com/2018/12/09/F80RVH.png) 69 | 70 | 这个结构是在参考的了 Angular 项目结构的基础上结合个人的经验构建出来的,现在你可能不是很明白为什么要这样设计,不要着急,你只要先总体的看一下它的结构,对它有一个大概的了解即可,继续往下读,你会明白为什么这样设计。 71 | 72 | ### 2、模块设计 73 | 74 | 这里采用了多模块设计,angularjs 的 module 一直饱受病诟,它希望开发者通过 angular.module 来对项目进行更合理的划分,但是这个 module 实在是太弱了。首先它是脱离了文件系统的,而 es6 的 module 则是完全基于文件来设计的,一个 .js 文件就是一个 esModule ,所以当这两个结合到一块的时候,还是有些怪异的。其次 angular.module 顶多算是一个不可检测的命名空间,何为不可检测?就是当你拿到这个 module 的时候,你不知道这个 module 下面到底有哪些东西,另外当你想在 moduleA 下面注册 MyService 的时候,可能 moduleA 依赖了 moduleB ,而 moduleB 可能已经注册了一个功能不一样的 MyService ,这些你明白什么叫不可检测了吧。造成这个原因主要是因为在 angularjs 诞生的时候,还有 esModule 的概念。 75 | 76 | 既然 angular.module 这么弱,为什么还要用它呢,干脆在整个项目中只用一个 module,这样不就可以最小化上面的那些问题了吗。的确可以这样,但是这样一来,我们打包出的文件体积就太大了,也许你想到了用按需加载的方式来切分代码,但是在 angularjs 中,只有 module 可以动态加载,而且 component 等等都是在项目启动的时候就已经创建好了,如果你想动态的加载一个 component ,你只能去动态的加载注册这个 component 的 module 。所以这个 module 虽然不好用,但是又不得不用。 77 | 78 | 在整个项目中,首先定义了个三个 module : 79 | 80 | 1. 根模块:`angular.module('app')` 81 | 2. 通用模块:`angular.module('app.common')` 用于注册一些通用的 service、component等 82 | 3. 路由模块:`angular.module('app.router')` 配置路由和路由钩子 83 | 84 | 这三个 module 是在首次启动的时候就创建好了,所以在这个三个 module 中注册的 service、component等都是可以在整个项目中直接使用的,其次是每一个页面可以定义为一个 module ,这里建议将每一个顶级的路由对应的所有代码都放在一个 module 中,然在 router module 中根据需求直接 require 这个 module 或者使用 oclazyload 动态加载。如果项目加大,可以具体到每一个组路由定义为一个 module 。 85 | 86 | ### 3、干掉filter 87 | 88 | `filter` 也是一个饱受争议的设计,虽然它很有用也很好用,但是极为消耗性能的,在 `$digest` 过程中,filter会执行很至少两次,在很多关于angularJS的讨论中,都不推荐使用它。 89 | 90 | 其实 filter 的作用就是转换数据,当你需要处理数据的时候,你可以写一些专门处理数据的函数,然后在 controller 或者 service 中调用它,而视图只负责显示数据。 91 | 92 | ### 4、新的写法 93 | 94 | #### service 95 | 96 | 在 angularjs 中,注册一个自定义服务有三种写法:`provider`、`factory`、`service` ,我们都知道 factory 和 service 都是基于 provider 封装的。而 service 的写法是最适合用 es6 的 class 来写: 97 | 98 | ```javascript 99 | export class UserService { 100 | 101 | static $inject = ['$http']; 102 | 103 | constructor($http) { 104 | this.$http = $http; 105 | } 106 | 107 | getUser(userId) { 108 | return this.$http 109 | .get(`/user/${userId}`) 110 | .then(response => { 111 | if (response.status === 200) { 112 | return response.data; 113 | } 114 | }); 115 | } 116 | } 117 | ``` 118 | 119 | #### controller 120 | 121 | AngularJS 1.2 版本开始提供了一个 `controllerAs` 语法,让 `controller` 成为了一个纯净的 `ViewModel` ,而且 AngularJS 是通过 `new` 关键字把它当成构造函数来调用的,所以 controller 的写法和 service 完全一样: 122 | 123 | ```javascript 124 | class UserController { 125 | 126 | static $inject = ['$stateParams', 'UserService']; 127 | 128 | user; 129 | 130 | constructor($stateParams, UserService) { 131 | this.UserService = UserService; 132 | this.userId = $stateParams.id; 133 | } 134 | 135 | $onInit() { 136 | this.UserService 137 | .getUser(this.userId) 138 | .then(user => this.user = user); 139 | } 140 | } 141 | ``` 142 | 143 | #### 更安全的注册 144 | 145 | 什么叫更安全的注册呢,我们以上面的 UserService 为例,虽然我们我们创建的类名为 UserService ,但在将 UserService 导入到 module 的是时候,我们可以注册为 XxxService : 146 | 147 | ```javascript 148 | import { UserService } from './user.service'; 149 | angular 150 | .module('user') 151 | .service('XxxService', UserService); 152 | ``` 153 | 154 | 这样一来就全乱了套了,那样才能避免这样的情况呢?angularjs 的 .service 、 .component、 .directive 等方法除了可以用 `userModule.service('UserService', UserService)` 的写法注册,还可以用对象的方式注册 `userModule.service({UserService: UserService})` 。利用对象方式的写法,我们可以 es6 的属性的简洁表示法来注册 `userModule.service({UserService})` ,通过这种学法来注册 service、component、directive 这样就可以避免上述情况发生了。 155 | 156 | ### 5、router 157 | 158 | [ui-router](https://ui-router.github.io/ng1/) 绝对是 AngularJS 的标配了,它最大的优势就是解决了路由的嵌套。 159 | 160 | `ui-router` 还支持 [Route to component](https://ui-router.github.io/ng1/tutorial/hellosolarsystem) ,所以项目里所有的页面都是组件。 161 | 162 | ### 6、按需加载 163 | 164 | 前面提到,在 angularjs 中按需加载的最小单位是 ngModule ,所以如果需要按需加载的话,将按需加载的代码用一个新的 angular.module 包装一下,然后通过 [oclazyload](git://github.com/ocombe/ocLazyLoad.git) 来加载 ngModule 。这里建议在路由层面做按需加载,具体代码可以查看 [routes.js](https://github.com/onlymisaky/AngularJS-ES6/blob/master/src/router/routes.js) 。 165 | 166 | ### 7、未解决的问题 167 | 168 | #### css Module 169 | 其实我已经有相应的解决方案了,我们可以在控制器中引入样式,然后将样式挂在 `vm` 上,然后在 `view` 中使用,具体的写法如下 170 | ```javascript 171 | import styles from './a.css'; 172 | class User { 173 | constructor() { 174 | this.styles = styles; 175 | } 176 | } 177 | ``` 178 | ```html 179 |
180 | ``` 181 | ```css 182 | .text-center { 183 | text-align: center; 184 | } 185 | ``` 186 | 在我看来这种实现方式是在是太惨不忍睹了,所以暂时用 **id选择器** 方式解决。 187 | 188 | ## 参考 189 | - [AngularJS styleguide (ES2015)](https://github.com/toddmotto/angularjs-styleguide) 190 | - [Angular 1.x和ES6的结合](https://github.com/xufei/blog/issues/29) 191 | - [Angular1.x + es6 开发风格指南](https://github.com/kuitos/kuitos.github.io/issues/34) 192 | - [Angular沉思录(三)Angular中的模块机制](https://github.com/xufei/blog/issues/17) 193 | - [基于ui-router的非侵入式angular按需加载方案](https://github.com/kuitos/kuitos.github.io/issues/31) 194 | - [Lazy load AngularJS with Webpack](https://michalzalecki.com/lazy-load-angularjs-with-webpack/) 195 | - [angular-1-5-components-app](https://github.com/toddmotto/angular-1-5-components-app) 196 | --------------------------------------------------------------------------------