diff --git a/public/docs/ts/latest/cookbook/_data.json b/public/docs/ts/latest/cookbook/_data.json index e4363318ef..f9f973b5bd 100644 --- a/public/docs/ts/latest/cookbook/_data.json +++ b/public/docs/ts/latest/cookbook/_data.json @@ -4,13 +4,13 @@ "navTitle": "Overview", "description": "A collection of recipes for common Angular application scenarios" }, - + "a1-a2-quick-reference": { "title": "Angular 1 to 2 Quick Reference", "navTitle": "Angular 1 to 2 Quick Ref", "intro": "Learn how Angular 1 concepts and techniques map to Angular 2" }, - + "component-communication": { "title": "Component Interaction", "intro": "Share information between different directives and components" @@ -29,5 +29,10 @@ "ts-to-js": { "title": "TypeScript to JavaScript", "intro": "Convert Angular 2 TypeScript examples into ES5 JavaScript" + }, + + "esnext": { + "title": "ESNext Primer", + "intro": "Learn about ES6+ features and how they are used with Angular" } } diff --git a/public/docs/ts/latest/cookbook/esnext.jade b/public/docs/ts/latest/cookbook/esnext.jade new file mode 100644 index 0000000000..e3ba1469a2 --- /dev/null +++ b/public/docs/ts/latest/cookbook/esnext.jade @@ -0,0 +1,492 @@ +include ../_util-fns + +- var top="vertical-align:top" + +:marked + # Who is this guide for? + + This guide is intended to provide developers who are unfamiliar with ES6 and want to learn the + primary features used in Angular. This is not a complete guide to all of the new features + available to you, but a tour of the most commonly used with Angular. + + It is possible to write Angular using ES5 syntax (the common version of JavaScript since 2009), + so why should you bother to learn ES6 anyways? + + * Angular 2 was designed with using ES6 syntax in mind. + * It provides improved clarity and consistency in coding. + * Many examples and tutorials are written in ES6. + * You can choose to use as much or as little as you wish. + + Many developers find using ES6 increases code quality, makes it easier to read code and understand + intention, and can help enforce some best practices. Let's take a look at the primary features, + and see how they are used with Angular. + + ## Classes + + Classes provide a new syntax for declaring objects. They are primarily used when creating Angular + entities such as components and services. Classes are purely a new syntax, they do not introduce + new features. Let's see a class and compare it to a similar approach using a constructor function + like you would write with ES5. + +table(width="100%") + col(width="50%") + col(width="50%") + tr + th Class + th ES5 Constructor Function + tr(style=top) + td + :marked + ``` + class Person { + constructor(name) { + this.name = name; + } + + getName() { + return this.name; + } + } + var user = new Person('Jeremy'); + user.getName(); // Returns 'Jeremy' + ``` + td + :marked + ``` + function Person (name) { + this.name = name; + } + + Person.prototype.getName = function() { + return this.name; + }; + + var user = new Person('Jeremy'); + user.getName(); // Returns 'Jeremy' + ``` + +:marked + In this example, we define a new class called `Person`. The `constructor()` method executes as soon + as the class is instanciated using the `new` keyword, and can accept arguments. Methods are defined + like a function, but without using the keyword `function`. + + The biggest advantages of a class are to provide a consistency in the syntax for declaring objects, + and can help enforce some best practices like strict mode. + + ## Modules + + Modules enable code encapsulation and the ability to import objects from other files. Most other + languages have had the concept of modules (perhaps under another name), such as Ruby's `require` or + Java's `import`. However, JavaScript has lacked a native module system. + + There are some module systems for JavaScript, primarily CommonJS and AMD. Another option is use + of a global variable that acts as a namespace for various shared objects. With native JavaScript + modules, we now have a clean syntax for importing modules that looks like this. + +table(width="100%") + col(width="50%") + col(width="50%") + tr + th Export (person.js) + th Import (user.js) + tr(style=top) + td + :marked + ``` + export class Person { + constructor(name) { + this.name = name; + } + + getName() { + return this.name; + } + } + ``` + td + :marked + ``` + import {Person} from './user'; + + var user = new Person('Jeremy'); + user.getName(); // Returns 'Jeremy' + ``` + +:marked + First, it is important to note that any file that has the `import` or `export` keywords are + automatically considered modules. The code inside of those files will not be available in the + global scope. + + On the left side, we have a file that contains our `Person` class. We add the `export` keyword + to declare that this object is to be made available. Since the `export` keyword was used, this file + is considered a module. + + The `import` keyword allows us to bring in a value from another location, and on the right side + you can see the syntax to load in the `Person` class. Again, the use of `import` makes this file + a module as well, so the variable `user` is not assigned to the global scope. + + Modules are a very important feature that helps us write better code that is encapsulated and isolated. + By avoiding code in the global scope, we can protect the privacy of code while still being able to share. + + ## Decorators + + Decorators are a way to declaratively add metadata to an object. Decorators are essentially functions + that receive a copy of the object and modify it. The concept is found in some other + languages, such as Python decorators or Java annotations. Decorators always start with the `@` symbol, + so they should be easy to spot, and they can be defined for a class or property. + + Let's make an example that decorates the `Person` class that assigns a property of `isHero` to indicate + if they are a hero. Let's imagine there is a decorator called `@Hero()` and use it in our example. + +table(width="100%") + col(width="50%") + col(width="50%") + tr + th Using a Decorator (person.js) + th Import (user.js) + tr(style=top) + td + :marked + ``` + @Hero() // Decorator used on Person + export class Person { + constructor(name) { + this.name = name; + } + + getName() { + return this.name; + } + } + ``` + td + :marked + ``` + import {Person} from './user'; + + var user = new Person('Jeremy'); + user.getName(); // Returns 'Jeremy' + user.isHero; // Returns true thanks to decorator + ``` + +:marked + This example allows us to see the power of decorators. Angular already defines several of these + to use when creating objects, such as `@Component()`, `@Directive()`, and `@Pipe()`. + + Technically, decorators are not officially part of the ECMAScript standard yet, but they are likely + to be implemented in an upcoming version of JavaScript. However, most compilers support it anyways. + + ## `let` and `const` Keywords + + There are two new ways to declare variables by using the `let` and `const` keywords. Until ES6, + a defined variable would be confined to the function it was defined within, or the global scope if + defined outside of a function. So when you use `var` the variable is available inside of the function. + + On the other hand, with ES6 variables defined using `let` and `const` are both block scoped. The concept + of block statement scope is new in ES6, and allows us to limit the scope for better encapsulation. Block + scope is essentially any place there are brackets `{}` enclosing some code, for example an `if`/`else` + block or `for` loop block. + + The `let` keyword will define a variable in the current block scope, and can easily replace the use of + `var` in most contexts. + + The `const` keyword also defines a variable in the current block scope, but is special in that you cannot + reassign a new value to the variable after it has been defined. You can modify the existing value, but you + are not allowed to Let's see how it is used in an example. + + ``` + import {Person} from './user'; + const powers = ['flight', 'x-ray vision', 'invisibility']; // Has scope of entire module, cannot be reassigned + + function pickRandomPower() { + let index = Math.floor(Math.random() * (powers.length + 1)); // Has scope of only function + let power = powers[index]; // Can access the variable from a higher scope + powers.splice(index, 1); // Can mutate the value of the const powers + return power; + } + + let user = new Person('Jeremy'); // Has scope of entire module, can be reassigned + if (user.isHero) { + let power = pickRandomPower(); // Only available inside of if statement, doesn't conflict with above + user.power = power; + } + + // Things you can't do + if (power === 'flight') { Cannot access `power` because it is limited to if blocks above + ... + } + powers = ['faster than bullet', 'premonitions', ''] // Cannot reassign the value of a const + ``` + + These two new ways to define variables are recommended since they enforce a more restrictive scope + onto the variables and help prevent variables from bleeding out of the scope they were defined. + + ## Arrow Functions + + One of the more difficult aspects of JavaScript is knowing what the keyword `this` currently refers to, + since it depends on the current context. With classes, it becomes easier to reason about how `this` works. + You may recall in the class example that `this.name` was used inside of the constructor and method, meaning + we can be sure they shared the same scope context. + + However, there are still situations where we'll need to write functions inside of our class methods. Often + we will want to have this inner function to retain the same context so we can assign values to the same + `this`. + + Arrow functions simply take the place of the traditional `function` keyword, and do not create a new scope. + The syntax for an arrow function has several possible formats. + + ``` + items.forEach((item) => { return item++ }); // Typical format, `() => {}` + items.forEach(item => { return item++ }); // Can optionally drop parenthenses when defining parameters + items.forEach((item) => item++); // Can optionally drop curly braces if the result of the statement is returned + ``` + + There are a whole lot of conditions with arrow functions. + + * They are always anonymous. You cannot name an arrow function. + * They do not bind `this`, instead use the same context. + * There is no `arguments` object for an arrow function, it may be available from a parent function though. + * The body of the function can be concise (implied return) or a block body (explicit return). + + Let's add a method to our class that will be able to load a person's biography. This conceptually uses the + Angular [Http client](../guide/server-communication.html), which you can review if you aren't familiar with it. + On the left, you can see the arrow function syntax in the `getBio()` method, and on the right you see a + common workaround to keep track of the correct context. + +table(width="100%") + col(width="50%") + col(width="50%") + tr + th Arrow functions in ES6 + th Workaround for ES5 + tr(style=top) + td + :marked + ``` + import {Http} from 'angular2/http'; + + export class Person { + constructor(@Inject('Http') http) { + this.http = http; + this.skills = []; + } + + getSkills() { + // Here the then promise resolve function is an arrow function, in the shorted syntax possible + this.http.get('/api/bio') + .then(response => this.bio = response.json()); + } + } + ``` + td + :marked + ``` + import {Http} from 'angular2/http'; + + export class Person { + constructor(@Inject('Http') http) { + this.http = http; + this.skills = []; + } + + getSkills() { + // Bind `this` into the inner function to share same scope + this.http.get('/api/bio').then(function(response) { + // Inside of new scope, so have to use `that` to reference class + that.bio = response.json() + }.bind(this)); + } + } + ``` + +:marked + There are other ways to work around this limitation, such as assigning `this` to a variable and + using it to reference the parent context. However, arrow functions provide a new syntax + to declare functions that do not create their own scopes. + + Not all functions should be written as arrow functions. If there is no reason to use the same scope, + then a normal function is a better option. Keep in mind since arrow functions are anonymous, any stack + traces will show anonymous functions instead of function names. + + ## Template literals + + With Angular, often developers write their HTML partial templates and store that in an external HTML file. + ES6 provides support for multiline strings (called template literals) using the back-tick character, + so you could also store them inline with your JavaScript code should you desire. + + These multiline strings can contain HTML characters, and even some basic expression interpolation. Normally + you won't use this with Angular, but it is available. Here is a comparision of how the template literal syntax + works, most commonly to be used with defining a template for a component. + +table(width="100%") + col(width="50%") + col(width="50%") + tr + th ES6 Template literals + th ES5 Strings + tr(style=top) + td + :marked + ``` + // With ES6, the whole template exists between back-ticks + // and can even have simple interpolation + @Component( + selector: 'person', + template: `

Hello {{name}}

+

View my profile.

` + ) + export class Person { + // Class contents here + } + ``` + td + :marked + ``` + // To declare the template inline with ES5, we must + // manually concatenate strings together + @Component({ + selector: 'person', + template: '

Hello {{name}}

' + + '

View my profile.

' + }) + export class Person { + // Class contents here + } + ``` + +:marked + You can use template literals anywhere you would use a string, but essentially JavaScript will + convert the multiline text into a standard JavaScript string object. This means you can still use + the string object methods on a template literal. + + ## Default Paremters + + ES6 gives us the ability to assign a default value for a function parameter before it is instanciated. + Until now, you would have to do some kind of comparision on the parameter in the function, check if it + is empty or invalid, and then assign a new value. This also works on class methods, since they are just + functions assigned to the prototype. + +table(width="100%") + col(width="50%") + col(width="50%") + tr + th ES6 Parameter Defaults + th ES5 Defaults + tr(style=top) + td + :marked + ``` + export class Person { + // Assign a value in function params to define default value + constructor(name = 'New Hero') { + this.name = name; + } + } + ``` + td + :marked + ``` + export class Person { + constructor(name) { + if (!name) { // Have to check if the value is empty + name = 'New Hero'; // Directly assign default value + } + this.name = name; + } + } + ``` + +:marked + The default value is only assigned if the function is called without passing the argument. You may still + need to check the value is passed is valid. + + ## Rest and Spread Operators + + The final feature of ES6 we'll look at are the rest and spread operators. In both cases the operator is + three dots `...`, but the placement of the operator changes the way it behaves. + + The rest operator is able to essentially take the place of the `arguments` feature by allowing you to + assign any additional inputs into an array. You use it in a function parameter by putting the `...` + in front of the last parameter like `function (name, ...skills) {}`. You can think of it as getting + the rest of the arguments as the parameter skills. + + The spread operator does the exact opposite of the rest operator, where it splits an array up and passes + the individual parts to the function. You can push multiple items onto array like `myarr.push(...items)`. + You can think of it as spreading the array out into separate parameters for a function call. + + Let's see how these can be used with a new function that takes any number of arguments and then adds them + to an array. + +table(width="100%") + col(width="50%") + col(width="50%") + tr + th ES6 Rest and Spread + th ES5 Equivilent + tr(style=top) + td + :marked + ``` + export class Person { + constructor(name = 'New Hero') { + this.name = name; + this.skills = []; + } + + // This is the rest operator, accepting all inputs as an array called skills + addSkills(...skills) { + // Spread operator, splitting each of the array items into individual parameters + this.skills.push(...skills); + } + } + ``` + td + :marked + ``` + export class Person { + constructor(name = 'New Hero') { + this.name = name; + this.skills = []; + } + + addSkills() { + for (let i = 0; i < arguments.length; i++) { + this.skills.push(arguments[i]); + } + } + } + ``` + +:marked + You can see with the rest operator we can avoid using `arguments` entirely, which is helpful + because it becomes more clear what our function is doing. This also means in an arrow function + that you can still capture the remaining parameters as an array. + + The spread operator then makes it easier to pass an array's contents as individual parameters. + In this case, you can pass as many items as you want to `splice` in order to append new items + to the array. + + With Angular, these are primarily used as a best practice. The use of arguments is discouraged + and takes more code to implement. + + ## A little more about ES6+ and transpilers + + ES6 is a significant evolution of the JavaScript language. It is officially known as ES2015, but + it is typically referred to as ES6. Many major additions and improvements were added to the + language that make writing modern web applications easier than before. In fact, more features + are being added on a yearly basis, so you should think about it not just as ES6 but as modern + JavaScript. + + Browser support for these features is limited. It will be a while before all major browsers + properly support the new syntax and since new features are added regularly (and users don't + always update quickly), we must employ the use of a transpiler to convert newer syntax into + a format current browsers can understand. + + In Angular, [TypeScript](https://typescriptlang.org) is the most common transpiler tool, since + Angular itself is built with TypeScript. You can use TypeScript just as a compiler even if you + don't use the typing features it provides. Alternatively, [Babel](https://babeljs.io) is a popular + choice with a large community. There are other options, most of which can be found at this [GitHub repo] + (https://github.com/addyosmani/es6-tools#transpilers). + + Your choice of transpiler is one that you can change at a later date, so we'd recommend starting + with TypeScript and changing if you feel the need.