angular.js fundamentals
DESCRIPTION
As present at FluentConf 2014 on March 11th, 2014. AngularJS is one of the most popular, and powerful, JavaScript frameworks for building rich client-side applications. AngularJS is both simultaneously both simple to use and extremely full featured. With AngularJS a little goes a long way, but to make the most of it, you need to know what you’re doing. In this workshop we will build a complex application to help exercise all of the salient points of the AngularJS framework. Topics covered include, ngResource, directives, fitlers, routing, templates, controllers, testing, and more. Code can be found at: https://github.com/markbates/fluent-2014TRANSCRIPT
@markbates
@markbates
FLUENT2014www.metacasts.tv
www.angularmasterclass.com
Angular Fundamentals
Enough Angular to be Dangerous!
What Will Cover?
What Will Cover?**hopefully
• Controllers!
• ngRoute!
• Templates!
• ngResource!
• Directives!
• Filters!
• Scope!
• Testing!
• Code Organization!
• Best Practices
Part 1• Features/Why
Angular?!
• Getting Started/Setting Up!
• Directives, Filters, and Data Binding!
• Controllers, Templates, and Scope!
• Modules!
• Routing!
• Custom Directives and Event Handling!
• Testing
Part 2
We Code!
Part 1
Features
Features• Plain JavaScript
• Data Binding
• Routing/PushState
• Testing
• Templates/Directives/Controllers
• Modular
• Dependency Injection
• jqLite
• Lightweight
Why Angular?
Philosophies
Backbone.js“minimal set of data-structure and view primitives
for building web application with JavaScript”
Ember“framework for creating ambitious web applications”
AngularJS“Toolset for building the framework
most suited to your application development”
Weight
“production” versions (minified) w/ required dependencies
AngularJS Ember Backbone.js
base 109kb 264kb 6.5kb
templating language built-in
90kb (handlebars) ??
data adapter built-in75kb
(ember-data)84kb
(jQuery)
support N/A 84kb (jQuery)
17kb (json2.js)
5.0kb (underscore.js
)
109kb 513kb 112.5kb
Mindshare
AngularJS Backbone.js Ember
Watchers 2,155 1,442 824
Stars 21,408 17,291 9,570
Forks 6,670 3,783 2,044
Github
“Basic” Models
Backbone.jsclass App.Beer extends Backbone.Model class App.Beers extends Backbone.Collection ! model: Beer
EmberApp.Beer = DS.Model.extend title: DS.attr("string") abv: DS.attr("number") country_id: DS.attr("number") brewery_id: DS.attr("number") brewery: DS.belongsTo("App.Brewery") country: DS.belongsTo("App.Country")
AngularJSApp.Beer = {}
“Remote” Models
Backbone.jsclass App.Beer extends Backbone.Model urlRoot: "/api/v1/beers" class App.Beers extends Backbone.Collection url: -‐> if @brewery_id? return "/api/v1/breweries/#{@brewery_id}/beers" else return "/api/v1/beers" model: Beer
EmberApp.Beer = DS.Model.extend title: DS.attr("string") abv: DS.attr("number") country_id: DS.attr("number") brewery_id: DS.attr("number") brewery: DS.belongsTo("App.Brewery") country: DS.belongsTo("App.Country")
EmberDS.RESTAdapter.reopen namespace: 'api/v1' App.Store = DS.Store.extend revision: 14 adapter: DS.RESTAdapter.create()
AngularJSApp.factory "Beer", ($resource) -‐> return $resource "/api/v1/beers/:id", {id: "@id"}, {update: {method: "PUT"}}
Routers
Backbone.js@Router = Backbone.Router.extend initialize: -‐> @countries = new Countries() routes: "breweries/:brewery_id": "brewery" "breweries/:brewery_id/edit": "breweryEdit" brewery: (brewery_id) -‐> @changeView(new BreweryView(collection: @countries, model: new Brewery(id: brewery_id))) breweryEdit: (brewery_id) -‐> @changeView(new BreweryEditView(collection: @countries, model: new Brewery(id: brewery_id))) changeView: (view) => @currentView?.remove() @currentView = view $("#outlet").html(@currentView.el)
Ember
App.Router.map -‐> @resource "brewery", {path: "brewery/:brewery_id"}
EmberApp.BreweryRoute = Ember.Route.extend model: (params)-‐> App.Brewery.find(params.brewery_id)
AngularJSApp.config ($routeProvider) -‐> $routeProvider .when("/breweries/:id", { templateUrl: "/assets/brewery.html", controller: "BreweryController" }) .when("/breweries/:id/edit", { templateUrl: "/assets/edit_brewery.html", controller: "EditBreweryController" })
Controllers/Views
Backbone.jsclass @BreweryEditView extends Backbone.View template: "brewery_edit" events: "click #save-‐button": "saveClicked" "keypress #brewery-‐title": "titleEdited" initialize: -‐> super @countriesView = new CountriesView(collection: @collection) @$el.html(@countriesView.el) @model.on "change", @render @model.fetch() render: => @$("#country-‐outlet").html(@renderTemplate()) return @
saveClicked: (e) => e?.preventDefault() attrs = title: @$("#brewery-‐title").val() synonyms: @$("#brewery-‐synonyms").val() address: @$("#brewery-‐address").val() @model.save attrs, success: (model, response, options) => App.navigate("/breweries/#{@model.id}", trigger: true) error: (model, xhr, options) -‐> errors = [] for key, value of xhr.responseJSON.errors errors.push "#{key}: #{value.join(", ")}" alert errors.join("\n") titleEdited: (e) => title = @$("#brewery-‐title").val() @$("h2").text(title) ! # further code omitted
EmberApp.BreweryController = Ember.ObjectController.extend save: -‐> @store.commit() # further code omitted
AngularJS@EditBreweryController = ($scope, $routeParams, $location, Brewery) -‐> $scope.brewery = Brewery.get(id: $routeParams.id) $scope.save = -‐> success = -‐> $location.path("/breweries/#{$routeParams.id}") $scope.errors = null failure = (object)-‐> $scope.errors = object.data.errors $scope.brewery.$update {}, success, failure
Templates
Backbone.js<h2><%= @model.displayName() %></h2> <form> <div class="control-‐group"> <label class="control-‐label" for="title">Title</label> <div class="controls"> <input type='text' class='input-‐xxlarge' value='<%= @model.get("title") %>'id='brewery-‐title'> </div> </div> <div class="control-‐group"> <label class="control-‐label" for="synonyms">Synonyms</label> <div class="controls"> <input type='text' class='input-‐xxlarge' value='<%= @model.get("synonyms") %>'id='brewery-‐synonyms'> </div> </div> <div class="control-‐group"> <label class="control-‐label" for="address">Address</label> <div class="controls"> <textarea class='input-‐xxlarge' id='brewery-‐address'><%= @model.get("address") %></textarea> </div> </div> <button class='btn btn-‐primary' id='save-‐button'>Save</button> <a href="/breweries/<%= @model.id %>" class='btn'>Cancel</a> </form>
Backbone.js<h2><%= @model.displayName() %></h2>
<form>
<div class="control-‐group">
<label class="control-‐label" for="title">Title</label>
<div class="controls">
<input type='text' class='input-‐xxlarge' value='<%= @model.get("title") %>'id='brewery-‐title'> </div>
</div>
<div class="control-‐group">
<label class="control-‐label" for="synonyms">Synonyms</label>
<div class="controls">
<input type='text' class='input-‐xxlarge' value='<%= @model.get("synonyms") %>'id='brewery-‐synonyms'>
</div>
</div>
<div class="control-‐group">
<label class="control-‐label" for="address">Address</label>
<div class="controls">
<textarea class='input-‐xxlarge' id='brewery-‐address'><%= @model.get("address") %></textarea>
</div>
</div>
<button class='btn btn-‐primary' id='save-‐button'>Save</button>
<a href="/breweries/<%= @model.id %>" class='btn'>Cancel</a>
</form>
Backbone.js<h2><%= @model.displayName() %></h2>
<form>
<div class="control-‐group">
<label class="control-‐label" for="title">Title</label>
<div class="controls">
<input type='text' class='input-‐xxlarge' value='<%= @model.get("title") %>'id='brewery-‐title'>
</div>
</div>
<div class="control-‐group">
<label class="control-‐label" for="synonyms">Synonyms</label>
<div class="controls">
<input type='text' class='input-‐xxlarge' value='<%= @model.get("synonyms") %>'id='brewery-‐synonyms'>
</div>
</div>
<div class="control-‐group">
<label class="control-‐label" for="address">Address</label>
<div class="controls">
<textarea class='input-‐xxlarge' id='brewery-‐address'><%= @model.get("address") %></textarea> </div>
</div>
<button class='btn btn-‐primary' id='save-‐button'>Save</button>
<a href="/breweries/<%= @model.id %>" class='btn'>Cancel</a>
</form>
<div class='span12'>
<h2>{{displayName}}</h2>
<h3>
{{cityState}}
{{#linkTo "country" country}}
{{country.title}}
{{/linkTo}}
</h3>
{{#if isEditing}}
<form>
<div class="control-‐group">
<label class="control-‐label" for="title">Title</label>
<div class="controls">
{{view Ember.TextField valueBinding="title" class='input-‐xxlarge'}}
</div>
</div>
<div class="control-‐group">
<label class="control-‐label" for="synonyms">Synonyms</label>
<div class="controls">
{{view Ember.TextField valueBinding="synonyms" class='input-‐xxlarge'}}
</div>
</div>
<div class="control-‐group">
<label class="control-‐label" for="synonyms">Synonyms</label>
<div class="controls">
{{view Ember.TextArea valueBinding="address" class='input-‐xxlarge'}}
</div>
</div>
<button class='btn btn-‐primary' {{action "save"}}>Save</button>
</form>
{{ else }}
{{ partial "brewery/show" }}
{{/if}}
</div>
Ember
<div class='span12'>
<h2>{{displayName}}</h2>
<h3>
{{cityState}}
{{#linkTo "country" country}}
{{country.title}}
{{/linkTo}}
</h3>
{{#if isEditing}}
<form>
<div class="control-‐group">
<label class="control-‐label" for="title">Title</label>
<div class="controls">
{{view Ember.TextField valueBinding="title" class='input-‐xxlarge'}}
</div>
</div>
<div class="control-‐group">
<label class="control-‐label" for="synonyms">Synonyms</label>
<div class="controls">
{{view Ember.TextField valueBinding="synonyms" class='input-‐xxlarge'}}
</div>
</div>
<div class="control-‐group">
<label class="control-‐label" for="synonyms">Synonyms</label>
<div class="controls">
{{view Ember.TextArea valueBinding="address" class='input-‐xxlarge'}}
</div>
</div>
<button class='btn btn-‐primary' {{action "save"}}>Save</button>
</form>
{{ else }}
{{ partial "brewery/show" }}
{{/if}}
</div>
Ember
<div class='span12'>
<h2>{{displayName}}</h2>
<h3>
{{cityState}}
{{#linkTo "country" country}}
{{country.title}}
{{/linkTo}}
</h3>
{{#if isEditing}}
<form>
<div class="control-‐group">
<label class="control-‐label" for="title">Title</label>
<div class="controls">
{{view Ember.TextField valueBinding="title" class='input-‐xxlarge'}}
</div>
</div>
<div class="control-‐group">
<label class="control-‐label" for="synonyms">Synonyms</label>
<div class="controls">
{{view Ember.TextField valueBinding="synonyms" class='input-‐xxlarge'}}
</div>
</div>
<div class="control-‐group">
<label class="control-‐label" for="synonyms">Synonyms</label>
<div class="controls">
{{view Ember.TextArea valueBinding="address" class='input-‐xxlarge'}}
</div>
</div>
<button class='btn btn-‐primary' {{action "save"}}>Save</button>
</form>
{{ else }}
{{ partial "brewery/show" }}
{{/if}}
</div>
Ember
<form>
<h3>{{brewery.title}}</h3>
<div ng-‐include='"/assets/_errors.html"'></div>
<div class="control-‐group">
<label class="control-‐label" for="title">Title</label>
<div class="controls">
<input type='text' class='input-‐xxlarge' ng-‐
model='brewery.title'>
</div>
</div>
<div class="control-‐group">
<label class="control-‐label" for="synonyms">Synonyms</label>
<div class="controls">
<input type='text' class='input-‐xxlarge' ng-‐
model='brewery.synonyms'>
</div>
</div>
<div class="control-‐group">
<label class="control-‐label" for="address">Address</label>
<div class="controls">
<textarea class='input-‐xxlarge' ng-‐model='brewery.address'></
textarea>
</div>
</div>
<button class='btn btn-‐primary' ng-‐click='save()'>Save</button>
</form>
AngularJS
AngularJS<form>
<h3>{{brewery.title}}</h3>
<div ng-‐include='"/assets/_errors.html"'></div>
<div class="control-‐group">
<label class="control-‐label" for="title">Title</label>
<div class="controls">
<input type='text' class='input-‐xxlarge' ng-‐model='brewery.title'>
</div>
</div>
<div class="control-‐group">
<label class="control-‐label" for="synonyms">Synonyms</label>
<div class="controls">
<input type='text' class='input-‐xxlarge' ng-‐model='brewery.synonyms'>
</div>
</div>
<div class="control-‐group">
<label class="control-‐label" for="address">Address</label>
<div class="controls">
<textarea class='input-‐xxlarge' ng-‐model='brewery.address'></textarea>
</div>
</div>
<button class='btn btn-‐primary' ng-‐click='save()'>Save</button>
</form>
<form>
<h3>{{brewery.title}}</h3>
<div ng-‐include='"/assets/_errors.html"'></div>
<div class="control-‐group">
<label class="control-‐label" for="title">Title</label>
<div class="controls">
<input type='text' class='input-‐xxlarge' ng-‐model='brewery.title'>
</div>
</div>
<div class="control-‐group">
<label class="control-‐label" for="synonyms">Synonyms</label>
<div class="controls">
<input type='text' class='input-‐xxlarge' ng-‐model='brewery.synonyms'>
</div>
</div>
<div class="control-‐group">
<label class="control-‐label" for="address">Address</label>
<div class="controls">
<textarea class='input-‐xxlarge' ng-‐model='brewery.address'></textarea>
</div>
</div>
<button class='btn btn-‐primary' ng-‐click='save()'>Save</button>
</form>
AngularJS
Pros/Cons
Backbone.js• Too simple
• Not opinionated enough
• “Memory” management
• Unstructured
• Spaghetti code
• Lightweight
• Not opinionated
• Simple
• Easy to read source
• “widget” development
Pros Cons
Ember• Too complex
• Overly opinionated
• Heavyweight
• ember-data - not production ready (very buggy)
• Little to no mind-share outside of Rails
• Difficult to read source code
• Structured
• Highly opinionated
• “less” code
• “large” apps
Pros Cons
AngularJS• Difficult to read source
code
• jQuery plugins require custom directives
• Large apps requiring self-imposed structure
• Lightly structured
• Lightly opinionated
• “less” code
• Plain JavaScript
• Simple/Powerful
• Easy to test
• Lightweight
• small, medium, or large apps
Pros Cons
Getting Started
5 Minute Break
Directives, Filters, and Data Binding
Directives<body ng-app>! <ng-view></ng-view>! <ul>! <li ng-repeat='comment in comments'>! {{comment.body}}! </li>! </ul>!</body>
Directives<body ng-app>! <ng-view></ng-view>! <ul>! <li ng-repeat='comment in comments'>! {{comment.body}}! </li>! </ul>!</body>
Directives<body ng-app>! <ng-view></ng-view>! <ul>! <li ng-repeat='comment in comments'>! {{comment.body}}! </li>! </ul>!</body>
Directives<body ng-app>! <ng-view></ng-view>! <ul>! <li ng-repeat='comment in comments'>! {{comment.body}}! </li>! </ul>!</body>
Directives<body ng-app>! <ng-view></ng-view>! <ul>! <li ng-repeat='comment in comments'>! {{comment.body}}! </li>! </ul>!</body>
Demo
Filters<ul>! <li ng-repeat='c in comments | orderBy:"date"'>! {{c.author | uppercase}}! </li>!</ul>
Filters<ul>! <li ng-repeat='c in comments | orderBy:"date"'>! {{c.author | uppercase}}! </li>!</ul>
Filters<ul>! <li ng-repeat='c in comments | orderBy:"date"'>! {{c.author | uppercase}}! </li>!</ul>
<ul>! <li ng-repeat='c in comments | orderBy:"date"'>! {{c.author | uppercase}}! </li>!</ul>
Filters
Demo
Controllers, Templates, and Scope
Controllers, Templates and Scope
Template
Controllers, Templates and Scope
Template Controller
Controllers, Templates and Scope
Template Controller
Controllers, Templates and Scope
Template Controller
Demo
Modules
Module
angular.module('app', []);
Module
Config
angular.module('app', []);
Module
Config Controller
angular.module('app', []);
Module
Config Controller Factories
angular.module('app', []);
Module
Config Controller Factories Directives
angular.module('app', []);
Module
Config Controller Factories Directives Filters
angular.module('app', []);
Module
Config Controller Factories Directives Filters
Routes
angular.module('app', []);
Demo
Routing
App.config(function($routeProvider, $locationProvider) {! $locationProvider.html5Mode(true);!! $routeProvider.when('/posts', {! controller: 'PostsIndexController',! templateUrl: 'posts/index.html'! })! .when('/posts/:id',{! controller: 'PostsShowController',! templateUrl: 'posts/show.html'! })! .otherwise({! redirectTo: '/posts'! });!});
Routing
App.config(function($routeProvider, $locationProvider) {! $locationProvider.html5Mode(true);!! $routeProvider.when('/posts', {! controller: 'PostsIndexController',! templateUrl: 'posts/index.html'! })! .when('/posts/:id',{! controller: 'PostsShowController',! templateUrl: 'posts/show.html'! })! .otherwise({! redirectTo: '/posts'! });!});
Routing
App.config(function($routeProvider, $locationProvider) {! $locationProvider.html5Mode(true);!! $routeProvider.when('/posts', {! controller: 'PostsIndexController',! templateUrl: 'posts/index.html'! })! .when('/posts/:id',{! controller: 'PostsShowController',! templateUrl: 'posts/show.html'! })! .otherwise({! redirectTo: '/posts'! });!});
Routing
App.config(function($routeProvider, $locationProvider) {! $locationProvider.html5Mode(true);!! $routeProvider.when('/posts', {! controller: 'PostsIndexController',! templateUrl: 'posts/index.html'! })! .when('/posts/:id',{! controller: 'PostsShowController',! templateUrl: 'posts/show.html'! })! .otherwise({! redirectTo: '/posts'! });!});
Routing
App.config(function($routeProvider, $locationProvider) {! $locationProvider.html5Mode(true);!! $routeProvider.when('/posts', {! controller: 'PostsIndexController',! templateUrl: 'posts/index.html'! })! .when('/posts/:id',{! controller: 'PostsShowController',! templateUrl: 'posts/show.html'! })! .otherwise({! redirectTo: '/posts'! });!});
Routing
App.config(function($routeProvider, $locationProvider) {! $locationProvider.html5Mode(true);!! $routeProvider.when('/posts', {! controller: 'PostsIndexController',! templateUrl: 'posts/index.html'! })! .when('/posts/:id',{! controller: 'PostsShowController',! templateUrl: 'posts/show.html'! })! .otherwise({! redirectTo: '/posts'! });!});
Routing
App.config(function($routeProvider, $locationProvider) {! $locationProvider.html5Mode(true);!! $routeProvider.when('/posts', {! controller: 'PostsIndexController',! templateUrl: 'posts/index.html'! })! .when('/posts/:id',{! controller: 'PostsShowController',! templateUrl: 'posts/show.html'! })! .otherwise({! redirectTo: '/posts'! });!});
Routing
App.config(function($routeProvider, $locationProvider) {! $locationProvider.html5Mode(true);!! $routeProvider.when('/posts', {! controller: 'PostsIndexController',! templateUrl: 'posts/index.html'! })! .when('/posts/:id',{! controller: 'PostsShowController',! templateUrl: 'posts/show.html'! })! .otherwise({! redirectTo: '/posts'! });!});
Routing
App.config(function($routeProvider, $locationProvider) {! $locationProvider.html5Mode(true);!! $routeProvider.when('/posts', {! controller: 'PostsIndexController',! templateUrl: 'posts/index.html'! })! .when('/posts/:id',{! controller: 'PostsShowController',! templateUrl: 'posts/show.html'! })! .otherwise({! redirectTo: '/posts'! });!});
Routing
App.config(function($routeProvider, $locationProvider) {! $locationProvider.html5Mode(true);!! $routeProvider.when('/posts', {! controller: 'PostsIndexController',! templateUrl: 'posts/index.html'! })! .when('/posts/:id',{! controller: 'PostsShowController',! templateUrl: 'posts/show.html'! })! .otherwise({! redirectTo: '/posts'! });!});
Routing
App.config(function($routeProvider, $locationProvider) {! $locationProvider.html5Mode(true);!! $routeProvider.when('/posts', {! controller: 'PostsIndexController',! templateUrl: 'posts/index.html'! })! .when('/posts/:id',{! controller: 'PostsShowController',! templateUrl: 'posts/show.html'! })! .otherwise({! redirectTo: '/posts'! });!});
Routing
Demo
Custom Directives and Event Handling
App.directive("upCaser", function() {! return {! link: function(scope, el, attrs) {! $(el).find('p').each(function(i, p) {! p = $(p);! p.text(p.text().toUpperCase());! });! }! };!});
Directives
DirectivesApp.directive("upCaser", function() {! return {! link: function(scope, el, attrs) {! $(el).find('p').each(function(i, p) {! p = $(p);! p.text(p.text().toUpperCase());! });! }! };!});
App.directive("upCaser", function() {! return {! link: function(scope, el, attrs) {! $(el).find('p').each(function(i, p) {! p = $(p);! p.text(p.text().toUpperCase());! });! }! };!});
Directives
App.directive("upCaser", function() {! return {! link: function(scope, el, attrs) {! $(el).find('p').each(function(i, p) {! p = $(p);! p.text(p.text().toUpperCase());! });! }! };!});
Directives
App.directive("upCaser", function() {! return {! link: function(scope, el, attrs) {! $(el).find('p').each(function(i, p) {! p = $(p);! p.text(p.text().toUpperCase());! });! }! };!});
Directives
App.directive("upCaser", function() {! return {! link: function(scope, el, attrs) {! $(el).find('p').each(function(i, p) {! p = $(p);! p.text(p.text().toUpperCase());! });! }! };!});
Directives
App.directive("upCaser", function() {! return {! link: function(scope, el, attrs) {! $(el).find('p').each(function(i, p) {! p = $(p);! p.text(p.text().toUpperCase());! });! }! };!});
Directives
App.directive("upCaser", function() {! return {! link: function(scope, el, attrs) {! $(el).find('p').each(function(i, p) {! p = $(p);! p.text(p.text().toUpperCase());! });! }! };!});
Directives
<div up-caser>! <p>some text</p>! <p>some other text</p>!</div>
Directives
Directives<up-caser>! <p>some text</p>! <p>some other text</p>!</up-caser>
App.directive("upCaser", function() {! return {! restrict: 'E',! link: function(scope, el, attrs) {! $(el).find('p').each(function(i, p) {! p = $(p);! p.text(p.text().toUpperCase());! });! }! };!});
Directives
Directives<up-caser>! <p>some text</p>! <p>some other text</p>!</up-caser>
App.directive("upCaser", function() {! return {! restrict: 'AEC',! link: function(scope, el, attrs) {! $(el).find('p').each(function(i, p) {! p = $(p);! p.text(p.text().toUpperCase());! });! }! };!});
Directives
Demo
Events
Scope
Events
$broadcast
$emit
App.directive("alerter", function() {! return {! restrict: 'E',! link: function(scope, el, attrs) {! setTimeout(function() {! scope.$emit("up", "Hello Up!");! scope.$broadcast("down", "Hello Down!");! scope.$apply();! }, 3000);! }! };!});
Events
App.directive("alerter", function() {! return {! restrict: 'E',! link: function(scope, el, attrs) {! setTimeout(function() {! scope.$emit("up", "Hello Up!");! scope.$broadcast("down", "Hello Down!");! scope.$apply();! }, 3000);! }! };!});
Events
App.directive("alerter", function() {! return {! restrict: 'E',! link: function(scope, el, attrs) {! setTimeout(function() {! scope.$emit("up", "Hello Up!");! scope.$broadcast("down", "Hello Down!");! scope.$apply();! }, 3000);! }! };!});
Events
App.directive("alerter", function() {! return {! restrict: 'E',! link: function(scope, el, attrs) {! setTimeout(function() {! scope.$emit("up", "Hello Up!");! scope.$broadcast("down", "Hello Down!");! scope.$apply();! }, 3000);! }! };!});
Events
App.directive("alerter", function() {! return {! restrict: 'E',! link: function(scope, el, attrs) {! setTimeout(function() {! scope.$emit("up", "Hello Up!");! scope.$broadcast("down", "Hello Down!");! scope.$apply();! }, 3000);! }! };!});
Events
App.directive("alerter", function() {! return {! restrict: 'E',! link: function(scope, el, attrs) {! setTimeout(function() {! scope.$emit("up", "Hello Up!");! scope.$broadcast("down", "Hello Down!");! scope.$apply();! }, 3000);! }! };!});
Events
App.controller('SomeController', function($scope) {! $scope.$on("up", function(data, message) {! $scope.up_message = message;! });!});
Events
App.controller('SomeController', function($scope) {! $scope.$on("up", function(data, message) {! $scope.up_message = message;! });!});
Events
App.controller('SomeController', function($scope) {! $scope.$on("up", function(data, message) {! $scope.up_message = message;! });!});
Events
App.controller('SomeController', function($scope) {! $scope.$on("up", function(data, message) {! $scope.up_message = message;! });!});
Events
App.controller('SomeController', function($scope) {! $scope.$on("up", function(data, message) {! $scope.up_message = message;! });!});
Events
App.controller('SomeController', function($scope) {! $scope.$on("up", function(data, message) {! $scope.up_message = message;! });!});
Events
Demo
App.controller('Main', function($scope) {! $scope.clicker = function() {! $scope.pressed = true;! };!});
Events
App.directive("alerter", function() {! return {! restrict: 'E',! link: function(scope, el, attrs) {! scope.$watch('pressed', function() {! if (scope.pressed) {! $(el).text('Oi!');! }! });! }! };!});
Events
App.directive("alerter", function() {! return {! restrict: 'E',! link: function(scope, el, attrs) {! scope.$watch('pressed', function() {! if (scope.pressed) {! $(el).text('Oi!');! }! });! }! };!});
Events
Demo
Testing
App.controller('FooController', function($scope) {!! $scope.setFoo = function(val) {! $scope.foo = val;! };!!});
Testing
describe('FooController', function() {!! beforeEach(function() {module('app')});!! beforeEach(inject(function($rootScope, $controller) {! this.scope = $rootScope.$new();! $controller('FooController', {$scope: this.scope})! }));!! describe('setFoo()', function() {!! it('sets the foo value', function() {! expect(this.scope.foo).not.toBeDefined();! this.scope.setFoo('Bar');! expect(this.scope.foo).toEqual('Bar');! });! });!});
Testing
describe('FooController', function() {!! beforeEach(function() {module('app')});!! beforeEach(inject(function($rootScope, $controller) {! this.scope = $rootScope.$new();! $controller('FooController', {$scope: this.scope})! }));!! describe('setFoo()', function() {!! it('sets the foo value', function() {! expect(this.scope.foo).not.toBeDefined();! this.scope.setFoo('Bar');! expect(this.scope.foo).toEqual('Bar');! });! });!});
Testing
describe('FooController', function() {!! beforeEach(function() {module('app')});!! beforeEach(inject(function($rootScope, $controller) {! this.scope = $rootScope.$new();! $controller('FooController', {$scope: this.scope})! }));!! describe('setFoo()', function() {!! it('sets the foo value', function() {! expect(this.scope.foo).not.toBeDefined();! this.scope.setFoo('Bar');! expect(this.scope.foo).toEqual('Bar');! });! });!});
Testing
describe('FooController', function() {!! beforeEach(function() {module('app')});!! beforeEach(inject(function($rootScope, $controller) {! this.scope = $rootScope.$new();! $controller('FooController', {$scope: this.scope})! }));!! describe('setFoo()', function() {!! it('sets the foo value', function() {! expect(this.scope.foo).not.toBeDefined();! this.scope.setFoo('Bar');! expect(this.scope.foo).toEqual('Bar');! });! });!});
Testing
describe('FooController', function() {!! beforeEach(function() {module('app')});!! beforeEach(inject(function($rootScope, $controller) {! this.scope = $rootScope.$new();! $controller('FooController', {$scope: this.scope})! }));!! describe('setFoo()', function() {!! it('sets the foo value', function() {! expect(this.scope.foo).not.toBeDefined();! this.scope.setFoo('Bar');! expect(this.scope.foo).toEqual('Bar');! });! });!});
Testing
describe('FooController', function() {!! beforeEach(function() {module('app')});!! beforeEach(inject(function($rootScope, $controller) {! this.scope = $rootScope.$new();! $controller('FooController', {$scope: this.scope})! }));!! describe('setFoo()', function() {!! it('sets the foo value', function() {! expect(this.scope.foo).not.toBeDefined();! this.scope.setFoo('Bar');! expect(this.scope.foo).toEqual('Bar');! });! });!});
Testing
describe('FooController', function() {!! beforeEach(function() {module('app')});!! beforeEach(inject(function($rootScope, $controller) {! this.scope = $rootScope.$new();! $controller('FooController', {$scope: this.scope})! }));!! describe('setFoo()', function() {!! it('sets the foo value', function() {! expect(this.scope.foo).not.toBeDefined();! this.scope.setFoo('Bar');! expect(this.scope.foo).toEqual('Bar');! });! });!});
Testing
describe('FooController', function() {!! beforeEach(function() {module('app')});!! beforeEach(inject(function($rootScope, $controller) {! this.scope = $rootScope.$new();! $controller('FooController', {$scope: this.scope})! }));!! describe('setFoo()', function() {!! it('sets the foo value', function() {! expect(this.scope.foo).not.toBeDefined();! this.scope.setFoo('Bar');! expect(this.scope.foo).toEqual('Bar');! });! });!});
Testing
describe('FooController', function() {!! beforeEach(function() {module('app')});!! beforeEach(inject(function($rootScope, $controller) {! this.scope = $rootScope.$new();! $controller('FooController', {$scope: this.scope})! }));!! describe('setFoo()', function() {!! it('sets the foo value', function() {! expect(this.scope.foo).not.toBeDefined();! this.scope.setFoo('Bar');! expect(this.scope.foo).toEqual('Bar');! });! });!});
Testing
describe('FooController', function() {!! beforeEach(function() {module('app')});!! beforeEach(inject(function($rootScope, $controller) {! this.scope = $rootScope.$new();! $controller('FooController', {$scope: this.scope})! }));!! describe('setFoo()', function() {!! it('sets the foo value', function() {! expect(this.scope.foo).not.toBeDefined();! this.scope.setFoo('Bar');! expect(this.scope.foo).toEqual('Bar');! });! });!});
Testing
describe('FooController', function() {!! beforeEach(function() {module('app')});!! beforeEach(inject(function($rootScope, $controller) {! this.scope = $rootScope.$new();! $controller('FooController', {$scope: this.scope})! }));!! describe('setFoo()', function() {!! it('sets the foo value', function() {! expect(this.scope.foo).not.toBeDefined();! this.scope.setFoo('Bar');! expect(this.scope.foo).toEqual('Bar');! });! });!});
Testing
Demo
Part 2
Setup!
Base Project!github.com/markbates/
fluent-2014
Node.js!http://nodejs.org
Lineman.js!npm install -g
Install Modules!npm install
Code Time!!
Thanks! @markbates
www.angularmasterclass.com