Exploring the Angular 1.5 .component() method
2016-02-18 09:28
726 查看
Angular 1.5 introduced the
Let’s compare the differences in syntax and the super neat abstraction that
The
I’ve prebuilt a simple
A live embed of the
We’ll continue building this alongside how we’d build the Angular 1.4 version to compare differences.
Nice and simple. Essentially the
With the
If we’re using a controller local to the component, we’ll do this:
If we’re using another Controller defined elsewhere, we’ll do this:
If we want to define
This then allows us to use
Now, there are some changes in
Possibility one uses this aptly named
This allows us to do the following inside
This saves adding the
We can add the
The third option, and better yet, completely removes all need to think about
The would-be
Based on this information, we add our
Things are becoming much simpler to use and define with this change.
The
That’s it for our Directive to Component refactor, however there are a few other changes worth exploring before we finish.
Inherited Directive or Component methods will be bound to the
Read my full write-up about one-way bindings.
Essentially we can just use a
Again, please note that Angular 1.5 isn’t released just yet, so this article uses an API thatmay be subject to slight change.
.component()helper method, which is much simpler than the
.directive()definition and advocates best practices and common default behaviours. Using
.component()will allow developers to write in an Angular 2 style as well, which will in turn make upgrading to Angular 2 an easier feat.
Let’s compare the differences in syntax and the super neat abstraction that
.component()gives us over using the
.directive()method.
Update: use component() now in Angular 1.3+
I’ve back-ported the Angular 1.5.component()functionality to Angular 1.3 and above! Read the article and grab the code on GitHub.
.directive() to .component()
The syntax change is very simple:// before module.directive(name, fn); // after module.component(name, options);
The
nameargument is what we want to define our Component as, the
optionsargument is a definition Object passed into the component, rather than a function that we know so well in versions 1.4 and below.
I’ve prebuilt a simple
countercomponent for the purposes of this exercise in Angular
1.4.xwhich we’ll refactor into a version
v1.5.0build to use
.component().
.directive('counter', function counter() { return { scope: {}, bindToController: { count: '=' }, controller: function () { function increment() { this.count++; } function decrement() { this.count--; } this.increment = increment; this.decrement = decrement; }, controllerAs: 'counter', template: [ '<div class="todo">', '<input type="text" ng-model="counter.count">', '<button type="button" ng-click="counter.decrement();">-</button>', '<button type="button" ng-click="counter.increment();">+</button>', '</div>' ].join('') }; });
A live embed of the
1.4.xDirective:
We’ll continue building this alongside how we’d build the Angular 1.4 version to compare differences.
Function to Object, method name change
Let’s start from the top and refactor thefunctionargument to become an
Object, and change the name from
.directive()to
.component():
// before .directive('counter', function counter() { return { }; }); // after .component('counter', { });
Nice and simple. Essentially the
return {};statement inside the
.directive()becomes the Object definition inside
.component()- easy!
“scope” and “bindToController”, to “bindings”
In a.directive(), the
scopeproperty allows us to define whether we want to isolate the
$scopeor inherit it, this has now become a sensible default to (usually) always make our Directives have isolate scope. So repeating ourselves each time just creates excess boilerplate. With the introduction of
bindToController, we can explicitly define which properties we want to pass into our isolate scope and bind directly to the Controller.
With the
bindingsproperty on
.component()we can remove this boilerplate and simply define what we want to pass down to the component, under the assumption that the component will have isolate scope.
// before .directive('counter', function counter() { return { scope: {}, bindToController: { count: '=' } }; }); // after .component('counter', { bindings: { count: '=' } });
Controller and controllerAs changes
Nothing has changed in the way we declarecontroller, however it’s now a little smarter and has a default
controllerAsvalue of
$ctrl.
If we’re using a controller local to the component, we’ll do this:
// 1.4 { ... controller: function () {} ... }
If we’re using another Controller defined elsewhere, we’ll do this:
// 1.4 { ... controller: 'SomeCtrl' ... }
If we want to define
controllerAsat this stage (which will over-ride the default
$ctrlvalue), we’ll need to create a new property and define the instance alias:
// 1.4 { ... controller: 'SomeCtrl', controllerAs: 'something' ... }
This then allows us to use
something.propinside our
templateto talk to the instance of the Controller.
Now, there are some changes in
.component()that make sensible assumptions and automatically create a
controllerAsproperty under the hood for us, and automatically assign a name based on three possibilities:
// inside angular.js controllerAs: identifierForController(options.controller) || options.controllerAs || '$ctrl',
Possibility one uses this aptly named
identifierForControllerfunction that looks like so:
// inside angular.js var CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/; function identifierForController(controller, ident) { if (ident && isString(ident)) return ident; if (isString(controller)) { var match = CNTRL_REG.exec(controller); if (match) return match[3]; } }
This allows us to do the following inside
.component():
// 1.5 { ... controller: 'SomeCtrl as something' ... }
This saves adding the
controllerAsproperty… however…
We can add the
controllerAsproperty to maintain backwards compatibility or keep it if that’s within your style for writing Directives/Components.
The third option, and better yet, completely removes all need to think about
controllerAs, and Angular automatically uses the name
$ctrl. For instance:
.component('test', { controller: function () { this.testing = 123; } });
The would-be
controllerAsdefinition automatically defaults to
$ctrl, so we can use
$ctrl.testingin our
templatewhich would give us the value of
123.
Based on this information, we add our
controller, and refactor our Directive into a Component by dropping the
controllerAsproperty:
// before .directive('counter', function counter() { return { scope: {}, bindToController: { count: '=' }, controller: function () { function increment() { this.count++; } function decrement() { this.count--; } this.increment = increment; this.decrement = decrement; }, controllerAs: 'counter' }; }); // after .component('counter', { bindings: { count: '=' }, controller: function () { function increment() { this.count++; } function decrement() { this.count--; } this.increment = increment; this.decrement = decrement; } });
Things are becoming much simpler to use and define with this change.
Template
There’s a subtle difference in thetemplateproperty worth noting. Let’s add the
templateproperty to finish off our rework and then take a look.
.component('counter', { bindings: { count: '=' }, controller: function () { function increment() { this.count++; } function decrement() { this.count--; } this.increment = increment; this.decrement = decrement; }, template: [ '<div class="todo">', '<input type="text" ng-model="$ctrl.count">', '<button type="button" ng-click="$ctrl.decrement();">-</button>', '<button type="button" ng-click="$ctrl.increment();">+</button>', '</div>' ].join('') });
The
templateproperty can be defined as a function that is now injected with
$elementand
$attrslocals. If the
templateproperty is a function then it needs to return an String representing the HTML to compile:
{ ... template: function ($element, $attrs) { // access to $element and $attrs return [ '<div class="todo">', '<input type="text" ng-model="$ctrl.count">', '<button type="button" ng-click="$ctrl.decrement();">-</button>', '<button type="button" ng-click="$ctrl.increment();">+</button>', '</div>' ].join('') } ... }
That’s it for our Directive to Component refactor, however there are a few other changes worth exploring before we finish.
Inheriting behaviour using “require”
If you’re not familiar with “require”, check my article on using require.{ ... require: { parent: '^parentComponent' }, controller: function () { // use this.parent to access required Objects this.parent.foo(); } ... }
Inherited Directive or Component methods will be bound to the
this.parentproperty in the Controller.
One-way bindings
A new syntax expression for isolate scope values, for example:{ ... bindings: { oneWay: '<', twoWay: '=' }, ... }
Read my full write-up about one-way bindings.
Disabling isolate scope
Components are always created with isolate scope. Here’s the relevant part from the source code:{ ... scope: {}, ... }
Stateless components
There’s now the ability to create “stateless” components, read my in-depth article onstateless components in the.component()method.
Essentially we can just use a
templateand
bindings:
var NameComponent = { bindings: { name: '=', age: '=' }, template: [ '<div>', '<p>Name: </p>', '<p>Age: </p>', '</div>' ].join('') }; angular .module('app', []) .component('nameComponent', NameComponent);
Sourcecode for comparison
Throughout the article I’ve referred to some Angular source code snippets to cross reference against. Here’s the source code below:this.component = function registerComponent(name, options) { var controller = options.controller || function() {}; function factory($injector) { function makeInjectable(fn) { if (isFunction(fn) || isArray(fn)) { return function(tElement, tAttrs) { return $injector.invoke(fn, this, {$element: tElement, $attrs: tAttrs}); }; } else { return fn; } } var template = (!options.template && !options.templateUrl ? '' : options.template); return { controller: controller, controllerAs: identifierForController(options.controller) || options.controllerAs || '$ctrl', template: makeInjectable(template), templateUrl: makeInjectable(options.templateUrl), transclude: options.transclude, scope: {}, bindToController: options.bindings || {}, restrict: 'E', require: options.require }; } // Copy any annotation properties (starting with $) over to the factory function // These could be used by libraries such as the new component router forEach(options, function(val, key) { if (key.charAt(0) === '$') { factory[key] = val; } }); factory.$inject = ['$injector']; return this.directive(name, factory); };
Again, please note that Angular 1.5 isn’t released just yet, so this article uses an API thatmay be subject to slight change.
Upgrading to Angular 2
Writing components in this style will allow you to upgrade your Components using.component()into Angular 2 very easily, it’d look something like this in ECMAScript 5 and new template syntax:
var Counter = ng .Component({ selector: 'counter', template: [ '<div class="todo">', '<input type="text" [(ng-model)]="count">', '<button type="button" (click)="decrement();">-</button>', '<button type="button" (click)="increment();">+</button>', '</div>' ].join('') }) .Class({ constructor: function () { this.count = 0; }, increment: function () { this.count++; }, decrement: function () { this.count--; } });
相关文章推荐
- AngularJs基本篇 二 (控制器属性 + 控制器方法)
- AngularJS 1.5.0-beta.2 and 1.4.8 have been released
- AngularJs基本篇 一 (简介+基本指令)
- AngularJS 2.0新特性有哪些
- Angular发布1.5正式版,专注于向Angular 2的过渡
- Angular发布1.5正式版,专注于向Angular 2的过渡
- AngularJS 2.0新特性有哪些
- Angular+Flask搭建一个记录工具
- AngularJS - 1 (basic concepts)
- 使用 AngularJS 的路由和模板实现单页应用 (Single Page)
- angularJs自定义指令
- 前端框架 angularjs
- Angular ngTable
- 给angular加上动画效遇到的问题总结
- AngularJS 最常用的功能汇总
- AngularJS身份验证的方法
- 优化Angular应用的性能
- 优化Angular应用的性能
- 基于AngularJS+HTML+Groovy实现登录功能
- AngularJS入门教程:日期格式化