Skip to content

Instantly share code, notes, and snippets.

@zshannon
Last active August 29, 2015 14:06
Show Gist options
  • Save zshannon/9ae367d203bd71c3660a to your computer and use it in GitHub Desktop.
Save zshannon/9ae367d203bd71c3660a to your computer and use it in GitHub Desktop.
AngularJS Tips
Angular apps are little separate kingdoms. You can't get into them from outside, and it's really frowned upon to use jQuery within them to access the DOM.
The loading order is somewhat important. I set it up like this: [jQuery, jQueryUI, Bootstrap, Underscore, Angular Library, *any Angular Plugins*, Angular Application init file, *rest of angular files*].
# Factory is angular for Model in MVC-speak
# @dispatched gets the application we setup in _init_app.coffee; 'Team' is the name of this model;
# the array param has the first n elements as the dependencies that become params for the last element,
# which defines and returns the model
@dispatched.factory 'Team', ['railsResourceFactory', 'railsSerializer', (railsResourceFactory, railsSerializer) ->
# initialize the object
Team = railsResourceFactory # using RailsResource for now, but plan to switch to [Angular-Data](http://angular-data.pseudobry.com/)
url: (context) -> # this gets called to generate every request URL
# if we have an instance, use its ID for Show/Update/Delete/etc.
if context[@idAttribute]?
"/teams/#{context[@idAttribute]}.json"
# otherwise we're working on the collection for Index/Create/Search/etc.
else
"/teams.json#{window.location.search}" # window.location.search is the query params like `?page=3&filter=pasta`
# team is the root node for JSON serialization from the API
# like: {'team':{name:'new orleans saints'}}
name: 'team'
extensions: ['snapshots'] # snapshots let us do undo
# I can add functions to the model here, too
# for example:
Team.prototype.helloName = (name) ->
"Hello, #{name}!"
Team
]
# Here's an example Controller. In Angular, Controllers and Controller/View hybrids in MVC-speak
# Every property of $scope is available as a variable in the View, including the functions
# The init syntax is similar to factory, except here we're depending on the Team model from team.coffee
@dispatched.controller('Teams', ['Team', '$scope', '$rootScope', (Team, $scope, $rootScope) ->
# initialize the scope
$scope.page = 1
$scope.teams = []
$scope.hasMore = true
$scope.loadingMore = false
# this gets called by our inifinite scrolling library
$scope.loadMore = ->
return true if ! $scope.hasMore || $scope.loadingMore
$scope.loadingMore = true
# NProgress is a library that shows the loading bar thing at the top of the page
# It's loaded as an external JS thing available globally (not related to angular)
NProgress.start()
# Team is from team.coffee. Query is a native function to RailsResource. Just calls index.
Team.query({page: $scope.page}).then (teams) -># it returns a promise . teams will be an array from the API
# I read somewhere this is a faster way to merge arrays in JS. Dunno.
for team in teams
$scope.teams.push team
# normal loading loop stuff
$scope.page++
$scope.hasMore = teams.length >= 25
$scope.loadingMore = false
NProgress.done()
# this gets called from the HTML view
# the HTML renderer already fetches page 1, so we load it here via JSON from the DOM
$scope.init = (teams) ->
for team in teams
$scope.teams.push new Team(team)
$scope.page++
$scope.hasMore = teams.length >= 25
# We use the Observer Pattern here to receive notes from other controllers
# There's another controller that presents a form to edit Teams that uses these
$rootScope.$on 'Teams.addedTeam', (event, team) ->
$scope.teams.push team
$rootScope.$on 'Teams.addedMember', (event, team, member) ->
console.log 'addedMember', team, member
team.members.push member
$rootScope.$on 'Teams.removedMember', (event, team, member) ->
console.log 'removedMember', team, member
team.members.splice _.indexOf(member), 1
# functions on $scope are available in the View
$scope.newTeam = () ->
team = new Team()
# this is observed by that other controller
$rootScope.$emit 'Teams.presentForm', team
true
$scope.editTeam = (team) ->
$rootScope.$emit 'Teams.presentForm', team
true
$scope.deleteTeam = (team, $index) ->
return false unless team.id?
NProgress.start()
team.delete().then -># wait for railsResource to finish, then update the UI
$scope.teams.splice $index, 1
NProgress.done()
true
$scope.editMembers = (team) ->
$rootScope.$emit 'Teams.presentMemberForm', team
true
])
<div class="page-head">
<h2>Manage Teams</h2>
<ol class="breadcrumb">
<li class="active"><%= @Company.name %></li>
<li class="active">Teams</li>
</ol>
</div>
<div class="row-fluid">
<div class="col-sm-12">
<!--
This is the $$ part of angular. You can use the Angular router if you want to be sad, or just use
whatever routing mechanism's already in your app and sprinkle `ng-controller="XyzController"`
throughout your app views. Angular will automatically link this view to the Controller!
`ng-init` gets called automatically when the view renders. Notice inside it we specify `init`
again because we can call whatever $scope function we want, the name's incidental.
-->
<div class="content" ng-controller="Teams" ng-init="init(<%= @Teams.to_json %>)">
<div class="table-responsive">
<table class="table no-border hover">
<thead class="no-border">
<tr>
<th><strong>ID</strong></th>
<th><strong>Name</strong></th>
<th><strong>Members</strong></th>
<!--
Notice `ng-click` here will call `$scope.newTeam()`
-->
<th class="text-right"><button class="btn btn-primary btn-sm btn-flat" ng-click="newTeam()">+Team</button></th>
</tr>
</thead>
<!--
the `infinite-scroll` attrs are used by an external angular library
-->
<tbody id="teams" class="no-border-y" infinite-scroll="loadMore()" infinite-scroll-distance="3">
<!--
here's the main angular magic show. `ng-repeat` will re-render that node and its children
for each element in $scope.teams
-->
<tr ng-repeat="team in teams">
<td>{{ team.id }}</td> <!-- pretty standard JS templating style here. -->
<td>{{ team.name }}</td>
<td>
<span class="label label-primary" ng-repeat="member in team.members" style="margin-right:3px;" ng-click="editMembers(team)">{{ member.name }} &lt;{{member.email}}&gt;</span>
<!-- `ng-show` is an example of how we update the view without directly manipulating the DOM. plain JS in there -->
<span class="label label-default" ng-show="! team.members.length">No members yet.</span>
<a class="label label-primary" ng-click="editMembers(team)"><i class="fa fa-plus"></i></a>
</td>
<td class="text-right">
<a class="label label-default" ng-click="editTeam(team)"><i class="fa fa-pencil"></i></a>
<!-- $index is a special variable that will be rendered as the index of this `ng-repeat` -->
<a class="label label-danger" ng-click="deleteTeam(team, $index)"><i class="fa fa-times"></i></a>
</td>
</tr>
<tr ng-show="! teams.length">
<td colspan="4"><p class="lead">No Teams yet. Add one with the blue button above.</p></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
@montasaurus
Copy link

1-team.coffee mentions a _init_app.coffee . Is that where you're setting the @dispatched variable?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment