https://github.com/danielabar/hands-on-angular-tuts
Learning Angular and Rails with Tuts Plus
https://github.com/danielabar/hands-on-angular-tuts
Last synced: 3 months ago
JSON representation
Learning Angular and Rails with Tuts Plus
- Host: GitHub
- URL: https://github.com/danielabar/hands-on-angular-tuts
- Owner: danielabar
- Created: 2014-12-26T20:58:44.000Z (over 11 years ago)
- Default Branch: master
- Last Pushed: 2015-01-02T19:46:32.000Z (over 11 years ago)
- Last Synced: 2025-12-20T02:50:22.549Z (6 months ago)
- Language: JavaScript
- Size: 492 KB
- Stars: 1
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
Hands on Angular
==========
> Learning Angular and Rails with Tuts Plus [course](https://code.tutsplus.com/courses/hands-on-angular).
## Protractor E2E Testing
First install Protractor
```bash
npm install protractor --save-dev
```
Use Protractor bin to install or update Selenium web driver
```bash
./node_modules/protractor/bin/webdriver-manager update
```
Configuration
```bash
cp ./node_modules/protractor/example/conf.js test/e2e.conf.js
```
Start web driver
```bash
./node_modules/protractor/bin/webdriver-manager start
```
Open another tab and run tests
```bash
./node_modules/protractor/bin/protractor test/e2e.conf.js
```
## Testing with Rails
Edit `Gemfile` add a development section
```ruby
group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem 'byebug'
# Access an IRB console on exception pages or by using <%= console %> in views
gem 'web-console', '~> 2.0'
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
gem 'spring'
# Testing
gem 'rspec-rails'
gem 'capybara'
gem 'selenium-webdriver'
end
```
Install new dependencies
```bash
bundle
```
Initialize rspec
```bash
rails g rspec:install
```
Run tests
```bash
rspec spec/features/navigation_spec.rb
```
## Repeater
`ng-repeat-start` and `ng-repeat-end` can be used to prolong the scope of `ng-repeat` iterator in a template.
[Example](sw_front/app/views/edges.html)
## Filter
By default, piping the output of a repeater through `filter` searches through ALL attributes
```html
```
To only search a single attribute, for example `name`
```html
```
Custom filter [example](sw_front/app/scripts/filters/edges.js) and [unit test](sw_front/test/spec/filters/edges.js).
## Select
To have angular populate a select box with options. For example, given that `categories` is in scope:
```html
```
## HTTP
Use Angular's http mock to unit test components that make a ajax requests.
To use it, inject `$httpBackend` into controller unit tests. [Example](sw_front/test/spec/controllers/edges.js)
When working with mock http, its a good idea to make sure there are no leftover pending requests.
Use `afterEach` to verify `verifyNoOutstandingExpectation` and `verifyNoOutstandingRequest`.
To simulate an http request in a test
```javascript
http.expectGET('/api/edges');
http.flush();
```
## Rails Controller
So that angular front end app can make /api calls to get edges.
First add api namespace to [routes](sw-backend/config/routes.rb)
Add a [controller](sw-backend/app/controllers/api/edges_controller.rb) and [controller test](sw-backend-spec/controllers/edges_controller_spec.rb)
## Rails Models
Generate the models
```bash
rails g model edge name description:text category:references
rails g model requirement name value mode
rails g model category name
```
Setup app and test databases
```bash
rake db:migrate
rake db:test:prepare
```
Use Active Model Serializer to generate JSON representation of models.
Add it to [gemfile](sw-backend/Gemfile), then run `bundle`
Generate
```bash
rails g serializer edge
rails g serializer requirement
rails g serializer category
```
Edit the generated `{model}_serializer.rb` files to specify attributes.
If any changes are made to `{timestamp}_create_{model}.rb` files to modify the database schema, run
```bash
rake db:drop
rake db:migrate
rake db:test:prepare
```
By default, rails active model serializer will generate root element. But angular app expects a list of results without a root element.
To fix this, specify `root: false` in rails controller
```ruby
module Api
class EdgesController < ApplicationController
def index
render json: Edge.all, root: false
end
end
end
```
## Express backend
Having trouble with rails active model serializer not following edge relationships and generating expected json.
To not get stuck, use express back end
```bash
cd sw-express
DEBUG=myapp ./bin/www
```
## Angular Forms
Example [view](sw_front/app/views/login.html) and [controller](sw_front/app/scripts/controllers/login.js)
To activate Angular form validations, form must have a name AND must disable HTML5 form validation.
The form name will appear as a property on $scope, which is then available in the controller.
```html
...
```
To have Angular control the form submission, use `ng-submit` directive
```html
...
```
To check if form is valid in a controller
```javascript
$scope.login = function() {
if ($scope.loginForm.$valid) {
// do something with form data...
}
}
```
Optionally, can choose to display error messages to user only when form is submitted.
To disable submit form button until form is valid
```html
Log In
```
## Angular Directive
Preferred approach is to display error messages on blur, i.e. when field loses focus.
This requires a [custom directive](sw_front/app/scripts/directives/cu-focus.js).
To use angular models in a directive, use `require: 'ngModel'.
This makes the model controller available as fourth argument in the `link` function.
See Angular [Directive doc](https://docs.angularjs.org/guide/directive) for more details, specificlly, "Creating Directives that Communicate".
`link` function in directive allows us to listen to events such as `blur`.
### Naming
camelCase is used javascript
```javascript
angular.module('swFrontApp')
.directive('cuFocus', function () {
return {
restrict: 'A',
require: 'ngModel',
// rest of logic goes here...
};
});
```
But snake-case is used in html
```html
```
## Token Based Authentication
Steps:
* Client sends credentials (such as email or username and password) to server, for example, via login form.
* Server checks if credentials are valid, generate unique token, saves it (eg: in a database), and returns token to client
* Client saves this token somewhere (cookie or local storage)
* Client includes this token on every subsequent request to server
* Server checks for token on every resource that requires authentication
### Angular HTTP Interceptor
Can configure an interceptor to intervene in all ajax requests, and modify request or response, success or error cases.
Interceptor is implemeted as a `factory`. For example, to check all response errors for 401 and redirect user to login page:
```javascript
.factory('myCustomHttpInterceptor', function($q, $location) {
return {
'response': function(response) {
return response;
},
'responseError': function(rejection) {
if (rejection.status === 401 && $location.url() !== '/login') {
$location.url('/login');
} else {
return $q.reject(rejection);
}
}
};
```
Can also use interceptor to modify every request, for example, suppose an auth_token is persisted in local storage,
and would like to include it as http header for every ajax request
```javascript
.factory('myCustomHttpInterceptor', function($q, $location) {
return {
'request': function(config) {
config.headers = config.headers || {};
if (localStorage.auth_token) {
config.headers.token = localStorage.auth_token;
}
return config;
}
};
```
To make your custom http interceptor actually be used by angular, use $httpProvider.
This would go somewhere in `app.js` where main Angular application (routes etc) is configured.
```javascript
.config(function($httpProvider) {
$httpProvider.interceptors.push('myCustomHttpInterceptor');
})
```
## ngResource
To create a new resource using ngResource
```html
```
```javascript
// services/edgeresource.js
angular.module('swFrontApp')
.factory('EdgeResource', function ($resource) {
return $resource('/api/edges');
});
// controllers/edge.js
angular.module('swFrontApp')
.controller('EdgesCtrl', function ($scope, EdgeResource) {
$scope.newEdge = new EdgeResource;
});
```
To delete a resource
```javascript
var deleteEdge = function(edge) {
edge.$delete();
}
```
But this only works, given that resource is defined as follows, specifying what object attribute to use for id, for example to use name as id
```javascript
angular.module('swFrontApp')
.factory('EdgeResource', function ($resource) {
return $resource('/api/edges/:id', {id: '@name'});
});
```
## Refactoring
Angular controllers tend to get big, means they're doing too much.
Look at the html view and determine if it can be split up into multiple templates and controllers, each with a single responsibility.