Getting Started in Angular

What I do

So You're Going To Teach Us Angular In 1Hr?!?!?!?!

No

Goals Of This Presentation

  • Give an understanding of the framework
  • Give some examples that can be referenced later
  • Help someone decide which paths to take when starting an Angular project.
  • Why

  • How

  • Next

Why use Angular?

What used to matter

What matters Now

Flourishing Community

Source Redmonk |  X: # Github Y: # Stackoverflow

Typical MVC

Typically an MVC framework will refer to the controller to update the view.

 

This works great for simple or extremely secure applications.

 

But not everything needs to be done on the server!

JQuery

Now we need a way to manage our view models.

 

One way to handle this is with JQuery. JQuery provides an easy interface for manipulating the DOM

 

JQuery tends to get really complicated. 

Server

Browser

JQuery

DOM

Angular

Framework to help manage data bindings.

 

Most important for real-time frequently updating applications. 

 

Compiles directly into the DOM. So no complex selectors.

Server

Browser

Angular

$rootScope

DOM

So What "IS" Angular?

  • Javascript
  • MVC platform for the client
  • Allows for efficient 2-way Data Binding
  • Supported by Google*

Grails/Angular Visualized

Pick Your Poison

  • 1.2.x
    • Use if you are using IE8-IE9
  • 1.3.x
    • Cutting edge stable
  • 1.4.x
    • Release Candidate
    • Improves Performance
  • 2.x
    • ECMA 6 Compatible
    • Not finished
    • Super-Fast/ Simple

What Is A

"Single Page Application"?

Easiest Metric: How many flashes do you see?

Routes

Instead of pages Angular uses routes.

 

Think of it as you render one page, then let Angular control navigation.

 

There are 3rd party routings like the Angular-UI project. However, we will talk more in the future of Angular section.

Single Page Apps Feel Less Like Web Apps

Directives

Routes will typically paint Directives. These are also known as "Web Components"

 

This allows for reusability and encapsulation.

 

Makes testing a whole lot easier.

 

More later 

A little outdated but...

Easy Transition To Native Application

Built In Interfaces

  • iOS: UIWebView
  • Android: WebView
  • Third Party : Cordova/PhoneGap

Testing

  • Unit Testing: Jasmine
  • End2End: Cucumber

How To Become an Angular Super Hero!

Be Patient!

OMG there is an Angular 2!

But it is 5x faster

Study The Code

https://github.com/angular/angular

https://github.com/angular/angular.js

Plnkr Helps You To Learn

Angular Application Structure

Project Structure

  • Maven Style

  • "Component" Style

Maven Style

Separate folders for each type of code

 

Tests are separate from actual code

 

Tries to produce "components" but just seems to add complexity.

 

Works well for "legacy" projects

"Component" Style

The idea is to break everything into small "units". To do this we include everything in one directory.

 

Fits with the idea of "Shadow DOM" and Web Components.

 

This will be fairly common in next-gen browsers.

The Core Of Angular: Modules

// Create a new Module
var app1 = angular.module('plunker', []);
// Access an existing module
var app2 = angular.module('plunker');
// Create another module with a dependency
var app3 = angular.module('other', [
   'restangular'
]);

$RootScope

  • RootScope is the "Scope God"
  • It manages and updates all of the scopes in your project.
  • It can be used to broadcast module wide messages.
  • Used to create scope instances in testing.

Next: The Controller

app.controller('MainCtrl', function($scope) {
  $scope.name = 'World';
});

Templating (HTML2JS)

.add-note.question
  .row
     h3.col-xs-offset-1 Add A Question
  .row.well(ng-if="questionCtrl.errorMessages.length > 0")
     .validation-error(ng-repeat="errorMessage in questionCtrl.errorMessages")
       p {{errorMessage}}
  .row
      .input-group
        label(for="questionText") Question Text
        input#questionText(name="questionText", type="text", ng-model="questionCtrl.newQuestion.text")
      .input-group  
        label(for="questionDesc") Question Description
        text-angular(ng-model="questionCtrl.newQuestion.desc")
  .row.pad-top 
    .col-sm-offset-2
      button(class="btn btn-primary",ng-click="questionCtrl.add()") Add Note
      
angular.module("main/partials/question/addQuestion.jade", []).run(["$templateCache", function($templateCache) {
  $templateCache.put("main/partials/question/addQuestion.jade",
    "<div class=\"add-note question\">\n" +
    "  <div class=\"row\">\n" +
    "    <h3 class=\"col-xs-offset-1\">Add A Question</h3>\n" +
    "  </div>\n" +
    "  <div ng-if=\"questionCtrl.errorMessages.length > 0\" class=\"row well\">\n" +
    "    <div ng-repeat=\"errorMessage in questionCtrl.errorMessages\" class=\"validation-error\">\n" +
    "      <p>{{errorMessage}}</p>\n" +
    "    </div>\n" +
    "  </div>\n" +
    "  <div class=\"row\">\n" +
    "    <div class=\"input-group\">\n" +
    "      <label for=\"questionText\">Question Text</label>\n" +
    "      <input id=\"questionText\" name=\"questionText\" type=\"text\" ng-model=\"questionCtrl.newQuestion.text\"/>\n" +
    "    </div>\n" +
    "    <div class=\"input-group\"> \n" +
    "      <label for=\"questionDesc\">Question Description</label>\n" +
    "      <text-angular ng-model=\"questionCtrl.newQuestion.desc\"></text-angular>\n" +
    "    </div>\n" +
    "  </div>\n" +
    "  <div class=\"row pad-top\"> \n" +
    "    <div class=\"col-sm-offset-2\">\n" +
    "      <button ng-click=\"questionCtrl.add()\" class=\"btn btn-primary\">Add Note</button>\n" +
    "    </div>\n" +
    "  </div>\n" +
    "</div>");
}]);

Dependency Injection

Dependency Injection Requires Annotaion

Dependency Injection

// Controller
function TestController ($scope, $http){...}
// Without annotation (cannot be minified)
app.controller('testCtrl', TestController)
// Using $inject
TestController.$inject = ['$scope', '$http'];
// Without using $inject 
app.controller('testCtrl', ['$scope', '$http', function($scope, $http) {...}]);

Services

  • A Singleton within the application
  • Shared between multiple controllers etc
  • Typically where you will interface with server services

Common Services

  • Most Angular services begin with $
    • $http, $compile, $rootScope
  • Third party services do not
    • restangular, angular-ui

Directives

  • Basically "Custom Tags"

  • Allow one to easily bind Angular to HTML

  • Probably one of the hardest concepts to understand

  • Prepend to avoid collisions

  • Gone in 2.0

List Directive

function ListDirective($templateCache) {
  return {
	  restrict: "E",
	  templateUrl: 'main/partials/question/listQuestion.jade',
	  controller:  'questionController',
	  //Example of how you could use controller as per https://github.com/angular/angular.js/issues/7635
	  //I left this out because it would add 3 lines and remove 1 ;-)
//	  controllerAs: 'questionCtrl'
  }
};
app.directive('jgNoteList', ListDirective);
Feature: Simple Feature
  As a user 
  I want to add a question

  Scenario: Adding a question
    Given I am on the main page
    When I click the add button
    And I fill out question information
    Then I should see the new question

CucumberJS

/*jslint node: true */
"use strict";
var Browser = require('zombie'),
    assert = require("assert"),
    Chance = require('chance');

var WorldConstructor = function WorldConstructor(callback) {
	this.browser = new Browser();
	this.chance = new Chance();
	var addForm = "#add-form";
	var _this = this;
	function addQuestionFormLoaded(window) {
		return window.document.querySelector(addForm);
	}
	var world = {
		visit : function(url, callback) {
			this.browser.visit(url);
			this.browser.wait(function() {
				console.log("Waiting to callback");
				callback();
			});
		}.bind(this),
		clickLink : function(selector, callback) {
			this.browser.clickLink(selector, function() {
				var promise = _this.browser.wait(addQuestionFormLoaded, null)
				console.log("Test 123");
				promise.then(function() {
					console.log("Test");
					callback();
				})
			});
		}.bind(this), 
		fillQuestionForm: function(callback){
			var title = this.chance.string({length: 20});
			assert.ok(this.browser.query(addForm), "It should have the add form");
			this.browser.fill('textArea[id^="taHtmlElement"]', "Test Desc").fill("#questionText", this.title);
			this.browser.pressButton("Add Note", function() {
				callback();
			});
		}.bind(this),
		checkResults: function(callback){
			var x = this.browser.html(".question-text");
			var hasText = (x.indexOf(this.title) > -1)
			assert.equal(hasText, true, "The title should show up somewhere in the questions text.");
			callback();
		}.bind(this)
	}

	callback(world); // tell Cucumber we're finished and to use our world
						// object instead of 'this'
};
exports.World = WorldConstructor;
var noteStepDefinitionWrapper = function() {

	this.World = require("../support/world.js").World; // overwrite default
	// World constructor

	this.Given(/^I am on the main page$/, function(callback) {
		console.log("step 1");
		this.visit('http://localhost:8080/grails-angular', callback);
	});

	this.When(/^I click the add button$/, function(callback) {
		console.log("step 2");
		this.clickLink('#add-question', callback);
	});

	this.When(/^I fill out question information$/, function(callback) {
		console.log("step 3");
		this.fillQuestionForm(callback);
	});
	this.Then(/^I should see the new question$/, function(callback) {
		console.log("step 4");
		this.checkResults(callback)
	});
};
module.exports = noteStepDefinitionWrapper;
describe("Testing the question", function() {
	var $controller,
	    $location,
	    $rootScope,
	    $scope,
	    controller,
	    questionService;
	    var item = {
			put:function(){},
			remove:function(){}
	    }, question;
	beforeEach(module('jg.ngGrails'));
	var wire = function( _$rootScope_, _$location_, _$controller_, _questionService_, $q){
		$controller = _$controller_;
		$location = _$location_;
		spyOn($location, 'path');
		$rootScope = _$rootScope_;
		$scope = $rootScope.$new();
		questionService = _questionService_;
		spyOn(item, 'put');
		spyOn(item, 'remove');
		var defer = $q.defer();
		spyOn(questionService, 'add').and.returnValue(defer.promise);
		defer.resolve();
		questionService.questions = [item,item];
		controller = $controller('questionController', { 
		  $scope: $scope
		});
	}
	beforeEach(inject(wire));
	it("Make sure vote up works", function() {
		expect(item.put).not.toHaveBeenCalled();
		controller.voteUp(1);
		expect(item.put).toHaveBeenCalled();
	});
	it("Make sure vote down works", function() {
		expect(item.put).not.toHaveBeenCalled();
		controller.voteDown(1);
		expect(item.put).toHaveBeenCalled();
	});
	it("Make sure remove works", function() {
		expect(item.remove).not.toHaveBeenCalled();
		controller.delete(1);
		expect(item.remove).toHaveBeenCalled();
	});
	it("Make sure the edit is working as expected", function(){
		expect(item.put).not.toHaveBeenCalled();
		expect($location.path).not.toHaveBeenCalled();
		controller.edit(1);
		expect(item.put).toHaveBeenCalled();
		expect($location.path).toHaveBeenCalledWith('');
	})
});

Jasmine test

ES6 And The Future Of Angular

What is ES6

  • A Standard Javascript
  • Slowly being integrated into modern browsers.
  • Allows "scoped" variables
  • Introduces native module concept
  • More of a "classy" language

Going

  • Controllers
  • Directives
  • Scope
  • Module
  • JQLite

Coming

  • More Generic Syntax
  • ShadowDOM
  • DI Query
  • Benchpress
  • WTF Instrumentation (Web Tracing Framework)

http://bit.ly/1tLYgXM

http://bit.ly/1tLYgXM

So How Can We Try This Today?!?!

Should I Develop For 1.X or 2.X?

  • Why

  • How

  • Next

Thanks

Questions?

Scope Lifecycle

  • ngRoute events

    • $routeChangeStart

    • $routeUpdate

    • $routeChangeSuccess

  • ngView events

    • $viewContentLoaded

  • controller initialization
  • link/compile
    • ​pre-link
    • post-link

Validation

@Validateable
class Question {
	Integer key
	String text
	String desc
	Integer voteCount
	List<String> errorMessages
	static constraints = {
		key unique: true
		text blank: false
		desc blank: false
		errorMessages nullable: true
	}
}
def create(obj){
	obj.key = key++
	obj.voteCount = 0
	//Validation needs to happen after adding the key
	obj.validate()
	if(!obj.hasErrors()){
		notes.put(obj.key.toString(), obj)
	}
	else{
		obj.errorMessages = obj?.errors?.allErrors?.collect{messageSource.getMessage(it,null)}
	}
	obj
}
Made with Slides.com