Skip to content

Instantly share code, notes, and snippets.

@kotojo kotojo/ngx-tut.md
Last active May 10, 2017

Embed
What would you like to do?
An intro to angular

Angular Intro

This is a short into to the Angular ecosystem and how it works. The goal is to get you to the point of a running application that uses most of the pieces needed to build a fully functional application.

We are going to be using the angular-cli to scaffold out the structure of our applciation, before we get started we are going to need to make sure we have a few tools installed.

Setup

The first thing we are going to need is node.js. If you don't have it I recommend getting it directly from nodejs.org. If you do have it, make sure you are using at least the most up to date long term support version (6.10 as of 05/09/17). If not, you can either update directly, or use nvm for mac/linux or nvm-windows (a seperate project) for windows if you need to keep an older version of node for work.

Next we are going to want to make sure npm (node's package manager) is at least 3.0. You can check the current version with npm -v. If you need to update on mac/linux you can run npm install -g npm@latest from command line, and for windows it is recommended to use npm-windows-upgrade.

Now we have our node environment lets get to the fun stuff! Run this from the command line.

npm install -g @angular/cli

This is going to take a bit of time to run, so go grab a coffee or something. If you end up getting an error during install about CERT_UNTRUSTED, and you're on the travelers network run this line and try again.

npm config set strict-ssl false

This is a hack. Npm doesn't trust the certificates that Travelers network is creating, so we are just bypassing that mechanism. If you want to do it the proper way I'll leave it to you to generate travelers certificate authority file (cafile) and run this afterwards.

npm config set cafile /path/to/cert.pem

Alright! We should now have angular-cli installed by this point, and we can test it with ng -v. It should show some nice ascii art and the version of angular-cli, node, and your os.

Angular-Cli

Instead of building our entire environment by hand and setting up build processes, this is a tool built by the angular team used to automate that away so we can focus on building our application. You can go to the github repo to see what it is generating with webpack, and other build processes stuff, but we are going to focus on the application development side today.

The first thing we need to do is make an application. Navigate to a directory you want the application to live (it will generate a top level folder) and run this.

ng new my-ng-app

Now let's take a look at what it made. Go into the my-ng-app folder and look at the package.json file. This has all our dependencies in it, but more importantly are the scripts. Angular-cli gave a few useful commands we can run with npm run [command]. Go ahead and run npm run start and navigate to http://localhost:4200 in your browser now. You should see the text "app works!".

Components

Let's start at the src/app/app.component.ts. It should look like this.

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'app works!';
}

The first line might look different to you if you aren't familiar with ES6 Modules. Angular has defined an object called Component in their library, and exported it for our use. We use the braces around the word Component because they export multiple objects from the same place, so we just specify the singular object we want instead of grabing the whole library. The string we are importing from here points to the node module '@angular/core'. Imports can also be done relative to the files location '../someFolder/someOtherCode'.

The Component object itself is a Typescript Decorator. Simply put, they are special functions that provide metadata to angular about our component. Our component passes metadata defining three things.

  • It is bound to the template located at ./app.component.html
  • It is bound to the styles located at ./app.component.css
  • When used in other templates it has an html selector of <app-root></app-root>

After this is our actual Component. Our component is an ES6 Class, which is just syntactic sugar over Javascript's existing prototypal system. This class only has one property title. We also exporting this Component using the module system. We will find out where it is going in a bit.

Interpolation

Where is that title going though? For that we are going to have to take a look at the app.component.html.

<h1>
  {{title}}
</h1>

If you are familiar with handlebars, or any other templating engine this should be pretty familiar. We are taking the title property from our AppComponent class and using interpolation in the template using the {{property}} syntax. Try chaning the property in the component. If you haven't stopped the command line process you should see your html update a moment after you save.

What if we wanted to display an array of objects on the screen? Angular provides us a useful way to do this with a structural directive called *ngFor. Go ahead and add the property todos to your component with this data.

todos = [
  'Get Coffee',
  'Smash Code',
  'Get Paid'
]

We can now bind this to the template using *ngFor like this.

<ul>
  <li *ngFor="let todo of todos">
    {{ todo }}
  </li>
</ul>

You should now see those laid out in a list. *ngFor gives us the ability to repeat a piece of html for each item in the array. There are a few different types of these directives like *ngFor, *ngIf, and *ngSwitch. You can find more info on these directives here

Property Binding

Besides being able to interpolate properties from the component into the template as text, we can also bind directly to element properties. Add this button element to your template.

<button [disabled]="unAuthorized">Can't touch this</button>

Now add an unAuthorized property to your component

unAuthorized = true;

You shouldn't be able to click that button now. Try setting it to true. There are also bindings for things like src, value, style, and class. Style and class do work differently than you would initalially expect though. I'll let you do exploring on those properties here if you want to read more.

Event Binding

What if we wanted to react to an event by the user like a button click? Angular provides a way to bind to all of the native dom events. Add this button to your template.

<button (click)="onClick()">Click me!</button>

This is the basic syntax to bind to a click event and have it call a method defined in our component. Currently this will throw an error since onClick is not defined. Let's go ahead and define it.

onClick() {
  this.title = 'I was clicked!';
}

The event handler can also be passed the event payload with $event like this.

<input (keyup)="onKey($event)">
onKey(event) {
  this.title = event.target.value;
}

We now have access to the event object like you would expect in a native EventListener or a jQuery onClick handler.

There are some issues about this though. It can make a tight coupling to the html implementation in the component if you are passing the whole dom event. Instead we can use a template reference variable. These are used to give us a reference to a dom element anywhere else in the template. Create one with the hash symbol # and the name you want to give it.

<input #temp (keyup)="onKey(temp.value)">
onKey(val) {
  this.title = val;
}

Now are component needs to do nothing to get the value from it's argument and is completely oblivious of our template's implementation.

Two way binding

So far we have been using one-way binding. The data only goes from the component to the template or from template to component, but not both. We could do something like this.

<input #temp [value]="title" (keyup)="onKey(temp.value)">

We now have the property title being bound to the element value from the component, and we are listening for key presses on the input, which takes its value and assigns it to the title property. This is very explict about what we are doing, and can be a good thing if you want extract validation on the input as they type. Angular gives us an easier way to handle two-way data-binding.

Let's update our input to look like this.

<input [(ngModel)]="title" />

Now delete that keyUp function, and BAM you're done! Angular wires up the value of title flowing into the template, and the value from the event flowing back into the title property on the component. The syntax for this is easier to remember when you think about it as a combination of property binding and event binding. You just combine the two to make [()]. Some might be wondering where ngModel came from. It is part of angular itself, and we'll see where it comes from in the next section.

Modules

Now that we have an idea about how components work, lets take a look at what's happening with our AppComponent. At the same level as the app.component.ts look at the file app.module.ts. An angular module is where you define how all the pieces of your application fit together. It also uses a decorator like the Component, but with different metadata needed. Let's look at what angular-cli set up for us.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Declarations are for our components. This tells angular what components we've defined ourselves, and allows them to be used in other templates for this module.

Imports are for importing entire other modules. There can be trees of modules, just like can be a tree of components for a module. These three are the main ones used by every program.

  • Every application that is executed in a browser needs the BrowserModule from angular to work.
  • The FormsModule is the module that gives us the ngModel definition. Without it our two-way data-binding doesn't work. It also provides other features when dealing with forms and validating them. Read more about forms here
  • The HttpModule is used for handling ajax requests. Currently we don't need this module, but we will use it shortly.

Providers are for declaring Services that are to be used by the module. We currently, don't have any, but will be creating one next.

Bootstrap tells the module what the top level node in this tree is. When the application starts it will start by rendering the AppComponent first. You can have multiple top level nodes, but for most applications this is rare.

If you noticed that we are also exporting this module, then you know we still have a little bit to go before we find out where this all ends. Let's follow it!

Open up main.ts one level up just under src. Here you see this.

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';

if (environment.production) {
  enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppModule);

This is where the magic happens. Angular-cli gives us an environment constants file with a single flag currently for production. When running npm start it is set to false, but is set to true when running npm run build. If we are in production it runs in ProdMode, which disables dual change detection and does other run time optimizations. Lastly it invokes the call to start the angular application with platformBrowserDynamic. This is the most basic runtime for the app as it ships with the compiler to the browser and does just in time (JIT) compilation of our components. We then pass our module to the bootstrapModule to give it something to start executing. From there is parsing the module metadata and gets all the dependencies it needs, and starts rendering our AppComponent.

Services

Now that we know how components work, let's talk about Services. A service is used to shared data across multiple components without them being explicitly tied to the other components. Let's create a service for getting our todos instead of having them stored in our AppComponent in case we want to share them later.

Create

First create a new folder under app called shared and a file called todos.service.ts under that. Adding .service or .component to file names is a convention used in Angular for easily figuring out what the purpose of this file is. There is also a similar convention for the class names. Components are {name}Component and Services are {name}Service.

Put this in your todos.service.ts and let's look it over.

import { Injectable } from '@angular/core';

@Injectable()
export class TodosService {
  private todos: Array<string> = [
    'Get Coffee',
    'Smash Code',
    'Get Paid'
  ];

  getTodos(): Array<string> {
    return this.todos;
  }
}

The first and third line are importing the Injectable decorator and decorating our TodosService class. This decorator is used for telling angular that this service might be injected into another service in the future, and prepares it for that. We don't need this now, but the error when forgetting it and needing it can be incredibly vague when it's not there, so it's recommended to always have it.

The property todos and method getTodos shouldn't look to foreign, but we've introduced types to our definitions. This is the typescript syntax for types

{property}: {type} = {optional initialization}

There are also two ways to denote an array. Array<type> or type[]. They mean the same thing, it's just preference on which to use. I prefer Array<type> because it is much more immediately apparent we are working with an array.

There is also one more keyword here we haven't seen before. private. Some people might be familiar with this syntax. What this does is make it so nothing can access this property directly outside of the class itself. By default typescript properties are public.

Declare

Now that we have a service, we need to let angular know about it. You've seen where to do that before, but there was nothing there. In the module declaration metadata was the providers key. This is where we need it add the service.

import { TodosService } from './shared/todos.service';
...
providers: [TodosService],

Providing the service to the module makes it so that any Component or Service in this module that uses our TodosService will be given the same instance of the service. This also makes it so angular is in charge of initializing our service. If we add or update our services constructor we don't have to go update all implementations of code initalizing it now.

Use

Now that we have written our service and angular knows about it, we need to do something with it! Let's start by deleting the hardcoded todos property out of the AppComponent and replace it with just a definition.

todos: Array<string> = [];

Now how do we use a service inside a component? Let's update our AppComponent and lets review it.

import { Component, OnInit } from '@angular/core';
import { TodosService } from './shared/todos.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  title = 'app works!';
  todos: Array<string> = [];

  constructor(private todosService: TodosService) {}

  ngOnInit() {
    this.todos = this.todosService.getTodos();
  }

  onClick() {
    this.title = 'I was clicked!';
  }

  onKey(val) {
    this.title = val;
  }
}

Let's start with the constructor function. Typescript has constructors like other class based languages that get initialized. We are using it here to create a private property called todosService of type TodosService. Angular sees this line and is smart enough to supply us with this modules instance of the TodosService. If you had a service that was meant to be used only by this component and it's direct children you would add the service to the components metadata like we did in the module instead

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  providers: [TodosService]
})

After we have the private todosService we aren't getting the todos in the constructor function, but instead in this ngOnInit function. The reason is because that function is part of the angular lifecycle. It will fire after component creation is complete and ready for template binding. This way we know the properties we are binding to are ready for data to be added in. To do this we imported the OnInit object from angular and added it to out class declaration with implements OnInit. What does that do? It tells typescript that we now have a contract for this class to follow this OnInit interface. If we don't have the ngOnInit function you will see that typescript throws an error when it compiles.

Now that we understand it all take a look at your browser and see that it's working!

Composing Components

So now we have a single component with a single service, but most applications are a bit more complicated than this. Let's say we think our todo list is gonna start getting more complicated and we want to break that out into its own component. In the app let's create a new todos folder with a component, template, and stylesheet. Here is what the structure and naming should look like.

app -
  todos-
    todos.component.ts
    todos.component.html
    todos.component.css

Now let's take our todo list out of the app template and put it in the todos template. Our todos template should look like this.

<ul>
  <li *ngFor="let todo of todos">
    {{ todo }}
  </li>
</ul>

Now let's create a basic todos component.

import { Component } from '@angular/core';

@Component({
  selector: 'app-todos',
  templateUrl: './todos.component.html',
  styleUrls: ['./todos.component.css']
})
export class TodosComponent {

}

Input

So now we have a Component, but don't have the todos anymore. We could do the same thing we did in the AppComponent and get them from the service, but now we have another spot manually managing its own state. Let's take a minute to talk about Smart and Dumb Components. This was a pattern popularized in the react community that has been embraced by many other frameworks since. The basic idea is smart components care about how things work while dumb components care about how they look. This allows use to have more resuability of our dumb components since they aren't coupled to implementation details, they are just given the data they need and render it accordingly. Let's see how we can do this with our TodosComponent.

Let's import a new decorator from angular called Input in our TodosComponent.

import { Component, Input } from '@angular/core'

This is used to declare an input property on our component. With an input property other components can bind to this property with the same syntax as property binding. Let's make an input property for todos.

...
export class TodosComponent {
  @Input() todos: Array<string>;
...

With this, we've wired up everything we need in the component itself, but we still need to render this component somewhere. Next let's make sure our application knows about component. Go to the app.module.ts and add the declaration for this component.

...
import { TodosComponent } from './todos/todos.component';
...
declarations: [
  AppComponent,
  TodosComponent
]
...

This allows us to use this component's selector (app-todos) in other components. Let's go the app.component.html and add it at the bottom.

<app-todos [todos]="todos"></app-todos>

We gone over how property binding works and this is exactly the same, but with custom properties. The left is the property we are binding to. The right side is the value we are passing to that property. If you changed the AppComponent.todos name to appTodos you would also have to change the template like this.

<app-todos [todos]="appTodos"></app-todos>

We now have our AppComponent that gets the todos and then passes them to the TodosComponent. This allows the TodosComponent to not worry about where the todos are coming from, it just needs to display them. Now if we wanted to use that component in a different module that doesn't get the todos exactly like this one, we don't need to worry about that in the component itself.

Output

So now we have a list of todos being rendered, but what if we wanted to be able to remove a todo. Keeping with the Smart/Dumb component pattern that shouldn't be handled by the TodosComponent itself if we want to keep that as our dumb component. The logic should live in the AppComponent. So we need to tell the component to delete a particular todo, but currently it is just a list of strings, so let's do two things. Define a Todo interface with a title and id, and update our todos list to use it.

Create todo.model.ts under the shared directory, then let's define our interface.

export interface Todo {
  id: number;
  title: string;
}

Now let's update our AppComponent to define todos as an Array of Todo objects.

import { Todo } from './shared/todo.model';
...
todos: Array<Todo> = [];

If you save this we will see that typescript is now yelling about our assignment in ngOnInit! It caught that we can't assign Array<string> to Array<Todo> for us. Let's update our TodosService.

import { Todo } from './todo.model';
...
private todos: Array<Todo> = [
  { id: 1, title: 'Get Coffee' },
  { id: 2, title: 'Smash Code' },
  { id: 3, title: 'Get Paid' }
];

getTodos(): Array<Todo> {
  return this.todos;
}

Lastly we need to update our TodosComponent and it's template to use the Todo model, and only show the title property.

import { Todo } from '../shared/todo.model';
...
@Input() todos: Array<Todo>;
<ul>
  <li *ngFor="let todo of todos">
    {{ todo.title }}
  </li>
</ul>

At this point we should be back to show the todos like we were before. Now let's write our deleteTodo function. Let's keep all the logic for manipulating todos in our TodosService so that we only ever have to write it once, regardless of where this service is used.

...
deleteTodo(todo: Todo): void {
  this.todos.splice(this.todos.indexOf(todo), 1);
}

So now we have a method to delete a todo, but how do we use it? Just like there is an Input decorator, there is an Output decorator. We can use this to fire a custom event from TodosComponent to our AppComponent that it can react to. Add this to our TodosComponent and it's template.

import { Component, Input, Output, EventEmitter } from '@angular/core';
...
@Output() deleted = new EventEmitter();
<ul>
  <li *ngFor="let todo of todos">
    {{ todo.title }} |
    <button (click)="delete.emit(todo)">X</button>
  </li>
</ul>

We are importing the Output decorator and using it simliarly to the Input decorator, but we are actually initalizing this property as a new EventEmitter. This is a class that does exactly what you'd expect, emit events. Now we added a button for each todo and gave it a click event that emits an event with the todo as the argument. Now we need to bind to this event just like we would any other. Add this to your AppComponent and it's template.

onDelete(todo: Todo) {
  this.todosService.deleteTodo(todo);
}
<app-todos [todos]="todos" (delete)="onDelete($event)"></app-todos>

Now we have bound the onDelete method to the delete event from our TodosComponent. When we click delete on a todo, it emits the delete event passing the todo object, then our AppComponent takes that todo and passes it to the onDelete method, which calls our todosService to delete the todo. If everything went according to plan you should see the todo disappear after clicking our button!

Http

Our TodosService is working well for us now, but most of the time we don't have hard coded items in our service to work with. We usually get them from our server, or an external api. Angular gives us a nice way to handle ajax with their Http library. We are going to use this to get data from an test api hosted online called json placeholder. We'll need to extend our model just a bit to fit their todos model. Update your todo.model.ts with these new properties.

export interface Todo {
  id: number;
  title: string;
  userId: number;
  completed: boolean;
}

Now let's setup our service to get and delete data from this external api.

import { Injectable } from '@angular/core';
import { Http, Headers } from '@angular/http';
import { Todo } from './todo.model';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';

@Injectable()
export class TodosService {
  private baseUrl = 'http://jsonplaceholder.typicode.com/todos';
  private headers = new Headers({'Content-Type': 'application/json'})

  constructor(private http: Http) {}

  getTodos(): Observable<Array<Todo>> {
    return this.http.get(this.baseUrl)
      .map(res => res.json());
  }

  deleteTodo(todo: Todo) {
    return this.http.delete(`${this.baseUrl}/${todo.id}`, { headers: this.headers })
      .map(res => null);
  }
}

There is quite a lot going on here. We have now imported Http into the service, and set it up with a constructor like we've seen before. We also set a baseUrl to point at the resource we are getting our todos from now. We also now have included this Observable object into the code.

Observables

Observables are part of the rxjs library and are a type of collection that can be watched and reacted to when there are changes to the collection. It is similar to a Promise, except that your .then function call will be called whenever there is a change to the colleciton. Angular makes heavy use of them in parts of the application, and Http library itself returns an Observable that contains our http Response.

We also imported, in a slightly different fashion than previous seen, the map operator. This style of importing doesn't bring in the code into a variable, but just executes the code that was in the file. In this case, it extends the Observable with a new method called map. Map is used exactly like the native Array.prototype.map method. There are a lot of operators, and they give you a lot of power, but can be very overwhelming. We aren't going to get into them here, but a great site for seeing what they do is called RxMarbles, which gives a visual representation of what each operator does.

Let's look at our getTodos function. We call http.get passing in our baseUrl. This is where the actual ajax call is made. Before we return this to the caller of the function, we map the value (a Response object) returned from http.get and call res.json(). This is the native Body.json function provided by browsers. So now we have transformed our Response into a json object to be sent to the caller.

deleteTodo is also very similar, but we used the new Template Literal to combine the baseUrl with the id of the todo we are deleting. We also have brought in the Headers object, and defined our Content-Type to pass along with the delete call. We end with mapping the response to return null since we have no need for the response object. Knowing that the call succeeded is enough information.

Our http calls have been using provided helpers to directly call get or delete, but there is also a base request that takes a url, and options object similar to the jquery ajax method.

Update Components

We have now updated our methods to get or delete todos, but this has broken the behavior in AppComponent. Let update the getTodos call first.

ngOnInit() {
  this.todosService.getTodos()
    .subscribe(todos => {
      this.todos = todos;
    });
}

This should look simliar to people familiar with promises. We are calling getTodos still, but instead of assigning its value directly to this.todos we are subscribing to it. Now when it finishes the request it will go though our service and then call our function, passing in the retrieved todos. Then we can assign the value to this.todos.

Let's update the onDelete function now.

onDelete(todo: Todo) {
  this.todosService.deleteTodo(todo)
    .subscribe(() => {
      this.todos.splice(this.todos.indexOf(todo), 1);
    });
}

We now call deleteTodo with the todo argument like before, but now subscribe to it. After the service has finished making the delete request it will call our function. Since we know it was successfully deleted from the server, we now remove it from the todos with have currently.

Error Handling

What about when something goes wrong? How do we handle that with Observables? The subscribe method has a very similar signature to Promises. There is a success, error, and complete callback to it. All we would need to do is add a second callback function to our subscribe and we can handle the error how we would want. You could show an error, make another attempt, or anything else you could think to do.

this.todosService.deleteTodo(todo)
  .subscribe(() => {
    this.todos.splice(this.todos.indexOf(todo), 1);
  }, err => {
    // handle error
  }, () => {
    // always do this thing
  });

Observable mutations can also get pretty complex, like all software, and each operation you do could have a chance of failing. This is where catch comes in. We can use in our TodosService to catch any error on our http calls and do something (ex. logging).

import { Http, Headers, Response } from '@angular/http';
...
import 'rxjs/add/operator/catch';
...
getTodos(): Observable<Array<Todo>> {
  return this.http.get(this.baseUrl)
    .map(res => res.json())
    .catch(this.handleError);
}

deleteTodo(todo: Todo) {
  return this.http.delete(`${this.baseUrl}/${todo.id}`, { headers: this.headers })
    .map(res => null)
    .catch(this.handleError);
}

private handleError (error: Response | any) {
  // In a real world app, you might use a remote logging infrastructure
  let errMsg: string;
  if (error instanceof Response) {
    const body = error.json() || '';
    const err = body.error || JSON.stringify(body);
    errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
  } else {
    errMsg = error.message ? error.message : error.toString();
  }
  console.error(errMsg);
  return Observable.throw(errMsg);
}

With this, it allows us to do generic error handling in the service, and allow each component to handle the rest however it sees fit.

Conclusion

With this we have created our own components and explored one-way and two-way data-binding. We've learned how to pass properties to a component and listen to events from a component. We've also tied into a service to get and delete data from a an external api. Our two components have a clear seperation of one smart and one dumb component.

From here I would recommend try to show the completed property on our todos, and make an http to update the property. If you get that working try making a TodoDetail component that renders just a currently selected todo, and allows you to edit it in a form. For this take a look at the offical angular tutorial, specifically the Master/Detail section. The angular forms documentation goes over a lot of things we did not cover about forms and form validation.

After that all I can recommend is GO BUILD SOMETHING!!!!!

@ColinRTaylor

This comment has been minimized.

Copy link

commented May 10, 2017

It's just 'Angular' not Angular 4+, see this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.