Sie sind auf Seite 1von 206

Routes

Wiring Together Views Section 1

Routes
Wiring Together Views Section 1
Logic in Our Routes Section 2

Directives
$scope Section 1

Scope the Config Object Section 2


Link Section 3

Services
5 Recipes and a Factory Section 1
Another Factory Section 2
Provider Section 3
$resource Section 4

Reusable Directives
Children and Parents Communicate Section 1
Filters Section 2
External libraries Section 3

Angular Note Wrangler

Starting With Static Pages


This was handed to us from our designer. All the pages are static.

Note Wrangler
notes-index.html
users-index.html

css
application.css
images

Organizing: Separate Directories for All


Separate controllers, services, lters, and directives into individual directories.

templates

Note Wrangler
notes-index.html
users-index.html
css
javascript
app.js

Note Wrangler
notes-index.html
users-index.html
css
javascript
app.js
controllers.js
lters.js
services.js
directives.js

controllers
lters
services
directives
templates

Organizing: Separate Files for All


Inside the controllers directory we will have a le for each controller.

Note Wrangler
notes-index.html
users-index.html
css
javascript
app.js

K e e p t h in g s
e n c a p s u la t e d a n d b it e -s
iz e

controllers
notes-create-controller.js
notes-edit-controller.js
notes-index-controller.js
notes-show-controller.js
lters
services

directives
templates

Back to Our Designers Static Files


Both of these static les have boilerplate code that is exactly the same.

Note Wrangler
notes-index.html
users-index.html
css
javascript
templates

Both are fully fleshed out files with


much redundancy <html>...</html>

notes-index.html

Duplicated code in both


users-index.html and notes-index.html

<!DOCTYPE html>
<html lang="en" ng-app="NoteWrangler">

<head>
<meta charset="utf-8">
<title>Note Wrangler</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="css/application.css" />
</head>
<body>
<div class="nav-wrapper has-dropdown">
<div class="nav-content">
<div class="wrapper">
<div class="nav-content-layout">
<div class="nav-list">
<a href="#/"
class="list-item"

notes-index.html

<div class="main-wrapper">
<div class="note-wrapper">
<div class="note-content">
<div class="notes-header">
<h1 title="Notes">Notes</h1>
</div>
<div class="note-wrapper">
<a class="card-notes" ng-repeat="note in notes">
<div class="card" title="{{note.title}}">
<h2 class="h3">{{note.title}}</h2>
<p>{{note.description}}</p>
</div>
</a>
</div>
</div>
</div>
</div>

Unique chunk inside


the main-wrapper div

users-index.html

notes-index.html

<div <div
class="main-wrapper">
class="main-wrapper">
<div class="note-wrapper">
<div class="users-wrapper">
<div class="note-content">
<h1>Users</h1>
<div class="notes-header">
<h1 title="Notes">Notes</h1>
</div><div class="users-wrapper">
<a class="card-users" ng-repeat="user in users">
<div class="card" title="{{user.name}}">
<div class="note-wrapper">
<h2 class="h3">{{user.name}}</h2>
<a class="card-notes"
ng-repeat="note in notes">
<p>{{user.bio}}</p>
</div>
<div class="card"
title="{{note.title}}">
</a>
<h2
class="h3">{{note.title}}</h2>
Unique chunk inside
</div>
<p>{{note.description}}</p>
</div>
</div>

</a>
</div>

the main-wrapper div

unique chunk inside


the main-wrapper

</div>
<!-- Load Js libs -->
<script src="./js/vendor/jquery.js"></script>
</div>
<script src="./js/vendor/angular.js"></script>
</div>
<script src="./js/vendor/angular-route.js"></script>

a Unique HTML Gets Its Own Template File


notes-index.html

users-index.html

<div class="users-wrapper">
<div class="note-wrapper">
<h1>Users</h1>
<div class="note-content">
<div class="notes-header">
<div class="users-wrapper">
<h1 title="Notes">Notes</h1>
<a class="card-users" ng-repeat="user in users">
</div>
<div class="card" title="{{user.name}}">
<h2 class="h3">{{user.name}}</h2>
<div class="note-wrapper">
<p>{{user.bio}}</p>
<a class="card-notes" ng-repeat="note in notes">
</div>
</a>
<div class="card" title="{{note.title}}">
</div>
<h2 class="h3">{{note.title}}</h2>
<p>{{note.description}}</p>
</div>
</div>

</a>
</div>

a Moving Templates Into Their Own Folder


We will place our static les into our templates/pages directory.

Note Wrangler

index.html
css

templates
pages

notes
notes-index.html
users
users-index.html

notes-index.html

k<div

class="note-wrapper"> ...

users-index.html
<divindex.html
class="users-wrapper">...

We can eliminate the duplication and remove the prex on our les.

a Moving Templates Into Their Own Folder


We will place our static les into our templates/pages directory.

Note Wrangler

index.html
css

templates
pages

notes
index.html
users
index.html

notes-index.html
notes/index.html

k<div

class="note-wrapper"> ...

users/index.html
users-index.html
users/index.html
<divindex.html
class="users-wrapper">...

We can eliminate the duplication and remove the prex on our les.

Creating the Main Index File


This is the rst le that will load up our Angular app.

Note Wrangler
index.html
css

templates
pages
notes
index.html
users
index.html

Unique content
removed

Add ng-app

<html lang="en" ng-app="NoteWrangler">


<head>...</head>
<body>
<div class="nav-list">
<a href="#/notes"> Notes </a>
<a href="#/users"> Users </a>
</div>
<div class="hero-wrapper"> ... </div>
<div class="main-wrapper">
</div>
<script src="/js/vendor/jquery.js"></script>
...

Current State of Our App

Note Wrangler
index.html
css

templates
pages
notes
index.html
users
index.html
javascript

Current State of Our App

Note Wrangler
index.html
css

templates
pages
notes
index.html
users
index.html
javascript

a The Notes URL Should Load Notes


If the route is http://example.com/#/notes, we want index.html to load the notes/index.html.

Note Wrangler
index.html
css

templates
pages
notes
index.html
users
index.html
javascript

Missin g Note List


How do we include templates?

_ Dening a Route
Angular routes allow us to map URLs to use templates so that every time the current route changes,
the included view changes with it.

Note Wrangler
index.html
css

templates
pages
notes
index.html
users
index.html
javascript
routes.js

http://example.com/#/notes

rout es.js fetc hes and load s note s/ind ex.ht ml

Four Steps to Route Happiness


a

Using ngView

Loading the ngRoute library

Importing ngRoute module

Dening routes

a Using ngView
This allows us to tell our Angular app where to load in templates that we will wire together shortly.
index.html

<html lang="en" ng-app="NoteWrangler">


<head>...</head>
<body>
<div class="nav-list">
<a href="#/notes"> Notes </a>
<a href="#/users"> Users </a>
</div>
<div class="hero-wrapper"> ... </div>
<div class="main-wrapper">
<div ng-view></div>
</div>
<script src="/js/vendor/jquery.js"></script>

notes/index.html
users/index.html

_ Loading the ngRoute Library


In order to slim the Angular core down, they put routing in a separate module. So you need to
explicitly import ngRoute in your application.
index.html

<div class="hero-wrapper"> ... </div>


<div class="main-wrapper">
<div ng-view></div>
</div>
<script src="/js/vendor/jquery.js"></script>
<script src="/js/vendor/angular-route.js"></script>

_ Including ngRoute in Our App


We need to include ngRoute in our main application module so our whole app will have access to
this service.
app.js
angular.module("NoteWrangler", ['ngRoute'])
Now we can create a routes.js le!
routes.js

angular.module('NoteWrangler')
.config(function($routeProvider){});
Well use $routeProvider in a minute to specify Routes.

_ Creating routes.js
Why arent we doing it the way we learned in Shaping up with Angular.js?

Setting your app module to a variable and reusing that variable is bad practice.
routes.js

angular.module('NoteWrangler')
.config(function($routeProvider){});

Re-declare your application module in every new file

_ Dening Routes With $routeProvider


Inside module.cong we can use one of $routeProviders methods to dene routes.
.when(path, route);
Adds a new route denition to the $route service.
.otherwise(params);
Sets route denition that will be used on route change when no other route denition is matched.

_ Declaring Our First Route


Lets go ahead and dene a route for /notes to load in the notes/index.html using
$routeProviders .when() method.
routes.js

angular.module('NoteWrangler')
.config(function($routeProvider) {
$routeProvider.when('/notes', {
templateUrl: '/templates/pages/notes/index.html'
})
});

Notes Route Now Loads In

_ Adding the Users Route


routes.js

angular.module('NoteWrangler')
.config(function($routeProvider) {
$routeProvider.when('/notes', {
templateUrl: 'templates/pages/notes/index.html',
})

});

.when('/users', {
templateUrl: 'templates/pages/users/index.html',
})

Notice the method chaining

Users Route Now Loads In


users/index.html
<div class="users-wrapper"> ...

_ Dening the Root Route


If the route is http://example.com/ though, we want index to load in the notes/index.html by default.

Note Wrangler
index.html

css

templates
pages

notes
index.html
users
index.html

Missin g Notes List!

_ Options for Loading In Notes for Root Route


routes.js

angular.module('NoteWrangler')
.config(function($routeProvider) {
$routeProvider.when('/notes', {
templateUrl: 'templates/pages/notes/index.html',
})
...
.when('/', {
templateUrl: 'templates/pages/notes/index.html',
})
});

_ Setting a Catch-all Route


routes.js

angular.module('NoteWrangler')
.config(function($routeProvider) {
$routeProvider.when('/notes', {
templateUrl: 'templates/pages/notes/index.html',
})
...
.when('/', {
templateUrl: 'templates/pages/notes/index.html',
})
.otherwise({ redirectTo: '/' });
});
.otherwise(params);

Sets route denition that will be used on route change when no other route denition is matched.

Templates Are Now Wired Up

Serving Up Our Application

Brackets
Node
Ruby on Rails
and so many more

Routes
Logic in Our Routes Section 2

Refresher on the $http Service


Can be used as a function with an options object.
$http({ method: 'GET', url: '/products.json' });
Or one of the shortcut methods.
angular.module('store', [ 'store-products' ])
.controller('StoreController', function($http) {
var notes = this;
$http.get('/products.json').success(function(data) {
store.products = data;
});

});

Remember $http from


Shaping up with Angular?

$http Service Distilled


Here is a reminder of the $http service, which is how we make an async request to a server.

$http.get retrieve data


$http.post create new data
$http.put update existing data
$http.delete destroy data
The $http service also gives us the ability to pass in data when needed.
$http({ method: 'POST', url: '/resource/path.json', data: noteData });

Routing Components
Not only can routes have templates associated with them, but they can also have their own
associated controllers.
Requests

/notes

controller
template

/users

controller
template

redirect

Inline Route Controller


There are two ways to link a controller to a route.
First, you can create a controller inside your route, inline, like so:
routes.js
angular.module('NoteWrangler')
.config(function($routeProvider) {
$routeProvider.when('/notes', {
templateUrl: 'templates/pages/notes/index.html',
controller: function(){}
})
});

Outside Route Controller


Alternatively, you can link to an already existing controller.

Note Wrangler
notes-index.html
users-index.html
css
javascript
app.js

notes-index-controller.js
angular.module('NoteWrangler')
.controller('NotesIndexController', function() {
});

controllers
notes-create-controller.js
notes-edit-controller.js
notes-index-controller.js
notes-show-controller.js
lters
services

directives
templates

Using Our Outside Route Controller


Specify the NotesIndexController and use controllerAs to assign it an alias for the template to use.
routes.js
angular.module('NoteWrangler')
.config(function($routeProvider) {
$routeProvider.when('/notes', {
templateUrl: 'templates/pages/notes/index.html',
controller: 'NotesIndexController',
controllerAs: 'indexController'
})
...
notes-index-controller.js
});

angular.module('NoteWrangler')
.controller('NotesIndexController', function() {
});

Assigning Notes to this.notes


Using $http GET method, we will grab notes and assign them to a variable on the controllers scope.

$http.get

retrieve data

notes-index-controller.js
angular.module('NoteWrangler')
.controller('NotesIndexController', function($http) {
var controller = this;
$http({method: 'GET', url: '/notes'}).success(function(data) {
controller.notes = data;
});
});

a Displaying Notes
Now we are ready to display a list of all the notes. We will use ngRepeat to loop through them.
notes/index.html
<div class="note-wrapper">
<a class="card-notes" ng-repeat="note in indexController.notes>
</a>
</div>

a Displaying Notes
With each note instance, we can print out note info on each card.
notes/index.html
<div class="note-wrapper">
<a class="card-notes" ng-repeat="note in indexController.notes">
<div class="card" title='{{note.title}}'>
<div ng-if='icon'>
<i class='icon icon-card {{note.icon}}'></i>
</div>
<h2 class='h3'>{{note.title}}</h2>
</div>
</a>
</div>

The Notes Are Now Loaded Dynamically

Displaying a Single Note


When a note is clicked, we want to display that note in more detail.

Displaying a Single Note


When a note is clicked, we want to route to a detail view of that note.
http://example.com/#/notes

notes-show.html

http://example.com/#/notes/<id>
notes[<id>].info

Note is clicked

Route changes
Route loads view

Linking to a Note and Declaring the Route


index.html
<div class='note-wrapper'>
<a class='card-notes'
ng-repeat='note in notes'
ng-href='#/notes/{{note.id}}'>
</a>
</div>
routes.js

Specifies route parameter

.when('/notes/:id', {
templateUrl: 'templates/pages/notes/show.html',
controller: 'NotesShowController',
controllerAs: 'showController'
})

$routeParams to Read the Parameters


By passing in $routeParams we can pull things (like note id) out and utilize them.
notes-show-controller.js

/notes/1

angular.module('NoteWrangler')
.controller('NotesShowController', function($http, $routeParams) {
var controller = this;
$http({method:'GET', url: '/notes/' + $routeParams.id})
.success(function(data){
this keyword
controller.note = data;
})
});
Assign to a variable in order to use this keyword inside the callback.

Creating the Show Template


notes-show-controller.js
angular.module('NoteWrangler')
.controller('NotesShowController', function($http, $routeParams) {
var controller = this;
$http({method:'GET', url: '/notes/' + $routeParams.id})
.success(function(data){
notes/show.html
controller.note = data;
<div class="card">
Displaying the note details
})
<h1>{{note.title}}</h1>
});
<p>Created by: {{note.user || note.user.username}}</p>
<h3>Description:</h3>
<p>{{note.description}}</p>
<h3>Contents:</h3>
<p>{{note.content}}</p>
</div>

Displaying a Single Note


It works!

Creating a New Note


We need to create a saveNote() function to be utilized by a form. This form will have a submit
event to call saveNote() and pass in the new note.
note-create-controller.js
angular.module('NoteWrangler')
.controller('NoteCreateController', function($http) {
var controller = this;
this.saveNote = function(note) {
$http({method: 'POST', url: '/notes', data: note})
};
});

passing note as a data option

What if Our $http Call Fails?


We can use callbacks, like .catch, to make error messages available to the template for display.
note-create-controller.js
angular.module('NoteWrangler')
.controller('NoteCreateController', function($http) {
var controller = this;
this.saveNote = function(note) {
controller.errors = null;
$http({method: 'POST', url: '/notes', data: note})
.catch(function(note) {
controller.errors = note.data.error;
})
If a new note fails to get created, you now have
};
an error message to display in the template.
});
<p ng-if="createController.errors"> {{createController.errors}} </p>

Backend API Calls


You might notice were making calls to our server to fetch /notes.
We wont be showing you in this course how to code a backend API.
We have several dierent courses on Code School, Express.js and
Rails courses, where you can learn how to create your own APIs.

Directives Level 2
$scope Section 1

Our Current Code


Every time we need to print a note, we are using the same code over and over.
notes.html

...
<div class="note-wrapper">
<a class="card-notes" ng-repeat="note in notes"
ng-href="#/notes/{{note.id}}">
<div class="card">
<h2 class="h3">{{card.header}}</h2>
</div>
</a>
</div>
...

Extracting Into a Directive Template


notes.html

...
<div class="note-wrapper">
<a class="card-notes" ng-repeat="note in notes"
ng-href="#/notes/{{note.id}}">
</a>
</div>
...

nw-card.html

<div class="card">
<h2 class="h3">{{card.header}}</h2>
</div>

Creating a New nwCard Directive


notes.html

...
<div class="note-wrapper">
<a class="card-notes" ng-repeat="note in notes" >
<nw-card></nw-card>
</a>
nw-card.js
</div>
...
angular.module("NoteWrangler")

.directive("nwCard", function() {
return {
restrict: "E",
templateUrl: "templates/directives/nw-card.html"
};
});

Creating a New nwCard Directive

nw-card.js

angular.module("NoteWrangler")
.directive("nwCard", function() {
return {
restrict: "E",
templateUrl: "templates/directives/nw-card.html"
};
});

Giving nwCards Controller an Alias


nw-card.js

angular.module("NoteWrangler")
.directive("nwCard", function() {
return {
restrict: "E",
templateUrl: "templates/directives/nw-card.html",
controller: function(){

};
});

},
controllerAs: "card"

Setting a Header With controllerAs Syntax


nw-card.js

angular.module("NoteWrangler")
.directive("nwCard", function() {
return {
restrict: "E",
templateUrl: "templates/directives/nw-card.html",
controller: function(){
this.header = "Note Title";
},
nw-card.html
controllerAs: "card"
<div class="card">
};
<h2 class="h3">{{card.header}}</h2>
});
</div>

Using this to bind


data for the template

Setting a Header With $scope Syntax


nw-card.js
angular.module("NoteWrangler")
.directive("nwCard", function() {
return {
restrict: "E",
templateUrl: "templates/directives/nw-card.html" ,
controller: function($scope){
$scope.header = "Note Title";
$
},
nw-card.html
controllerAs: "card"
};
<div class="card">
});
<h2 class="h3">{{card.header}}</h2>
</div>

Can get rid of


controllerAs and alias

Binding Header to $scope


nw-card.js
angular.module("NoteWrangler")
.directive("nwCard", function() {
return {
restrict: "E",
templateUrl: "templates/directives/nw-card.html",
controller: function($scope){
$scope.header = "Note Title";
$
}
};
nw-card.html
});
<div class="card">
<h2 class="h3">{{header}}</h2>
</div>

Both Syntaxes Attach to $scope


Uses a card alias to reference the controller and
this to reference $scope behind the scenes.
nw-card.js
controller: function($scope) {
this.header = "Note Title";
},
controllerAs: "card"
nw-card.html
<div class="card">
<h2 class="h3">{{card.header}}</h2>
</div>

References our controllers scope, which our


directive already has access to, so no alias needed.
nw-card.js
controller: function($scope) {
$
$scope.header = "Note Title";
}.

nw-card.html
<div class="card">
<h2 class="h3">{{header}}</h2>
</div>

Either Syntax Will Work


nw-card.js

nw-card.js
controller: function($scope) {
this.header = "Note Title";
},
controllerAs: "card"

controller: function($scope) {
$
$scope.header = "Note Title";
}.
nw-card.html

nw-card.html
<div class="card">
<h2 class="h3">{{card.header}}</h2>
</div>

<div class="card">
<h2 class="h3">{{header}}</h2>
</div>

S a m e r e s u lt

Directives Level 2
Scope the Config Object Section 2

Current Code
nw-card.js
angular.module("NoteWrangler")
.directive("nwCard", function() {
return {
restrict: "E",
templateUrl: "templates/directives/nw-card.html",

$
};
});

controller: function($scope){
$scope.header = "Note Title";
}

We Want to Number the Notes

Understanding $scope
We are going to simplify our note example and add numbers to the headers.
notes.html
...
<div class="note-wrapper">
<a class="card-notes">
<nw-card></nw-card>
<nw-card></nw-card>
<nw-card></nw-card>
<nw-card></nw-card>
</a>
</div>
example/nw-card.html
<div class="card">
<h2 class="h3">{{header}}</h2>
</div>

nw-card.js
angular.module("NoteWrangler")
.directive("nwCard", function() {
var num = 1;
return {
restrict: "E",
templateUrl: "example/nw-card.html",
controller: function($scope){
$scope.header = "Note Title" + num++;
};
});

Inherited Scope Can Be a Problem

All the same number?!

Directives Inherit Their Parents Scope by Default


Our parent is the next directive up in the hierarchy that creates a new scope.
index.html

<html lang="en" ng-app="NoteWrangler">


<head>...</head>
<body>
<div class="nav-list">
<a href="#/notes"> Notes </a>
<a href="#/users"> Users </a>
</div>
<div class="hero-wrapper"> ... </div>
<div class="main-wrapper">
<div ng-view></div>
</div>
<script src="/js/vendor/jquery.js"></script>

div scope

header

nw-card scope

Directives Inherit Their Parents Scope by Default


This wouldnt work how we want it to, though, because of inherited scope.
nw-card.js
angular.module("NoteWrangler")
.directive("nwCard", function() {
var num = 1;
return {
restrict: "E",
templateUrl: "templates/directives/nw-card.html",
controller: function($scope){
$scope.header = "Note Title" + num++;
}
...

div scope

header

nw-card scope

Inherited Scope Can Be a Problem

Updating each header every time


controller: function($scope){
$scope.header = "Note Title" + num++;
}

Isolating the Scope


By passing an object to the scope option, you are creating an isolate scope. This tells the directive to
keep scope inside of itself and not to inherit or share with other scopes.
nw-card.js
angular.module("NoteWrangler")
.directive("nwCard", function() {
var num = 1;
return {
restrict: "E",
templateUrl: "templates/directives/nw-card.html",
scope: {},
controller: function($scope){
$scope.header = "Note Title" + num++;
}
...

G iv e t h is d ir e c t iv e
it s o w n s c o p e !

Now With Isolate Scope


With isolate scope our header isnt overriding itself each time the directive is used.

It s W O R K IN G !

Directives and Scope


s
e
v

i scope: false
t
c lt
e
r au
i
D f
d e Inherited Scope
div scope

scope: { }
Isolate Scope
div scope

header

nw-card scope

nw-card scope

header

If child adds a new property,


it gets stored on the parent.

If child adds a new property,


it gets stored on itself.
Child no longer has access to
the parents scope.

Isolate Scope Has No Access to Parent Scope


We no longer have access to notes from ngRepeat.
notes.html
...
<div class="note-wrapper">
<a class="card-notes" ng-repeat="note in notes" >
<nw-card></nw-card>
</a>
</div>
...

nw-card.html

No access!

<div class="card">
<h2 class="h3">{{note.header}}</h2>
</div>

How can we use isolate scope and still access our notes array with titles?

Passing Header Into the Directive


We can set the note instance to a header variable and print it out in the template.
notes.html
...
<div class="note-wrapper">
<a class="card-notes" ng-repeat="note in notes" >
<nw-card header="{{note.title}}"></nw-card>
</a>
</div>
...

Setting note.title
to a header var

nw-card.html
<div class="card">
<h2 class="h3">{{header}}</h2>
</div>

Using header var


in the template

But to do this, we need an extra step inside our directive.

Back in Our Controller


nw-card.js
angular.module("NoteWrangler")
.directive("nwCard", function() {
var num = 1;
return {
restrict: "E",
templateUrl: "templates/directives/nw-card.html",
scope: {},
controller: function($scope){
$scope.header = "Note Title" + num++;
}
...

We Need a Way to Pass In a Header


<nw-card header="{{note.title}}"></nw-card>
nw-card.js
angular.module("NoteWrangler")
.directive("nwCard", function() {
var num = 1;
return {
restrict: "E",
templateUrl: "templates/directives/nw-card.html",
scope: {}
...
We need to tell our directive it might receive a header
variable and, if so, to bind it to the scope.

Binding a String to Our Scope


We just need a little conguration.
nw-card.js
angular.module("NoteWrangler")
.directive("nwCard", function() {
return {
restrict: "E",
templateUrl: "templates/directives/nw-card.html",
scope: {
header: "@"
@ passes in a string
}.
};
});
<nw-card header="{{note.title}}"></nw-card>
There are 3 options when binding data to an isolate scope: @, =, and & characters.

Two-way Binding
So if the value gets changed, it gets changed everywhere.
nw-card.js
angular.module("NoteWrangler")
.directive("nwCard", function() {
return {
restrict: "E",
templateUrl: "templates/directives/nw-card.html",
scope: {
header: "=" = two-way binds an object
}.
};
});
<nw-card header="note.header"></nw-card>

No longer need brackets

We Can Add Multiple Bindings


nw-card.js
angular.module("NoteWrangler")
.directive("nwCard", function() {
return {
restrict: "E",
templateUrl: "templates/directives/nw-card.html",
scope: {
header: "=",
icon: "="
}.
};
});

Directives Getting Notes Data


We are passing in a header and icon, just like we would pass in variables to a function.
notes.html
...
<div class="note-wrapper">
<a class="card-notes" ng-repeat="note in notes" >
<nw-card header="note.header" icon="note.icon"></nw-card>
</a>
</div>
...

nw-card.html
<div class="card">
<h2 class="h3">{{header}}</h2>
<i class="icon icon-card {{icon}}"></i>
</div>

Now Our Directives are Flexy!


<nw-card header="note.title" icon="note.icon"></nw-card>
Later on we can use this same directive again, but with a dierent use case.
<nw-card header="user.name" icon="user.image"></nw-card>

Directives Getting Notes Data


nw-card.js

Dierence Between $scope and Scope Object


nw-card.js
angular.module("NoteWrangler")
.directive("nwCard", function() {
var num = 1;
return {
restrict: "E",
templateUrl: "templates/directives/nw-card.html",
scope: {},
controller: function($scope){
$scope.header = "Note Title" + num++;
}
...
Allows you to set values as
properties on our scope object.

Allows you to create an isolate


scope private to this directive.

Directives Level 2
Link Section 3

We Want to Add Hover Description

Displaying Description on Card Click


notes-index.html
...
<nw-card header="note.title"
description="note.description"></nw-card>

nw-card.html
<div class="card">
<h2 class="h3">{{header}}</h2>
<p class="hidden">{{description}}</p>
</div>
When someone clicks, we want to toggle .hidden class.

style.css
.card {
position: relative;
}
...
.card p.hidden {
display: none;
}

jQuery to Toggle the Description


nw-card.html
<div class="card">
<h2 class="h3">{{header}}</h2>
<p class="hidden">{{description}}</p>
</div>
To add this click behavior, we need to run this jQuery:
$("div.card").on("click", function() {
$("div.card p").toggleClass("hidden");
});

But where does this go?

Finding a Location for Our jQuery


It may work, but this is a bad practice.
jQuery selector is searching the entire DOM.
The controller may run before the element exists.
nw-card.js
angular.module("NoteWrangler")
.directive("nwCard", function nwCardDirective(){
return {
...
controller: function(){
$("div.card").on("click", function(){
$("div.card p").toggleClass("hidden")
}
Not in the Controller? Where then?
}
...

Introducing Link
The link function is run after our directive has been compiled and linked up.
nw-card.js
angular.module("NoteWrangler")
.directive("nwCard", function nwCardDirective(){
return {
link: function(){
$("div.card").on("click", function(){
$("div.card p").toggleClass("hidden")
}.
}/
...
This is the best spot to do any DOM manipulation or logic functionality for your directive!

Link Solved One Problem


Still bad though! We are still searching the entire DOM for these elements.
nw-card.js
angular.module("NoteWrangler")
.directive("nwCard", function nwCardDirective(){
return {
link: function(){
$("div.card").on("click", function(){
$("div.card p").toggleClass("hidden")
}.
}/
...
We need a way to access just the HTML elements in the template.

Link has an element parameter!

Links Element Parameter


Links rst two automatically injected parameters are scope and element.
nw-card.js
angular.module("NoteWrangler")
.directive("nwCard", function nwCardDirective(){
return {
link: function(scope, element){
$("div.card").on("click", function(){
$("div.card p").toggleClass("hidden")
}.
}/
nw-card.html
...
<div class="card">
<h2 class="h3">{{header}}</h2>
<p class="hidden">{{description}}</p>
</div>

Refers to the outermost element


of the included template.

Using Element to Not Search the DOM


nw-card.js
angular.module("NoteWrangler")
.directive("nwCard", function nwCardDirective(){
return {
link: function(scope, element){
element.on("click", function(){
element("div.card p").toggleClass("hidden");
};
}/
...

Now we're no longer searching the DOM!

Links Attrs Parameter


Link has another parameter: attrs, which stands for attributes.
nw-card.js
angular.module("NoteWrangler")
.directive("nwCard", function nwCardDirective(){
return {
link: function(scope, element, attrs){
element.on("click", function(){
Refers to the attributes
element("div.card p").toggleClass("hidden");
};
on the directive element.
console.log(attrs.header);
}/
notes-index.html
...
...
<nw-card header="note.title"
description="note.description"></nw-card>

Another Example Using Link


We also need to parse the description for the body of our note cards.

See the Markdown?

Include markdown.js Library


Well need it to parse the body of our nwCard directives.
index.html
<!DOCTYPE html>
<html lang="en" ng-app="NoteWrangler">
...
<div class="main-wrapper">
<div ng-view></div>
</div>
<!-- Vendors -->
<script src="/js/vendor/markdown.js"></script>
...

Use the Markdown Library


Use the library to change the body from Markdown to HTML.
index.html
nw-card.js

<nw-card header="note.title" body="note.description" ...></nw-card>

angular.module("NoteWrangler")
.directive("nwCard", function() {
return {
...
scope: {
header: "=",
body: "=",
...
},
link: function(scope, element) {
scope.body = markdown.toHTML( scope.body );

Problem: Tags in Our Body


Now we have a new problem: We seem to be getting the HTML tags in our output.
Angular doesnt know if it can trust this injected HTML.
How do we tell Angular its safe?
Well need to use two new things:
ngBindHtml
$sce

Step 1: Use ngBindHtml Attribute in Template


The {{}} bindings in templates wont insert HTML directly for safety reasons. We need to use the
ngBindHtml attribute to bind the body.
nw-card.html
<div class="card">
...
<div class="card-hidden">
<a ng-href="#/notes/{{id}}">{{body}}</a>
</div>
nw-card.html
</div>
<div class="card">
...
<div class="card-hidden">
<a ng-href="#/notes/{{id}}" ng-bind-html="body"></a>
</div>
</div>

Step 2: Use $sce Service in the Directive


We can tell Angular the HTML we want to insert is safe by using the $sce service.
nw-card.js

angular.module("NoteWrangler")
.directive("nwCard", function($sce) {
return {
...
link: function(scope, element) {
scope.body = $sce.trustAsHtml( markdown.toHTML(scope.body));
}
Strict Contextual Escaping service tells Angular, I trust this as HTML;
dont worry about escaping HTML that could be potentially unsafe.

Cards Now Have Beautiful Bodies

Services Level 3

5 Recipes and a Factory Section 1

Current State of Our App

Note Wrangler
index.html

css

js
app.js

controllers
app.js

lters
directives
templates

Controllers Currently Have A JAX $http Calls


notes-edit-controller.js
angular.module("NoteWrangler")
.controller("NotesEditController", function($scope, $http) {
$scope.updateNote = function(noteObj) {
$http({method: "PUT", url: "/notes", data: noteObj})
...

This code is ne, but its not reusable across other parts
of our application. It is also going to be harder to test.

Where should this code be then?

7 A Service for Your Data


Services should hold functions responsible for connecting and fetching data, and then sharing it
across our application.

Note Wrangler
Controller

index.html

css

js
app.js

controllers
directives
services
templates

Service

Service

function
function
function
function

Directive

Filter

7 A Service for Your Data


Services should hold functions responsible for connecting and fetching data, and then sharing it
across our application.

Note Wrangler
Controller

index.html

css

js
app.js

controllers
directives
services
templates

Service

Service

function
function
function
function

Directive

Filter

7 Creating and Including the Service File

Note Wrangler
index.html

css

js
app.js

controllers
directives

services
note.js

templates

Creat e

index.html
...
<ng-view>
<script src="vendor/jquery.js"></script>
<script src="vendor/angular.js"></script>
<script src="vendor/angular-route.js"></script>
<script src="/js/app.js"></script>
<script src="/js/routes.js"></script>
<!-- Services -->
<script src="/js/services/note.js"></script>
</body>
Include
</html>

7 5 Service Recipes
We need to choose a recipe. There are 5 total recipes that range in complexity and customization.
Factory and Provider are the two most commonly used for creating a Service.

Value
Factory
Service
Provider
Constant

2 most common

7 2 Most Used Service Recipes


Factory

Most common ly used

Used for sharing functions and objects across an application.

Provider

Commo nly used

Used for sharing methods and objects (like a Factory), but allows for conguration.

7 Creating a Service With the Factory Recipe


Use the Factory recipe to register the service with our app module.
services/note.js
angular.module("NoteWrangler")
.factory("Note", function NoteFactory() {
...
});

Remember the
naming convention
<name> + <recipe>

Factory recipe
angular.module("<ModuleName>")
.factory("<ServiceName>", function <ServiceName>Factory() {
return { <object containing shared functions> }
});

7 Populating Our Factory Service


notes-index-controller.js
angular.module("NoteWrangler")
.controller("NotesIndexController", function($scope, $http) {
$scope.notes = $http({method: "GET", url: "/notes"}); )};
});
services/note.js

Service

all
create

Inside our factory service well dene reusable


methods that make our $http calls.

7 Populating Our Factory Service


The all method gets a list of all the notes , and create posts a new note.
services/note.js
angular.module("NoteWrangler")
.factory("Note", function NoteFactory() {
return {
all: function() {
return $http({method: "GET", url: "/notes"}); )};
)},
create: function(note) {
return $http({method: "POST", url: "/notes", data: note});
)}
}
});

7Calling Our Factory Service


services/note.js
angular.module("NoteWrangler")
.factory("Note", function NoteFactory() {
return {
all: function() { return $http({method: "GET", url: "/notes"}); )},
...
notes-index-controller.js
angular.module("NoteWrangler")
.controller("NotesIndexController", function($scope) {
$http({method: "GET", url: "/notes"})
.success(function(data) {
$scope.notes = data;
});
});

7Calling Our Factory Service


Our notes controller now needs to use the notes factory.

notes-index-controller.js
angular.module("NoteWrangler")
.controller("NotesIndexController", function($scope) {
$http({method: "GET", url: "/notes"})
.success(function(data) {
$scope.notes = data;
});
});

7 Injecting Factory Service


To use methods from the service, we need to rst inject it into our controller.
notes-index-controller.js
angular.module("NoteWrangler")
.controller("NotesIndexController", function($scope, Note) {
.success(function(data) {
$scope.notes = data;
});
});

7 Calling Our Factory Service


Now our controller has access to, and can call, the notes factory.
notes-index-controller.js
angular.module("NoteWrangler")
.controller("NotesIndexController", function($scope, Note) {
Note.all()
.success(function(data) {
$scope.notes = data;
});
});
Notice Notes has no $. The $ prex is reserved for built-in Angular services.

Services Level 3

Another Factory Section 2

Our Problem
We want to show images for each of our users.

Each user should have a


glamorous photo!

One Solution Is Gravatar


Gravatar is a free service that allows people to upload images associated with an email address.
Then our application can use
the image associated with
our users email.

Providing Our App With Gravatar Images


In order to access someones Gravatar, we need to hash their email address and append it to a URL.
1. Hash users email address into a hash.
alyssa@codeschool.com

bf4ee76b5f3a6bfed26bca5460bc3f22

2. Add this hash onto a Gravatar URL.


http://www.gravatar.com/avatar/bf4ee...png
3. Use this URL in a template.
<img ng-src='http://...bf4ee...png'/>

Template

7Shared Functionality?! Service Time!


We need to generate these avatar links for each user and use them inside multiple controllers.

oller
Contr

Service

Controller

Template

a
Template

7Creating the Service


Lets use the Factory recipe again to create a Gravatar-image-grabbing service.
services/gravatar.js
angular.module("NoteWrangler")
.factory( "Gravatar",function GravatarFactory() {
return { };
});

7Customizable Avatar Size Variable


We need some reusable variables to generate each users avatar link. Creating these outside of our
factorys return will ensure they are private to this service.
services/gravatar.js
angular.module("NoteWrangler")
.factory("Gravatar", function GravatarFactory() {
var avatarSize = 80; // Default size
return { };
});

7Customizable Avatar Size Variable


services/gravatar.js
angular.module("NoteWrangler")
.factory("Gravatar", function GravatarFactory() {
var avatarSize = 80; // Default size
return { };
});

http://www.gravatar.com/avatar/bf4ee76b5f3a6bfed26bca5460bc3f22?size=80.png

?size=100

?size=500

We can customize the size of the image we


receive from Gravatar by adding the size
parameter to the end of the URL.

7Reusable Gravatar Link Variable


Along with a default avatar size to append to the link, we need a reusable link variable for the
Gravatar site.
services/gravatar.js
angular.module("NoteWrangler")
.factory("Gravatar", function GravatarFactory() {
var avatarSize = 80; // Default size
var avatarUrl = "http://www.gravatar.com/avatar/";
return { };
});

http://www.gravatar.com/avatar/bf4ee76b5f3a6bfed26bca5460bc3f22?size=80.png

Gravatar Steps
Back to the steps for how our app can use the Gravatar image:
1. Hash users email into a hash.
alyssa@codeschool.com

bf4ee76b5f3a6bfed26bca5460bc3f22

2. Add this hash onto a Gravatar URL.


http://www.gravatar.com/avatar/bf4ee...png
3. Use this URL in a template.
<img ng-src='http://...bf4ee...png'/>

Hashing the Email


Now for the rst step: how to hash-ify a users email.
1. Hash users email into a hash.
alyssa@codeschool.com

bf4ee76b5f3a6bfed26bca5460bc3f22

CryptoJS is a simple JavaScript library that has an .MD5 function. Pass this function a string and it
will return that string hash-ied.

CryptoJS.MD5(email)
Include this link to give us access to the above call:
index.html
<script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/md5.js"></script>

Gravatar Steps
Back to the steps for providing our application with Gravatar images:
1. Hash users email into a hash.

oalyssa@codeschool.com

bf4ee76b5f3a6bfed26bca5460bc3f22

2. Add this hash onto a Gravatar URL.


http://www.gravatar.com/avatar/bf4ee...png

7Generating Avatar Links With MD5 Hash


Right now our function is only returning the hashed user email. We need to append it to the link.
While we are generating the link, lets go ahead and append a default size.
services/gravatar.js
angular.module("NoteWrangler")
.factory("Gravatar", function GravatarFactory(){
var avatarSize = 80; // Default size
var avatarUrl = "http://www.gravatar.com/avatar/";
return {
generate: function(email){
return avatarUrl + CryptoJS.MD5(email) + "?size=" + avatarSize.toString();
}.

};

});

7Using Gravatar URL Service in Template


services/gravatar.js
angular.module("NoteWrangler")
.factory("Gravatar", function GravatarFactory(){
var avatarSize = 80; // Default size
var avatarUrl = "http://www.gravatar.com/avatar/";
return {
generate: function(email){
return avatarUrl + CryptoJS.MD5(email) + "?size=" + avatarSize.toString();
}.

};

});

To call this service, we need to inject it, and then call the generate function.

Gravatar.generate("alyssa@codeschool.com")

7Simplifying Our One Function Factory


services/gravatar.js
angular.module("NoteWrangler")
.factory("Gravatar", function GravatarFactory(){
var avatarSize = 80; // Default size
var avatarUrl = "http://www.gravatar.com/avatar/";
return function(email){
return avatarUrl + CryptoJS.MD5(email) + "?size=" + avatarSize.toString();

};

});

If a factory service only has one function, we can simplify it.


Gravatar("alyssa@codeschool.com")

m Calling Factory Service in a Controller


users-index-controller.js
angular.module("NoteWrangler")
.controller("UsersIndexController", function($scope, Gravatar) {
$scope.gravatarUrl = function(email) {
return Gravatar(email);
}
});

a Templates Have Access to Users Gravatar URLs


Our templates now have access to each users Gravatar URL.
a templates/users/index.html

<div class="users-wrapper">
<a class="card-users" ng-repeat="user in users" ...>
<nw-card header="user.name"
image="gravatarUrl(user.email)"
body="user.bio"
id="user.id">
</nw-card>
</a>
</div>

scope: {
image: "=",
...
}

a Now We Can Display the Image


Use ngSrc to load it.
a templates/directives/nw-card.html

<div class="card" title="{{header}}">


<img ng-src='{{image}}' ng-if='image'>
<div ng-if='icon'>
<i class="icon icon-card {{icon}}"></i>
</div>
<h2 class="h3">{{header}}</h2>
...
</div>

Users now have


glamorous photos

75 Service Recipes
- Value

Used often

The simplest service recipe used for sharing a value that is used throughout your app repeatedly.

- Factory

Most common ly used

Shares methods and objects.

- Service

Rarely used
Shares instances of methods and objects.

- Provider

Commo nly used

Shares methods and objects (like a Factory), but allows for conguration.

- Constant

Used less often

Shares a value within application conguration.

Services Level 3
Provider Section 3

Current Gravatar Factory


We used a factory to generate our Gravatar URL.
services/gravatar.js
angular.module("NoteWrangler")
.factory("Gravatar", function GravatarFactory() {
var avatarSize = 80; // Default size
var avatarUrl = "http://www.gravatar.com/avatar/";
return function(email){
return avatarUrl + CryptoJS.MD5(email) + "?size=" + avatarSize.toString();
};
});
The problem with this is our avatarSize is hard coded and not congurable.
The solution is simple: Use the Provider recipe instead!

7Provider Service Recipe


The solution is simple: Use the Provider recipe instead!

- Factory

Used for sharing functions and objects across an application.

- Provider

Shares methods and objects (like a Factory), but allows for conguration.
.provider("<Service Name>", function <Service Name>Provider() {
this.$get <function or object of functions>
});

All providers define a $get function

7Provider Service Recipe


The $get function is where you return an object and inject services.
.provider("example", function exampleProvider() {
this.$get = function($http){};
});
Providers run before everything else, so the only thing you can inject into them is other providers.
.provider("example", function exampleProvider(otherProvider) {
this.$get <function or object of functions>
});

7Provider Service Recipe


In order to turn our Gravatar service into a provider, we need to dene a $get function.
services/gravatar.js
angular.module("NoteWrangler")
.provider("Gravatar", function GravatarProvider() {
var avatarSize = 80; // Default size
var avatarUrl = "http://www.gravatar.com/avatar/";
this.$get = function() {
return function(email){
return avatarUrl + CryptoJS.MD5(email) + "?size=" + avatarSize.toString();
}
};
});

m Calling the Provider Service in a Controller


Calling the provider is just like calling the factory.
users-index-controller.js
angular.module("NoteWrangler")
.controller("UserIndexController", function($scope, Gravatar) {
$scope.gravatarUrl = function(user) {
return Gravatar(user.email);
}
});

But how do we do the configuration bit?

Setting Congurable Items


We need to dene a set method for each item we want to be congurable.
services/gravatar.js
angular.module("NoteWrangler")
.provider("Gravatar",function GravatarProvider() {
var avatarSize = 80; // Default size
var avatarUrl = "http://www.gravatar.com/avatar/";
this.setSize = function(size) {
avatarSize = size;
}.
this.$get = function(){...}
});

Calling the Conguration Method


We set these congurable items in our app.js le that will run before everything else.
app.js
angular.module("NoteWrangler", ["ngRoute", "Gravatar" ])
.config(function (GravatarProvider) {
GravatarProvider.setSize(100);
});
services/gravatar.js
this.setSize = function(size) {
avatarSize = size;
}.
...

Notice we pass in
GravatarProvider
not just Gravatar

Gravatar Provider Works

Reusable Directives Level 4

Helping Child and Parent Communicate Section 1

Displaying Only Categories of Note


Our notes index page currently displays all our notes. Thats great, but wouldnt it be even better if
we could only display certain categories of notes at once?

Creating a Category Select Directive


index.html
...
<nw-category-select></nw-category-select>
nw-category-select.js
angular.module("NoteWrangler")
.directive("nwCategorySelect", function() {
return {
replace: true,
restrict: "E",
templateUrl: "/templates/directives/nw-category-select.html"
};
});

nw-category-select.js
angular.module("NoteWrangler")
.directive("nwCategorySelect", function() {
return {
...

};
});

Retrieving a List of Categories


Our CategorySelect needs access to the list of categories, so lets use a category service to do this.
nw-category-select.js
angular.module("NoteWrangler")
.directive("nwCategorySelect", function(Category) {
return {
...
controller: function($scope) {},
link: function(scope, element, attrs) {}
};
});

Should we do this in
controller or link ?

Creating Our Link Function


Inside our link function, we will call the Category Service we created to populate the category select.
nw-category-select.js
angular.module("NoteWrangler")
.directive("nwCategorySelect", function(Category) {
return {
...
link: function(scope, element, attrs) {
scope.categories = Category.query();
}
};
});

Data access should be done


from the link function

Mocking Up Templates
This directive will be a composite directive with multiple nested templates.
category select

category select items

nw-category-select.html

nw-category-item.html

Our child directives are going to have additional functionality,


which is why we decided to do it this way.

Templating the Outer Parent Directive


category select

category select items

nw-category-select.html
<div class="sort-menu">
<h2>Categories</h2>
<div class="card">
//Child Select Items Go Here
</div>
</div>

nw-category-item.html

Calling the Inner Child Directive


To nest templates, we simply add the child directives HTML tag inside the parents view.
category select

nw-category-select.html

<div class="sort-menu">
<h2>Categories</h2>
<div class="card">
<nw-category-item
category select items
ng-repeat="category in categories"
category="category">
</nw-category-item>
</div>
</div>
nw-category-item.html

This will loop through each category,


creating a select item for each of them

Templating the Inner Child Directive


Now populate the category-item template with an icon and name.
category select

category select items

nw-category-select.html
<div class="sort-menu">
<h2>Categories</h2>
<div class="card">
<nw-category-item ... >
</nw-category-item>
</div>
</div>

nw-category-item.html
<a class="sort-menu-item">
<i class="icon left {{category.icon}}"></i>
{{category.name}} {{categoryCount()}}
</a>

Creating Our Inner Child Directive


Now we will create a category select item directive.
nw-category-item.js
angular.module("NoteWrangler")
.directive("nwCategoryItem", function() {
return {
restrict: "E",
templateUrl: "/templates/directives/nw-category-item.html"
};
});

Creating Our Inner Child Directive


Child directive is expecting a category to be passed to it, so lets bind it in the directives isolate scope.
nw-category-item.js
angular.module("NoteWrangler")
.directive("nwCategoryItem", function() {
return {
restrict: "E",
templateUrl: "/templates/directives/nw-category-item.html"
scope: {
category: "="
nw-category-select.html
}
...
};
<nw-category-item
});
ng-repeat="category in categories"
category="category">
</nw-category-item>

Displaying Categories
Categories are now showing up, but a couple of things need to happen in order to lter these notes
that are being displayed on index.html.

How do we set an activeCategory?

Where to Keep Track of Active Category


When a category is clicked, it needs to be set as the activeCategory. There can only be one active
category set at a time. If that category is active, it needs to change style.

nwCategorySelect

$scope.activeCategory
sample values

nwCategoryItem

"Testing"

nwCategoryItem

"Question"

nwCategoryItem
nwCategoryItem

"Best Practice"
"Code Snippet"

Where to Keep Track of Active Category


nwCategorySelect
nwCategoryItem
nwCategoryItem
nwCategoryItem
nwCategoryItem

$scope.activeCategory
sample values

"Testing"
"Question"

"Best Practice"
"Code Snippet"

We could pass activeCategory around and share a


value with everyone. However, thats not good
object-oriented code (separation of concerns).

Instead, were going to let nwCategorySelect manage the activeCategory and allow the inner
nwCategoryItems to get or set the active value when they need to.

Dening Our setActiveCategory Function


Create setActiveCategory function that will assign clicked category to the activeCategory variable.
nw-category-select.js
angular.module("NoteWrangler")
.directive("nwCategorySelect", function(Category) {
return {
...
controller: function($scope) {
this.setActiveCategory = function(category){
$scope.activeCategory = category;.
...

Inner categoryItems Need Parents Method


When a nwCategoryItem is clicked, it needs to be able to call the parents setActiveCategory method.

nwCategorySelect
nwCategoryItem
nwCategoryItem
nwCategoryItem
nwCategoryItem

nw-category-select.js
...
controller: function($scope) {
this.setActiveCategory = function(category){
$scope.activeCategory = category .name;.
};
...

How do we get our child directives access to this method?

Requiring a Directive to Share Its Controller


Using require will allow us to share a controller between directives.
nw-category-item.js
angular.module("NoteWrangler")
.directive("nwCategoryItem", function() {
return {
nw-category-select.js
...
scope: {
controller: function($scope) { ... }
category: "="
},
require: "^nwCategorySelect"
The symbol`^` in front of the directive
};
name indicates its a parent directive.
});

Next, how do we use controller functions?

Requiring a Directive to Share Its Controller


Using require will allow us to share a controller between directives.
nw-category-item.js
angular.module("NoteWrangler")
.directive("nwCategoryItem", function() {
return {
...
scope: {
category: "="
},
require: "^nwCategorySelect"
};
});

Adding Links 4th Parameter


The required directives controller is passed in as links 4th parameter and can be named whatever
you would like.
nw-category-item.js
angular.module("NoteWrangler")
.directive("nwCategoryItem", function() {
return {
...
require: "^nwCategorySelect",
link: function(scope, element, attrs, nwCategorySelectCtrl) {
};
});

};

Creating a Method to Call From Template


First, we are going to create a makeActive function on the scope of our category items. Now, all we
need to do is call this method on category item click.
nw-category-item.js
angular.module("NoteWrangler")
.directive("nwCategoryItem", function() {
return {
...
require: "^nwCategorySelect",
link: function(scope, element, attrs, nwCategorySelectCtrl) {
scope.makeActive = function(){
nwCategorySelectCtrl.setActiveCategory( scope.category );
}.

...

};

Calling makeActive with ngClick


nw-category-item.js

...

scope.makeActive = function(){
nwCategorySelectCtrl.setActiveCategory( scope.category );
}.

Calling makeActive from category-items template using ngClick:


nw-category-item.html

<a class="sort-menu-item" ng-click="makeActive()" >


<i class="icon left {{category.icon}}></i> {{category.name}}
</a>

Creating categoryActive()
nw-category-select.js
angular.module("NoteWrangler")
.directive("nwCategorySelect", function(Category) {
return {
...
controller: function($scope) {
this.getActiveCategory = function() {
return $scope.activeCategory;
}

}
};
});

this.setActiveCategory = function(category) {...}


return this;

Creating a Method to Toggle Active Styles


Check to see if the current items category is the one that is active so classes can be toggled.
nw-category-item.js
...
link: function(scope, element, attrs, nwCategorySelectCtrl) {
scope.makeActive = function() {}
scope.categoryActive = function() {
return nwCategorySelectCtrl.getActiveCategory() === scope.category.name;
}.
nw-category-item.html
<a class="sort-menu-item" ng-click="makeActive()" >
<i class="icon left {{category.icon}}"></i> {{category.name}}
</a>

Creating a Click Event to Use categoryActive


nw-category-item.js

...

link: function(scope, element, attrs, nwCategorySelectCtrl) {


scope.makeActive = function() {}
scope.categoryActive = function() {
return nwCategorySelectCtrl.getActiveCategory() === scope.category.name;
}.

nw-category-item.html

Assigning a class based on the return of our categoryActive function.

<a class="sort-menu-item"
ng-click="makeActive()" ng-class="{'active': categoryActive()}" >
<i class="icon left {{category.icon}}"></i> {{category.name}}
</a>

Reusable Directives Level 4


Filters Section 2

Using a Filter to Finish the Job


Now that we have our nwCategorySelect directive, and active classes are working properly, we need
to lter the displayed notes based on the activeCategory.

Remember Filters?
A lter is an easy way to format our content before it gets printed in our template.

date
lowercase
uppercase
limitTo
orderBy

: Formats date to a string based on the requested format.


: Converts string to lowercase.
: Converts string to uppercase.
: Creates a new array or string containing only a specied number of elements.
: Orders a specied array by the expression predicate (numerical, alphabetical).

{{ date_expression | date : format }}


{{ 603606171000

| date : "MM/dd/yyyy" }}

02/16/1989

The lter Filter


Selects a subset of items from an array and returns it as a new array.
The lter method takes a string, object, or function:

{{ ["Alyssa", "Nicoll", "loves", "Angular", "to", "pieces"] | filter:"a" }}

In this case, it selects all the words containing a:

["Alyssa","Angular"]
Were using it to search our array

Filtering an Array of Objects


var notesArray =
[{
"UserId": user.id,
"category": {"name": "Question",
"icon": "question"},
"description" : "Define Service",
"title" : "What is a Service",
"content": "Service: Angular,
},{
"UserId": user.id,
"category": {"name": Best Practice",
"icon": thumbs up"},
"description": "NgModel Best Practice",
"title" : "NgModel BP",
"content" : "Always use dot synt
}]

If we only want notes that have a category.name


of Question, we would lter like this:
filter:{category: {name: 'Question' }}

Filtering our Notes Array


index.html
...
<div class="note-wrapper">
<a ng-repeat="note in notes | filter:{category: {name: 'Question' }}"
ng-href="#/notes/{{note.id}}" class="card-notes" >
<nw-card ... ></nw-card>
</a>
</div>
<nw-category-select></nw-category-select>

Filter Returns an Array


Filter returns a new notesArray which contains the proper category name.
var notesArray =
[{
"UserId": user.id,
"category": {"name": "Question",
"icon": "question"},
"link" : "",
"description" : "Define Service",
"title" : "What is a Service",
"content": "Service: Angular,
},{
"UserId": user.id,
"category": {"name": Best Practice",
"icon": thumbs up"},
"link" :https://www.youtu,
"description": "NgModel Best Practice",
"title" : "NgModel BP",
"content" : "Always use dot synt
}]

var notesArray =
[{
"UserId": user.id,
"category": {"name": "Question",
"icon": "question"},
"link" : "",
"description" : "Define Service",
"title" : "What is a Service",
"content": "Service: Angular,
}]

Using lter to Sort Notes


We need to lter notes dynamically, so we use the activeCategory to designate which category is
currently selected.
index.html
...
<div class="note-wrapper">
<a ng-repeat="note in notes | filter: {category: {name: activeCategory}}"
ng-href="#/notes/{{note.id}}" class="card-notes" >
<nw-card ... ></nw-card>
</a>
2-way binding
</div>
<nw-category-select active-category="activeCategory"></nw-category-select>
We need to pass the activeCategory into the nwCategorySelect,
so the value is shared through 2-way binding.

Adding 2-way Binding for activeCategory


We need to create an isolate scope and give it access to the activeCategory.
nw-category-select.html
<nw-category-select active-category="activeCategory"></nw-category-select>
nw-category-select.js
angular.module('NoteWrangler')
.directive("nwCategorySelect", function(Category) {
return {
...
scope: {
activeCategory: "="
}
};
});

Filt

er

is W
ork
ing!

Lets Add a Search Box

Searching With lter Filter


We can use lter Filter to create a quick and easy search!
index.html
<input ng-model="search.title"/>
...
<div class="note-wrapper">
<a class="card-notes" ng-repeat="note in notes | filter:search"
ng-href="#/notes/{{note.id}}">
<nw-card ... ></nw-card>
</a>
</div>
<nw-category-select active-category="currentCategory" notes="notes">
</nw-category-select>
This will properly search on just the title values.

Searching With lter Filter

Searching With a Wildcard


We can use $ to search across all properties on our notes.
index.html
<input ng-model="search.$"/>
...
<div class="note-wrapper">
<a class="card-notes" ng-repeat="note in notes | filter:search"
ng-href="#/notes/{{note.id}}">
<nw-card ... ></nw-card>
</a>
</div>
<nw-category-select active-category="currentCategory" notes="notes">
</nw-category-select>

Searching With lter Filter

Chaining Filters
What if we want to use two dierent lters on the same ngRepeat?
index.html
<input ng-model="search.$"/>
...
<div class="note-wrapper">
<a class="card-notes"
ng-repeat="note in notes | filter:activeCategory | filter:search"
ng-href="#/notes/{{note.id}}">
Using another pipe
<nw-card ... ></nw-card>
separate filters
</a>
</div>
<nw-category-select active-category="activeCategory" notes="notes">
</nw-category-select>

to

Chaining lters

Reusable Directives Level 4


External Libraries Section 3

Creating a Directive for 3rd-Party Plugins


What an ugly tooltip! Lets replace this tooltip with a prettier Bootstrap tooltip. We can create a
directive to accomplish this!

Including Bootstraps tooltip.js


Before we create a directive to use a 3rd-party plugin, lets include the plugin.
index.html
<script src="/js/vendor/bootstrap.js"></script>

Creating a Title Attribute Directive


Naming this directive title will allow us to override the HTML title attribute and replace the hideous
default tooltip. When creating a directive to override a default, do not give a namespace.
title.js

<div class="card" title="{{header}}">

angular.module("NoteWrangler")
.directive("title", function() {
return {
restrict: "A",
link: function(scope, element) {
// Append Custom Tooltip here
}.
};
});

By def ault , dire ctiv es


are rest rict ed to
attr ibut e 'A' type and
are ther efo re redu ndan t

Alternative Link Syntax in the Directive!


If a directive is only returning the link function, there is an alternative way to write it.
title.js
.directive("title", function() {
return {
link: function(scope, element) {
};
});

}.

The two became one

title.js
.directive("title", function() {
return function(scope, element) {
};
});

title.js
.directive("title", function() {
return function(scope, element) {
};
});

Adding tooltip Code to link Function


Now we will call Bootstraps .tooltip() method on our element inside link and pass it container:body.
title.js
angular.module("NoteWrangler")
.directive("title", function() {
return function(scope, element) {
element.tooltip({ container: "body" });
};
});

Current Default tooltip


We already have a title on our cards to display their headers on hover in a tooltip.
title.js
angular.module("NoteWrangler")
.directive("title", function() {
return function(scope, element) {
element.tooltip({ container: "body" });
};.
});

A Problem With Our New Tooltip


When we refresh, we get our new tooltip to replace the old one, but our header is no longer being
interpolated Whats happening here?

title.js
angular.module("NoteWrangler")
.directive("title", function() {
return function(scope, element) {
element.tooltip({ container: "body" });
};.
});
Because link is only run one time, it runs before Angular had the value for
{{header}} and then the tooltip is stuck with {{header}} as its text for all time.

Adding the Attribute Directive to the Template


This is easily remedied by calling $timeout. This will cause Angular to run through an entire event
loop before replacing our tooltip.

title.js
angular.module("NoteWrangler")
.directive("title", function() {
return function(scope, element, $timeout) {
$timeout(function(){
element.tooltip({ container: "body" });
});
};.
});

IC E
BEST PRACT

Directives Should Clean Up After Themselves


Our tooltip now works, but as a best practice, we need to clean up after our directive.

title.js
angular.module("NoteWrangler")
.directive("title", function() {
return function(scope, element, $timeout) {

...

scope.$on('$destroy', function() {
element.tooltip('destroy');
});

};
});

A clean DOM is a happy DOM


Destroy method is called whenever the directive is removed (o hover) and its scope is destroyed.

When to Use Controller/Link


Inside directives, it is best practice to use controller only when you want to share
functions with other directives. All other times you should use link.
angular.module("NoteWrangler")
.directive("nwExample", function() {
return {
controller: function($scope) { },
link: function(scope, element) { }
};
});

Das könnte Ihnen auch gefallen