Introduction
Introduce some bacis and important knowledge of AngularJs.
Background
I have used AngularJS for a while, I use it because our customer demanded us to use that technology in our project. From totally blank to have some understanding about AngularJs, I think it's necessary to write something about my understanding to AngularJS.
1.Two-way data binding
There are many different kinds of MV** frameworks that are very popular, and AngularJS is one of them (MVVM).
The most important thing for MV** framework is that they can separate View layer and Model layer, decouple your code. MVC, MVP, MVVM all have the same objective and the difference between them is how to associate the model layer with the view layer.
How the data flow between model layer and view layer is the key problem. The two-way data binding is that the change on view can reflect on the model layer and vice versa. So how the AngularJS does the two-way binding and how it becomes dirty-check ? let's start from a front-end question:
html:
<input type="button" value="increase 1" id="J-increase" />
<span id="J-count"></span>
js:
<script>
var bindDate = {
count: 1,
appy: function () {
document.querySelector('#J-count').innerHTML = this.count;
},
increase: function () {
var _this = this;
document.querySelector('#J-increase').addEventListener('click', function () {
_this.count++;
appy();
}, true);
},
initialize: function () {
this.appy();
this.increase();
}
};
bindDate.initialize();
</script>
As above example, there are two processes:
a. view layer affects model layer: click button on page would increase the count number.
b. model layer reflects view layer: after the 'count' changed, it would represent on view via 'apply' function.
This is the way when we use jQuery or other libraries, there are three obvious defects:
a. It involves too many DOM operations.
b. The process is complicated.
c. Code coupling is too high, not easy to write unit tests.
Let's see how AngularJS deal with data:
Step 1, add Watcher: when the data changes definition which object needs to be check, need to register first.
$watch: function(watchExp, listener, objectEquality) {
var scope = this,
array = scope.$$watchers,
watcher = {
fn: listener,
last: initWatchVal,
get: get,
exp: watchExp,
eq: !!objectEquality
};
if (!array) {
array = scope.$$watchers = [];
}
array.unshift(watcher);
}
Step 2, dirty-check: when the data under a specified scope changed, need to loop the registered $$watchers = [...]
$digest: function() {
while (length--) {
watch = watchers[length];
watch.fn(value, lastValue, scope);
}
}
this implemented the two-way data binding, you can see that similar to a customize event and that used observer design pattern or publisher-subscriber.
2.Dependency Injection
Let's see if we don't use DI (Dependency Injection) how to solve the interdependence between objects.
function Car() {
...
}
Car.prototype = {
run: function () {...}
}
function Benz() {
var cat = new Car();
}
Benz.prototype = {
...
}
As above, class Benz dependents on class Car, it solves the dependence relationship by using an internal New. It's obviously not good to do like that, the code coupling is high and not beneficial for maintenance.
We know javascript language doesn't have annotation mechanism, let's see how AngularJS achieve it.
Step 1, Analog comment
function annotate(fn, strictDi, name) {
var $inject;
if (!($inject = fn.$inject)) {
$inject = [];
$inject.push(name);
}else if (isArray(fn)) {
$inject = fn.slice(0, last);
}
return $inject;
}
createInjector.$$annotate = annotate;
Step 2, Create injection object
function createInjector(modulesToLoad, strictDi) {
var providerCache = {
$provide: {
provider: supportObject(provider),
factory: supportObject(factory),
service: supportObject(service),
value: supportObject(value),
constant: supportObject(constant),
decorator: decorator
}
},
instanceCache = {},
instanceInjector = (instanceCache.$injector =
createInternalInjector(instanceCache, function(serviceName, caller) {
var provider = providerInjector.get(serviceName + providerSuffix, caller);
return instanceInjector.invoke(provider.$get, provider, undefined, serviceName);
}));
return instanceInjector;
}
Step 3, Get inject object
function invoke(fn, self, locals, serviceName) {
var args = [],
$inject = annotate(fn, strictDi, serviceName);
for (...) {
key = $inject[i];
args.push(
locals && locals.hasOwnProperty(key)
? locals[key]
: getService(key, serviceName)
);
}
if (isArray(fn)) {
fn = fn[length];
}
return fn.apply(self, args);
}
so if there is no anotation then simulate one, that why PPK said angular is "a front-end framework by non-front-enders for non-front-enders".
3.Controller communication
In the real development work, the app system will become very big, one app could not have only one controller, how to deal with the communication between different controllers ? There are two main ways:
1.Event mechanism: register event on $rootScope, in this way it will register too many events on the $rootScope and that will cause a lot of problems.
app.controller('controller1', function ($rootScope) {
$rootScope.$on('eventType', function (arg) {
......
})
})
app.controller('controller2', function ($rootScope) {
$rootScope.$emit('eventType',arg);
or
$rootScope.$broadcast('eventType',arg);
})
2. Use service: make full use of angular characteristics of DI, use angular service is a singleton feature, it will play a role as a bridge between different controller.
app.service('Message', function () {
return {
count: void(0);
}
})
app.controller('controller1', function ($scope, Message) {
$scope.count = 1;
Message.count = $scope.count;
});
app.controller('controller2', function ($scope, Message) {
$scope.num = Message.count;
});
4.Service features
1. Singleton: In AngularJS only service can do dependency injection, something like controller, directive etc don't have this feature, angular service only provide some basic services, it wouldn't associated with specific business, but controller, directive all closely associated with the specific business, so we need to keep the service unique.
2. Lazy new: angular will generate the provider of service first, but it did not generate the corresponding service instantly, it will be instantiated only when we need to use it.
3. The category of provider: provider(), factory, service, value, constant. Which provider is the underlying implementation, other ways are base on it. Note that all these services all need to add $get method eventually, because all the specific service is generated by executing the $get method.
5.The implementation of directive
Directive's compiler has two phases: compile, link. In short, the compiler phase mainly handle template DOM, In this phase it does not involve scope problems, i.e. no data rendering. For example, ng-Repeat directive just using compile to modify template, after executed compile it will return link function override the following link functions; in the other hand, the link is mainly responsible for data rendering, it divides into two steps. The compile order of these two steps is reversed, post-link will compile the internal then external. This is safe for directie compile, because directive can embed directive, in the meanwhile link will handle the real DOM, that will involve DOM operation performance issue.