Angular 1.5 introduced the .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 name argument is what we want to define our Component as, the options argument 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 counter component for the purposes of this exercise in Angular1.4.x which we’ll refactor into a version v1.5.0 build 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.x Directive:

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 the function argument 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 scope property allows us to define whether we want to isolate the$scope or 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 bindings property 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 declare controller, however it’s now a little smarter and has a default controllerAs value 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 controllerAs at 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.prop inside our template to talk to the instance of the Controller.

Now, there are some changes in .component() that make sensible assumptions and automatically create a controllerAs property 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 identifierForController function 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 controllerAs property… however

We can add the controllerAs property 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 aboutcontrollerAs, and Angular automatically uses the name $ctrl. For instance:

.component('test', {
controller: function () {
this.testing = 123;
}
});

The would-be controllerAs definition automatically defaults to $ctrl, so we can use$ctrl.testing in our template which 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 controllerAs property:

// 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 the template property 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 template property can be defined as a function that is now injected with $elementand $attrs locals. If the template property 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.parent property 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 template and 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--;
}
});
 
 

Exploring the Angular 1.5 .component() method的更多相关文章

  1. [AngularJS] Exploring the Angular 1.5 .component() method

    Angualr 1.4: .directive('counter', function counter() { return { scope: {}, restrict: 'EA', transclu ...

  2. [Angular] Test Container component with async provider

    The main idea for testing contianer component is to make sure it setup everythings correctlly. Call ...

  3. 【Angular】No component factory found for ×××.

    报错现象: 用modal打开某个组件页面时报错 报错:No component factory found for UpdateAuthWindowComponent. Did you add it ...

  4. Angular(二) - 组件Component

    1. 组件Component示例 2. Component常用的几个选项 3. Component全部的选项 3.1 继承自@Directive装饰器的选项 3.2 @Component自己特有的选项 ...

  5. [Angular 2] Exposing component properties to the template

    Showing you how you can expose properties on your Controller to access them using #refs inside of yo ...

  6. [Angular 2] Component relative paths

    Oingial aritial --> Link Take away: import { Component, OnInit } from '@angular/core'; @Component ...

  7. [Angular & Unit Testing] Testing a RouterOutlet component

    The way to test router componet needs a little bit setup, first we need to create a "router-stu ...

  8. [Angular Tutorial] 13 -REST and Custom Services

    在这一步中,我们将会改变我们获取数据的方式. ·我们定义一个代表RESTful客户端的自定义服务.使用这个客户端,我们可以用一种更简单的方法向服务端请求数据,而不用处理更底层的$httpAPI,HTT ...

  9. Angular 2 to Angular 4 with Angular Material UI Components

    Download Source - 955.2 KB Content Part 1: Angular2 Setup in Visual Studio 2017, Basic CRUD applicat ...

随机推荐

  1. css中的列表属性

    list-style-type设定引导列表的符号类型,可以设置多种符号类型,值为disc.circle.square等 list-style-image使用图像作为定制列表的符号 list-style ...

  2. 【转】Paxos算法深入分析

    http://blog.csdn.net/anderscloud/article/details/7175209 在分布式系统设计领域,Paxos可谓是最重要一致性的算法.Google的大牛们称   ...

  3. [转]SQLITE3 C语言接口 API 函数简介

    SQLITE3 C语言接口 API 函数简介 说明:本说明文档属作者从接触 SQLite 开始认识的 API 函数的使用方法, 由本人翻译, 不断更新. /* 2012-05-25 */ int sq ...

  4. Magento中,调用静态块的几种方法

    在后台创建一个order_form静态块Block Title :Order FormIdentifier :order_formStatus :EnabledContent :自定义内容 1.如果要 ...

  5. Push推送原理

    Push 的工作机制 APNS 是Apple Push Notification Service(Apple Push服务器)的缩写,是苹果的服务器. 推送可以分为三个阶段. 第一阶段:.net应用程 ...

  6. 触控(Touch) 、 布局(Layout)

    1 使用触控实现一个简易的画板 1.1 问题 触控(Touch)是一个UITouch类型的对象,当用户触摸了屏幕上的视图时自动被创建,通常使用触控实现绘图.涂鸦.手写等功能.本案例使用触控实现一个简易 ...

  7. 《MORE EFFECTIVE C++》条款27 要求或者禁止对象分配在堆上

    1. 要求对象分配在堆上 临时对象一般是存在于栈中的,或者是静态对象存在于常量存储区的.那么当创建一个这样的对象的时候,一般是需要隐式或显式地调用构造函数,在销毁的时候调用析构函数的.可以从这方面入手 ...

  8. ZOJ 3872--解题报告

    题目相关: 3872相关链接: http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=5520 Edward拥有一组数列. 其定义了Be ...

  9. ABAP 使用的字符类型

    1.ABAP基本数据类型 类型        描述                属性 C            字符类型           默认长度1,最大长度不限N            数字类 ...

  10. Sprint第二个冲刺(第二天)

    一.Sprint 计划会议:      在这次会议中我们主要讨论了我们正在做的几个任务,比如说在美化按钮和增添图片上我们都发表了自己的想法,卓炜杰同学也把我们的想法进行分析,寻求最适合的方法.在查看用 ...