未加星标

Angular 2 Patterns for Angular 1.x Apps

字体大小 | |
[前端(javascript) 所属分类 前端(javascript) | 发布者 店小二05 | 时间 2016 | 作者 红领巾 ] 0人收藏点击收藏

If you’re planning to upgrade your codebase to Angular 2, there are particular things you can begin to do to start getting into the Angular 2 mindset. In this article, we’ll be going through some things you can do to an existing 1.x codebase to bring it into shape for any future Angular 2 refactoring.

As a side note, even if you are not planning to move to Angular 2 in the near or distance future, getting your Angular 1.x app into the latest best practices state will benefit your development in many many ways, ranging from application maintenance all the way through to writing better javascript for the future.

Use ES6

We'll start with ES6 here, or ES2015 for the pedantic. Using ES6 will get your app half-way there in terms of using a "modern" JavaScript tech stack.

You can start converting your ES5 apps across to ES6 with ease, and one file at a time as well. This gives you a lot of breathing room for short bursts of "modernizing" your app(s). Let's compare some ES5 versus ES6 code:

// ES5 version
function TodoController(TodoService) {
var ctrl = this;
ctrl.$onInit = function () {
this.todos = this.todoService.getTodos();
};
ctrl.addTodo = function (event) {
this.todos.unshift({ event.label, id: this.todos.length + 1 });
};
ctrl.completeTodo = function (event) {
this.todos[event.index].complete = true;
};
ctrl.removeTodo = function (event) {
this.todos.splice(event.index, 1);
};
}
angular
.module('app')
.controller('TodoController', TodoController);

The ES5 version uses plain old JavaScript functions this is great, and perfectly acceptable. However, if you're considering leaping to ES6, a class may make a lot more sense, as well as being in line with Angular 2's component classes.

The common thing we need to do to the above code is reference the this keyword of the controller so that we are able to use it across different lexical scopes. I prefer this over Function.prototype.bind as it's clearer to me what's happening when using the ctrl namespacing, plus it's a little bit faster.

With that in mind, let's convert the code to ES6:

// ES6
export default class TodoController {
constructor(TodoService) {
this.todoService = TodoService;
}
$onInit() {
this.todos = this.todoService.getTodos();
}
addTodo({ label }) {
this.todos.unshift({ label, id: this.todos.length + 1 });
}
completeTodo({ index }) {
this.todos[index].complete = true;
}
removeTodo({ index }) {
this.todos.splice(index, 1);
}
}

You can see here that we've freed things up from Angular's boilerplate to a standalone piece of business logic for our component. We're using an ES6 class with the $onInit lifecycle hook as a property on the class. We're also using object destructuring inside the addTodo , completeTodo and removeTodo functions to only fetch the label or index property that's returned through the arguments.

So where is the angular.module().controller() call? If you're using the right patterns with components, you can bind the exported controller onto the component. This means that it doesn't even need to be registered with the Angular core.

An example:

// todo.component.js
import angular from 'angular';
import controller from 'TodoController';
const todos = {
controller,
template: `
<div>
<todo-form
new-todo="$ctrl.newTodo"
on-add="$ctrl.addTodo($event);">
</todo-form>
<todo-list
todos="$ctrl.todos"
on-complete="$ctrl.completeTodo($event);"
on-delete="$ctrl.removeTodo($event);">
</todo-list>
</div>
`
};
export default todos;

In this example, we're importing just the controller under the default export, which means that we can call it whatever we want. For ES6 shorthand property setting on the component Object, we can just pass in controller ; this is essentially the same as controller: controller . For more Angular ES6 patterns, check out my Angular 1.x ES2015 styleguide , updated with component architecture practices.

Favor immutable operations

We can take this one step further and begin to incorporate immutable patterns. So far, we're using "mutable" patterns, which means that we are mutating state. Thinking about immutable operations is a great way to develop with a uni-directional dataflow.

So what is a mutable operation? In the above examples we were doing:

removeTodo({ index }) {
this.todos.splice(index, 1);
}

Using .splice() will actually mutate the existing Array. This is fine in general practice, but we want to be more intelligent about our mutations and state changes, being careful not to cause any unintended side effects, and think about performance. Libraries like React and frameworks like Angular 2 can actually perform faster Object diffing by seeing what's changed, rather than predicting and re-rendering an entire collection (for example).

This is where we would construct a new collection and bind it instead. In our removeTodo example, it would look like this:

removeTodo({ todo }) {
this.todos = this.todos.filter(({ id }) => id !== todo.id);
}

In this instance, we're using Array.prototype.filter to return a new collection of data. This allows us to construct our own dataset using an immutable operation, as .filter() does/will not mutate the original Array.

From this we construct our new Array, by filtering out the todo that did in fact match the current item being iterated over. Using .filter() will simply produce false on this expression, in turn removing it from the new collection. The initial reference to this.todos has not been changed at this point we've simply iterated and created a collection based on an expression we provided to fetch all of the todos that are not being removed.

We can also perform time travel debugging whilst using immutable operations, allowing us to step through state mutations and debug code more easily. There is much more power in controlling what state mutations are made, after which we rebind to this.todos once we are ready.

A full look at immutable operations would look as follows:

class TodoController {
constructor(TodoService) {
this.todoService = TodoService;
}
$onInit() {
this.todos = this.todoService.getTodos();
}
addTodo({ label }) {
this.todos = [{ label, id: this.todos.length + 1 }, ...this.todos];
}
completeTodo({ todo }) {
this.todos = this.todos.map(
item => item.id === todo.id ? Object.assign({}, item, { complete: true }) : item
);
}
removeTodo({ todo }) {
this.todos = this.todos.filter(({ id }) => id !== todo.id);
}
}

This will allow you to, if you deem it necessary, use something like Redux inside Angular 1, and move it to Angular 2 as well. For Angular 2, I'd recommend ngrx/store as the go-to state management library, for Angular 1, $ngRedux is of the most popular.

Consider TypeScript

TypeScript is becoming the standard for JavaScript development in Angular 2, whether you like it or not and for good reasons. Despite some features that deviate to looking like backend languages, TypeScript does make sense. If you are considering using Angular 2, then TypeScript is probably a wise idea for you if you're going to upgrade an existing codebase.

Use Components over template Directives

Before we get to Components, let's start with Directives. There was, and still is, much confusion around what the definition of a "Directive" actually is. Is it a template? Does it contain view logic? Does it manipulate the DOM? Does it do all the things and end up messy? Maybe…

To sum up, a Directive is/should:

Manipulate the DOM; Not contain a template or view logic; Bind to existing DOM to extend it's behavior/functionality.

If you think about ng-repeat , for example, this is a behavioral directive that reconstructs the DOM based on the data input into it. It does not go ahead and create a bunch of code that you did not ask it to. When you need to write templates that contain view logic, this is where a component comes in.

A Component is/should:

Create new HTML; Accept data/state; Define application architecture; Render further components to extend your component tree.

Based on this, the idea is that when you want custom DOM manipulation, which we occasionally need to access the DOM with frameworks, then a Directive is the place for that.

Understand component architecture

Component architecture is a pretty new concept to the Angular world, and it's been kicking around in React for years. Angular 2 saw an opportunity in React's component-based approach and uni-directional data flow and stood on its shoulders.

When you think and architect your application in a tree of components, rather than thinking about "pages" and "views", dataflow and predictability become much easier to reason with, and, in my experience, you end up writing a lot less code.

Essentially, you'll want to architect in a tree of components, and understand the different flavors of components. Typically we have smart and dumb components, otherwise known as stateful and stateless components. Dan Abramov has written about this in more depth I urge you to check it out.

Favor component-routing

Again, when moving away from the views/pages mentality, we should favor component routing. The latest release of ui-router which you should 100% be using for Angular 1.x applications not only supports routing to components instead of views, but it also supports Angular 2, and React. It's magical.

An example of a component route:

// referencing the "todos" component we illustrated above
$stateProvider
.state('todos', {
url: '/todos',
component: 'todos',
resolve: {
todos: TodoService => TodoService.getTodos()
}
});

Inside the todos state, we're using resolve to fetch todos, rather than inside the controller. This may make more sense for preloading data before you hit that routed component. Interestingly, we can use the todos property inside resolve to get that data passed to us as a component binding called todos :

import angular from 'angular';
import controller from 'TodoController';
const todos = {
bindings: {
todos: '<'
},
controller,
template: `
<div>
<todo-form
new-todo="$ctrl.newTodo"
on-add="$ctrl.addTodo($event);">
</todo-form>
<todo-list
todos="$ctrl.todos"
on-complete="$ctrl.completeTodo($event);"
on-delete="$ctrl.removeTodo($event);">
</todo-list>
</div>
`
};
export default todos;

What is this mystical '<' syntax? One-way dataflow. Let's explore a little further with a different example.

Favor one-way dataflow

One-way dataflow is predictable and easier to debug. The idea is that data is passed down, mutated, and then events are passed back up to inform the parent that something needs to change. This concept applies in Angular 1.x components, Angular 2 and also React (however, we are in no way limited to just those three).

Let's assume we want to add a new todo. We have our addTodo function that accepts an event Object, but we destructure it to just fetch our label property:

addTodo({ label }) {
this.todos = [{ label, id: this.todos.length + 1 }, ...this.todos];
}

From this, we are adding the new todo at the beginning of a new Array, and, using the ES6 spread operator, we're spreading the existing this.todos Array into the new one, thus creating our new collection with immutable operators. When the this.todos changes, our binding using < is passed new data, which then delegates to the <todo-list> , thus rendering the new reflected change in the Array:

const todoList = {
bindings: {
todos: '<',
onComplete: '&',
onDelete: '&'
},
template: `
<ul>
<li ng-repeat="todo in $ctrl.todos">
<todo
item="todo"
on-change="$ctrl.onComplete($locals);"
on-remove="$ctrl.onDelete($locals);">
</todo>
</li>
</ul>
`
};

The one-way syntax we're using here is against the todos coming into todoList . When the parent data changes, it will be reflected down into the child component, forcing a DOM re-render with the new addition.

Where to go from here

If you want to learn more, check out the full code demo of the todo lists with one-way dataflow and immutable operations. For more on these practices, you can review my ES6 + Angular 1.5 components styleguide .

本文前端(javascript)相关术语:javascript是什么意思 javascript下载 javascript权威指南 javascript基础教程 javascript 正则表达式 javascript设计模式 javascript高级程序设计 精通javascript javascript教程

主题: ReactJavaScriptJavaHTML
分页:12
转载请注明
本文标题:Angular 2 Patterns for Angular 1.x Apps
本站链接:http://www.codesec.net/view/480804.html
分享请点击:


1.凡CodeSecTeam转载的文章,均出自其它媒体或其他官网介绍,目的在于传递更多的信息,并不代表本站赞同其观点和其真实性负责;
2.转载的文章仅代表原创作者观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,本站对该文以及其中全部或者部分内容、文字的真实性、完整性、及时性,不作出任何保证或承若;
3.如本站转载稿涉及版权等问题,请作者及时联系本站,我们会及时处理。
登录后可拥有收藏文章、关注作者等权限...
技术大类 技术大类 | 前端(javascript) | 评论(0) | 阅读(29)