Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Example Branch #1

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions EVENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
## Event Passing

Angular's event model allows different, loosely connected $scopes to communicate with each other without knowing the details of the $scopes they are communicating with.

### Listening for Events

To receive an event, simply register the listener on the $scope you wish to receive the events for using the $on method. For instance, inside of a given controller:

```
self.handleCustomEvent = function( $event, eventParameter ) {
console.log("Received a custom.event message with parameters " + eventParameter );
$scope.lastCustomEventParameter = eventParameter;
} );

$scope.$on( 'custom.event', self.handleCustomEvent );
```

Event listeners are typically used within the context of a controller. When this is true, they have the same lifespan as the controller, and are removed when the $scope they are attached to is destroyed. However, it is also possible to listen for events from elsewhere by attaching the event handler to $rootScope. This allows for event passing between services as well as controllers, and makes it easy for unrelated services to effect each others' state information.

```
$rootScope.$on( 'custom.event', function( $event, data ) {
console.log("Received a custom.event on $rootScope!", data );
} );
```

_IMPORTANT NOTE_: event handlers attached to $rootScope.

### Raising Events

Reference material [here](https://docs.angularjs.org/api/ng/type/$rootScope.Scope).

There are two ways to send events from a given $scope, with very different use cases depending on the scope tree you are using.

Imagine a scope tree with this structure (but imagine that it is sparkly and not built from ASCII characters):

```
$rootScope
|...A
. |...B
. |...C
. . |...D
. . |...E
. . . |...X
. . . |...Y
. . . |...Z
. |...F
. . |...G
. . |...H
. |...I
|...J
|...K
```

Now consider two different situations. In one, a child scope -- for instance, a search result -- needs to inform a parent scope that something has happened. In this case, use the $emit method. This passes the message UP the hierarchy, terminating at $rootScope.

Example:

```
$scope.$emit( 'custom.event', 'Test');
```

If the $scope associated with Z $emits the event, the message will be applied to the $scopes E, C, A, and $rootScope.

If the $scope associated with I $emits the event, it will be applied to the $scopes J and $rootScope.

In the other situation, a parent $scope needs to broadcast an event to a child $scope. In this case, use `$scope.$broadcast`. This sends the given event downward into the $scope hierarchy.

Example:

```
$scope.$broadcast( 'custom.event', 'Test');
```

For instance, if the $scope C broadcasts the event via `$scope.$broadcast( 'custom.event', "Test" )`, the event will be applied to scopes D, E, X, Y, and Z.

_IMPORTANT NOTE_: the higher in the scope hierarchy an event is broadcast from, the more expensive the operation is.





153 changes: 153 additions & 0 deletions SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# Recap

## Encapsulating angular components inside of requireJS AMDs

An AMD is just a container that declares its own requirements. The `define` method declares the module using two parameters.

```
define( requirements, definition );
```

The first parameter `requirements` is an array of other modules this module needs to work, and the second parameter is a function that accepts those required modules after they've been initialized, instantiates the module, and returns it.

```
define( [ 'angular', /* I need angular */
'LocalStorageModule', /* I also want to use local storage */
'SomeModule', /* I want another module */
'lib/SomeOtherModule' ], /* And another one */
function( angular, LocalStorageModule, SomeModule, SomeOtherModule ) {

var angularModule = /* do work here */;
return angularModule;

} );
```

To create the angular component inside the AMD, you simply declare the constructor for that component as a function:

```
var MyAngularModule = function( $rootScope, $http, $timeout, localStorageService ) {
/* define the angular component here */
};
```

Then we tell angular which other angular components this component needs to use using the explicit direct injection property, `$inject`:

```
MyAngularModule.$inject = [ '$rootScope', '$http', '$timeout', 'localStorageService' ];
```

These angular components will be directly injected into your component's constructor. _IMPORTANT_: you must make sure that the list of components in `$inject` match the parameters of your component constructor exactly!

### Putting It Together

An empty component declaration is just a RequireJS define block that contains an AngularJS module declaration and returns it, and looks just like this:

```
define( [ 'angular', /* I need angular */
'LocalStorageModule', /* I also want to use local storage */
'SomeModule', /* I want another module */
'lib/SomeOtherModule' ], /* And another one */
function( angular, LocalStorageModule, SomeModule, SomeOtherModule ) {

var MyComponent = function( $rootScope, $http, $location, $timeout ) {

/* here is where the good stuff goes */
};

MyComponent.$inject = [ '$rootScope', '$http', '$location', '$timeout' ];

return MyComponent;

} );
```

This pattern works for angular services, controllers, filters, and directives.

## Services are Simple

Services (also [providers and factories](http://stackoverflow.com/questions/15666048/angularjs-service-vs-provider-vs-factory)) are singletons that maintain their state across the entire lifespan of the application.

Our most common use for services is to act as an API client. Services could also be used to provide utility functions, manage application state across controllers, or many many many other things.

When using an angular service as an API client, the logic can be extremely straightforward, like this:

```
var MyService = function( $http ) {

this.getSomeData = function( whichData ) {
return $http.get( "https://api.wherever.com/data/" + whichData );
};

this.postSomeData = function( whichData, dataValue ) {
return $http.post( "https://api.wherever.com/data/" + whichData, dataValue );
};
};

MyService.$inject = ['$http'];
```

_Interesting Topic_: many people prefer to use Angular's ngResource module (`$resource`) to interact with APIs. You can find out more about ngResource [here](https://docs.angularjs.org/api/ngResource/service/$resource).

### More Sophisticated API clients

Most API interaction is more complicated than the example above. It is often desirable to use `$http` directly (documentation [here](https://docs.angularjs.org/api/ng/service/$http)).

```
var MyBetterService = function( $http ) {
var config = angular.module("config");

/* Get a TV show by its ID. Resolves with the raw $http response. */
this.getTVShow = function( tvID ) {
return $http( {
method: "GET",
url: config.apiUrl + "/tv/" + tvID,
params: {
api_key: config.apiKey
}
} );
};

/* Get details about a specific season of a TV show by its tv ID and the season ID. Resolves with the raw $http response. */
this.getTVShowSeason = function( tvShowID, tvShowSeasonID ) {
return $http( {
method: "GET",
url: config.apiUrl + "/tv/" + tvShowID + "/season/" + tvShowSeasonID,
params: {
api_key: config.apiKey,
language: "es"
}
} );
};
};

MyService.$inject = ['$http'];
```

### Transforming API Response Data using promise chaining

Sometimes you will want to extract specific data from an API call, or preprocess that data in some way. You could always do this sort of work in a controller, but this tends to produce code that is tangled and difficult to maintain. In general it is best to process the data the comes back from an API endpoint *before* you return it to the caller.

For example, imagine that you want to retrieve only the list of seasons from TMDBAPI's [tv endpoint](http://docs.themoviedb.apiary.io/#reference/tv/tvid/get).

```
this.getTVShowSeasonList = function( tvID ) {
var requestPromise = $http( {
method: "GET",
url: config.apiUrl + "/tv/" + tvID,
params: {
api_key: config.apiKey
}
} );

return requestPromise.then( function( response ) {
// Our television show information is in response.data

var seasons = [];
angular.forEach( response.data.seasons, function( season ) {
seasons.push( season );
} );
return seasons;
} );
};
```
8 changes: 5 additions & 3 deletions src/main/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ define([ 'angular',
'config/config',
'ngRoute', 'ngResource', 'LocalStorageModule',
'tmdb/services/TMDBAPIService',
'tmdb/services/AppStateService',
'tmdb/partials/search/SearchController',
'tmdb/partials/home/HomeController',
'tmdb/partials/movie/MovieController',
Expand All @@ -41,7 +42,7 @@ define([ 'angular',
'tmdb/directives/awesomeSearch',
'tmdb/directives/awesomeSearchResults'],
function( angular, config, $resource, $location, LocalStorageModule,
TMDBAPIService, SearchController, HomeController, MovieController,
TMDBAPIService, AppStateService, SearchController, HomeController, MovieController,
PersonController, AwesomeSearchController, AwesomeSearchResultsController,
RemoteImageLoader, searchDirective, popularMoviesDirective,
personDetailDirective, personCrewDirective, personCastDirective,
Expand All @@ -63,7 +64,7 @@ define([ 'angular',
}]);

app.service( "TMDBAPIService", TMDBAPIService);

app.service( "AppStateService", AppStateService );

app.controller( "AwesomeSearchResultsController", AwesomeSearchResultsController );
app.directive( "awesomeSearchResults", awesomeSearchResultsDirective );
Expand Down Expand Up @@ -92,6 +93,7 @@ define([ 'angular',
$routeProvider.when( '/', { templateUrl: '/tmdb/partials/home/home.html', controller: 'HomeController' } );
$routeProvider.when( '/movie/:id', { templateUrl: '/tmdb/partials/movie/movie.html', controller: 'MovieController' } );
$routeProvider.when( '/person/:id', { templateUrl: '/tmdb/partials/person/person.html', controller: 'PersonController' } );
$routeProvider.when( '/tv/:id', { templateUrl: '/tmdb/partials/tv/tv.html' } );
$routeProvider.otherwise( {
template: function() {
throw 'An internal error occurred because the given path does not resolve to a known route.';
Expand All @@ -101,4 +103,4 @@ define([ 'angular',

return app;
}
);
);
43 changes: 6 additions & 37 deletions src/main/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,56 +2,25 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<title>TMDB API Programming Test</title>
<title>Awesome TMDB App</title>

<!-- Default Styles (DO NOT TOUCH) -->
<link rel="stylesheet" type="text/css" href="/css/app.css">
<!-- RequireJS // -->
<script src="/vendor/requirejs/require.js" data-main="main"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.4.0/css/font-awesome.min.css">
<style type="text/css">
html
html
{
font-size: 14px;
}
/* the height of the header decides when to use the window scroll jquery function to start sticking the searchbox */
.header
{
background: #777;
color: #fff;
font-size: 3rem;
height: 200px;
padding-top: 2rem;
text-align: center;
width: 100%;
}
.sticky-search
{
background: #f2f2f2;
border: 1px solid #ccc;
box-sizing: border-box;
webkit-box-sizing: border-box;
moz-box-sizing: border-box;
outline: none;
padding: 1.5rem;
display: block;
font-size: 1.5rem;
width: 100%;
}
/* again this is just for demo purpose */
.bodyarea
{
display: block;
height: 2000px;
width: 100%;
}
</style>
</style>
</head>
<body>
<div class="wrapper">
<awesome-search/>
<div class="wrapper container">
<ng-view></ng-view>
</div>


<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<!-- Default JS (DO NOT TOUCH) -->
Expand Down
Loading