uniquejava / blog

My notes regarding the vibrating frontend :boom and the plain old java :rofl.
Creative Commons Zero v1.0 Universal
11 stars 5 forks source link

AngularJS #25

Open uniquejava opened 8 years ago

uniquejava commented 8 years ago

books

Pro AngularJS.

💯 Angular源码解析

http://www.html-js.com/article/column/693

https://scotch.io/tutorials/angular-routing-using-ui-router

image

👍 ngRoutes vs ui-router详解 http://www.cnblogs.com/lovesueee/p/4442509.html

👍 scopes and digest http://teropa.info/blog/2013/11/03/make-your-own-angular-part-1-scopes-and-digest.html

👍AngularJS性能调优 https://github.com/xufei/blog/issues/23

angular快速上手

  1. angular 和 简单的路由 https://scotch.io/tutorials/single-page-apps-with-angularjs-routing-and-templating 代码在此: https://github.com/uniquejava/angular-spa
  2. angular animate https://scotch.io/tutorials/animating-angularjs-apps-ngview 代码在此: https://github.com/uniquejava/angular-animate
  3. AngularJS Routing Using UI-Router https://scotch.io/tutorials/javascript/angular-routing-using-ui-router 代码在此: https://github.com/uniquejava/angular-ui-router
  4. Angular 使用ui-router创建多步骤表单 https://scotch.io/tutorials/angularjs-multi-step-form-using-ui-router 代码在此: https://github.com/uniquejava/angular-multi-step
uniquejava commented 8 years ago

翻页的动画效果合集(酷毙了): https://github.com/codrops/PageTransitions

ng-repeat

<div ng-repeat="p in vm.points" class="prg-box" ng-class="{'first': $first, 'last': vm.points.length>1 && $last}"
     ng-style="{left: p.left}">
  <div class="prg-text" ng-bind-html="p.text"></div>
  <div class="bottomArrow"></div>
</div>

$http

data可以是string或是js object

$http.get(url, config)
$http.post(url, data, config)
$http.put(url, data, config)
$http.delete(url, config)
$http.head()
$http(config)

config对象

{
// 所用的HTTP方法
method: 'POST',

// 请求的目标URL
url: '',

// URL的参数
params: {key1: "value1"},

// 额外的请求头
headers: {},

// XHR请求终止前的超时时间(单位是ms)
timeout: 3000,

// XHR GET请求的缓存开关
cache: false, 

// 在与后端交换数据前或交换后, 对数据进行处理的数据变换函数
transformRequest: function(){},
transformResponse: function(){}
}

处理回调: $http(config).then(successCb, errorCb)或$http(config).success(cb).error(cb), 其中cb包含4个参数.

function cb(
  data, // 实际的响应数据
  status, // 响应的HTTP状态
  headers, // 访问HTTP响应头信息的函数
  config // 请求触发时提供的配置对象
){}

http interceptor

设想我们要重试失败的请求, 我们可以定义一个拦截器用于检查响应状态码, 并在探测到HTTP Service Unavailable(503,服务无效)状态码时重试此请求, 代码大略如下:

解释见书P105页

var xx = angular.module('xx', []);
xx.config(function($httpProvider){
  $httpProvider.responseInterceptors.push('retryInterceptor');
})
.factory('retryInterceptor', function($injector, $q){
  return function(responsePromise){
    return responsePromise.then(null, function(errResponse){
      if(errResponse.status === 503){
        return $injector.get('$http')(errResponse.config);
      } else {
        return $q.reject(errResponse);
      }
    }
  }
})
uniquejava commented 8 years ago

$q

详见书P84页

// 基本用法 
function getXx() {
  var d = $q.defer()
  d.resolve(...);
  d.reject(new Error(...))
  return d.promise;
}

getXx().then(successCb, errorCb);

// 链式处理promise
function slice(pizza){
  return 'sliced ' + pizza; 
}
getXx().then(slice).then(successCb, errorCb)

// 聚合多个promise
$q.all([p1, p2]).then(cb1, cb2)

// 包装任意对象为promise
$q.when('hello world')

Angular表单系列文章:

  1. Angular表单提交 (vs jquery ajax) https://scotch.io/tutorials/submitting-ajax-forms-the-angularjs-way
  2. Angular表单验证 https://scotch.io/tutorials/angularjs-form-validation
  3. 处理单选和复选 https://scotch.io/tutorials/handling-checkboxes-and-radio-buttons-in-angular-forms
  4. 使用ngRepeat 和 ngForm 构建动态表单 https://scotch.io/tutorials/building-dynamic-angular-forms-with-ngrepeat-and-ngform
uniquejava commented 8 years ago

整合 interceptors 和 toastr

配置interceptor

https://stackoverflow.com/questions/21269007/interceptor-not-working/21269115#21269115

配置toastr

https://github.com/Foxandxss/angular-toastr

bower install angular-toastr --save

angular.module('app', ['ngAnimate', 'toastr'])

最简单的用法

app.controller('foo', function($scope, toastr) {
  toastr.success('Hello world!', 'Toastr fun!');
});

在httpInterceptor中依赖toastr会报循环依赖的错误.

解决办法: There are two simple ways to solve that kind of circular dependency problem

  1. decouple the services by using events: when an error is detected in the interceptor, broadcast an event (with the error information) on the root scope. In the toastr service (or in a service that uses toastr), listen on these events on the root scope and display them.

  2. instead of injecting toastr in the interceptor, inject the $injector service. And when you need a toastr instance, call $injector.get('toastr').

我最终选择了第二种.

使用toastr

有两种办法, 一种是直接在interceptor中使用$injector.get('toastr').error('message here') 这是我实际选择的方式.

另一种是在interceptor中使用$rootScope.$broadcast('message', ..), 然后在config中$rootScope.on('message', ....);: 见: http://thoughtdelimited.org/thoughts/post.cfm/angular-material-design-mdtoast-and-httpinterceptors

Cyper实战

综合以上, 我定义了如下InterceptorService.js

(function () {
  'use strict';
  angular.module('myapp')
    .config(['$httpProvider', function ($httpProvider) {
      $httpProvider.interceptors.push('errorInterceptor');
    }])
    .config(['toastrConfig', function (toastrConfig) {
      angular.extend(toastrConfig, {
        autoDismiss: true,
        containerId: 'toast-container',
        maxOpened: 0,
        newestOnTop: true,
        positionClass: 'toast-top-right',
        preventDuplicates: false,
        preventOpenDuplicates: false,
        target: 'body'
      });
    }])
    .factory('errorInterceptor', errorInterceptor);

  /** @ngInject */
  function errorInterceptor($q, $rootScope, $injector) {
    return {
      request: function (config) {
        return config || $q.when(config);
      },
      requestError: function (request) {
        return $q.reject(request);
      },
      response: function (response) {
        return response || $q.when(response);
      },
      responseError: function (response) {
        var status = response.status;
        var msg;
        if (status === -1) {
          msg = 'Backend service is not available.';
        } else if (status === 401) {
          msg = status + ': You are not authorized, please login first.';
        } else if (status === 403) {
          msg = status + ': You have no permission.';
        } else if (status === 404) {
          msg = status + ': Requested resource is not available.';
        } else if (status >= 500) {
          msg = status + ': Internal Server Error.';
        }

        $injector.get('toastr').error(msg);

        return $q.reject(response);
      }
    };
  }

})();

select

http://stackoverflow.com/questions/18202106/ng-options-with-simple-array-init 最终的代码

<div class="form-group">
    <label for="reg-loc-province">省份/直辖市</label>
    <select class="form-control" id="reg-loc-province"
            ng-model="user.address.province" ng-options="b for b in provs track by b">
    </select>
</div>
uniquejava commented 8 years ago

use underscore in angularjs

http://stackoverflow.com/questions/14968297/use-underscore-inside-angular-controllers

When you include Underscore, it attaches itself to the window object, and so is available globally.

So you can use it from Angular code as-is.

You can also wrap it up in a service or a factory, if you'd like it to be injected:

var underscore = angular.module('underscore', []);
underscore.factory('_', ['$window', function($window) {
  return $window._; // assumes underscore has already been loaded on the page
}]);

And then you can ask for the _ in your app's module:

// Declare it as a dependency of your module
var app = angular.module('app', ['underscore']);

// And then inject it where you need it
app.controller('Ctrl', function($scope, _) {
  // do stuff
});

既然_已经注入到了window对象, 为什么还要像上面这样定义成angularjs的service呢?

答案: its necessary when you add 'use strict' to your file. Since underscore/lodash isn't defined it will throw ReferenceError: is not defined... you have to inject it, or use window.

uniquejava commented 8 years ago

文件上传 file upload

  1. AngularJs: How to check for changes in file input fields?

http://stackoverflow.com/questions/17922557/angularjs-how-to-check-for-changes-in-file-input-fields

这个问题好多人收藏!!!!

常见错误:

  1. angular NaNunction%2C%20got%20undefined exception 绝对是controller没有正确的注册
  2. angularjs $compile/ctreq?p0=ngModel&p1=ngChange ng-change对应的元素上必须包含ng-model http://stackoverflow.com/questions/22403507/ng-change-is-throwing-error-compilectreq
uniquejava commented 8 years ago

项目经验

var UserSchema = new Schema({
    username: {type: String},
    svcs: [{
        _id: {type: mongoose.Schema.ObjectId, default: new mongoose.Types.ObjectId()},
        name: {type: String}, // 名称
        descr: {type: String}, // 描述
        image: {type: String} // 图片
    }]
});
  1. 给nested array中的object设定id, 用时间戳就行, 保证不会被修改. 方便根据这个字段更新object.
  2. 给nested array设定了_id字段,当向array中push对象的时候, 只有对象没有_id属性 才会自动给定_id值, 否则就会使用给定的default _id值,即使是undefined.
  3. 上面的设定会导致每次push到array中的所有object有相同的_id值...OMG, 所以还是在外部生成好_id值,,然后传给push.
  4. 注意字段类型为ObjectId类型的时候的查询条件....

在controller中像这样构造值:

svc = {
            _id: req.body._id || new ObjectId,
            name: req.body.name,
            descr: req.body.descr,
            image: req.body.image,
        };

在service中像这样做增删改..

if (operation === 'add') {
    X.update({'loginname': loginname}, {$push: {'svcs': svc}}, function (err) {
        if(err) {
            return callback(err);
        } else {
            X.findOne({'loginname': loginname}, 'svcs', function (err, svcs) {
                callback(err, svcs);
            });
        }
    });

} else if (operation === 'delete') {
    X.update({'loginname': loginname}, {$pull: {'svcs': {_id: ObjectId(svc._id)}}}, callback);

} else if (operation === 'update') {
    X.update({'loginname': loginname, 'svcs._id': ObjectId(svc._id)},
        {
            $set: {
                'svcs.$.name': svc.name,
                'svcs.$.descr': svc.descr,
                'svcs.$.image': svc.image,
            }
        }, callback);

}

啊, 突然意识到在add的时候不用再做一次查询以获得新增对象_id的值, 因为_id在外部已经生成好了.

uniquejava commented 7 years ago

config 和 run 的区别

Configuration block – This block is executed during the provider registration and configuration phase. Only providers and constants can be injected into configuration blocks. This block is used to inject module wise configuration settings to prevent accidental instantiation of services before they have been fully configured. This block is created using config() method.

Run block – This block is executed after the configuration block. It is used to inject instances and constants. This block is created using run() method. This method is like as main method in C or C++. The run block is a great place to put event handlers that need to be executed at the root level for the application. For example, authentication handlers.

示例: http://www.angularjshub.com/examples/modules/configurationrunphases/

uniquejava commented 7 years ago

service vs provider vs factory

http://stackoverflow.com/questions/15666048/angularjs-service-vs-provider-vs-factory

uniquejava commented 7 years ago

notes

The main role of the top-level controller in the SportsStore application is to define the data that will be used inthe different views that the application will display. As you will see—and as I describe in detail in Chapter 13—an AngularJS can have multiple controllers arranged in a hierarchy. Controllers arranged in this way can inherit data andlogic from controllers above them, and by defining the data in the top-level controller, I can make it easily available to the controllers that I will be defining later.

I need to make sure the inline script element (the one that defines the module) appears before the one that imports the file (which extends the module).

I will be using the sportsStoreCtrl controller to support the entire application, so I have applied it to the body element so that the view it supports is the entire set of content elements. This will start to make more sense when I begin to add other controllers to support specific features.

There are no hard-and-fast rules about when you should add a component to an existing module or create a new one. I tend to create modules when I am defining functionality that I expect to reuse in a different application later. Custom filters tend to be reusable because data formatting is something that almost all AngularJS applications require and most developers end up with a utility belt of common formats they require.

Changes that a filter makes to the data affect only the content displayed to the user and do not modify the original data in the scope.

Notice that I am able to define the script element for the customFilters.js file after the one that creates the sportsStore module and declares a dependency on the customFilters module. This is because AngularJS loads all of the modules before using them to resolve dependencies. The effect can be confusing: The order of the script elements is important when you are extending a module (because the module must already have been defined) but not when defining a new module or declaring a dependency on one

You might be wondering why the view can’t access the constant values directly, instead of requiring everything to be explicitly exposed via the scope. The answer is that AngularJS tries to prevent tightly coupled components, which I described in Chapter 3. If views could access services and constant values directly, then it would be easy to end up with endless couplings and dependencies that are hard to test and hard to maintain.

It may not be obvious when testing the changes, but obtaining the data via Ajax highlights one of the most important aspects of AngularJS development, which is the dynamic nature of scopes. When the application first starts, the HTML content is generated and displayed to the user even though there is no product information available. At some point after the content has been rendered, the data will arrive from the server and be assigned to the data.products variable in the scope. When this happens, AngularJS updates all of the bindings and the output from behaviors that depend on the product data, ensuring that the new data is propagated throughout the application. In essence, AngularJS scopes are live data stores, which respond and propagate changes. You will see countless examples of this propagation of changes throughout the book.

uniquejava commented 7 years ago

There are three benefits to using partial views. The first is to break up the application into manageable chunks, as I have done here. The second is to create fragments of HTML that can be used repeatedly in an application. The third is to make it easier to show different areas of functionality to the user as they use the application—I’ll return to this benefit in the “Defining URL Routes” section later in the chapter.

When using the ng-include directive, I specified the name of the file as a literal value in single quotes. If I had not done this, then the directive would have looked for a scope property to get the name of the file.

uniquejava commented 7 years ago

angularjs controller的生命周期

http://stackoverflow.com/questions/16094940/what-is-the-lifecycle-of-an-angularjs-controller

配合ui-router, 当切换state时, controller连同$scope会一起销毁. 可以在$scope.on('destroy', callback)中清理$scope的资源. (比如在外部对象上注册的subscriber).

angularjs controller调用了两次

一共有7种原因: 见 https://stackoverflow.com/questions/15535336/combating-angularjs-executing-controller-twice?rq=1

ui-router中动态设置page title

见: https://stackoverflow.com/questions/23813599/set-page-title-using-ui-router

There is a another way of doing this by combining most of the answers here already. I know this is already answered but I wanted to show the way I dynamically change page titles with ui-router.

If you take a look at ui-router sample app, they use the angular .run block to add the $state variable to $rootScope.

// It's very handy to add references to $state and $stateParams to the $rootScope
// so that you can access them from any scope within your applications.
// For example, <li ng-class="{ active: $state.includes('contacts.list') }"> 
// will set the <li> to active whenever 'contacts.list' or one of its 
// decendents is active.

.run([ '$rootScope', '$state', '$stateParams',
function ($rootScope, $state, $stateParams) {
  $rootScope.$state = $state;
  $rootScope.$stateParams = $stateParams;
}])

With this defined you can then easily dynamically update your page title with what you have posted but modified to use the defined state:

Setup the state the same way:

.state('home', {
    url: '/home',
    templateUrl : 'views/home.html',
    data : { pageTitle: 'Home' }
})

But edit the html a bit...

<title ng-bind="$state.current.data.pageTitle"></title>

I can't say this is any better than the answers before... but was easier for me to understand and implement. Hope this helps someone!