Table of Contents
- Notes
- Installing Node
- Visual Studio Code
- TypeScript Crash-course
- Angular
- Deep Dive
- RxJS
Notes
- The book assumes that you are working in a Unix-like environment. If you are on Windows you can use Cygwin so that you can follow along with the bash terminal commands.
- All the project files for the book are hosted on github: https://github.com/aminmeyghani/angular2-intro. You can clone the repository and check out the project files. Throughout the book, you will see references to the project files. Those refer to this repository. For example,
angular2-intro/project-files/hello-angular
refers to thehello-angular
folder inside theproject-files
folder. - Make sure you have
git
installed on your machine. That is, make sure you get an output forgit --version
. - The book assumes that you have a working knowledge of JavaScript and Angular 1.x
- Node is heavily used throughout the book. Make sure that you follow the “Node” chapter to install Node and set permissions correctly.
- All the keyboard shortcuts are mac-based. But if you are using a non-mac machine, you can almost always replace
command
withctrl
and you should be good. For example, if you a see a shortcut likecommand + shift + b
, you can usectrl + shift + b
wherectrl
is obviously thecontrol
key!
Installing Node
You can use nvm to install and manage Node on your machine. Copy the install script and run it:
After that, make a new terminal window and make sure that it is installed, by running:
Now you can use nvm
to install Node 0.12.9
by running:
After that, nvm is going to load version 0.12.9 automatically. If it doesn’t, you can load it in the current shell, with:
Note that you can load any node version in the current shell with nvm use 0.x.y
after installing that version.
Also note that if you want to make 0.12.9
the default Node version on your machine, you can do so by running the following:
Then you can verify that it is the default version by making a new terminal window and typing node -v
.
Permissions
Never use sudo
to install packages, never do sudo npm install <package>
. If you get permission errors while installing without sudo
, you can own the folders instead. So for example, if you get an error like:
you can own the folder with:
You can own folders until Node doesn’t complain.
Installing live-server
Install a package to verify that node is installed and everything is wired up correctly. We are going to use live-server
through the book. So let’s install that with:
Then, you should be able to run live-server
in any folder to serve the content of that folder:
Visual Studio Code
Visual Studio Code is a good IDE for developing web apps. In this chapter we will look at installing and configuring VSCode.
Visual Studio Code Basics
- Install Visual Studio Code from: https://code.visualstudio.com/
- You can open new projects by going to the
File > Open
tag, to etierh open a folder containing your project or a single file - Some useful keyboard shortcuts are:
-
command + b
: to close/open the file navigator -
command + shift + p
: to open the prompt
-
- To install extensions open the prompt with
command + shift + p
and type: - You can change the keyboard shortcuts settings from
Preferences > Keyboard Shortcuts
. Open the settings and then you can add your own shortcuts:
Setting up VSCode for TypeScript
In this section we are going to set up Visual Studio Code for TypeScript. The project files for this chapter are in angular2-intro/project-files/vscode
. You can either follow along or check out the folder to see the final result.
Installing TypeScript
Before anything, we need to install the TypeScript compiler. You can install the TypeScript compiler with npm:
Then to verify that it is installed, run tsc -v
to see the version of the compiler. You will get an output like this:
In addition to the compiler, we also need to install the TypeScript Definition manager for DefinitelyTyped (tsd). You can install tsd with:
Using TSD, you can search and install TypeScript definition files directly from the community driven DefinitelyTyped repository. To verify that tsd is installed, run tsd with the version
flag:
You should get an output like this:
After tsd
and tsc
are installed, we can compile a hello world program:
make a file called hello.ts
on your desktop:
Then, put some TypeScript code in the file:
Then you can compile the file to JavaScript:
It should output a file in Desktop/hello.js
:
Now that your TypeScript compiler setup, we can move on to configuring Visual Studio Code.
Add VSCode Configurations
- First download and install Visual Studio Code from the VSCode Website
- After installing VSCode, open it and then make a new window:
File > New Window
- Then, make a folder on your desktop for a new project:
mkdir ~/Desktop/vscode-demo
- After that, open the folder in VSCode:
File > open
and select thevscode-demo
folder on your desktop. - Now we need to make three configuration files:
-
tsconfig.json
: configuration for the TypeScript compiler -
tasks.json
: Task configuration for VSCode to watch and compile files -
launch.json
: Configuration for the debugger
-
The tsconfig.json
file should be in the root of the project. Let’s make the file and put the following in it:
Now to make the tasks.json
file. Open the prompt with command + shift + p
and type:
Then put the following in the file and save the file:
The last thing that we need to set up is the debugger, i.e. the launch.json
file. Right click on the .vscode
folder in the file navigator and make a new file called launch.json
and put in the following:
After you save the file, you should be able to see the debugger in the debugger dropdown options.
Now, we are ready to make the main.ts
file in the root of the project:
main.ts
Now you can start the task to watch the files and compile as you work. Open the prompt with command + shift + p
and type:
you can also use the command + shift + b
keyboard shortcut instead. This will start the debugger and watch the files. After making a change to main.ts
, you should be able to see the output in the output
folder.
After the build task is running, we can put a breakpoint anywhere in our TypeScript code. Let’s put a breakpoint on line 2 by clicking on the margin. Then start the debugger by going to the debugger tab and clicking the green play icon.
Now you should see that the program will stop at the breakpoint and you should be able to step over or into your program.
To stop the task you can terminate it. Open the prompt and type:
You can learn more about running TypeScript with VSCode on MSDN’s blog.
Running VSCode from the Terminal
If you want to run VSCode from the terminal, you can follow the guide on VSCode’s website. Below is the summary of the guide:
MAC
Add the following to your “bash” file:
Linux
Windows
You might need to log off after the installation for the change to the PATH environmental variable to take effect.
Debugging App from VSCode
The “vscode-chrome-debug” extension allows you to attach VSCode to a running instance of chrome. This makes it very convenient because you can put breakpoints in your TypeScript code and run the debugger to debug your app. Let’s get started.
In order to install the extension open the prompt in VSCode with command + shift + p
and type:
hit enter and then type:
Then just click on the result to install the extension. Restart VSCode when you are prompted.
After installing the extension, we need to update or create a launch.json
file for debugging. You can create one in the .vscode
folder. After you created the file, put in the following:
Notes:
- Depending on your platform you need to change the
runtimeExecutable
path to Chrome’s executable path. After configuring the debugger you need to have a server running serving the app. You can change theurl
value accordingly. Also make sure that thewebRoot
path is set to the root of your web server. - After that it is a good idea to close all the instances of chrome. Then, put a breakpoint in your code and run the debugger. If everything is set up correctly, you should see an instance of chrome running in incognito mode. To trigger the breakpoint, just reload the page and you should be able to see the debugger paused at the breakpoint.
- Also make sure that you have the compiler running so that you can use the JavaScript output and the sourcemaps to use the debugger. See the TypeScript and VSCode set up for more details.
TypeScript Crash-course
In this chapter we will quickly go through the most important concepts in TypeScript so that you can have a better understanding of Angular code that you will write. Knowing TypeScript definitely helps to understand Angular, but again it is not a requirement. The project files for this chapter are in angular2-intro/project-files/typescript
.
TypeScript Basics
- TypeScript is a superset of JavaScript with additional features, among which optional types is the most notable. This means that any valid JavaScript code (ES 2015/2016…) is valid TypeScript code. You can basically change the extension of the file to
.ts
and compile it with the the TypeScript compiler. - TypeScript defines 7 primary types:
- boolean:
var isDone: boolean = false;
- number:
var height: number = 6;
- string:
var name: string = "bob";
- array:
var list:number[] = [1, 2, 3];
alsovar list:Array<number> = [1, 2, 3];
- enum:
enum Color {Red, Green, Blue};
- any:
var notSure: any = 4;
- void:
function hello(): void { console.log('hello'); }
- boolean:
Interface
- An Interface is defined using the
interface
keyword - Interfaces are used only during compilation time to check types
- By convention, interface definitions start with an
I
, e.g. :IPoint
- Interfaces are used in classical object oriented programming as a design tool
- Interfaces don’t contain implementations
- They provide definitions only
- When an object implements an interface, it must adhere to the contract defined by the interface
- An interface defines what properties and methods an object must implement
- If an object implements an interface, it must adhere to the contract. If it doesn’t the compiler will let us know.
- Interfaces also define custom types
Basic Interface
Below is an example of an Interface that defines two properties and three methods that implementers should provide implementations for:
Using the interface above we can create an object that adheres to the interface:
Notice that we had to provide values to all the properties defined by the Interface, and the implementations for all the methods defined by the Interface.
And then of course you can use your object methods to perform operations:
Classes as Interfaces
Because classes define types as well, they can also be used as interfaces. If you have an interface you can extend it with a class for example:
First we are defining a class called Point
that defines two fields. Then we define an interface called Point3d
that extends the Point
by adding a third field.
An then we create a point of type point3d
and assign a value to it. We read the value and it outputs 1
.
Classes
- Classes are heavily used in classical object oriented programming
- It defines what an object is and what it can do
- A class is defined using the
class
keyword followed by a name - By convention, the name of the class start with an uppercase letter
- A class can be used to create multiple objects (instances) of the same class
- An object is created from a class using the
new
keyword - A class can have a
constructor
which is called when an object is made from the class - Properties of a class are called instance variables and its functions are called the class methods
- Access modifiers can be used to make them public or private
- The instance variables are attached to the instance itself but not the prototype
- Methods however are attached to the prototype object as opposed to the instance itself
- Classes can inherit functionality from other classes, but you should favor composition over inheritance or make sure you know when to use it
- Classes can implement interfaces
Let’s make a class definition for a car and incrementally add more things to it. The project files for this section are in angular2-intro/project-files/typescript/classes/basic-class
.
Adding an Instance Variable
The Car
class definition can be very simple and can define only a single instance variable that all cars can have:
-
Car
is the name of the class, which also defines the custom typeCar
-
distance
is a property that tracks the distance that car has traveled - Distance is of type
number
and only acceptsnumber
type.
Now that we have the definition for a car, we can create a car from the definition:
-
myCar:Car
means thatmyCar
is of typeCar
-
new Car()
creates an instance from theCar
definition. -
myCar.distance = 0
sets the initial value of thedistance
to 0 for the newly createdcar
Adding a Method
So far our car doesn’t have any definitions for any actions. Let’s define a move
method that all the cars can have:
-
move():void
means thatmove
is a method that does not return any value, hencevoid
. - The body of the method is defined in
{ }
-
this
refers to the instance, thereforethis.distance
points to thedistance
property defined on the car instance. - Now you can call the
move
method on the car instance to increment thedistance
value by 1:
Using Access Modifiers
If you wanted to tell the compiler that the distance
variable is private and can only be used by the object itself, you can use the private
modifier before the name of the property:
- There are 3 main access modifiers in TypeScript:
private
,public
, andprotected
: -
private
modifier means that the property or the method is only defined inside the class only. -
protected
modifier means that the property or the method is only accessible inside the class and the classes derived from the class. -
public
is the default modifier which means the property or the method is the accessible everywhere and can be accessed by anyone.
Adding a constructor
A constructor
is a special method that gets called when an instance is created from a class. A class may contain at most one constructor declaration. If a class contains no constructor declaration, an automatic constructor is provided.
Let’s add a constructor to the Car
class that initializes the distance
value to 0. This means that all the cars that are crated from this class, will have their distance
set to 0 automatically:
-
constructor()
is called automatically when a new car is created - Parameters are passed to the constructor in the
()
- The body of the constructor is defined in the
{ }
Now, let’s customize the car’s constructor to accept distance
as a parameter:
- On line 3 we are passing distance as a parameter. This means that when a new instance is created, a value should be passed in to set the distance of the car.
- On line 4 we are assigning the value of distance to the value that is passed in
This pattern is so common that TypeScript has a shorthand for it:
Note that the only thing that we had to do was to add private distance
in the constructor parameter and remove the this.distance
and distance: number
. TypeScript will automatically generate that. Below is the JavaScript outputed by TypeScript:
Now that our car expects a distance
we have to always supply a value for the distance when creating a car. You can define default values if you want so that the car is instantiated with a default value for the distance if none is given:
Now if I forget to pass a value for the distance
, it is going to be set to zero by default:
Note that if you pass a value, it will override the default value:
Setters and Getters (Accessors)
It is a very common pattern to have setters and getters for properties of a class. TypeScript provides a very simple syntax to achieve that. Let’s take our example above and add a setter and getter for the distance property. But before that we are going to rename distance
to _distance
to make it explicit that it is private. It is not required but it is a common pattern to prefix private properties with an underscore.
In order to create the getter method, we are going to use the get
keyword and the name for the property followed by ()
:
Now we can get the value of distance
:
Note on line 2 that we didn’t call a function. Behind the scenes, TypeScript creates a property for us, that’s why it is not a method. Below is the relevant generated JavaScript:
JavaScript behind the scenes calls the get function for you to get the value, and that’s why we simply did car2.distance
as opposed to car2.distance()
. For more information about Object.defineProperty
checkout the MDN docs.
Similar to the getter, we can define a setter as well:
Now we can both get and set the distance value:
Note that if we take out the setter, we won’t be able to assign a new value to distance
.
Static Methods and Properties
Static methods and properties belong to the class but not the instances. For example, the Array.isArray
method is only accessible through the Array
but not an instance of an array:
- On line 2 we are trying to access the
isArray
method, but obviously it is not defined becauseisArray
is a static method. - On line three we are calling the static
isArray
method fromArray
and we can check ifx
is an array.
If you look at the Array documentation you can see that methods and properties are either defined on the Array.prototype
or Array
:
-
Array.prototype.x
: makesx
available to all the instances ofArray
-
Array.x
:x
is static and only available throughArray
.
Now that we have some context, let’s see how you can define static methods and properties in TypeScript. Consider the code below:
- On line 2 we are defining a static property called
controls
using thestatic
modifier. Then we specify the form and then assign a value for it. - On line 5 we are defining a static method called
isAuto
using the thestatic
modifier. This method simply returns the value ofisAuto
from the staticcontrol
object. Not that we get access to the class using the name of the class as opposed to usingthis
. i.e.return Car.controls.isAuto
Implementing an Interface
Classes can implement one or multiple interfaces. We can make the Car
class implement two interfaces:
Making the Car
class implement the interfaces:
The above example is silly, but it shows the point that a class can implement one or more interfaces. Now if the class does not provide implementations for any of the interfaces, the compiler will complain. For example, if we leave out the distance
instance variable, the compiler will print out the following error:
error TS2420: Class ‘Car’ incorrectly implements interface ‘ICarProps’. Property ‘distance’ is missing in type ‘Car’.
Inheritance
In Object-oriented programming, a class can inherit from another class which helps to define shared attributes and methods among objects. Although this pattern is very useful, it should be used cautiously as it can lead to code that is hard to maintain. You can learn more about classical inheritance and prototypical inheritance by watching Eric Elliot’s talk at O’Reilly’s Fluent Conference. The project files for this section are in angular2-intro/project-files/typescript/classes/inheritance
.
Let’s get started by creating a base class called Vehicle
. This class is going to be the base class for other classes that we create later.
There is nothing special in this class. We are just creating a class that has two private properties (name, distance) and we are creating the setters and getters for them. Additionally, we are defining the toString
method that JavaScript internally calls in “textual contexts”. The constructor is the most notable of all the other methods:
- It sets the
name
property to “Vehicle” for all the instances - It also sets the
distance
property to 0.
This means that when a class extends the Vehicle
class, it will have to call the constructor of Vehicle
using the super
keyword. Let’s do that now by creating two classes called Car
and Truck
that inherit from the Vehicle
class:
cars.ts
- The
Car
class and theTruck
class both look almost identical. They both inherit from theVehicle
using theextends
keyword. - They both call the
Vehicle
’s constructor in their own constructor method before implementing their own:constructor(name?: string) { super(); }
- They both take an optional
name
property to set the name of the vehicle. If not name is provided, it will be set to either ‘Car’ or ‘Truck’
Now let’s create the main
file and run the file:
- On line 1 we are importing the
Car
and theTruck
class. - and then we create a
Car
andTruck
instance and log their names and distance to the console.
Run the build task (command + shift + b) and run the file (F5) and you should see the output:
You can play around with the code above an try passing a string when instantiating a Car
or a Truck
to see the name change.
TODO
constructor overloading
Class Decorators
There are different types of decorators in TypeScript. In this section we are going to focus on Class Decorators.
TODO
add content
Modules
- In TypeScript you can use modules to organize your code, avoid polluting the global space, and expose functionalities for others to use.
- Multiple modules can be defined in the same file. However, it makes more sense to keep on module per file
- If you want, you can split a single module across multiple files
- If you decide to split a module across different files, this is how you would do it:
- Create the module file:
mymodule.ts
and declare your module there:module MyModule {}
- Create another file:
mymodule.ext1.ts
and on top of the file add:/// <reference path="mymodule.ts" />
. Then in the file, you can use the same name of the module and add more stuff to it:module MyModule { // other stuff... }
- Then in your main file, you need two things on top of the file:
/// <reference path="mymodule.ts" />
/// <reference path="mymodule.ext1.ts" />
- Then, you can use the name of your module to refer to the symbols defined:
MyModule.something
,MyModule.somethingElse
- Create the module file:
- TypeScript has two system: one used internally and the other used externally
- External modules are used if your app uses CommonJS or AMD modules. Otherwise, you can use TypeScript’s internal module system
- Using TypeScript’s internal module system, you can:
- use the
module
keyword to define a module:module MyModule { ... }
- split modules into different files that contribute to a single module
- use the
/// <reference path="File.ts" />
tag to tell the compiler how files are related to each other when modules are split across files
- use the
- Using TypeScript’s external module system:
- you cannot use the
module
keyword. Themodule
keyword is used only by the internal module system. - instead of the
reference
tag, you can use theimport
keyword to define the relationship between modules - you can import symbols using the file name:
import mymodule = require('mymodule')
- you cannot use the
The project files for this chapter are in angular2-intro/project-files/typescript/modules
.
Simple Module
Let’s create a simple module that contains two classes. The first class is a vehicle class and the second is a car class that inherits from the vehicle class. Then we are going to expose the car class to the outside world and import it from another file. The project files for this section are in angular2-intro/project-files/typescript/modules/basic-module
.
First, create the main.ts
file and copy paste the following:
main.ts
- On line 1 we are defining the module called
MyModule
. - Inside this module we have defined a class called
Vehicle
that has a distance property and a setter and getter.
Now we want to create a class and export it so that it can be imported by others:
main.ts
- On line 9 we are using the
export
keyword to indicate that theCar
class is exposed and can be used by others.
Now, let’s create a car using the Car
class defined in the MyModule
module:
Note that we accessed the Car
class using the MyModule
symbol: MyModule.Car
. Now we can split up the module into its own file and import it into the main file. Let’s create a file called MyModule.ts
and move the module definition to that file. Now in our main file we are just going to import the module and use the car class from it.
main.ts
Note that we can create an alias to the MyModule
using import AliasName = MyModule
. Now you can reference the module name with AliasName
:
Now if we run this in debug mode, the compiler will complain that it can’t find the MyModule
reference. Because of that we need to make some changes to our config files. First, we are going to add the out
property in the tsconfig.json
file. This will tell the compiler to compile all the files into a single file:
So our tsconfig.json
file will look like this:
Now if you run the build, you should see that all the project has been compiled into output/run.js
. In addition to the tsconfig.json
file, we are going to update the launch.json
file and add a new configuration field:
Now we should be able to use the debugger and put breakpoints in our TypeScript files. Select TS All Debugger
from the debugger dropdown and run the debugger and it should stop if you put a breakpoint in any of your TypeScript files.
NOTE Using the configuration files above we can compile all the TypeScript files into a single JavaScript file. But sometimes that is not what you want. Be aware that using the above configuration you will not get an output for each TypeScript file.
Splitting Internal Modules
Internal modules in TypeScript are open ended. This means that you can define a module with the same name in different files and keep adding to it. This is also known as merging. In this section we are going to demonstrate merging multiple files that contribute to a single module called Merged
. The project files for this section are in angular2-intro/project-files/typescript/modules/merged-module
.
First, we are going to make two files: A.ts
and B.ts
. In each file we are going to define the Merged
module:
and then the B.ts
file:
We just created two files called A.ts
and B.ts
and each file we defined the Merged
module and added a class to each and exported it. Now we are going to make the main.ts
file and reference these two files:
And now we can use the classes defined in the Merged
module, that is the Car
and the Door
class:
if you run the build task (command + shift + b) and hit F5 you should see the following output:
External Modules
In addition to TypeScript’s internal module system, you can use external modules as well. In this section we are going to demonstrate how you can use external modules in TypeScript. The project files for this section are in angular2-intro/project-files/typescript/modules/external-module
.
Let’s say I have a JavaScript Node module defined in CommonJS format in a file called common.js
:
In order to import this we need to do two things: first, we need to install Node’s Type Definitions. Then we need to require the module. To install Node’s Type Definitions run the following the terminal in the root of your project:
Now you should see a folder called typings
containing the type definitions. Now that we have Node’s type definitions, let’s add a reference to it on top of main.ts
:
and then we are going to require the module and log it to the console:
After running the build task ( command + shift + b ), and running the file (F+5) you should see the following output:
Note the configuration files that we are using:
tsconfig.json
launch.json
Decorators
- Decorators can be used to add additional properties and methods to existing objects.
- Decorators are a declarative way to add metadata to code.
- There are four decorators: ClassDecorator, PropertyDecorator, MethodDecorator, ParameterDecorator
- TypeScript supports decorators and does not know about Angular’s specific annotations.
- Angular provides annotations that are made with decorators behind the scenes
Method Decorators
Goals:
- make a method decorator called log
.
- Decorate someMethod
in a class using @log
In the usage, someMethod
has been decorated with log
using the @
symbol. @log
is decorating someMethod
because it is placed right before the method.
- Decorator Implementation:
A method decorators takes a 3 arguments:
-
target
: the method being decorated. -
key
: the name of the method being decorated. -
value
: a property descriptor of the given property if it exists on the object, undefined otherwise. The property descriptor is obtained by invoking theObject.getOwnPropertyDescriptor
function.
TODO
- Add decorator content for each type.
Angular
This chapter will walk you through the main concepts in Angular. We will start by looking at components, and then we move onto pipes, services, events and other concepts. By the end of the chapter you should have a basic understanding of the most important concepts in Angular.
The goal of this chapter is to get your feet wet without scaring you with a lot of details. Don’t worry, there will be a lot coming in later chapters.
Project Files
Running the Project Files
First, make sure that you have cloned the code repo somewhere on your machine:
In order to run the project files, you need to do two things:
- First, install the server dependencies and run the server in the root of the repo:
After the dependencies are installed, it will open up the browser at port 8080.
- The next step is to install the dependencies for angular examples. Go to
project-files/angular-examples
and install the dependencies:
After following the steps above, you should be able to see the examples in the browser. For example, if you want to see the basic-component
example, you can go to the following url:
Starter Project
There is a starter project in angular-examples/starter
. You can make a copy of that folder if you want to work on something new. The steps for running the project is the same for all the projects:
- Install the dependencies for the dev server in the root of the repo with
npm i
(needed once) - Start the dev server in the root of the repo with
npm start
- Install the dependencies for angular examples:
cd project-files/angluar-examples && npm i
(needed once) - Open your project in VSCode:
code project-files/angular-examples/starter
- Close all chrome instances (quit out of Chrome)
- In VSCode start the build with
command + shift + b
and run the app by hitting F5 on the keyboard
- If you don’t want to use VSCode, you can use any other editor that you want. But make sure that you run the TypeScript compiler in the project folder:
cd project-files/angular-examples/starter && tsc -w
.
Using the Docs
Angular API reference can be found at: https://angular.io/docs/ts/latest/api.
If you are looking for annotations or decorators, look for the keyword followed by metdata
. For example, if you want to look up the Component decorator, you would look for: ComponentMetadata
. Below are the common metadata class names:
ComponentMetadata
DirectiveMetadata
PipeMetadata
InjectMetadata
InjectableMetadata
TODO
Common Interfaces
OnInit
TODO
Common Enums
ChangeDetectionStrategy
TODO
Metadata Classes Cheatsheet
- Angular uses Metadata to decorate classes, methods and properties.
- The most notable Metadata is the
@component
Metadata. - Metadta classes are very convenient and they make it easy to work with components, services and the dependency injection system
Below is a list of Angular’s core Metadata classes categorized under directives/components, pipes and di.
Directive/component Meta-data
-
Component: used to define a component
- View: used to define the template for a component
- ViewChild: used to configure a view query
- ViewChildren: used to configure a view query
-
Directive: used to define a directive
- Attribute used to grab the value of an attribute on an element hosting a directive
- ContentChild: used to configure a content query
- ContentChildren: used to configure a content query
- Input: used to define the input to a directive/component
- Output: used to define the output events of a directive/component
- HostBinding: used to declare a host property binding
- HostListener: used to declare a host listener
Pipes
- Pipe: used to declare reusable pipe function
DI
- Inject: parameter metadata that specifies a dependency.
- Injectable: a marker metadata that marks a class as available to Injector for creation.
- Host: Specifies that an injector should retrieve a dependency from any injector until reaching the closest host.
- Optional: parameter metadata that marks a dependency as optional
- Self: Specifies that an Injector should retrieve a dependency only from itself.
- SkipSelf: Specifies that the dependency resolution should start from the parent injector.
- Query: Declares an injectable parameter to be a live list of directives or variable bindings from the content children of a directive.
-
ViewQuery: Similar to
QueryMetadata
, but querying the component view, instead of the content children.
TODO
Component Basics
- Technically speaking components are directives that extend directives with views
- A component encapsulates a specific piece of functionality and components work together to deliver app’s functionality
- Generally speaking, every app has a root component that bootstraps the application. And when the app is bootstraped, Angular starts from the root component and resolves the sub trees of components
In this section we are going to write a simple HelloAngular
component, compile it and run it in the browser. In addition, we will configure VSCode to build the TypeScript files as we go.
Note that there is a lot to talk about components. We are going dive into components a lot more in later chapters, but for now let’s just keep things simple.
The project files for this chapter are in angular2-intro/project-files/angular-examples/basic-component
You can either follow along or just look at the final result
In order to run the project files, please refer to the Running the Project Files section.
Getting Started
Make a folder on your desktop called hello-angular
and navigate to it:
Start npm in this folder with npm init
and accept all the defaults.
After that, add the dependencies
and devDependencies
field to your package.json
file:
your package.json
file should look something like the follwoing:
Then run npm i
to install the dependencies.
After all the dependencies are installed, start VSCode in this folder with code .
Then create a index.html
file in the root of the project and put in the following:
index.html
This loads all the necessary scripts that we need to run Angular in the browser.
Note
If you need to support older browsers, you need to include the es6-shims
before everything else:
Making a Simple Component
Let’s start by making the main.ts
file in the root of the project. In this file we are going to define the main component called HelloAngular
and then bootstrap the app with it:
main.ts
- On line 1 we are importing the
component
meta data (annotation) and theonInit
interface. - On line 2 we are loading the
bootstrap
method that bootstraps the app given a component. - On line 4, we are defining a component using the
component
decorator. The@component
is technically a class decorator because it precedes theHelloAngular
class definition. - On line 5, we are telling angular to look out for the
app
tag. So when Angular looks at the html and comes across the<app></app>
tag, it is going to load the template (on line 6) and instantiates the class for it (defined on line 9). - On line 9, we are defining a class called
HelloAngular
that defines the logic of the component. And for fun, we are implementing theOnInit
interface to log something to the console when the component is ready with its data. We will learn more about the lifeCycle hooks later. - Last but not least, we call the
bootstrap
method with theHelloAngular
class as the first argument to bootstrap the app with theHelloAngular
component.
Compiling the Component
Now we need to compile the file to JavaScript. We can do it from the terminal, but let’s stick to VSCode. In order to that, we need to make two config files:
- First is the standard
tsconfig.json
file - And the
tasks.json
file for VSCode to do the compiling
Create the tsconfig.json
file in the root of the project and put in the following:
tsconfig.json
Then create the tasks.json
in the .vscode
folder in the root of the project and put in the following:
.vscode/tasks.json
- Now we can build the TypeScript files as we work. We just need to start the build task with
command + shift + b
or using the prompt. If you want to use the prompt do the following:- Use
command + shift + p
to open the prompt - Then, type
> run build task
and hit enter to start the build task.
- Use
- After you run the build task, you should see an
output
file generated withmain.js
and the source maps in it. - The task is watching the files and compiling as you go. To stop the task, open the prompt and type:
Loading the Component
After compiling the component, we need to load it to the index.html
file with Systemjs
. Open the index.html
file and replace <!-- add systemjs settings later -->
with the following:
Now we can use our component in the body of the html:
It is finally time to serve the app. You can serve the app in the current directory using the live-server
:
If everything is wired up correctly, you should be able to see the following:
Debugging the component
You can connect chrome’s debugger to VSCode using the chrome debugger extension for Visual Studio Code. See the Debugging App from VSCode section in case you missed to install it. But, assuming that you have the extension installed, you can debug your app from VSCode. In order to do that, we need to create a launch.json
file in the .vscode
folder:
After you created the file, put in the following configuration in the file:
Before running the debugger:
- Make sure that all instances of chrome are closed. It makes it easier to run the debugger from VSCode itself.
- Make sure that the
runtimeExecutable
path is valid. This value would be different depending on your OS. - Make sure that the
url
value is valid as well. Theurl
value has to match the path that you see when you run a server serving the files. - Set a breakpoint on a line in
main.ts
file and then run the debugger under the debugger tab.
In order to run the debugger, select Launch Chrome Debugger
in the dropdown under the debugger tab and either click on the play icon or hit F5 on the keyboard. After that, an instance of Chrome should be opened in incognito mode. In order to trigger the debugger just refresh the page and you should be able to see the debugger pausing in VSCode. If everything is set up correctly you should be able to see something like the following screenshot:
Component Inputs
- You can pass data to a component.
- You can either use the
inputs
array on a component or annotate an instance variable with theInput
decorator - Once you specify the inputs to your component, they become available in the
ngOnInit
method - You can implement the
ngOnInit
and access the input instance variables - You can use the
[propname]="data"
to set thepropname
to whateverdata
evaluates to - Note that if you set
[propname]="'data'"
,propname
will be set to the literaldata
string
Project files
The project files for this section are in angular2-intro/project-files/angular-examples/component-input.
Getting Started
In order to demonstrate component inputs, we are going to create a user
component and pass name
, lastName
, and userId
to it. So our final html tag would look something like the following:
And the template for the component will be:
which would output: Hello, Tom Johnson id: 1
.
To get started, let’s define the User
component:
- On line 4 we are defining the inputs as an array of strings
Then, we are going to use the User
component inside our app’s template:
because we are using the User
component in the app, we need to register it with the app by adding User
class to the list of directives
of the app component:
and at the end we need to bootstrap the app:
Now, notice that instead of adding the inputs to the inputs
array, we could have decorated the instance variables with the @Input
decorator:
Binding Data to Properties
Now, let’s see how we can bind to a property from another component. For this example, we are going to continue with our User
component and create a new component called Permission
. Then we are going to use the the Permission
component inside the User
component and set the uid
of Permission
by the userId
of the User
.
The Permission
component is defined as follows:
- On line 6 we are defining
uid
to be an input instance variable. It’s value is set from outside. - In the constructor we are setting a default value for the restriction.
- Then in the
ngOnInit
hook, we are evaluating the value ofrestriction
based on the given id provided by other components, in this case theUser
component - In this silly example, if the passed id is
1
, we will set therestriction
toadmin
, otherwise we set it tonormal
.
then we are going to register the Permission
component with the User
component so that we can use it in the User
template:
then we can update the User
template to include the Permission
:
- Note that on line 6 we are setting the
uid
ofPermission
byuserId
available from theUser
component.
If you run the app you should see the following printed to the page:
Binding to DOM Properties
In addition to custom properties, you can bind to DOM properties. Below are some examples:
Binding to style
We can bind to the style property of DOM nodes. In the example below, if the value of isDone
is true, we set the style.textDecoration
to line-through
, otherwise we won’t set it to anything
Binding to class
In addition to the style
property, we can also bind to the class
property. In the example below, we are setting the class name to “collapsed” if the isCollapsed
value is true and expanded
if the the value is false:
Binding to ‘hidden’
You can bind to the hidden property of a DOM node and show or hide the element. In the example below, we are hiding the element if the isVisible
value is true:
Binding to textContent
We can bind to the textContent
property and set the text content of a node. In the example below, we are setting the text content by reading the value of an input:
Component Output/Events
- Events can be emitted from components. These events can be either custom or they could be DOM events
- The syntax is
(eventname)="fn()"
whereeventname
is the name of the event andfn
is the handler function - The handler function is called when the event is fired
- For example, if you want to handle a click event you can do:
(click)="handler()"
. In this case thehander
is called whenever the click event is fired off - You can use Angular’s
EventEmitter
to fire off custom events
Custom Output Events
Project Files
The project files for this section are in angular2-intro/project-files/angular-examples/component-output-events.
Final Result
The goal of this section is to show you how to create a component that contains a button that when is clicked, calls a handler defined by the component’s class. The final html will look like the following:
That idea is very simple: every time we click on the button we want to increment the value by one. In addition to that, we want to be able to hook into a custom event and run the addOne
method whenever the event is fired:
Getting Started
Let’s get started by defining our Adder
component:
Now, we are just going to register Adder
with our root component:
after you bootstrap the app and run it you should be able to see a button that when clicked increments the value by one.
Using EventEmitter
Now, let’s see how we can use the EventEmitter
to increment the value by one every time a custom event is fired every second. In order to achieve that, we are going to create an attribute directive called AdderAuto
. Start by importing the Directive
metadata class:
and then define the selector for the directive:
-
selector: '[adder-auto]'
means that angular will target any element that has theadder-auto
attribute and will create an instance of the class. Now we need to define the class for our directive:
In this class we need to define a custom event output hook. We are going to call it myevent
. The same way that you can hook into (click)
, we want to be able to use (myevent)
. To achieve that, we need to create an instance variable and decorate it with the Output
decorator:
- If you notice,
myevent
is of typeEventEmitter
that emit events of type string - In the constructor we are creating an instance of
EventEmitter
. So now we can usemyevent
to emit events - We can use
setInterval
to emit event from our custom event every second
Now we can register AdderAuto
with the Adder
component and run the addOne
method every second:
and then we can update the template:
- first we are adding the attribute directive
adder-auto
on the span - second, we are using the
myevent
hook and attachingaddOne
handler to it. This means that whenever themyevent
event is triggered, run theaddOne
handler.
The Adder
component now looks like the following with the updated template:
Now if you run the code, you should be able to see the number incrementing by one every second.
Binding to DOM Events
In addition to custom output events, we can bind to almost all of the native DOM events. Below you can find some common events with examples:
Inputs
Mouse
Event Delegation/Bubbling
When an event is raised by an element, by default it bubbles up the html hierarchy. For example, if you have a table and attached a click handler to the table itself, you can catch the row that was clicked by only attaching a single handler. This method is useful when you are dealing with situations where you don’t want to attach event handlers to every elements, and you just want to attach one. Below is an example of event delegation, detecting the row that was clicked on the table:
Notice how we have a single click
handler only on the table itself. The $event
is the bubbled event that we can catch to do interesting stuff with. In this case we are just reading the textContent
from the target: event.target.textContent
:
In the method above we are checking if a td
was clicked on. If so, we set the this.rowClicked
to the td’s value, otherwise we set it to an empty string.
ViewChildren
- The children elements located inside of its template of a component are called
- Metadata classes:
@ViewChildren
,@ViewChild
TODO
ContentChildren
- Elements used between the opening and closing tags of the host element of a given component
- Metadata classes:
@ContentChildren
,@ContentChild
TODO
ViewProviders
TODO
Providers
TODO
Directives
- Directives and components hand-in-hand are the fundamental building blocks of any Angular app
- Directives are components without templates. Conversely, components are directives without templates
- Directives allow you to attach behavior to elements in the DOM
- A directive is defined using the
@directive
decorator - There are three types of directives in Angular:
- Structural
- Attribute
- Components
- Evey directive metadata, has the following options:
- selector
- host
- …
- The
selector
attribute uses a css selector to find the element. However, parent-child relationship selectors are not supported - You can use the following possible selectors:
element
[attribute]
.classname
:not()
.some-class:not(div)
- The
host
option defines:- Property bindings
- Event handlers
- attributes
TODO(other decorator options)
Web Components Basics
Web Components are made up four specifications:
- Custom Elements: enabling custom html tags
- Shadow DOM: enabling isolation for custom elements
- HTML Templates: enabling to define html template fragments
- HTML Imports: enabling html fragment imports
Custom Elements
TODO
Shadow DOM
- Enables a node to express three subtrees:
- Light DOM: visible DOM notes inside the custom element tag/DOM supplied by the consumer
- Shadow DOM: private to the element and hidden from others and attached to the element’s shadow root
- Composed DOM: Rendered by the browser by distributing the light DOM into the Shadow DOM
- Logical DOM = Light DOM + Shadow DOM. The developer interacts with this layer
TODO
HTML Templates
TODO
HTML Imports
TODO
host
TODO
selector
TODO
Attribute Directives
- The Attribute directive changes the appearance or behavior of an element.
- Angular has several built-in attribute directives, namely
NgClass
andNgStyle
- It is a good idea to prefix your directives with a prefix. You cannot use the
ng
prefix since it’s already used by Angular. - When you apply the attribute directive to an element, the element will be knownn as the host.
- For example, if you had a directive called
my-directive
and applied it in<div class="hello"> <span my-directive> ... </span> </div>
, thespan
would be the host.
TODO (writing a custom attribute directive)
selector TODO: details
host TODO: details
Input TODO: details
Output TODO: details
ElementRef TODO: details**
Renderer TODO: details**
Structural Directives
- The Structural directive changes the DOM layout by adding and removing DOM elements
- Angular has several built-in structural directives, namely
NgIf
,NgSwitch
, andNgFor
- When working with structural directives, we should ask ourselves to think carefully about the consequences of adding and removing elements and of creating and destroying components
- Angular uses the html5
<template>
tag to add or remove DOM elements - By default, Angular replaces
<template>
with<script>
tag if no behavior is attached - The
*
before a directive name is a shorthand for including the directive content in the<template>
tag - Below you can see the built-in
NgIf
directive with and without the asterisks*
:
With *
Without *
Notice how the <p>
tag is wrapped with a <template>
and the condition is bound to the [ngIf]
property of the directive
TODO (writing a custom structural directive)
TemplateRef: TODO: details
ViewContainerRef: TODO: details
@Input() set myUnless(condition: boolean) {}
: TODO: details
Built-in Directives
Angular has a couple of useful built-in directives.
TODO(Note on directive names, docs and template usage)
NgClass
-
import {NgClass} from 'angular2/common';
,directives: [NgClass]
- Template Usage:
<div class="button" [ngClass]="{active: isActive, disabled: !isActive}"
Note that we are using ngClass
in the template, but not NgClass
NgIf
Usage
or in long-hand form:
Details
From the docs: “The ngIf directive does not hide the element. Using browser developer tools we can see that, when the condition is true, the top paragraph is in the DOM and the bottom disused paragraph is completely absent from the DOM! In its place are empty <script>
tags. We could hide the unwanted paragraph by setting its css display style to none. The element would remain in the DOM while invisible. Instead we removed it with ngIf.
The difference matters. When we hide an element, the component’s behavior continues. It remains attached to its DOM element. It continues to listen to events. Angular keeps checking for changes that could affect data bindings. Whatever the component was doing it keeps doing.
Although invisible, the component — and all of its descendant components — tie up resources that might be more useful elsewhere. The performance and memory burden can be substantial and the user may not benefit at all.
On the positive side, showing the element again is very quick. The component’s previous state is preserved and ready to display. The component doesn’t re-initialize — an operation that could be expensive.
ngIf is different. Setting ngIf to false does affect the component’s resource consumption. Angular removes the element from DOM, stops change detection for the associated component, detaches it from DOM events (the attachments that it made) and destroys the component. The component can be garbage-collected (we hope) and free up memory.
Components often have child components which themselves have children. All of them are destroyed when ngIf destroys the common ancestor. This cleanup effort is usually a good thing.
Of course it isn’t always a good thing. It might be a bad thing if we need that particular component again soon.
The component’s state might be expensive to re-construct. When ngIf becomes true again, Angular recreates the component and its subtree. Angular runs every component’s initialization logic again. That could be expensive … as when a component re-fetches data that had been in memory just moments ago.”
NgSwitch
Usage
TODO
NgFor
Usage
or in long-hand form:
TODO(Details)
Accessing Directives from Parents
TODO (access directives on parent elements)
Accessing Directives from Children
TODO (access directives on children and descendants)
Change Detection
- In Angular2 you can limit the change detection scope to components
- Using
chageDection
property we can choose a change detection strategy for a component - The
changeDetection
field accept one of the following values:-
ChangeDetectionStrategy.Default
: sets detector mode toCheckAlways
-
ChangeDetectionStrategy.OnPush
: sets detector mode toCheckOnce
. This will limit change detection to the bindings affecting the component only -
ChangeDetectionStrategy.Detached
: change detector sub tree is not a part of the main tree and should be skipped -
ChangeDetectionStrategy.CheckAlways
: after calling detectChanges the mode of the change detector will remainCheckAlways
-
ChangeDetectionStrategy.Checked
: change detector should be skipped until its mode changes toCheckOnce
-
ChangeDetectionStrategy.CheckOnce
: after calling detectChanges the mode of the change detector will becomeChecked
-
- Having the ability to specify change detection strategy can reduce the number of checks and improve app’s performance
Pipes
- Pipes allow you to transform values in templates before they are outputed to the view.
- Pipes were formerly known as filters in Angular 1.x
- A pipe is defined using the
@pipe
class decorator - The pipe decorator takes name as a parameter defining the name of the pipe:
@pipe({ name: 'myPipe' })
- Every pipe class has a
transform
method that transforms input to outputs:- The first parameter is the input to the pipe
- The second parameter is the list of arguments passed to the pipe
- Give the following pipe in a template:
{{ data | somePipe:1:'px'}}
:-
data
is the input to pipe – the first parameter of the transform method -
[1, 'px']
is the arguments to the pipe – the second parameter of the transform method
-
- A pipe can be as simple as:
- If you want to use a pipe, you need to register your pipe class with the components in the pipes array:
- Pipes can be chained:
input | pipe1 | pipe2 | pipe3
input | pipe1 : output1
output1 | pipe2: output2
output2 | pipe3 : finalOutput
Basic Pipe
Let’s make a basic pipe called pixel
that takes a value as the input and appends ‘px’ to the end of it. The project files for this section are in angular2-intro/project-files/angular-examples/pipes/basic-pipe.
Start by making a copy of the “starter” folder and call it “basic-pipe” and put it in project-files/angular-examples
. Then, open the folder in VSCode: code project-files/angular-examples/basic-pipe
and start the build with command + shift + b
.
Then, create a file for the pipe and call it pixel.pipe.ts
in the root of the project.
After that we need to do couple of things to define the pipe:
- Import the Pipe Class Metadata from angular core:
import {Pipe} from 'Angular/core'
- Then create a class defining the Pipe:
- Implement the
transform
method in the class: - After implementing the method, we need to decorate the class and give the pipe a name that we want to use in our templates:
- As the last step we are going to export the class by putting the
export
keyword behind the class:
Now, your file should look like the following:
Now, let’s go back to the main.ts
file and import our pipe:
After importing our pipe, we should register it with our component by adding it to the pipes
array:
Now that we have registered the pipe, we can use it in our template in templates/app.tpl.html
:
You should be all set now. You can set the url in your launch.json
file and hit F5:
If your server is running you should be able to see the following output:
Chaining Pipes
Let’s continue where we left off with the “pixelPipe” and add another pipe called “round” that rounds down given values, that is:
The project files for this section are in angular2-intro/project-files/angular-examples/pipes/pipe-chaining.
We are going to add the “roundPipe” to our “basic-pipe” project. Let’s get started by adding the round.pipe.ts
file in the root of the project:
This Pipe is not complicated at all. We are just returning the floor of the input. We are also converting the input to number by putting a +
before input.
Now, let’s import the pipe into our main.ts
file:
and then we have to add the pipe to the list of pipe array:
after that we are going to add the following to our templates/app.tpl.html
file:
After running the app you should see 34.px
as the output on the page.
Pipes with Parameters
In this section we are going to extend our ‘pixel’ pipe to accept an optional parameter to set the unit. As a result, we are going to rename the ‘pixel’ pipe to ‘unit’ to make it more generic. This pipe will take the unit as an optional argument. If no argument is passed, it will default to ‘px’. That is:
You can look at the project files in angular2-intro/project-files/angular-examples/pipes/pipe-unit.. AFter refactoring the name of the Pipe, we just need to change the implementation of the “UnitPipe”:
unit.pipe.ts
- On line 5, we are grabbing the first parameter that is passed in and setting it to the
unit
variable. And if the value is not set, we are setting ‘px’ as the default value. - And finally we are returning
input + unit
.
That’s basically all we have to do. Note that you can pass multiple parameters separated by :
and they all become available in the args
array. So if you wanted to expand this pipe, this is how your template would look like:
And the args
array would be: ['em', 2]
.
Async Pipes
Async Pipes can be used for values that will be resolved after some asynchronous operation like getting a value after making a http call.
TODO
Built-in Pipes
In this section we are going to look at the pipes that Angular provides out of the box.
- AsyncPipe: used to work with asynchronous values
- CurrencyPipe: used to format a number as a local currency
- DatePipe: used to format a date object to a readable string
- DecimalPipe: used to format numbers
-
JsonPipe: calls
JSON.stringify
on the input and useful for debugging - LowerCasePipe: used to convert a string to lowercase letters
- PercentPipe: used to format a number as percentage
- SlicePipe: used to create a subset of list or string
- UpperCasePipe: used to transform a text to upper case
Date
-
input
: a date object or a number (milliseconds since UTC epoch) -
optionalFormat
: a string used to format the output. It specifies which components of date and time to include in the output
Using predefined formats
Usage Example: {{ input | date:'short'}}
+————–+—————————+
| Name | Example |
+==============+===========================+
| short
| 9/3/2010, 12:05 PM |
+————–+—————————+
| shortDate
| 9/3/2010 |
+————–+—————————+
| medium
| Sep 3, 2010, 12:05:08 PM |
+————–+—————————+
| mediumDate
| Sep 3, 2010 |
+————–+—————————+
| longDate
| September 3, 2010 |
+————–+—————————+
| fullDate
| Friday, September 3, 2010 |
+————–+—————————+
| shortTime
| 12:05 PM |
+————–+—————————+
| mediumTime
| 12:05:08 PM |
+————–+—————————+
Using Custom Formats
- Generally speaking every date object has a year, month, day, hour, minute, and second.
- Using a custom string, you can specify which component you would like to include in the output.
+——+—————–+—–+————-+————+——–+——–+ | Year | Month | Day | Weekday | Hour | Minute | Second | +======+=================+=====+=============+============+========+========+ | y | M, MMM, MMMM | d | EEE, EEEE | j, h, H | m | s | +——+—————–+—–+————-+————+——–+——–+ | 2016 | 1, Jan, January | 1 | Sun, Sunday | 1, 1 AM, 1 | 1 | 1 | +——+—————–+—–+————-+————+——–+——–+
Note that every single letter identifier can be used twice to denote a double digit numeric value. For example, yy
will result in 15
for the year value. Below is a table just to be thorough:
Double Digit
+——+——-+—–+—————-+——–+——–+ | Year | Month | Day | Hour | Minute | Second | +======+=======+=====+================+========+========+ | yy | MM | dd | jj, hh, HH | mm | ss | +——+——-+—–+—————-+——–+——–+ | 16 | 01 | 01 | 01, 01 AM, 01 | 01 | 01 | +——+——-+—–+—————-+——–+——–+
Details for Month/Weekday/Hour are summarized in the tables below:
Month Details
+——-+———–+———–+ | M | MMM | MMMM | +=======+===========+===========+ | Digit | Abbr Name | Full Name | +——-+———–+———–+ | 1 | Jan | January | +——-+———–+———–+
Weekday Details
+———–+———–+ | EEE | EEEE | +===========+===========+ | Abbr Name | Full Name | +———–+———–+ | Sun | Sunday | +———–+———–+
Hour Details
+——-+————–+———-+ | j | h | H | +=======+==============+==========+ | Digit | Hour12 AM/PM | Military | +——-+————–+———-+ | 13 | 1 PM | 13 | +——-+————–+———-+
Slice
- The slice pipe is useful when you want a subset of a list or string. One of the common use cases are in when iterating over a list with
ngFor
for example.
TODO
Decimal
- Used for formatting numbers given a decimal formatter
TODO
Percent
TODO
Currency
TODO
Async
TODO
Json
TODO
LowerCase
TODO
UpperCase
TODO
Dependency Injection
Dependency Injection is a coding pattern in which a class receives its dependencies from external sources rather than creating them itself. In order to achieve Dependency Injection we need a Dependency InjectionFramework to handle the dependencies for us. Using a DI framework, you simply ask for a class from the injector instead of worrying about the dependencies inside the class itself.
Angular has a standalone module that handles Dependency Injection. This framework can also be used in non-Angular applications to handle Dependency Injection.
TODO
Services and Providers
- A service is nothing more than a class in Angular 2. It remains nothing more than a class until we register it with the Angular injector.
- When you bootstrap your app, Angular creates an injector on the fly that can inject services and other dependencies throughout the app.
- You can register the service or the dependencies during when bootstrapping the app or when defining a component.
- If you have a class called
MyService
, you can register it with the Injector and then you can inject it everywhere: - Providers is a way to specify what services are available inside the component in a hierarchical fashion.
- A provider can be a class, a value or a factory.
- Providers create the instances of the things that we ask the injector to inject.
-
[SomeService];
is short for[provide(SomeService, {useClass:SomeService})];
where the first param is the token, and the second is the definition object. - A simple object can be passed to the Injector to create a Value Provider:
- You can also use a factory as a provider.
- You can use a factory function that creates a properly configured Service:
- Defining object dependencies is simple. You can make a plain JavaScript object available for injection using a string-based token and the
@Inject
decorator:
Simple Service
In this section we are going to make a simple service and use it in our root component.
Project Files
The project files for this section are in angular2-intro/project-files/angular-examples/services/simple-service;
Getting Started
Let’s get started by creating a class, called StudentSvc
that represents our service:
There is nothing special about this class. It’s just a class the has a method to return the list of all students. Now, we are going to make it special by decorating it with the Injectable
decorator. But, first we need to import Injectable
from Angular:
After importing the Injectable
metadata class, we can decorate our class:
Now we have an injectable class and the injector would know how to create an instance of it when we need to inject it. And that’s what we are going to do next. We are going to add StudentSvc
in the list of viewProviders
of the root component:
The last thing we need to do is to inject the service in the constructor of our root component:
- In the constructor, we are defining a variable to be of type
StudentSvc
. By doing that the injector will create an instance from theStudentSvc
to be used - And on line 6 we are calling the
getAll
method from the service to get a list of all students.
Finally, we can verify that the getAll
method is actually called by printing the students in the template:
app.tpl.html
and it would output:
Data and State Management
- Angular is flexible and doesn’t prescribe a recipe for managing data in your apps
- Since observables are integrated into Angular, you can take advantage of observables to manage data and state
- You ca use services to manage streams that emit models
- Components can subscribe to the streams maintained by services and render accordingly.
- For example, you can have a service for a Todo app that contains a stream of todos and a
ListComponent
can listen for todos and render when a new task is added. - You may have another component that listens for the user that has been assigned to a task provided by a service.
- For example, you can have a service for a Todo app that contains a stream of todos and a
- The steps for creating different parts of an app can be summarized in three steps:
- Defining a Model using a class
- Defining the service
- Defining the component
Observables
- Observables can help manage async data
- Observables are similar to Promises but with a lot of differences
- Observables emit multiple values over time as opposed to one
- Angular embraces observables using the RxJS library.
- Observables emit events and observers observe observables.
- An observer subscribes to events emitted from an observable.
- RxJS has an object called subject that can be used both as an observer or an observable. Subject can be imported from
RxJS
very easily: - A subscription can be canceled by calling the
unsubscribe
method.
TODO
State Management with Observables
- There are several ways to manage state, one of them is using observables
- Observables can be used to represent the state of the app
- Changes in the state are represented as an observable
TODO
Http
- Using the
Http
class, you can interact with API endpoints - Http is available as an injectable class
-
Http
has a request method that returns an Observable which will emit a single Response when a response is received. - You can inject
http
in the constructor of a class:constructor(http: Http) {...}
Getting Data from Server
In this section we are going to use the http
class to get a list of students from a server by hitting /api/students
Project Files
The project files for this section are in angular2-intro/project-files/angular-examples/http/get-students
Getting Started
Before anything, let’s add the http.js
file from Angular’s bundle. In your index.html
file add the following to the head tag:
After that, we are going to make a service that handles getting data from the endpoint. We are going to call this StudentSvc
:
- On line 1, we are using the
Injectable
decorator to make our class injectable - In the constructor we are injecting the
Http
service and making a reference to it in a private variablehttp
- The
getStudents
method makes aGET
call to our local endpoint an returns anObservable
Now that we have the StudentSvc
service, we can create a component and inject the StudentSvc
to it:
In addition to the StudentSvc
, we also need to add HTTP_PROVIDERS
in the providers array:
After adding the providers, we can define the component class:
If you notice, we are injecting the StudentSvc
in the constructor and we are calling the getStudents
method in the constructor. The getStudents
returns an observable that we can subscribe to get the data out as they arrive. We also call the json
method on each response to get the JSON data.
After getting the data, we can print the result in the view:
app.tpl.html
Here we are using the built-in ngFor
directive to loop through the array of students and print their name and last name to the page.
Working with Forms
Angular has convenient methods for working with forms, including validation.
TODO
Angular Router
Angular has a stand-alone module responsible for handling routing.
TODO
Unit Testing
Unit testing with Angular requires some set up. First, let’s gather all the libraries and modules that we need.
TODO
Deep Dive
Let’s deep dive into Angular and RxJS concepts
Components in Depth
- A component declares a reusable building block of an app
- A TypeScript class is used to define a component coupled with the
@component
decorator
The @component
decorator defines the following:
- selector:
string
value defining the css selector targeting an html element - inputs:
array of string
values defining the inputs to the component - outputs:
array of string
values defining the output of the component - properties:
array of string
values defining the properties - events:
array of string
values defining the events - host?: {[‘string’]: ‘string’},
- providers:
array of objects
: defines the set of injectable objects that are visible to a directive/component and its light DOM children. - exportAs:
string
value defining the exported value - moduleId:
string
value defining the module id - viewProviders?:
array of objects
: defines the set of injectable objects that are visible to a directive/component view DOM children. - queries: {[key: string]: any},
- changeDetection:
ChangeDetectionStrategy
object defining the strategy for detecting changes:-
ChangeDetectionStrategy.Default
: sets detector mode toCheckAlways
-
ChangeDetectionStrategy.OnPush
: sets detector mode toCheckOnce
-
ChangeDetectionStrategy.Detached
: change detector sub tree is not a part of the main tree and should be skipped -
ChangeDetectionStrategy.CheckAlways
: after calling detectChanges the mode of the change detector will remainCheckAlways
-
ChangeDetectionStrategy.Checked
: change detector should be skipped until its mode changes toCheckOnce
-
ChangeDetectionStrategy.CheckOnce
: after calling detectChanges the mode of the change detector will becomeChecked
-
- templateUrl:
string
value for the url path to the template - template:
string
value for the template - styleUrls:
array of string
values defining url paths to css files - styles:
array of string
values defining css styles:- styles: [‘.myclass { color: #000;}’],
- directives:
array
of directives used in the component - pipes:
array
of pipes used in the component - encapsulation:
ViewEncapsulation
value that defines template and style encapsulation options:-
ViewEncapsulation.None
: means do not provide any style encapsulation -
ViewEncapsulation.Emulated
: No Shadow DOM but style encapsulation emulation using extra attributes on the DOM (default method) -
ViewEncapsulation.Native
: means provide native shadow DOM encapsulation and styles appear in component’s template inside the shadow root.
-
TODO
RxJS
- RxJS is a library for reactive programming
- Reactive programming is a natural way of thinking about asynchronous code which concerns itself with operations on event streams
- RxJS is used in this paradigm to compose asynchronous data/event streams
- Using the reactive paradigm, you can perform operations on streams using a consistent interface regardless of the data source
- Streams of events/data are known as Observables, i.e. a data/event stream = observable
- A program can be just composed of different data streams (observables)
TODO