当前位置: 首页 > news >正文

3 - Components

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

In the previous step, we saw how a controller and a template worked together to convert a static HTML page into a dynamic view. This is a very common pattern in Single-Page Applications in general (and Angular applications in particular):

  • Instead of creating a static HTML page on the server, the client-side code "takes over" and interacts dynamically with the view, updating it instantly to reflect changes in model data or state, usually as a result of user interaction (we'll see an example shortly instep 5).

The template (the part of the view containing the bindings and presentation logic) acts as a blueprint for how our data should be organized and presented to the user. The controller provides the context in which the bindings are evaluated and applies behavior and logic to our template.

There are still a couple of areas we can do better:

  1. What if we want to reuse the same functionality in a different part of our application ?
    We would need to duplicate the whole template (including the controller). This is error-prone and hurts maintainability.
  2. The scope, that glues our controller and template together into a dynamic view, is not isolated from other parts of the page. What this means is that a random, unrelated change in a different part of the page (e.g. a property-name conflict) could have unexpected and hard-to-debug side effects on our view.

    (OK, this might not be a real concern in our minimal example, but it is a valid concern for bigger, real-world applications.)

Workspace Reset Instructions ➤

The most important changes are listed below. You can see the full diff on GitHub.

Components to the rescue!

Since this combination (template + controller) is such a common and recurring pattern, Angular provides an easy and concise way to combine them together into reusable and isolated entities, known as components. Additionally, Angular will create a so called isolate scope for each instance of our component, which means no prototypal inheritance and no risk of our component affecting other parts of the application or vice versa.

Since this is an introductory tutorial, we are not going to dive deep into all features provided by Angular components. You can read more about components and their usage patterns in the Components section of the Developer Guide.

In fact, one could think of components as an opinionated and stripped-down version of their more complex and verbose (but powerful) siblings, directives, which are Angular's way of teaching HTML new tricks. You can read all about them in the Directivessection of the Developer Guide.

(Note: Directives are an advanced topic, so you might want to postpone studying them, until you have mastered the basics.)

To create a component, we use the .component() method of an Angular module. We must provide the name of the component and the Component Definition Object (CDO for short).

Remember that (since components are also directives) the name of the component is in camelCase (e.g. myAwesomeComponent), but we will use kebab-case (e.g. my-awesome-component) when referring to it in our HTML. (See here for a description of different case styles.)

In its simplest form, the CDO will just contain a template and a controller. (We can actually omit the controller and Angular will create a dummy controller for us. This is useful for simple "presentational" components, that don't attach any behavior to the template.)

Let's see an example:

angular.
  module('myApp').
  component('greetUser', {
    template: 'Hello, {{$ctrl.user}}!',
    controller: function GreetUserController() {
      this.user = 'world';
    }
  });

Now, every time we include <greet-user></greet-user> in our view, Angular will expand it into a DOM sub-tree constructed using the provided template and managed by an instance of the specified controller.

But wait, where did that $ctrl come from and what does it refer to ?

For reasons already mentioned (and for other reasons that are out of the scope of this tutorial), it is considered a good practice to avoid using the scope directly. We can (and should) use our controller instance; i.e. assign our data and methods on properties of our controller (the "this" inside the controller constructor), instead of directly to the scope.

From the template, we can refer to our controller instance using an alias. This way, the context of evaluation for our expressions is even more clear. By default, components use $ctrl as the controller alias, but we can override it, should the need arise.

There are more options available, so make sure you check out the API Reference, before using .component() in your own applications.

Using Components

Now that we know how to create components, let's refactor the HTML page to make use of our newly acquired skill.


app/index.html:

<html ng-app="phonecatApp">
<head>
  ...
  <script src="bower_components/angular/angular.js"></script>
  <script src="app.js"></script>
  <script src="phone-list.component.js"></script>
</head>
<body>

  <!-- Use a custom component to render a list of phones -->
  <phone-list></phone-list>

</body>
</html>


app/app.js:

// Define the `phonecatApp` module
angular.module('phonecatApp', []);


app/phone-list.component.js:

// Register `phoneList` component, along with its associated controller and template
angular.
  module('phonecatApp').
  component('phoneList', {
    template:
        '<ul>' +
          '<li ng-repeat="phone in $ctrl.phones">' +
            '<span>{{phone.name}}</span>' +
            '<p>{{phone.snippet}}</p>' +
          '</li>' +
        '</ul>',
    controller: function PhoneListController() {
      this.phones = [
        {
          name: 'Nexus S',
          snippet: 'Fast just got faster with Nexus S.'
        }, {
          name: 'Motorola XOOM™ with Wi-Fi',
          snippet: 'The Next, Next Generation tablet.'
        }, {
          name: 'MOTOROLA XOOM™',
          snippet: 'The Next, Next Generation tablet.'
        }
      ];
    }
  });

Voilà! The resulting output should look the same, but let's see what we have gained:

  • Our phone list is reusable. Just drop <phone-list></phone-list> anywhere in the page to get a list of phones.
  • Our main view (index.html) is cleaner and more declarative. Just by looking at it, we know there is a list of phones. We are not bothered with implementation details.
  • Our component is isolated and safe from "external influences". Likewise, we don't have to worry, that we might accidentally break something in some other part of the application. What happens inside our component, stays inside our component.
  • It's easier to test our component in isolation.

tutorial_03.png

A note on file naming:

It is a good practice to distinguish different types of entities by suffix. In this tutorial, we are using the .component suffix for components, so the definition of a someComponent component would be in a file named some-component.component.js.

Testing

Although we have combined our controller with a template into a component, we still can (and should) unit test the controller separately, since this is where our application logic and data reside.

In order to retrieve and instantiate a component's controller, Angular provides the $componentController service.

The $controller service that we used in the previous step can only instantiate controllers that were registered by name, using the .controller() method. We could have registered our component controller this way, too, if we wanted to. Instead, we chose to define it inline — inside the CDO — to keep things localized, but either way works equally well.


app/phone-list.component.spec.js:

describe('phoneList', function() {

  // Load the module that contains the `phoneList` component before each test
  beforeEach(module('phonecatApp'));

  // Test the controller
  describe('PhoneListController', function() {

    it('should create a `phones` model with 3 phones', inject(function($componentController) {
      var ctrl = $componentController('phoneList');

      expect(ctrl.phones.length).toBe(3);
    }));

  });

});

The test retrieves the controller associated with the phoneList component, instantiates it and verifies that the phones array property on it contains three records. Note that the data is now on the controller instance itself, not on a scope.

Running Tests

Same as before, execute npm test to run the tests and then watch the files for changes.

Experiments

  • Try the experiments from the previous step, this time on the phoneList component.

  • Add a couple more phone lists on the page, by just adding more <phone-list></phone-list> elements in index.html. Now add another binding to the phoneList component's template:

    template:
        '<p>Total number of phones: {{$ctrl.phones.length}}</p>' +
        '<ul>' +
        ...

    Reload the page and watch the new "feature" propagate to all phone lists. In real-world applications, where the phone lists could appear on several different pages, being able to change or add something in one place (e.g. a component's template) and have that change propagate throughout the application, is a big win.

Summary

You have learned how to organize your application and presentation logic into isolated, reusable components. Let's go to step 4 to learn how to organize our code in directories and files, so it remains easy to locate as our application grows.

转载于:https://my.oschina.net/changeme/blog/805685

相关文章:

  • ceph 手工部署集群
  • 转载:align
  • codewars020: The Clockwise Spiral 数字顺时针螺旋矩阵
  • Servlet引擎Jetty之入门1
  • PHP SPL中提供了SplFileInfo和SplFileObject两个类来处理文件操作。
  • pg_dump实例详解(备份postgresql和greenplum数据库)
  • GATK使用说明(一)
  • linux应用之apache的源码安装(centos)
  • linux sudo 命令
  • 客户端Socket
  • 一张图看懂开源许可协议,开源许可证GPL、BSD、MIT、Mozilla、Apache和LGPL的区别...
  • 画风清奇!盘点各编程语言中有趣的开源项目!
  • 前端学HTTP之报文首部
  • 【干货分享】流程DEMO-制度发文和干部任免
  • [充电]多线程无锁编程--原子计数操作:__sync_fetch_and_add等12个操作
  • [iOS]Core Data浅析一 -- 启用Core Data
  • 3.7、@ResponseBody 和 @RestController
  • Hibernate【inverse和cascade属性】知识要点
  • JS函数式编程 数组部分风格 ES6版
  • ng6--错误信息小结(持续更新)
  • PAT A1050
  • Solarized Scheme
  • Spark学习笔记之相关记录
  • 聊聊flink的TableFactory
  • 巧用 TypeScript (一)
  • 用简单代码看卷积组块发展
  • 《码出高效》学习笔记与书中错误记录
  • 选择阿里云数据库HBase版十大理由
  • # MySQL server 层和存储引擎层是怎么交互数据的?
  • #define
  • (1/2) 为了理解 UWP 的启动流程,我从零开始创建了一个 UWP 程序
  • (2)STM32单片机上位机
  • (C语言)求出1,2,5三个数不同个数组合为100的组合个数
  • (pytorch进阶之路)扩散概率模型
  • (附源码)ssm基于jsp的在线点餐系统 毕业设计 111016
  • (剑指Offer)面试题41:和为s的连续正数序列
  • (九)信息融合方式简介
  • (欧拉)openEuler系统添加网卡文件配置流程、(欧拉)openEuler系统手动配置ipv6地址流程、(欧拉)openEuler系统网络管理说明
  • (十二)devops持续集成开发——jenkins的全局工具配置之sonar qube环境安装及配置
  • (四)linux文件内容查看
  • (四)鸿鹄云架构一服务注册中心
  • (算法)求1到1亿间的质数或素数
  • (原创)Stanford Machine Learning (by Andrew NG) --- (week 9) Anomaly DetectionRecommender Systems...
  • (转)四层和七层负载均衡的区别
  • .bashrc在哪里,alias妙用
  • .net MySql
  • .NET 发展历程
  • .net 前台table如何加一列下拉框_如何用Word编辑参考文献
  • .NET 设计一套高性能的弱事件机制
  • .net6解除文件上传限制。Multipart body length limit 16384 exceeded
  • .Net面试题4
  • .one4-V-XXXXXXXX勒索病毒数据怎么处理|数据解密恢复
  • @WebService和@WebMethod注解的用法
  • [ 云计算 | AWS 实践 ] Java 如何重命名 Amazon S3 中的文件和文件夹
  • [1525]字符统计2 (哈希)SDUT