{"id":34258040,"url":"https://github.com/danielabar/hands-on-angular-tuts","last_synced_at":"2026-03-11T09:30:57.617Z","repository":{"id":25099905,"uuid":"28521029","full_name":"danielabar/hands-on-angular-tuts","owner":"danielabar","description":"Learning Angular and Rails with Tuts Plus","archived":false,"fork":false,"pushed_at":"2015-01-02T19:46:32.000Z","size":504,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-12-20T02:50:22.549Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/danielabar.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-12-26T20:58:44.000Z","updated_at":"2020-05-27T22:05:34.000Z","dependencies_parsed_at":"2022-08-23T19:01:03.933Z","dependency_job_id":null,"html_url":"https://github.com/danielabar/hands-on-angular-tuts","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/danielabar/hands-on-angular-tuts","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielabar%2Fhands-on-angular-tuts","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielabar%2Fhands-on-angular-tuts/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielabar%2Fhands-on-angular-tuts/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielabar%2Fhands-on-angular-tuts/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/danielabar","download_url":"https://codeload.github.com/danielabar/hands-on-angular-tuts/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielabar%2Fhands-on-angular-tuts/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30377269,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-11T06:09:32.197Z","status":"ssl_error","status_checked_at":"2026-03-11T06:09:17.086Z","response_time":84,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-12-16T14:56:29.116Z","updated_at":"2026-03-11T09:30:57.594Z","avatar_url":"https://github.com/danielabar.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"Hands on Angular\n==========\n\n\u003e Learning Angular and Rails with Tuts Plus [course](https://code.tutsplus.com/courses/hands-on-angular).\n\n## Protractor E2E Testing\n\nFirst install Protractor\n\n  ```bash\n  npm install protractor --save-dev\n  ```\n\nUse Protractor bin to install or update Selenium web driver\n\n  ```bash\n  ./node_modules/protractor/bin/webdriver-manager update\n  ```\n\nConfiguration\n\n  ```bash\n  cp ./node_modules/protractor/example/conf.js test/e2e.conf.js\n  ```\n\nStart web driver\n\n  ```bash\n  ./node_modules/protractor/bin/webdriver-manager start\n  ```\n\nOpen another tab and run tests\n\n  ```bash\n  ./node_modules/protractor/bin/protractor test/e2e.conf.js\n  ```\n\n## Testing with Rails\n\nEdit `Gemfile` add a development section\n\n  ```ruby\n  group :development, :test do\n    # Call 'byebug' anywhere in the code to stop execution and get a debugger console\n    gem 'byebug'\n\n    # Access an IRB console on exception pages or by using \u003c%= console %\u003e in views\n    gem 'web-console', '~\u003e 2.0'\n\n    # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring\n    gem 'spring'\n\n    # Testing\n    gem 'rspec-rails'\n    gem 'capybara'\n    gem 'selenium-webdriver'\n  end\n  ```\n\nInstall new dependencies\n\n  ```bash\n  bundle\n  ```\n\nInitialize rspec\n\n  ```bash\n  rails g rspec:install\n  ```\n\nRun tests\n\n  ```bash\n  rspec spec/features/navigation_spec.rb\n  ```\n\n## Repeater\n\n`ng-repeat-start` and `ng-repeat-end` can be used to prolong the scope of `ng-repeat` iterator in a template.\n[Example](sw_front/app/views/edges.html)\n\n## Filter\n\nBy default, piping the output of a repeater through `filter` searches through ALL attributes\n\n  ```html\n  \u003cli ng-repeat-start=\"edge in edges | filter:filterBy.search\"\"\u003e\n    ```\n\nTo only search a single attribute, for example `name`\n\n  ```html\n  \u003cli ng-repeat-start=\"edge in edges | filter: {name: filterBy.search}\"\u003e\n  ```\n\nCustom filter [example](sw_front/app/scripts/filters/edges.js) and [unit test](sw_front/test/spec/filters/edges.js).\n\n## Select\n\nTo have angular populate a select box with options. For example, given that `categories` is in scope:\n\n  ```html\n  \u003cselect name=\"searchByCategory\" id=\"searchByCategory\" class=\"form-control\"\n    ng-model=\"filterBy.category\"\n    ng-options=\"c.name for c in categories\"\u003e\n  \u003c/select\u003e\n  ```\n\n## HTTP\n\nUse Angular's http mock to unit test components that make a ajax requests.\nTo use it, inject `$httpBackend` into controller unit tests. [Example](sw_front/test/spec/controllers/edges.js)\n\nWhen working with mock http, its a good idea to make sure there are no leftover pending requests.\nUse `afterEach` to verify `verifyNoOutstandingExpectation` and `verifyNoOutstandingRequest`.\n\nTo simulate an http request in a test\n\n  ```javascript\n  http.expectGET('/api/edges');\n  http.flush();\n  ```\n\n## Rails Controller\n\nSo that angular front end app can make /api calls to get edges.\n\nFirst add api namespace to [routes](sw-backend/config/routes.rb)\n\nAdd a [controller](sw-backend/app/controllers/api/edges_controller.rb) and [controller test](sw-backend-spec/controllers/edges_controller_spec.rb)\n\n## Rails Models\n\nGenerate the models\n\n  ```bash\n  rails g model edge name description:text category:references\n  rails g model requirement name value mode\n  rails g model category name\n  ```\n\nSetup app and test databases\n\n  ```bash\n  rake db:migrate\n  rake db:test:prepare\n  ```\n\nUse Active Model Serializer to generate JSON representation of models.\nAdd it to [gemfile](sw-backend/Gemfile), then run `bundle`\n\nGenerate\n\n  ```bash\n  rails g serializer edge\n  rails g serializer requirement\n  rails g serializer category\n  ```\nEdit the generated `{model}_serializer.rb` files to specify attributes.\n\nIf any changes are  made to `{timestamp}_create_{model}.rb` files to modify the database schema, run\n\n  ```bash\n  rake db:drop\n  rake db:migrate\n  rake db:test:prepare\n  ```\nBy default, rails active model serializer will generate root element. But angular app expects a list of results without a root element.\nTo fix this, specify `root: false` in rails controller\n\n  ```ruby\n  module Api\n    class EdgesController \u003c ApplicationController\n      def index\n        render json: Edge.all, root: false\n      end\n    end\n  end\n  ```\n\n## Express backend\n\nHaving trouble with rails active model serializer not following edge relationships and generating expected json.\nTo not get stuck, use express back end\n\n  ```bash\n  cd sw-express\n  DEBUG=myapp ./bin/www\n  ```\n\n## Angular Forms\n\nExample [view](sw_front/app/views/login.html) and [controller](sw_front/app/scripts/controllers/login.js)\n\nTo activate Angular form validations, form must have a name AND must disable HTML5 form validation.\nThe form name will appear as a property on $scope, which is then available in the controller.\n\n  ```html\n  \u003cform class=\"form\" name=\"loginForm\" novalidate\u003e\n    ...\n  \u003c/form\u003e\n  ```\n\nTo have Angular control the form submission, use `ng-submit` directive\n\n  ```html\n  \u003cform class=\"form\" name=\"loginForm\" novalidate ng-submit=\"login()\"\u003e\n    ...\n  \u003c/form\u003e\n  ```\n\nTo check if form is valid in a controller\n\n  ```javascript\n  $scope.login = function() {\n    if ($scope.loginForm.$valid) {\n      // do something with form data...\n    }\n  }\n  ```\n\nOptionally, can choose to display error messages to user only when form is submitted.\n\nTo disable submit form button until form is valid\n\n  ```html\n  \u003cbutton class=\"btn btn-primary\" type=\"submit\" ng-disabled=\"loginForm.$invalid\"\u003eLog In\u003c/button\u003e\n  ```\n\n## Angular Directive\n\nPreferred approach is to display error messages on blur, i.e. when field loses focus.\nThis requires a [custom directive](sw_front/app/scripts/directives/cu-focus.js).\n\nTo use angular models in a directive, use `require: 'ngModel'.\nThis makes the model controller available as fourth argument in the `link` function.\nSee Angular [Directive doc](https://docs.angularjs.org/guide/directive) for more details, specificlly, \"Creating Directives that Communicate\".\n\n`link` function in directive allows us to listen to events such as `blur`.\n\n### Naming\n\ncamelCase is used javascript\n\n  ```javascript\n  angular.module('swFrontApp')\n    .directive('cuFocus', function () {\n      return {\n        restrict: 'A',\n        require: 'ngModel',\n        // rest of logic goes here...\n      };\n    });\n  ```\n\nBut snake-case is used in html\n\n  ```html\n  \u003cinput id=\"userEmail\" type=\"email\" name=\"email\" class=\"form-control\" ng-model=\"user.email\" required cu-focus/\u003e\n  ```\n\n## Token Based Authentication\n\nSteps:\n\n* Client sends credentials (such as email or username and password) to server, for example, via login form.\n* Server checks if credentials are valid, generate unique token, saves it (eg: in a database), and returns token to client\n* Client saves this token somewhere (cookie or local storage)\n* Client includes this token on every subsequent request to server\n* Server checks for token on every resource that requires authentication\n\n### Angular HTTP Interceptor\n\nCan configure an interceptor to intervene in all ajax requests, and modify request or response, success or error cases.\nInterceptor is implemeted as a `factory`. For example, to check all response errors for 401 and redirect user to login page:\n\n  ```javascript\n  .factory('myCustomHttpInterceptor', function($q, $location) {\n    return {\n      'response': function(response) {\n        return response;\n      },\n     'responseError': function(rejection) {\n        if (rejection.status === 401 \u0026\u0026 $location.url() !== '/login') {\n          $location.url('/login');\n        } else {\n          return $q.reject(rejection);\n        }\n      }\n    };\n    ```\n\nCan also use interceptor to modify every request, for example, suppose an auth_token is persisted in local storage,\nand would like to include it as http header for every ajax request\n\n  ```javascript\n  .factory('myCustomHttpInterceptor', function($q, $location) {\n    return {\n      'request': function(config) {\n        config.headers = config.headers || {};\n        if (localStorage.auth_token) {\n          config.headers.token = localStorage.auth_token;\n        }\n        return config;\n      }\n    };\n  ```\n\nTo make your custom http interceptor actually be used by angular, use $httpProvider.\nThis would go somewhere in `app.js` where main Angular application (routes etc) is configured.\n\n  ```javascript\n  .config(function($httpProvider) {\n    $httpProvider.interceptors.push('myCustomHttpInterceptor');\n  })\n  ```\n\n## ngResource\n\nTo create a new resource using ngResource\n\n  ```html\n  \u003c!-- edges.html --\u003e\n  \u003cform name=\"newEdgeForm\" ng-submit=\"createEdge()\"\u003e\n    \u003cinput type=\"text\" name=\"name\" class=\"form-control\" ng-model=\"newEdge.name\" /\u003e\n    \u003ctextarea name=\"description\" class=\"form-control\" ng-model=\"newEdge.description\"\u003e\u003c/textarea\u003e\n    \u003c!-- remainder of newEdge bound input fields... --\u003e\n  \u003c/form\u003e\n  ```\n\n  ```javascript\n  // services/edgeresource.js\n  angular.module('swFrontApp')\n    .factory('EdgeResource', function ($resource) {\n      return $resource('/api/edges');\n    });\n\n  // controllers/edge.js\n  angular.module('swFrontApp')\n    .controller('EdgesCtrl', function ($scope, EdgeResource) {\n      $scope.newEdge = new EdgeResource;\n    });\n  ```\n\nTo delete a resource\n\n  ```javascript\n  var deleteEdge = function(edge) {\n    edge.$delete();\n  }\n  ```\n\nBut 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\n\n  ```javascript\n  angular.module('swFrontApp')\n    .factory('EdgeResource', function ($resource) {\n      return $resource('/api/edges/:id', {id: '@name'});\n    });\n  ```\n\n## Refactoring\n\nAngular controllers tend to get big, means they're doing too much.\nLook at the html view and determine if it can be split up into multiple templates and controllers, each with a single responsibility.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanielabar%2Fhands-on-angular-tuts","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdanielabar%2Fhands-on-angular-tuts","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanielabar%2Fhands-on-angular-tuts/lists"}