<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="https://raibledesigns.com/roller-ui/styles/rss.xsl" media="screen"?><rss version="2.0" 
  xmlns:dc="http://purl.org/dc/elements/1.1/"
  xmlns:atom="http://www.w3.org/2005/Atom" >
<channel>
  <title>Raible Designs</title>
  <link>https://raibledesigns.com/rd/</link>
      <atom:link rel="self" type="application/rss+xml" href="https://raibledesigns.com/rd/feed/entries/rss?tags=javascript" />
    <description>Raible Designs is an Enterprise Open Source Consulting company. We specialize in UI and Full Stack Architectures using HTML5, CSS, JavaScript and Java. We love HTML5, Angular, Bootstrap, Spring Boot, and especially JHipster.</description>
  <language>en-us</language>
  <copyright>Copyright 2026</copyright>
  <lastBuildDate>Thu, 30 Apr 2026 03:43:46 -0600</lastBuildDate>
  <generator>Apache Roller (incubating) 5.0.3 (1388864191739:dave)</generator>
        <item>
    <guid isPermaLink="true">https://raibledesigns.com/rd/entry/life_as_an_open_source</guid>
    <title>Life as an Open Source Developer</title>
    <dc:creator>Matt Raible</dc:creator>
    <link>https://raibledesigns.com/rd/entry/life_as_an_open_source</link>
        <pubDate>Thu, 3 Nov 2016 16:29:01 -0600</pubDate>
    <category>Open Source</category>
    <category>angularjs</category>
    <category>angular2</category>
    <category>opensource</category>
    <category>javascript</category>
    <category>stormpath</category>
    <category>springboot</category>
    <category>jhipster</category>
    <category>java</category>
    <category>github</category>
            <description>&lt;p&gt;
It&apos;s been a little over a month since I started my &lt;a href=&quot;https://raibledesigns.com/rd/entry/life_update_a_summer_to&quot;&gt;
new gig at Stormpath&lt;/a&gt;. I gotta say, life is great as an open source developer! Yes, I did start working for them as a consultant in April, so it&apos;s not a huge change for me.
However, I only recently realized I haven&apos;t written a &lt;em&gt;single line&lt;/em&gt; of proprietary code the entire time.
My &lt;a href=&quot;https://github.com/mraible&quot;&gt;GitHub contributions&lt;/a&gt; look pretty good this year. They&apos;re nothing like &lt;a href=&quot;https://github.com/mojavelinux&quot;&gt;@mojavelinux&lt;/a&gt;, 
or &lt;a href=&quot;https://github.com/dsyer&quot;&gt;@dsyer&lt;/a&gt;, but I&apos;ll get there. &lt;img src=&quot;//raibledesigns.com/images/smileys/wink.gif&quot; class=&quot;smiley&quot; alt=&quot;;)&quot; title=&quot;;)&quot;&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://github.com/mraible&quot; title=&quot;GitHub Profile - November 3, 2016&quot;&gt;
    &lt;img src=&quot;https://c7.staticflickr.com/6/5703/30128917414_8e7c7a8e57_z.jpg&quot; width=&quot;640&quot; alt=&quot;GitHub Profile - November 3, 2016&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;It&apos;s also been a bit more stress than I&apos;m used to. I think this comes from a couple things: 1) turning my hobby into my job and 2)
    I&apos;ve set a lot of high expectations for myself. As a developer evangelist, I get to create my own job. That means I can
    speak at the conferences I want to, write the code I want to, create the blog posts I want to, and everything else in between.
&lt;/p&gt;
&lt;p&gt;At the end of September, I finished &lt;a href=&quot;http://www.jhipster-book.com/#!/news/entry/book-updated-for-jhipster-3-and-jhipster-gets-dirty&quot;&gt;updating the 
JHipster Mini-Book for JHipster 3.x&lt;/a&gt;. It&apos;s gone through tech editing and it&apos;s being copy-edited right now. I hope to release it within a week. 
&lt;/p&gt;
&lt;p&gt;In early October, I said I&apos;d commit to writing one blog post per week, develop a JHipster module for Stormpath, and help get their 
    Angular 2 support good enough for an alpha release. I&apos;m happy to report I&apos;ve been able to accomplish most of these and I hope to show off
    our Angular 2 support soon.&lt;/p&gt;
&lt;p&gt;
I then channeled my efforts into integrating Stormpath&apos;s Java SDK with their AngularJS directives. You can read about how I did that in
&lt;a href=&quot;https://stormpath.com/blog/angularjs-spring-boot-tutorial&quot;&gt;Get Started with AngularJS, Spring Boot, and Stormpath&lt;/a&gt;.
Unlike &lt;a href=&quot;https://raibledesigns.com/rd/entry/getting_started_with_angularjs&quot;&gt;my previous AngularJS tutorial&lt;/a&gt;, this one connects to a 
backend and shows how to communicate with Spring Boot cross-domain.
&lt;/p&gt;
&lt;p&gt;If you like to read code more than words, you can look at the &lt;a href=&quot;https://github.com/stormpath/angularjs-spring-boot-stormpath-example&quot;&gt;example project&apos;s commits
on GitHub&lt;/a&gt;.
&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create an AngularJS UI: &lt;a href=&quot;https://github.com/stormpath/angularjs-spring-boot-stormpath-example/commit/652ee29d9a002f5d437d356481809fe74114fe7e&quot;&gt;search&lt;/a&gt; and &lt;a href=&quot;https://github.com/stormpath/angularjs-spring-boot-stormpath-example/commit/9a06e9071d5db9710c3a8555c0dfe81c752f2242&quot;&gt;edit&lt;/a&gt; features&lt;/li&gt;
&lt;li&gt;Create a Spring Boot app with Stormpath: &lt;a href=&quot;https://github.com/stormpath/angularjs-spring-boot-stormpath-example/commit/740ed84ccb16c94bfb6451c453c325b7f86fa870&quot;&gt;app from start.stormpath.io&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Develop an API to CRUD people with Spring Data REST: &lt;a href=&quot;https://github.com/stormpath/angularjs-spring-boot-stormpath-example/commit/f223f26dba108e864cec271b32b856423bc12d74&quot;&gt;/api/people&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Integrate AngularJS and Spring Boot apps: &lt;a href=&quot;https://github.com/stormpath/angularjs-spring-boot-stormpath-example/commit/88f43da9fc14bb59e6d1b7f36f658730029b4bd7&quot;&gt;cross-domain&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Integrate Stormpath into AngularJS for login, registration and forgot password: &lt;a href=&quot;https://github.com/stormpath/angularjs-spring-boot-stormpath-example/commit/2eee2b677237f793bf4ff25b6705d9c72efc984d&quot;&gt;Stormpath Angular SDK&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;
Last week, I released a &lt;a href=&quot;https://jhipster.github.io/modules/marketplace/#/details/generator-jhipster-stormpath&quot;&gt;JHipster module
 that integrates Stormpath&lt;/a&gt;. This exercise was good because I was able to identify some gaps in Stormpath&apos;s SDKs &lt;em&gt;and&lt;/em&gt; fix them.
Getting something to work made me feel good; having the ability to improve the developer experience
was even better! Of course, &lt;a href=&quot;https://stormpath.com/blog/stormpath-jhipster-application&quot;&gt;I blogged about what I learned&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;
This week, I edited and code reviewed some posts from Karl Penzhorn on &lt;a href=&quot;https://stormpath.com/blog/crud-application-react-spring-boot-user-authentication&quot;&gt;
React with Spring Boot&lt;/a&gt; and using &lt;a href=&quot;https://stormpath.com/blog/optimize-react-webpack&quot;&gt;webpack with React&lt;/a&gt;. I also got to &lt;a href=&quot;https://github.com/mattlewis92/generator-angular-library/issues/14&quot;&gt;
bang my head against the wall&lt;/a&gt; writing Angular 2 tests. If you&apos;re writing a module for Angular 2, &lt;a href=&quot;https://www.npmjs.com/package/generator-angular2-module&quot;&gt;
generator-angular2-module&lt;/a&gt; provides a nice starting point.&lt;/p&gt;
&lt;p&gt;Last, but certainly not least, I&apos;ll be speaking at a few events about Microservices, JHipster, Angular 2 and Stormpath in the near feature. &lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;&lt;a href=&quot;https://nofluffjuststuff.com/conference/denver/2016/11/session?id=38028&quot;&gt;Rocky Mountain Software Symposium&lt;/a&gt;, November 19&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://therichwebexperience.com/conference/clearwater/2016/12/speakers/matt_raible&quot;&gt;The Rich Web Experience&lt;/a&gt;, December 9&lt;/li&gt;
    &lt;li&gt;A joint talk at &lt;a href=&quot;http://www.meetup.com/DenverJavaUsersGroup/events/231602438/&quot;&gt;Denver JUG&lt;/a&gt; with the infamous &lt;a href=&quot;https://twitter.com/starbuxman&quot;&gt;Josh Long&lt;/a&gt;, December 14&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you have any questions about developer evangelism, the technologies I mentioned in this post, or Stormpath, please let me know. Otherwise, I hope to see you on the road soon!
    &lt;/p&gt;</description>          </item>
    <item>
    <guid isPermaLink="true">https://raibledesigns.com/rd/entry/getting_started_with_angular_cli</guid>
    <title>Getting Started + Testing with Angular CLI and Angular 2 (RC5)</title>
    <dc:creator>Matt Raible</dc:creator>
    <link>https://raibledesigns.com/rd/entry/getting_started_with_angular_cli</link>
        <pubDate>Tue, 23 Aug 2016 17:18:41 -0600</pubDate>
    <category>The Web</category>
    <category>npm</category>
    <category>jasmine</category>
    <category>angular2</category>
    <category>karma</category>
    <category>javascript</category>
    <category>typescript</category>
    <category>node</category>
    <category>angular-cli</category>
    <category>protractor</category>
    <category>asciidoctor</category>
    <atom:summary type="html">&lt;p&gt;I started creating Angular 2 applications when it was in beta (&lt;a
    href=&quot;//raibledesigns.com/rd/entry/getting_started_with_angular_2&quot;&gt;back in March&lt;/a&gt;). To keep up with Angular 2&apos;s
    changes,
    I wrote a &lt;a href=&quot;//raibledesigns.com/rd/entry/getting_started_with_angular_2_rc1&quot;&gt;tutorial about developing with
        RC1&lt;/a&gt; in June. Earlier this month, RC5 was released
    and &lt;a href=&quot;https://angular.io/docs/ts/latest/cookbook/rc4-to-rc5.html&quot;&gt;many things changed once again&lt;/a&gt;. I think
    Scott Davis sums it up nicely in a tweet.&lt;/p&gt;

&lt;div style=&quot;margin: 0 auto; max-width: 500px&quot;&gt;
    &lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;They keep saying &quot;Release Candidate&quot;, but I
        don&apos;t think it means what they think it means...&lt;br&gt;&lt;br&gt;/cc &lt;a
            href=&quot;https://twitter.com/hashtag/angular2?src=hash&quot;&gt;#angular2&lt;/a&gt; &lt;a
            href=&quot;https://twitter.com/hashtag/rc5?src=hash&quot;&gt;#rc5&lt;/a&gt; &lt;a href=&quot;https://t.co/WmNalTYgTN&quot;&gt;https://t.co/WmNalTYgTN&lt;/a&gt;
    &lt;/p&gt;&amp;mdash; Scott Davis (@scottdavis99) &lt;a href=&quot;https://twitter.com/scottdavis99/status/763399251229417472&quot;&gt;August
        10, 2016&lt;/a&gt;&lt;/blockquote&gt;
    &lt;script async src=&quot;//platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;/div&gt;

&lt;p&gt;To keep up with the rapid pace of change in Angular 2, I decided to write another tutorial, this time using &lt;a
    href=&quot;https://github.com/angular/angular-cli&quot;&gt;Angular CLI&lt;/a&gt;. The biggest change I found since
    writing the last tutorial is testing infrastructure changes. Since &lt;a
        href=&quot;https://angular.io/docs/ts/latest/testing/&quot;&gt;Angular&apos;s Testing documentation&lt;/a&gt; hasn&apos;t been updated
    recently, hopefully this tutorial will help.&lt;/p&gt;</atom:summary>        <description>&lt;p&gt;I started creating Angular 2 applications when it was in beta (&lt;a
    href=&quot;//raibledesigns.com/rd/entry/getting_started_with_angular_2&quot;&gt;back in March&lt;/a&gt;). To keep up with Angular 2&apos;s
    changes,
    I wrote a &lt;a href=&quot;//raibledesigns.com/rd/entry/getting_started_with_angular_2_rc1&quot;&gt;tutorial about developing with
        RC1&lt;/a&gt; in June. Earlier this month, RC5 was released
    and &lt;a href=&quot;https://angular.io/docs/ts/latest/cookbook/rc4-to-rc5.html&quot;&gt;many things changed once again&lt;/a&gt;. I think
    Scott Davis sums it up nicely in a tweet.&lt;/p&gt;

&lt;div style=&quot;margin: 0 auto; max-width: 500px&quot;&gt;
    &lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;They keep saying &quot;Release Candidate&quot;, but I
        don&apos;t think it means what they think it means...&lt;br&gt;&lt;br&gt;/cc &lt;a
            href=&quot;https://twitter.com/hashtag/angular2?src=hash&quot;&gt;#angular2&lt;/a&gt; &lt;a
            href=&quot;https://twitter.com/hashtag/rc5?src=hash&quot;&gt;#rc5&lt;/a&gt; &lt;a href=&quot;https://t.co/WmNalTYgTN&quot;&gt;https://t.co/WmNalTYgTN&lt;/a&gt;
    &lt;/p&gt;&amp;mdash; Scott Davis (@scottdavis99) &lt;a href=&quot;https://twitter.com/scottdavis99/status/763399251229417472&quot;&gt;August
        10, 2016&lt;/a&gt;&lt;/blockquote&gt;
    &lt;script async src=&quot;//platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;/div&gt;

&lt;p&gt;To keep up with the rapid pace of change in Angular 2, I decided to write another tutorial, this time using &lt;a
    href=&quot;https://github.com/angular/angular-cli&quot;&gt;Angular CLI&lt;/a&gt;. The biggest change I found since
    writing the last tutorial is testing infrastructure changes. Since &lt;a
        href=&quot;https://angular.io/docs/ts/latest/testing/&quot;&gt;Angular&apos;s Testing documentation&lt;/a&gt; hasn&apos;t been updated
    recently, hopefully this tutorial will help.&lt;/p&gt;
&lt;p&gt;Below is a table of contents in case you want to skip right to a particular section.&lt;/p&gt;

&lt;div id=&quot;toc&quot; class=&quot;toc&quot;&gt;
    &lt;ul&gt;
        &lt;li&gt;&lt;a href=&quot;//raibledesigns.com/rd/entry/getting_started_with_angular_cli#_what_you_ll_build&quot;&gt;What you&apos;ll build&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;//raibledesigns.com/rd/entry/getting_started_with_angular_cli#_what_you_ll_need&quot;&gt;What you&apos;ll need&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;//raibledesigns.com/rd/entry/getting_started_with_angular_cli#_create_your_project&quot;&gt;Create your project&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;//raibledesigns.com/rd/entry/getting_started_with_angular_cli#_run_the_application&quot;&gt;Run the application&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;//raibledesigns.com/rd/entry/getting_started_with_angular_cli#_add_a_search_feature&quot;&gt;Add a search feature&lt;/a&gt;
            &lt;ul class=&quot;sectlevel2&quot;&gt;
                &lt;li&gt;&lt;a href=&quot;//raibledesigns.com/rd/entry/getting_started_with_angular_cli#_the_basics&quot;&gt;The Basics&lt;/a&gt;&lt;/li&gt;
                &lt;li&gt;&lt;a href=&quot;//raibledesigns.com/rd/entry/getting_started_with_angular_cli#_the_backend&quot;&gt;The Backend&lt;/a&gt;&lt;/li&gt;
            &lt;/ul&gt;
        &lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;//raibledesigns.com/rd/entry/getting_started_with_angular_cli#_add_an_edit_feature&quot;&gt;Add an edit feature&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;//raibledesigns.com/rd/entry/getting_started_with_angular_cli#_testing&quot;&gt;Testing&lt;/a&gt;
            &lt;ul class=&quot;sectlevel2&quot;&gt;
                &lt;li&gt;&lt;a href=&quot;//raibledesigns.com/rd/entry/getting_started_with_angular_cli#_unit_test_the_searchservice&quot;&gt;Unit test the SearchService&lt;/a&gt;&lt;/li&gt;
                &lt;li&gt;&lt;a href=&quot;//raibledesigns.com/rd/entry/getting_started_with_angular_cli#_unit_test_the_searchcomponent&quot;&gt;Unit test the SearchComponent&lt;/a&gt;&lt;/li&gt;
                &lt;li&gt;&lt;a href=&quot;//raibledesigns.com/rd/entry/getting_started_with_angular_cli#_integration_test_the_search_ui&quot;&gt;Integration test the search UI&lt;/a&gt;&lt;/li&gt;
                &lt;li&gt;&lt;a href=&quot;//raibledesigns.com/rd/entry/getting_started_with_angular_cli#_testing_the_search_feature&quot;&gt;Testing the search feature&lt;/a&gt;&lt;/li&gt;
                &lt;li&gt;&lt;a href=&quot;//raibledesigns.com/rd/entry/getting_started_with_angular_cli#_testing_the_edit_feature&quot;&gt;Testing the edit feature&lt;/a&gt;&lt;/li&gt;
            &lt;/ul&gt;
        &lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;//raibledesigns.com/rd/entry/getting_started_with_angular_cli#_continuous_integration&quot;&gt;Continuous Integration&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;//raibledesigns.com/rd/entry/getting_started_with_angular_cli#_source_code&quot;&gt;Source code&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;//raibledesigns.com/rd/entry/getting_started_with_angular_cli#_summary&quot;&gt;Summary&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
&lt;/div&gt;

&lt;h3 id=&quot;_what_you_ll_build&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#_what_you_ll_build&quot;&gt;&lt;/a&gt;What you&apos;ll build&lt;/h3&gt;
&lt;p&gt;You&apos;ll build a simple web application with Angular CLI, a new tool for Angular 2 development. You&apos;ll
    create an application with search and edit features.&lt;/p&gt;

&lt;h3 id=&quot;_what_you_ll_need&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#_what_you_ll_need&quot;&gt;&lt;/a&gt;What you&apos;ll need&lt;/h3&gt;

&lt;ul&gt;
    &lt;li&gt;About 30-40 minutes.
    &lt;/li&gt;
    &lt;li&gt;A favorite text editor or IDE. I recommend &lt;a href=&quot;https://www.jetbrains.com/idea/&quot;&gt;IntelliJ
        IDEA&lt;/a&gt; and its
        &lt;a href=&quot;https://plugins.jetbrains.com/plugin/8395?pr=idea&quot;&gt;Angular 2 TypeScript Live
            Templates
            plugin&lt;/a&gt;.
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://nodejs.org/&quot;&gt;Node.js&lt;/a&gt; and npm installed. I recommend using &lt;a
        href=&quot;https://github.com/creationix/nvm&quot;&gt;nvm&lt;/a&gt;.
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://github.com/angular/angular-cli&quot;&gt;Angular CLI&lt;/a&gt; installed. If you don&apos;t have
        Angular CLI installed, install it using &lt;code&gt;npm install -g angular-cli@latest&lt;/code&gt;.
    &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The latest release of Angular CLI (beta 10) uses Angular 2 RC4. Because of this, I used
    the master branch of Angular CLI to create this tutorial. To do this, clone &lt;a
        href=&quot;https://github.com/angular/angular-cli&quot;&gt;angular-cli&lt;/a&gt; and
    run &lt;code&gt;npm link&lt;/code&gt; in the directory you cloned it into. If you have issues,
    see &lt;a href=&quot;https://github.com/angular/angular-cli/issues/1773&quot;&gt;#1733&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
    &lt;a href=&quot;https://augury.angular.io/&quot;&gt;Angular Augury&lt;/a&gt; is a Google Chrome Dev Tools
    extension for debugging Angular 2 applications.
    I haven&apos;t needed it much myself, but I can see how it might come in handy.&lt;/p&gt;

&lt;h3 id=&quot;_create_your_project&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#_create_your_project&quot;&gt;&lt;/a&gt;Create your project&lt;/h3&gt;
&lt;p&gt;Create a new project using the &lt;code&gt;ng new&lt;/code&gt; command:&lt;/p&gt;

&lt;pre&gt;ng new ng2-demo&lt;/pre&gt;

&lt;p&gt;This will create a &lt;code&gt;ng2-demo&lt;/code&gt; project and run &lt;code&gt;npm install&lt;/code&gt; in it. It takes
    about a minute to complete, but will vary based on your internet connection speed.&lt;/p&gt;

&lt;pre&gt;[mraible:~/dev] 45s $ ng new ng2-demo
installing ng2
  create .editorconfig
  create README.md
  create src/app/app.component.css
  create src/app/app.component.html
  create src/app/app.component.spec.ts
  create src/app/app.component.ts
  create src/app/environment.ts
  create src/app/index.ts
  create src/app/shared/index.ts
  create src/favicon.ico
  create src/index.html
  create src/main.ts
  create src/system-config.ts
  create src/tsconfig.json
  create src/typings.d.ts
  create angular-cli-build.js
  create angular-cli.json
  create config/environment.dev.ts
  create config/environment.js
  create config/environment.prod.ts
  create config/karma-test-shim.js
  create config/karma.conf.js
  create config/protractor.conf.js
  create e2e/app.e2e-spec.ts
  create e2e/app.po.ts
  create e2e/tsconfig.json
  create e2e/typings.d.ts
  create .gitignore
  create package.json
  create public/.npmignore
  create tslint.json
  create typings.json
Successfully initialized git.
- Installing packages for tooling via npm
  -- es6-shim (global)
  -- angular-protractor (global dev)
  -- jasmine (global dev)
  -- selenium-webdriver (global dev)

Installed packages for tooling via npm.
[mraible:~/dev] 1m5s $&lt;/pre&gt;

&lt;p&gt;You can see the what version of Angular CLI you&apos;re using with &lt;code&gt;ng --version&lt;/code&gt;.&lt;/p&gt;

&lt;pre&gt;$ ng --version
angular-cli: local (v1.0.0-beta.11-webpack.2, branch: master)
node: 4.4.7
os: darwin x64&lt;/pre&gt;

&lt;h3 id=&quot;_run_the_application&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#_run_the_application&quot;&gt;&lt;/a&gt;Run the application&lt;/h3&gt;

&lt;p&gt;The project is configured with a simple web server for development. To start it, run:&lt;/p&gt;

&lt;pre&gt;ng serve&lt;/pre&gt;

&lt;p&gt;You should see a screen like the one below at &lt;a href=&quot;http://localhost:4200&quot;&gt;http://localhost:4200&lt;/a&gt;.&lt;/p&gt;

&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;img src=&quot;https://raw.githubusercontent.com/mraible/ng2-demo/master/src/assets/images/default-homepage.png&quot;
         alt=&quot;Default Homepage&quot; width=&quot;600&quot;&gt;
&lt;/p&gt;
&lt;p&gt;You can make sure your new project&apos;s tests pass, run &lt;code&gt;ng test&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;$ ng test
Built project successfully. Stored in &quot;dist/&quot;.
...
Chrome 52.0.2743 (Mac OS X 10.11.6): Executed 2 of 2 SUCCESS (0.039 secs / 0.012 secs)&lt;/pre&gt;

&lt;h3 id=&quot;_add_a_search_feature&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#_add_a_search_feature&quot;&gt;&lt;/a&gt;Add a search feature&lt;/h3&gt;
&lt;p&gt;To add a search feature, open the project in an IDE or your favorite text editor. For IntelliJ IDEA, use
    File &amp;gt; New Project &amp;gt; Static Web and point to the &lt;code&gt;ng2-demo&lt;/code&gt; directory.&lt;/p&gt;

&lt;h3 id=&quot;_the_basics&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#_the_basics&quot;&gt;&lt;/a&gt;The Basics&lt;/h3&gt;
&lt;p&gt;In a terminal window, cd into your project&apos;s directory and run the following command. This will
    create a search component.&lt;/p&gt;
&lt;pre&gt;$ ng g component search
installing component
  create src/app/search/search.component.css
  create src/app/search/search.component.html
  create src/app/search/search.component.spec.ts
  create src/app/search/search.component.ts
  create src/app/search/index.ts&lt;/pre&gt;

&lt;div class=&quot;quote&quot;&gt;
    &lt;p style=&quot;margin-top: 0&quot;&gt;&lt;strong&gt;Adding a Search Route&lt;/strong&gt;&lt;br&gt;

        In previous versions of CLI, you could generate a route
        &lt;strong&gt;and&lt;/strong&gt; a component. However, since beta 8, route generation has been
        disabled. This will likely be re-enabled in a future release.&lt;/p&gt;

    &lt;p&gt;The &lt;a href=&quot;https://angular.io/docs/ts/latest/guide/router.html&quot;&gt;Router
        documentation&lt;/a&gt;
        for Angular 2 RC5 provides the information you need to
        setup a route to the &lt;code&gt;SearchComponent&lt;/code&gt; you just generated. Here&apos;s a quick
        summary:&lt;/p&gt;

    &lt;p&gt;Create &lt;code&gt;src/app/app.routing.ts&lt;/code&gt; to define your routes.&lt;/p&gt;

    &lt;pre class=&quot;brush: js&quot;&gt;
import { Routes, RouterModule } from &apos;@angular/router&apos;;
import { SearchComponent } from &apos;./search/index&apos;;

const appRoutes: Routes = [
  { path: &apos;search&apos;, component: SearchComponent },
  { path: &apos;&apos;, redirectTo: &apos;/search&apos;, pathMatch: &apos;full&apos; }
];

export const appRoutingProviders: any[] = [];

export const routing = RouterModule.forRoot(appRoutes);
&lt;/pre&gt;
    &lt;p class=&quot;alert alert-warning&quot;&gt;
        Without the last path to redirect, there&apos;s a &lt;a
        href=&quot;http://stackoverflow.com/questions/38998085/error-cannot-match-any-routes-in-angular-2-rc5-with-angular-cli&quot;&gt;Cannot
        match any routes: &apos;&apos;&lt;/a&gt; console error.
    &lt;/p&gt;
    &lt;p&gt;In &lt;code&gt;src/app/app.module.ts&lt;/code&gt;, import the two constants you exported and
        configure them in &lt;code&gt;@NgModule&lt;/code&gt;:&lt;/p&gt;

    &lt;pre class=&quot;brush: js&quot;&gt;
import { routing, appRoutingProviders } from &apos;./app.routing&apos;;

import { SearchComponent } from &apos;./search/search.component&apos;;

@NgModule({
  ...
  imports: [
    ...
    routing
  ],
  providers: [appRoutingProviders],
  ...
})
export class AppModule { }
&lt;/pre&gt;
    &lt;p&gt;In &lt;code&gt;src/app/app.component.html&lt;/code&gt;, add a &lt;code&gt;RouterOutlet&lt;/code&gt; to display routes.&lt;/p&gt;

    &lt;pre class=&quot;brush: xml&quot;&gt;
&amp;lt;!-- Routed views go here --&amp;gt;
&amp;lt;router-outlet&amp;gt;&amp;lt;/router-outlet&amp;gt;
&lt;/pre&gt;
    &lt;p style=&quot;margin-bottom: 0&quot;&gt;Now that you have routing setup, you can continue writing the search feature.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;To allow navigation to the &lt;code&gt;SearchComponent&lt;/code&gt;, you can add a link in &lt;code&gt;src/app/app.component.html&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;brush: xml&quot;&gt;
&amp;lt;nav&amp;gt;
  &amp;lt;a routerLink=&quot;/search&quot; routerLinkActive=&quot;active&quot;&amp;gt;Search&amp;lt;/a&amp;gt;
&amp;lt;/nav&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Open &lt;code&gt;src/app/search/search.component.html&lt;/code&gt; and replace its default HTML with the following:&lt;/p&gt;
&lt;pre class=&quot;brush: xml&quot;&gt;
&amp;lt;h2&amp;gt;Search&amp;lt;/h2&amp;gt;
&amp;lt;form&amp;gt;
  &amp;lt;input type=&quot;search&quot; name=&quot;query&quot; &amp;#91;(ngModel)&amp;#93;=&quot;query&quot; (keyup.enter)=&quot;search()&quot;&amp;gt;
  &amp;lt;button type=&quot;button&quot; (click)=&quot;search()&quot;&amp;gt;Search&amp;lt;/button&amp;gt;
&amp;lt;/form&amp;gt;
&amp;lt;pre&amp;gt;{{searchResults | json}}&amp;lt;/pre&amp;gt;
&lt;/pre&gt;
&lt;p&gt;If you still have &lt;code&gt;ng serve&lt;/code&gt; running, your browser should refresh automatically.
    If not, navigate to &lt;a href=&quot;http://localhost:4200&quot;&gt;http://localhost:4200&lt;/a&gt;, and you should see
    the search form.&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;img src=&quot;https://raw.githubusercontent.com/mraible/ng2-demo/master/src/assets/images/search-without-css.png&quot;
         alt=&quot;Search component&quot; width=&quot;600&quot;&gt;
&lt;/p&gt;
&lt;p&gt;If you want to add CSS for this components, open &lt;code&gt;src/app/search/search.component.css&lt;/code&gt; and
    add some CSS. For example:&lt;/p&gt;
&lt;pre class=&quot;brush: css&quot;&gt;
:host {
  display: block;
  padding: 0 20px;
}
&lt;/pre&gt;
&lt;p&gt;This section has shown you how to generate a new component to a basic Angular 2 application
    with Angular CLI. The next section shows you how to create a use a JSON file and &lt;code&gt;localStorage&lt;/code&gt; to
    create a fake API.&lt;/p&gt;

&lt;h3 id=&quot;_the_backend&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#_the_backend&quot;&gt;&lt;/a&gt;The Backend&lt;/h3&gt;
&lt;p&gt;To get search results, create a &lt;code&gt;SearchService&lt;/code&gt; that makes HTTP requests to a JSON
    file. Start by generating a new service.&lt;/p&gt;
&lt;pre&gt;ng g service search&lt;/pre&gt;
&lt;p&gt;Move the generated &lt;code&gt;search.service.ts&lt;/code&gt; and its test to &lt;code&gt;app/shared/search&lt;/code&gt;. You
    will likely need to create this directory.&lt;/p&gt;
&lt;p&gt;Then, create &lt;code&gt;src/app/shared/search/data/people.json&lt;/code&gt; to hold your data.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
[
  {
    &quot;id&quot;: 1,
    &quot;name&quot;: &quot;Peyton Manning&quot;,
    &quot;phone&quot;: &quot;(303) 567-8910&quot;,
    &quot;address&quot;: {
      &quot;street&quot;: &quot;1234 Main Street&quot;,
      &quot;city&quot;: &quot;Greenwood Village&quot;,
      &quot;state&quot;: &quot;CO&quot;,
      &quot;zip&quot;: &quot;80111&quot;
    }
  },
  {
    &quot;id&quot;: 2,
    &quot;name&quot;: &quot;Demaryius Thomas&quot;,
    &quot;phone&quot;: &quot;(720) 213-9876&quot;,
    &quot;address&quot;: {
      &quot;street&quot;: &quot;5555 Marion Street&quot;,
      &quot;city&quot;: &quot;Denver&quot;,
      &quot;state&quot;: &quot;CO&quot;,
      &quot;zip&quot;: &quot;80202&quot;
    }
  },
  {
    &quot;id&quot;: 3,
    &quot;name&quot;: &quot;Von Miller&quot;,
    &quot;phone&quot;: &quot;(917) 323-2333&quot;,
    &quot;address&quot;: {
      &quot;street&quot;: &quot;14 Mountain Way&quot;,
      &quot;city&quot;: &quot;Vail&quot;,
      &quot;state&quot;: &quot;CO&quot;,
      &quot;zip&quot;: &quot;81657&quot;
    }
  }
]
&lt;/pre&gt;
&lt;p&gt;Modify &lt;code&gt;src/app/shared/search/search.service.ts&lt;/code&gt; and provide &lt;code&gt;Http&lt;/code&gt; as
    a dependency in its constructor. In this same file, create a &lt;code&gt;getAll()&lt;/code&gt; method to gather all the people. Also,
    define the &lt;code&gt;Address&lt;/code&gt; and &lt;code&gt;Person&lt;/code&gt; classes that JSON will be marshalled to.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
import { Injectable } from &apos;@angular/core&apos;;
import { Http, Response } from &apos;@angular/http&apos;;

@Injectable()
export class SearchService {
  constructor(private http: Http) {}

  getAll() {
    return this.http.get(&apos;app/shared/search/data/people.json&apos;).map((res: Response) =&gt; res.json());
  }
}

export class Address {
  street: string;
  city: string;
  state: string;
  zip: string;

  constructor(obj?: any) {
    this.street = obj &amp;&amp; obj.street || null;
    this.city = obj &amp;&amp; obj.city || null;
    this.state = obj &amp;&amp; obj.state || null;
    this.zip = obj &amp;&amp; obj.zip || null;
  }
}

export class Person {
  id: number;
  name: string;
  phone: string;
  address: Address;

  constructor(obj?: any) {
    this.id = obj &amp;&amp; Number(obj.id) || null;
    this.name = obj &amp;&amp; obj.name || null;
    this.phone = obj &amp;&amp; obj.phone || null;
    this.address = obj &amp;&amp; obj.address || null;
  }
}
&lt;/pre&gt;
&lt;p&gt;To make these classes available for consumption by your components, edit &lt;code&gt;src/app/shared/index.ts&lt;/code&gt;
    and add the following:&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;export * from &apos;./search/search.service&apos;;&lt;/pre&gt;
&lt;p&gt;In &lt;code&gt;search.component.ts&lt;/code&gt;, add imports for these classes.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;import { Person, SearchService } from &apos;../shared/index&apos;;&lt;/pre&gt;
&lt;p&gt;You can now add &lt;code&gt;query&lt;/code&gt; and &lt;code&gt;searchResults&lt;/code&gt; variables. While you&apos;re
    there, modify the constructor to inject the &lt;code&gt;SearchService&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
export class SearchComponent implements OnInit {
  query: string;
  searchResults: Array&amp;lt;Person&gt;;

  constructor(private searchService: SearchService) {}
&lt;/pre&gt;
&lt;p&gt;Then implement the &lt;code&gt;search()&lt;/code&gt; method to call the service&apos;s &lt;code&gt;getAll()&lt;/code&gt; method.&lt;/p&gt;

&lt;pre class=&quot;brush: js&quot;&gt;
search(): void {
  this.searchService.getAll().subscribe(
    data =&gt; { this.searchResults = data; },
    error =&gt; console.log(error)
  );
}
&lt;/pre&gt;
&lt;p&gt;At this point, you&apos;ll likely see the following message in your browser&apos;s console.&lt;/p&gt;
&lt;pre&gt;ORIGINAL EXCEPTION: No provider for SearchService!&lt;/pre&gt;
&lt;p&gt;To fix the &quot;No provider&quot; error from above, update &lt;code&gt;app.component.ts&lt;/code&gt; to import the &lt;code&gt;SearchService&lt;/code&gt;
    and add the service to the list of providers.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
import { SearchService } from &apos;./shared/index&apos;;

@Component({
  ...
  styleUrls: [&apos;app.component.css&apos;],
  viewProviders: [SearchService]
})
&lt;/pre&gt;
&lt;p&gt;Now clicking the search button should work. To make the results look better, remove the
    &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; tag and replace it with a &lt;code&gt;&amp;lt;table&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;brush: xml&quot;&gt;
&amp;lt;table *ngIf=&quot;searchResults&quot;&amp;gt;
  &amp;lt;thead&amp;gt;
  &amp;lt;tr&amp;gt;
    &amp;lt;th&amp;gt;Name&amp;lt;/th&amp;gt;
    &amp;lt;th&amp;gt;Phone&amp;lt;/th&amp;gt;
    &amp;lt;th&amp;gt;Address&amp;lt;/th&amp;gt;
  &amp;lt;/tr&amp;gt;
  &amp;lt;/thead&amp;gt;
  &amp;lt;tbody&amp;gt;
  &amp;lt;tr *ngFor=&quot;let person of searchResults; let i=index&quot;&amp;gt;
    &amp;lt;td&amp;gt;{{person.name}}&amp;lt;/td&amp;gt;
    &amp;lt;td&amp;gt;{{person.phone}}&amp;lt;/td&amp;gt;
    &amp;lt;td&amp;gt;{{person.address.street}}&amp;lt;br/&amp;gt;
      {{person.address.city}}, {{person.address.state}} {{person.address.zip}}
    &amp;lt;/td&amp;gt;
  &amp;lt;/tr&amp;gt;
  &amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Then add some additional CSS to improve its table layout.&lt;/p&gt;
&lt;pre class=&quot;brush: css&quot;&gt;
table {
  margin-top: 10px;
  border-collapse: collapse;
}

th {
  text-align: left;
  border-bottom: 2px solid #ddd;
  padding: 8px;
}

td {
  border-top: 1px solid #ddd;
  padding: 8px;
}
&lt;/pre&gt;
&lt;p&gt;Now the search results look better.&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;img
        src=&quot;https://raw.githubusercontent.com/mraible/ng2-demo/master/src/assets/images/search-results.png&quot;
        alt=&quot;Search Results&quot; width=&quot;600&quot;&gt;
&lt;/p&gt;
&lt;p&gt;But wait, we still don&apos;t have search functionality! To add a search feature, add a
    &lt;code&gt;search()&lt;/code&gt; method to &lt;code&gt;SearchService&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
search(q: string) {
  if (!q || q === &apos;*&apos;) {
    q = &apos;&apos;;
  } else {
    q = q.toLowerCase();
  }
  return this.getAll().map(data =&gt; {
    let results: any = [];
    data.map(item =&gt; {
      if (JSON.stringify(item).toLowerCase().includes(q)) {
        results.push(item);
      }
    });
    return results;
  });
}
&lt;/pre&gt;
&lt;p&gt;Then refactor &lt;code&gt;SearchComponent&lt;/code&gt; to call this method with its &lt;code&gt;query&lt;/code&gt; variable.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
search(): void {
  this.searchService.search(this.query).subscribe(
    data =&gt; { this.searchResults = data; },
    error =&gt; console.log(error)
  );
}
&lt;/pre&gt;
&lt;p&gt;Now search results will be filtered by the query value you type in.&lt;/p&gt;
&lt;p&gt;This section showed you how to fetch and display search results. The next section builds on
    this and shows how to edit and save a record.&lt;/p&gt;

&lt;h3 id=&quot;_add_an_edit_feature&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#_add_an_edit_feature&quot;&gt;&lt;/a&gt;Add an edit feature&lt;/h3&gt;

&lt;p&gt;Modify &lt;code&gt;search.component.html&lt;/code&gt; to add a click handler for editing a person.&lt;/p&gt;

&lt;pre class=&quot;brush: xml&quot;&gt;
&amp;lt;td&amp;gt;&amp;lt;a (click)=&quot;onSelect(person)&quot;&amp;gt;{{person.name}}&amp;lt;/a&amp;gt;&amp;lt;/td&amp;gt;
&lt;/pre&gt;

&lt;div class=&quot;alert alert-warning&quot;&gt;
    &lt;p&gt;In previous versions of Angular 2, you could embed a link with parameters directly into the HTML.
        For example:&lt;/p&gt;
    &lt;pre&gt;&amp;lt;a &amp;#91;routerLink&amp;#93;=&quot;&amp;#91;&apos;/edit&apos;, person.id&amp;#93;&quot;&amp;gt;&lt;/pre&gt;

    &lt;p&gt;Unfortunately, this doesn&apos;t work with RC5. Another issue is adding &lt;code&gt;href=&quot;&quot;&lt;/code&gt; causes
        the page to refresh. Without &lt;code&gt;href&lt;/code&gt;, the link doesn&apos;t look like a link.
        If you know of a solution to this problem, please send me a pull request.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Then add &lt;code&gt;onSelect(person)&lt;/code&gt; to &lt;code&gt;search.component.ts&lt;/code&gt;. You&apos;ll need to import
    &lt;code&gt;Router&lt;/code&gt; and set it as a local variable to make this work.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
import { Router } from &apos;@angular/router&apos;;
...
export class SearchComponent implements OnInit {
  ...

  constructor(private searchService: SearchService, private router: Router) { }

  ...

  onSelect(person: Person) {
    this.router.navigate([&apos;/edit&apos;, person.id]);
  }
}
&lt;/pre&gt;
&lt;p&gt;Run the following command to generate an &lt;code&gt;EditComponent&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;$ ng g component edit
installing component
  create src/app/edit/edit.component.css
  create src/app/edit/edit.component.html
  create src/app/edit/edit.component.spec.ts
  create src/app/edit/edit.component.ts
  create src/app/edit/index.ts&lt;/pre&gt;
&lt;p&gt;Add a route for this component in &lt;code&gt;app.routing.ts&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
import { EditComponent } from &apos;./edit/index&apos;;

const appRoutes: Routes = [
  { path: &apos;search&apos;, component: SearchComponent },
  { path: &apos;edit/:id&apos;, component: EditComponent },
  { path: &apos;&apos;, redirectTo: &apos;/search&apos;, pathMatch: &apos;full&apos; }
];
&lt;/pre&gt;
&lt;p&gt;Update &lt;code&gt;src/app/edit/edit.component.html&lt;/code&gt; to display an editable form. You might notice
    I&apos;ve added &lt;code&gt;id&lt;/code&gt; attributes to most elements. This is to
    make things easier when writing integration tests with Protractor.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
&amp;lt;div *ngIf=&quot;person&quot;&amp;gt;
  &amp;lt;h3&amp;gt;{{editName}}&amp;lt;/h3&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;label&amp;gt;Id:&amp;lt;/label&amp;gt;
    {{person.id}}
  &amp;lt;/div&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;label&amp;gt;Name:&amp;lt;/label&amp;gt;
    &amp;lt;input &amp;#91;(ngModel)&amp;#93;=&quot;editName&quot; name=&quot;name&quot; id=&quot;name&quot; placeholder=&quot;name&quot;/&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;label&amp;gt;Phone:&amp;lt;/label&amp;gt;
    &amp;lt;input &amp;#91;(ngModel)&amp;#93;=&quot;editPhone&quot; name=&quot;phone&quot; id=&quot;phone&quot; placeholder=&quot;Phone&quot;/&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;fieldset&amp;gt;
    &amp;lt;legend&amp;gt;Address:&amp;lt;/legend&amp;gt;
    &amp;lt;address&amp;gt;
      &amp;lt;input &amp;#91;(ngModel)&amp;#93;=&quot;editAddress.street&quot; id=&quot;street&quot;&amp;gt;&amp;lt;br/&amp;gt;
      &amp;lt;input &amp;#91;(ngModel)&amp;#93;=&quot;editAddress.city&quot; id=&quot;city&quot;&amp;gt;,
      &amp;lt;input &amp;#91;(ngModel)&amp;#93;=&quot;editAddress.state&quot; id=&quot;state&quot; size=&quot;2&quot;&amp;gt;
      &amp;lt;input &amp;#91;(ngModel)&amp;#93;=&quot;editAddress.zip&quot; id=&quot;zip&quot; size=&quot;5&quot;&amp;gt;
    &amp;lt;/address&amp;gt;
  &amp;lt;/fieldset&amp;gt;
  &amp;lt;button (click)=&quot;save()&quot; id=&quot;save&quot;&amp;gt;Save&amp;lt;/button&amp;gt;
  &amp;lt;button (click)=&quot;cancel()&quot; id=&quot;cancel&quot;&amp;gt;Cancel&amp;lt;/button&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Modify &lt;code&gt;EditComponent&lt;/code&gt; to import model and service classes and to use the
    &lt;code&gt;SearchService&lt;/code&gt; to get data.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
import { Component, OnInit, OnDestroy } from &apos;@angular/core&apos;;
import { Address, Person, SearchService } from &apos;../shared/index&apos;;
import { Subscription } from &apos;rxjs&apos;;
import { ActivatedRoute, Router } from &apos;@angular/router&apos;;

@Component({
  selector: &apos;app-edit&apos;,
  templateUrl: &apos;edit.component.html&apos;,
  styleUrls: [&apos;edit.component.css&apos;]
})
export class EditComponent implements OnInit, OnDestroy {
  person: Person;
  editName: string;
  editPhone: string;
  editAddress: Address;

  sub: Subscription;

  constructor(private route: ActivatedRoute,
              private router: Router,
              private service: SearchService) {
  }

  ngOnInit() {
    this.sub = this.route.params.subscribe(params =&gt; {
      let id = + params[&apos;id&apos;]; // (+) converts string &apos;id&apos; to a number
      this.service.get(id).subscribe(person =&gt; {
        if (person) {
          this.editName = person.name;
          this.editPhone = person.phone;
          this.editAddress = person.address;
          this.person = person;
        } else {
          this.gotoList();
        }
      });
    });
  }

  ngOnDestroy() {
    this.sub.unsubscribe();
  }

  cancel() {
    this.router.navigate([&apos;/search&apos;]);
  }

  save() {
    this.person.name = this.editName;
    this.person.phone = this.editPhone;
    this.person.address = this.editAddress;
    this.service.save(this.person);
    this.gotoList();
  }

  gotoList() {
    if (this.person) {
      this.router.navigate([&apos;/search&apos;, {term: this.person.name} ]);
    } else {
      this.router.navigate([&apos;/search&apos;]);
    }
  }
}
&lt;/pre&gt;
&lt;p&gt;Modify &lt;code&gt;SearchService&lt;/code&gt; to contain functions for finding a person by their id, and saving
    them. While you&apos;re in there, modify the &lt;code&gt;search()&lt;/code&gt; method to
    be aware of updated objects in &lt;code&gt;localStorage&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
search(q: string) {
  if (!q || q === &apos;*&apos;) {
    q = &apos;&apos;;
  } else {
    q = q.toLowerCase();
  }
  return this.getAll().map(data =&gt; {
    let results: any = [];
    data.map(item =&gt; {
      // check for item in localStorage
      if (localStorage[&apos;person&apos; + item.id]) {
        item = JSON.parse(localStorage[&apos;person&apos; + item.id]);
      }
      if (JSON.stringify(item).toLowerCase().includes(q)) {
        results.push(item);
      }
    });
    return results;
  });
}

get(id: number) {
  return this.getAll().map(all =&gt; {
    if (localStorage[&apos;person&apos; + id]) {
      return JSON.parse(localStorage[&apos;person&apos; + id]);
    }
    return all.find(e =&gt; e.id === id);
  });
}

save(person: Person) {
  localStorage[&apos;person&apos; + person.id] = JSON.stringify(person);
}
&lt;/pre&gt;
&lt;p&gt;You can add CSS to &lt;code&gt;src/app/edit/edit.component.css&lt;/code&gt; if you want to make the form look a
    bit better.&lt;/p&gt;
&lt;pre class=&quot;brush: css&quot;&gt;
:host {
  display: block;
  padding: 0 20px;
}

button {
  margin-top: 10px;
}
&lt;/pre&gt;
&lt;p&gt;At this point, you should be able to search for a person and update their information.&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;img src=&quot;https://rawgit.com/mraible/ng2-demo/master//src/assets/images/edit-form.png&quot;
         alt=&quot;Edit form&quot;
         width=&quot;600&quot;&gt;
&lt;/p&gt;
&lt;p&gt;The &amp;lt;form&amp;gt; in &lt;code&gt;src/app/edit/edit.component.html&lt;/code&gt; calls a &lt;code&gt;save()&lt;/code&gt;
    function to update a person&apos;s data. You already implemented this above.
    The function calls a &lt;code&gt;gotoList()&lt;/code&gt; function that appends the person&apos;s name to the URL when
    sending the user back to the search screen.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
gotoList() {
  if (this.person) {
    this.router.navigate([&apos;/search&apos;, {term: this.person.name} ]);
  } else {
    this.router.navigate([&apos;/search&apos;]);
  }
}
&lt;/pre&gt;
&lt;p&gt;Since the &lt;code&gt;SearchComponent&lt;/code&gt; doesn&apos;t execute a search automatically when you execute this
    URL, add the following logic to do so in its constructor.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
import { Router, ActivatedRoute } from &apos;@angular/router&apos;;
import { Subscription } from &apos;rxjs&apos;;
...
  sub: Subscription;

  constructor(private searchService: SearchService, private router: Router, private route: ActivatedRoute) {
    this.sub = this.route.params.subscribe(params =&gt; {
      if (params[&apos;term&apos;]) {
        this.query = decodeURIComponent(params[&apos;term&apos;]);
        this.search();
      }
    });
  }
&lt;/pre&gt;
&lt;p&gt;You&apos;ll want to implement &lt;code&gt;OnDestroy&lt;/code&gt; and define the &lt;code&gt;ngOnDestroy&lt;/code&gt; method to
    clean up this subscription.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
import { Component, OnInit, OnDestroy } from &apos;@angular/core&apos;;

export class SearchComponent implements OnInit, OnDestroy {
...
  ngOnDestroy() {
    this.sub.unsubscribe();
  }
}
&lt;/pre&gt;
&lt;p&gt;After making all these changes, you should be able to search/edit/update a person&apos;s information. If
    it works - nice job!&lt;/p&gt;

&lt;h3 id=&quot;_testing&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#_testing&quot;&gt;&lt;/a&gt;Testing&lt;/h3&gt;

&lt;p&gt;Now that you&apos;ve built an application, it&apos;s important to test it to ensure it works. The best reason for
    writing tests is to automate your testing. Without tests, you&apos;ll likely be testing manually. This manual testing will
    take longer and longer as your application grows.&lt;/p&gt;
&lt;p&gt;In this section, you&apos;ll learn to use &lt;a href=&quot;http://jasmine.github.io/&quot;&gt;Jasmine&lt;/a&gt; for unit testing
    controllers and &lt;a href=&quot;https://angular.github.io/protractor/&quot;&gt;Protractor&lt;/a&gt; for
    integration testing. Angular&apos;s testing documentation lists &lt;a
        href=&quot;https://angular.io/docs/ts/latest/guide/testing.html&quot;&gt;good reasons to test&lt;/a&gt;, but doesn&apos;t currently have many examples.&lt;/p&gt;

&lt;h3 id=&quot;_unit_test_the_searchservice&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#_unit_test_the_searchservice&quot;&gt;&lt;/a&gt;Unit test
    the SearchService&lt;/h3&gt;
&lt;p&gt;Modify &lt;code&gt;src/app/shared/search/search.service.spec.ts&lt;/code&gt; and setup the test&apos;s infrastructure
    using &lt;a href=&quot;https://angular.io/docs/js/latest/api/http/testing/MockBackend-class.html&quot;&gt;MockBackend&lt;/a&gt;
    and &lt;a href=&quot;https://angular.io/docs/ts/latest/api/http/index/BaseRequestOptions-class.html&quot;&gt;BaseRequestOptions&lt;/a&gt;.
&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
import { MockBackend } from &apos;@angular/http/testing&apos;;
import { Http, ConnectionBackend, BaseRequestOptions, Response, ResponseOptions } from &apos;@angular/http&apos;;
import { SearchService } from &apos;./search.service&apos;;
import { tick, fakeAsync } from &apos;@angular/core/testing/fake_async&apos;;
import { inject, TestBed } from &apos;@angular/core/testing/test_bed&apos;;

describe(&apos;SearchService&apos;, () =&gt; {
  beforeEach(() =&gt; {

    TestBed.configureTestingModule({
      providers: [
        {
          provide: Http, useFactory: (backend: ConnectionBackend, defaultOptions: BaseRequestOptions) =&gt; {
          return new Http(backend, defaultOptions);
        }, deps: [MockBackend, BaseRequestOptions]
        },
        {provide: SearchService, useClass: SearchService},
        {provide: MockBackend, useClass: MockBackend},
        {provide: BaseRequestOptions, useClass: BaseRequestOptions}
      ]
    });
  });
});
&lt;/pre&gt;
&lt;p&gt;If you run &lt;code&gt;ng test&lt;/code&gt;, you will likely see some errors about the test stubs that Angular
    CLI created for you. You can ignore these for now.&lt;/p&gt;
&lt;pre&gt;ERROR in [default] /Users/mraible/ng2-demo/src/app/edit/edit.component.spec.ts:10:20
Supplied parameters do not match any signature of call target.

ERROR in [default] /Users/mraible/ng2-demo/src/app/search/search.component.spec.ts:10:20
Supplied parameters do not match any signature of call target.&lt;/pre&gt;
&lt;p&gt;Add the first test of &lt;code&gt;getAll()&lt;/code&gt; to &lt;code&gt;search.service.spec.ts&lt;/code&gt;. This test shows
    how &lt;code&gt;MockBackend&lt;/code&gt; can be used to mock results and set the response.&lt;/p&gt;
&lt;p class=&quot;alert alert-info&quot;&gt;
    &lt;strong&gt;TIP:&lt;/strong&gt;
    When you are testing code that returns either a Promise or an RxJS Observable, you can use
    the &lt;code&gt;fakeAsync&lt;/code&gt; helper to test that code as if it were synchronous.
    Promises are be fulfilled and Observables are notified immediately after you call &lt;code&gt;tick()&lt;/code&gt;.
&lt;/p&gt;
&lt;p&gt;The test below should be on the same level as &lt;code&gt;beforeEach&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
it(&apos;should retrieve all search results&apos;,
  inject([SearchService, MockBackend], fakeAsync((searchService: SearchService, mockBackend: MockBackend) =&gt; {
    let res: Response;
    mockBackend.connections.subscribe(c =&gt; {
      expect(c.request.url).toBe(&apos;app/shared/search/data/people.json&apos;);
      let response = new ResponseOptions({body: &apos;[{&quot;name&quot;: &quot;John Elway&quot;}, {&quot;name&quot;: &quot;Gary Kubiak&quot;}]&apos;});
      c.mockRespond(new Response(response));
    });
    searchService.getAll().subscribe((response) =&gt; {
      res = response;
    });
    tick();
    expect(res[0].name).toBe(&apos;John Elway&apos;);
  }))
);
&lt;/pre&gt;
&lt;p&gt;Notice that tests continually run as you add them when using &lt;code&gt;ng test&lt;/code&gt;. You can run tests
    once by using &lt;code&gt;ng test --watch=false&lt;/code&gt;.
    You will likely see &quot;Executed 5 of 5 &lt;span style=&quot;color: red&quot;&gt;(1 FAILED)&lt;/span&gt;&quot; in your terminal.
    Add a couple more tests for filtering by search term and fetching by id.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
it(&apos;should filter by search term&apos;,
  inject([SearchService, MockBackend], fakeAsync((searchService: SearchService, mockBackend: MockBackend) =&gt; {
    let res;
    mockBackend.connections.subscribe(c =&gt; {
      expect(c.request.url).toBe(&apos;app/shared/search/data/people.json&apos;);
      let response = new ResponseOptions({body: &apos;[{&quot;name&quot;: &quot;John Elway&quot;}, {&quot;name&quot;: &quot;Gary Kubiak&quot;}]&apos;});
      c.mockRespond(new Response(response));
    });
    searchService.search(&apos;john&apos;).subscribe((response) =&gt; {
      res = response;
    });
    tick();
    expect(res[0].name).toBe(&apos;John Elway&apos;);
  }))
);

it(&apos;should fetch by id&apos;,
  inject([SearchService, MockBackend], fakeAsync((searchService: SearchService, mockBackend: MockBackend) =&gt; {
    let res;
    mockBackend.connections.subscribe(c =&gt; {
      expect(c.request.url).toBe(&apos;app/shared/search/data/people.json&apos;);
      let response = new ResponseOptions({body: &apos;[{&quot;id&quot;: 1, &quot;name&quot;: &quot;John Elway&quot;}, {&quot;id&quot;: 2, &quot;name&quot;: &quot;Gary Kubiak&quot;}]&apos;});
      c.mockRespond(new Response(response));
    });
    searchService.search(&apos;2&apos;).subscribe((response) =&gt; {
      res = response;
    });
    tick();
    expect(res[0].name).toBe(&apos;Gary Kubiak&apos;);
  }))
);
&lt;/pre&gt;
&lt;h3 id=&quot;_unit_test_the_searchcomponent&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#_unit_test_the_searchcomponent&quot;&gt;&lt;/a&gt;Unit
    test the SearchComponent&lt;/h3&gt;
&lt;p&gt;To unit test the &lt;code&gt;SearchComponent&lt;/code&gt;, create a &lt;code&gt;MockSearchProvider&lt;/code&gt; that has
    &lt;a  href=&quot;http://angular-tips.com/blog/2014/03/introduction-to-unit-test-spies/&quot;&gt;spies&lt;/a&gt;.
    These allow you to &lt;em&gt;spy&lt;/em&gt; on functions to check if they were called.&lt;/p&gt;
&lt;p&gt;Create &lt;code&gt;src/app/shared/search/mocks/search.service.ts&lt;/code&gt; and populate it with spies for each
    method, as well as methods to set the response and subscribe to results.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
import { SpyObject } from &apos;./helper&apos;;
import { SearchService } from &apos;../search.service&apos;;
import Spy = jasmine.Spy;

export class MockSearchService extends SpyObject {
  getAllSpy: Spy;
  getByIdSpy: Spy;
  searchSpy: Spy;
  saveSpy: Spy;
  fakeResponse: any;

  constructor() {
    super( SearchService );

    this.fakeResponse = null;
    this.getAllSpy = this.spy(&apos;getAll&apos;).andReturn(this);
    this.getByIdSpy = this.spy(&apos;get&apos;).andReturn(this);
    this.searchSpy = this.spy(&apos;search&apos;).andReturn(this);
    this.saveSpy = this.spy(&apos;save&apos;).andReturn(this);
  }

  subscribe(callback: any) {
    callback(this.fakeResponse);
  }

  setResponse(json: any): void {
    this.fakeResponse = json;
  }
}
&lt;/pre&gt;
&lt;p&gt;In this same directory, create a &lt;code&gt;helper.ts&lt;/code&gt; class to implement the
    &lt;code&gt;SpyObject&lt;/code&gt; that &lt;code&gt;MockSearchService&lt;/code&gt; extends.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
import {StringMapWrapper} from &apos;@angular/core/src/facade/collection&apos;;

export interface GuinessCompatibleSpy extends jasmine.Spy {
  /** By chaining the spy with and.returnValue, all calls to the function will return a specific
   * value. */
  andReturn(val: any): void;
  /** By chaining the spy with and.callFake, all calls to the spy will delegate to the supplied
   * function. */
  andCallFake(fn: Function): GuinessCompatibleSpy;
  /** removes all recorded calls */
  reset();
}

export class SpyObject {
  static stub(object = null, config = null, overrides = null) {
    if (!(object instanceof SpyObject)) {
      overrides = config;
      config = object;
      object = new SpyObject();
    }

    let m = StringMapWrapper.merge(config, overrides);
    StringMapWrapper.forEach(m, (value, key) =&amp;gt; { object.spy(key).andReturn(value); });
    return object;
  }

  constructor(type = null) {
    if (type) {
      for (let prop in type.prototype) {
        let m = null;
        try {
          m = type.prototype&amp;#91;prop&amp;#93;;
        } catch (e) {
          // As we are creating spys for abstract classes,
          // these classes might have getters that throw when they are accessed.
          // As we are only auto creating spys for methods, this
          // should not matter.
        }
        if (typeof m === &apos;function&apos;) {
          this.spy(prop);
        }
      }
    }
  }

  spy(name) {
    if (!this&amp;#91;name&amp;#93;) {
      this&amp;#91;name&amp;#93; = this._createGuinnessCompatibleSpy(name);
    }
    return this&amp;#91;name&amp;#93;;
  }

  prop(name, value) { this&amp;#91;name&amp;#93; = value; }

  /** @internal */
  _createGuinnessCompatibleSpy(name): GuinessCompatibleSpy {
    let newSpy: GuinessCompatibleSpy = &amp;lt;any&amp;gt;jasmine.createSpy(name);
    newSpy.andCallFake = &amp;lt;any&amp;gt;newSpy.and.callFake;
    newSpy.andReturn = &amp;lt;any&amp;gt;newSpy.and.returnValue;
    newSpy.reset = &amp;lt;any&amp;gt;newSpy.calls.reset;
    // revisit return null here (previously needed for rtts_assert).
    newSpy.and.returnValue(null);
    return newSpy;
  }
}
&lt;/pre&gt;
&lt;p&gt;Alongside, create &lt;code&gt;routes.ts&lt;/code&gt; to mock Angular&apos;s &lt;code&gt;Router&lt;/code&gt; and &lt;code&gt;ActivatedRoute&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
import { ActivatedRoute, Params } from &apos;@angular/router&apos;;
import { Observable } from &apos;rxjs&apos;;

export class MockActivatedRoute extends ActivatedRoute {
  params: Observable&amp;lt;Params&amp;gt;

  constructor(parameters?: { [key: string]: any; }) {
    super();
    this.params = Observable.of(parameters);
  }
}

export class MockRouter {
  navigate = jasmine.createSpy(&apos;navigate&apos;);
}
&lt;/pre&gt;
&lt;p&gt;With mocks in place, you can &lt;code&gt;TestBed.configureTestingModule()&lt;/code&gt; to setup &lt;code&gt;SearchComponent&lt;/code&gt;
    to use these as providers.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
import { ActivatedRoute, Router } from &apos;@angular/router&apos;;
import { MockActivatedRoute, MockRouter } from &apos;../shared/search/mocks/routes&apos;;
import { MockSearchService } from &apos;../shared/search/mocks/search.service&apos;;
import { SearchComponent } from &apos;./search.component&apos;;
import { TestBed } from &apos;@angular/core/testing/test_bed&apos;;
import { FormsModule } from &apos;@angular/forms&apos;;
import { SearchService } from &apos;../shared/search/search.service&apos;;

describe(&apos;Component: Search&apos;, () =&gt; {
  let mockSearchService: MockSearchService;
  let mockActivatedRoute: MockActivatedRoute;
  let mockRouter: MockRouter;

  beforeEach(() =&gt; {
    mockSearchService = new MockSearchService();
    mockActivatedRoute = new MockActivatedRoute({&apos;term&apos;: &apos;peyton&apos;});
    mockRouter = new MockRouter();

    TestBed.configureTestingModule({
      declarations: [SearchComponent],
      providers: [
        {provide: SearchService, useValue: mockSearchService},
        {provide: ActivatedRoute, useValue: mockActivatedRoute},
        {provide: Router, useValue: mockRouter}
      ],
      imports: [FormsModule]
    });
  });
});
&lt;/pre&gt;
&lt;p&gt;Add two tests, one to verify a search term is used when it&apos;s set on the component, and a second
    to verify search is called when a term is passed in as a route parameter.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
it(&apos;should search when a term is set and search() is called&apos;, () =&gt; {
  let fixture = TestBed.createComponent(SearchComponent);
  let searchComponent = fixture.debugElement.componentInstance;
  searchComponent.query = &apos;M&apos;;
  searchComponent.search();
  expect(mockSearchService.searchSpy).toHaveBeenCalledWith(&apos;M&apos;);
});

it(&apos;should search automatically when a term is on the URL&apos;, () =&gt; {
  let fixture = TestBed.createComponent(SearchComponent);
  fixture.detectChanges();
  expect(mockSearchService.searchSpy).toHaveBeenCalledWith(&apos;peyton&apos;);
});
&lt;/pre&gt;

&lt;p&gt;After adding these tests, you should see the first instance of all tests passing (Executed 8 of 8
    &lt;span style=&quot;color: green&quot;&gt;SUCCESS&lt;/span&gt;).&lt;/p&gt;
&lt;p&gt;Update the test for &lt;code&gt;EditComponent&lt;/code&gt;, verifying fetching a single record works. Notice
    how you can access the component directly with &lt;code&gt;fixture.debugElement.componentInstance&lt;/code&gt;, or
    its rendered version with &lt;code&gt;fixture.debugElement.nativeElement&lt;/code&gt;.
&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
import { MockSearchService } from &apos;../shared/search/mocks/search.service&apos;;
import { EditComponent } from &apos;./edit.component&apos;;
import { TestBed } from &apos;@angular/core/testing/test_bed&apos;;
import { SearchService } from &apos;../shared/search/search.service&apos;;
import { MockRouter, MockActivatedRoute } from &apos;../shared/search/mocks/routes&apos;;
import { ActivatedRoute, Router } from &apos;@angular/router&apos;;
import { FormsModule } from &apos;@angular/forms&apos;;

describe(&apos;Component: Edit&apos;, () =&gt; {
  let mockSearchService: MockSearchService;
  let mockActivatedRoute: MockActivatedRoute;
  let mockRouter: MockRouter;

  beforeEach(() =&gt; {
    mockSearchService = new MockSearchService();
    mockActivatedRoute = new MockActivatedRoute({&apos;id&apos;: 1});
    mockRouter = new MockRouter();

    TestBed.configureTestingModule({
      declarations: [EditComponent],
      providers: [
        {provide: SearchService, useValue: mockSearchService},
        {provide: ActivatedRoute, useValue: mockActivatedRoute},
        {provide: Router, useValue: mockRouter}
      ],
      imports: [FormsModule]
    });
  });

  it(&apos;should fetch a single record&apos;, () =&gt; {
    const fixture = TestBed.createComponent(EditComponent);

    let person = {name: &apos;Emmanuel Sanders&apos;, address: {city: &apos;Denver&apos;}};
    mockSearchService.setResponse(person);

    fixture.detectChanges();
    // verify service was called
    expect(mockSearchService.getByIdSpy).toHaveBeenCalledWith(1);

    // verify data was set on component when initialized
    let editComponent = fixture.debugElement.componentInstance;
    expect(editComponent.editAddress.city).toBe(&apos;Denver&apos;);

    // verify HTML renders as expected
    let compiled = fixture.debugElement.nativeElement;
    expect(compiled.querySelector(&apos;h3&apos;).innerHTML).toBe(&apos;Emmanuel Sanders&apos;);
  });
});
&lt;/pre&gt;
&lt;p&gt;You should see &quot;Executed 8 of 8 &lt;span style=&quot;color: green&quot;&gt;SUCCESS&lt;/span&gt; (0.238 secs / 0.259
    secs)&quot; in the shell window that&apos;s running &lt;code&gt;ng test&lt;/code&gt;. If you don&apos;t, try cancelling the command and
    restarting.&lt;/p&gt;
&lt;h3 id=&quot;_integration_test_the_search_ui&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#_integration_test_the_search_ui&quot;&gt;&lt;/a&gt;Integration test the search UI&lt;/h3&gt;
&lt;p&gt;To test if the application works end-to-end, you can write tests with &lt;a
    href=&quot;http://angular.github.io/protractor&quot;&gt;Protractor&lt;/a&gt;. These are also known as integration
    tests, since they test the &lt;em&gt;integration&lt;/em&gt; between all layers of your application.&lt;/p&gt;
&lt;p&gt;To verify end-to-end tests work in the project before you begin, run the following commands in three
    different console windows.&lt;/p&gt;
&lt;pre&gt;ng serve
ng e2e&lt;/pre&gt;
&lt;p&gt;All tests should pass.&lt;/p&gt;
&lt;pre&gt;$ ng e2e

&amp;gt; ng2-demo@0.0.0 pree2e /Users/mraible/dev/ng2-demo
&amp;gt; webdriver-manager update

Updating selenium standalone to version 2.52.0
downloading https://selenium-release.storage.googleapis.com/2.52/selenium-server-standalone-2.52.0.jar...
Updating chromedriver to version 2.21
downloading https://chromedriver.storage.googleapis.com/2.21/chromedriver_mac32.zip...
chromedriver_2.21mac32.zip downloaded to /Users/mraible/dev/ng2-demo/node_modules/protractor/selenium/chromedriver_2.21mac32.zip
selenium-server-standalone-2.52.0.jar downloaded to /Users/mraible/dev/ng2-demo/node_modules/protractor/selenium/selenium-server-standalone-2.52.0.jar

&amp;gt; ng2-demo@0.0.0 e2e /Users/mraible/dev/ng2-demo
&amp;gt; protractor &quot;config/protractor.conf.js&quot;

[00:01:07] I/direct - Using ChromeDriver directly...
[00:01:07] I/launcher - Running 1 instances of WebDriver
Spec started

  ng2-demo App
    &amp;#10004; should display message saying app works

Executed 1 of 1 spec SUCCESS in 0.684 sec.
[00:01:09] I/launcher - 0 instance(s) of WebDriver still running
[00:01:09] I/launcher - chrome #01 passed

All end-to-end tests pass.&lt;/pre&gt;
&lt;h3 id=&quot;_testing_the_search_feature&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#_testing_the_search_feature&quot;&gt;&lt;/a&gt;Testing the
    search feature&lt;/h3&gt;
&lt;p&gt;Create end-to-end tests in &lt;code&gt;e2e/search.e2e-spec.ts&lt;/code&gt; to verify the search feature works.
    Populate it with the following code:&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
describe(&apos;Search&apos;, () =&gt; {

  beforeEach(() =&gt; {
    browser.get(&apos;/search&apos;);
    element(by.linkText(&apos;Search&apos;)).click();
  });

  it(&apos;should have an input and search button&apos;, () =&gt; {
    expect(element(by.css(&apos;app-root app-search form input&apos;)).isPresent()).toEqual(true);
    expect(element(by.css(&apos;app-root app-search form button&apos;)).isPresent()).toEqual(true);
  });

  it(&apos;should allow searching&apos;, () =&gt; {
    let searchButton = element(by.css(&apos;button&apos;));
    let searchBox = element(by.css(&apos;input&apos;));
    searchBox.sendKeys(&apos;M&apos;);
    searchButton.click().then(() =&gt; {
      var list = element.all(by.css(&apos;app-search table tbody tr&apos;));
      expect(list.count()).toBe(3);
    });
  });
});
&lt;/pre&gt;
&lt;h3 id=&quot;_testing_the_edit_feature&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#_testing_the_edit_feature&quot;&gt;&lt;/a&gt;Testing the edit feature&lt;/h3&gt;
&lt;p&gt;Create a &lt;code&gt;e2e/edit.e2e-spec.ts&lt;/code&gt; test to verify the &lt;code&gt;EditComponent&lt;/code&gt; renders a
    person&apos;s information and that their information can be updated.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
describe(&apos;Edit&apos;, () =&gt; {

  beforeEach(() =&gt; {
    browser.get(&apos;/edit/1&apos;);
  });

  let name = element(by.id(&apos;name&apos;));
  let street = element(by.id(&apos;street&apos;));
  let city = element(by.id(&apos;city&apos;));

  it(&apos;should allow viewing a person&apos;, () =&gt; {
    expect(element(by.css(&apos;h3&apos;)).getText()).toEqual(&apos;Peyton Manning&apos;);
    expect(name.getAttribute(&apos;value&apos;)).toEqual(&apos;Peyton Manning&apos;);
    expect(street.getAttribute(&apos;value&apos;)).toEqual(&apos;1234 Main Street&apos;);
    expect(city.getAttribute(&apos;value&apos;)).toEqual(&apos;Greenwood Village&apos;);
  });

  it(&apos;should allow updating a name&apos;, function () {
    let save = element(by.id(&apos;save&apos;));
    // send individual characters since sendKeys passes partial values sometimes
    // https://github.com/angular/protractor/issues/698
    &apos; Won!&apos;.split(&apos;&apos;).forEach((c) =&gt; name.sendKeys(c));
    save.click();
    // verify one element matched this change
    var list = element.all(by.css(&apos;app-search table tbody tr&apos;));
    expect(list.count()).toBe(1);
  });
});
&lt;/pre&gt;
&lt;p&gt;Run &lt;code&gt;ng e2e&lt;/code&gt; to verify all your end-to-end tests pass. You should see a success message
    similar to the one below in your terminal window.&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;img  src=&quot;https://raw.githubusercontent.com/mraible/ng2-demo/master/src/assets/images/protractor-success.png&quot;
          alt=&quot;Protractor success&quot; width=&quot;600&quot;&gt;
&lt;/p&gt;
&lt;p&gt;If you made it this far and have all your specs passing - congratulations! You&apos;re well on your way to
    writing quality code with Angular 2 and verifying it works.&lt;/p&gt;
&lt;p&gt;You can see the test coverage of your project by opening &lt;code&gt;coverage/index.html&lt;/code&gt; in your
    browser. You might notice that the new components and service could use some additional coverage. If you feel
    the need to improve this coverage, please send me a pull request!&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;img
        src=&quot;https://raw.githubusercontent.com/mraible/ng2-demo/master/src/assets/images/test-coverage.png&quot;
        alt=&quot;Test coverage&quot; width=&quot;600&quot;&gt;
&lt;/p&gt;
&lt;h3 id=&quot;_continuous_integration&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#_continuous_integration&quot;&gt;&lt;/a&gt;Continuous Integration&lt;/h3&gt;
&lt;p&gt;At the time of this writing, Angular CLI did not have any continuous integration support. However, it&apos;s
    easy to add with &lt;a href=&quot;https://travis-ci.org/&quot;&gt;Travis CI&lt;/a&gt;. If you&apos;ve checked in your project to GitHub,
    you can easily use Travis CI.
    Simply login and enable builds for the GitHub repo you created the project in. Then add the following
    &lt;code&gt;.travis.yml&lt;/code&gt;
    in your root directory and &lt;code&gt;git push&lt;/code&gt;. This will trigger the first build.&lt;/p&gt;
&lt;pre&gt;
language: node_js
sudo: true

cache:
  directories:
    - node
    - node_modules

dist: trusty

node_js:
  - &apos;5.6.0&apos;

branches:
  only:
  - master

before_install:
 - npm install -g angular-cli
 - export CHROME_BIN=/usr/bin/google-chrome
 - export DISPLAY=:99.0
 - sh -e /etc/init.d/xvfb start
 - sudo apt-get update
 - sudo apt-get install -y libappindicator1 fonts-liberation
 - wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
 - sudo dpkg -i google-chrome*.deb

script:
 - ng test --watch false # http://mseemann.de/frontend/2016/05/31/setup-angular-2-app-part-1.html
 - ng serve &amp;
 - ng e2e

notifications:
  webhooks:
    on_success: change  # options: [always|never|change] default: always
    on_failure: always  # options: [always|never|change] default: always
    on_start: false     # default: false
&lt;/pre&gt;
&lt;div class=&quot;paragraph&quot;&gt;
    &lt;p&gt;&lt;a href=&quot;https://travis-ci.org/mraible/ng2-demo/builds/154182594&quot;&gt;Here&lt;/a&gt; is a build showing all unit
        and integration tests passing.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id=&quot;_source_code&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#_source_code&quot;&gt;&lt;/a&gt;Source code&lt;/h3&gt;
&lt;p&gt;A completed project with this code in it is available on GitHub at &lt;a
    href=&quot;https://github.com/mraible/ng2-demo&quot;&gt;https://github.com/mraible/ng2-demo&lt;/a&gt;.
    If you have ideas for improvements, please leave a comment or send a pull request.
&lt;/p&gt;
&lt;p&gt;
    This tutorial was originally written using &lt;a href=&quot;http://asciidoctor.org/&quot;&gt;Asciidoctor&lt;/a&gt;.
    This means you can &lt;a href=&quot;http://gist.asciidoctor.org/?github-mraible%2Fng2-demo%2F%2FREADME.adoc&quot;&gt;read it using DocGist&lt;/a&gt; if you like.
&lt;/p&gt;
&lt;h3 id=&quot;_summary&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#_summary&quot;&gt;&lt;/a&gt;Summary&lt;/h3&gt;
&lt;p&gt;I hope you&apos;ve enjoyed this in-depth tutorial on how to get started with Angular 2 and Angular CLI.
    Angular CLI takes much of the pain out of setting up an Angular 2 project and using Typescript. I expect great
    things from Angular CLI, mostly because the Angular 2 setup process can be tedious and CLI greatly simplifies things. &lt;/p&gt;
</description>          </item>
    <item>
    <guid isPermaLink="true">https://raibledesigns.com/rd/entry/testing_angular_2_0_rc1</guid>
    <title>Testing Angular 2.0 RC1 Applications</title>
    <dc:creator>Matt Raible</dc:creator>
    <link>https://raibledesigns.com/rd/entry/testing_angular_2_0_rc1</link>
        <pubDate>Mon, 6 Jun 2016 09:57:13 -0600</pubDate>
    <category>The Web</category>
    <category>protractor</category>
    <category>asciidoctor</category>
    <category>npm</category>
    <category>angular2</category>
    <category>javascript</category>
    <category>karma</category>
    <category>git</category>
    <category>jasmine</category>
    <category>node</category>
    <atom:summary type="html">&lt;p&gt;As mentioned &lt;a href=&quot;//raibledesigns.com/rd/entry/getting_started_with_angular_2_rc1&quot;&gt;on Friday&lt;/a&gt;, there&apos;s been
    quite a bit that&apos;s changed with Angular 2 between its
    Beta 9 and RC 1 releases. This article is an update to the
    &lt;a href=&quot;//raibledesigns.com/rd/entry/testing_angular_2_applications&quot;&gt;Testing Angular 2 Applications&lt;/a&gt; I wrote in
    March.
    That tutorial was based on Angular 2.0 Beta 9. Rather than simply updating that
    tutorial and blog post for 2.0 RC1, I decided to create a new version for posterity&apos;s sake. The 2.0 Beta 9 version
    will
    remain on my blog and I&apos;ve &lt;a href=&quot;https://github.com/mraible/angular2-tutorial/releases/tag/2.0.0-beta.9&quot;&gt;tagged
        the source on GitHub&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;If you&apos;ve already read the first version of &lt;a href=&quot;//raibledesigns.com/rd/entry/testing_angular_2_applications&quot;&gt;Testing
    Angular 2 Applications&lt;/a&gt;,
    checkout the &lt;a
        href=&quot;https://github.com/mraible/angular2-tutorial/pull/2/files#diff-c5cd85f8ff52aad4b08a3dd38575dddf&quot;&gt;
        diff of the Asciidoctor version&lt;/a&gt; to see what&apos;s changed.&lt;/p&gt;
&lt;div class=&quot;sect1&quot;&gt;
    &lt;h3 id=&quot;what_you_ll_build&quot;&gt;What you&apos;ll build&lt;/h3&gt;
    &lt;div class=&quot;sectionbody&quot;&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;You&apos;ll learn to use &lt;a href=&quot;http://jasmine.github.io/&quot;&gt;Jasmine&lt;/a&gt; for unit testing controllers and &lt;a
                href=&quot;http://www.protractortest.org/&quot;&gt;Protractor&lt;/a&gt; for
                integration testing. See Angular 2&apos;s &lt;a href=&quot;https://angular.io/docs/ts/latest/guide/testing.html&quot;&gt;guide
                    to unit testing&lt;/a&gt;
                if you&apos;d like more information on testing and why it&apos;s important.&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;The best reason for writing tests is to automate your testing. Without tests, you&apos;ll likely be testing
                manually.
                This manual testing will take longer and longer as your codebase grows.&lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;sect1&quot;&gt;
    &lt;h3 id=&quot;what_you_ll_need&quot;&gt;What you&apos;ll need&lt;/h3&gt;
    &lt;div class=&quot;sectionbody&quot;&gt;
        &lt;div class=&quot;ulist&quot;&gt;
            &lt;ul&gt;
                &lt;li&gt;About 15-30 minutes.&lt;/li&gt;
                &lt;li&gt;A favorite text editor or IDE. I recommend &lt;a href=&quot;https://www.jetbrains.com/idea/&quot;&gt;IntelliJ
                    IDEA&lt;/a&gt;.
                &lt;/li&gt;
                &lt;li&gt;&lt;a href=&quot;http://git-scm.com/&quot;&gt;Git&lt;/a&gt; installed.&lt;/li&gt;
                &lt;li&gt;&lt;a href=&quot;http://nodejs.org/&quot;&gt;Node.js&lt;/a&gt; and npm installed. I recommend using &lt;a
                    href=&quot;https://github.com/creationix/nvm&quot;&gt;nvm&lt;/a&gt;.
                &lt;/li&gt;
            &lt;/ul&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;sect1&quot;&gt;
    &lt;h3 id=&quot;get_the_tutorial_project&quot;&gt;Get the tutorial project&lt;/h3&gt;
    &lt;div class=&quot;sectionbody&quot;&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;Clone the &lt;a href=&quot;https://github.com/mraible/angular2-tutorial&quot;&gt;angular2-tutorial repository&lt;/a&gt;, checkout the &lt;code&gt;testing-start&lt;/code&gt; branch, and install its dependencies.&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;listingblock&quot;&gt;
            &lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;git clone https://github.com/mraible/angular2-tutorial.git
cd angular2-tutorial
git checkout testing-start
npm install&lt;/pre&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;If you haven&apos;t completed the &lt;a href=&quot;//raibledesigns.com/rd/entry/getting_started_with_angular_2_rc1&quot;&gt;Getting
                Started with Angular 2.0 RC1&lt;/a&gt; tutorial,
                you should peruse it so you understand how this application works.
                You can also simply start the app with &lt;code&gt;npm start&lt;/code&gt; and view it in your browser at &lt;a
                    href=&quot;http://localhost:5555/&quot; class=&quot;bare&quot;&gt;http://localhost:5555/&lt;/a&gt;.
            &lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;</atom:summary>        <description>&lt;p&gt;As mentioned &lt;a href=&quot;//raibledesigns.com/rd/entry/getting_started_with_angular_2_rc1&quot;&gt;on Friday&lt;/a&gt;, there&apos;s been
    quite a bit that&apos;s changed with Angular 2 between its
    Beta 9 and RC 1 releases. This article is an update to the
    &lt;a href=&quot;//raibledesigns.com/rd/entry/testing_angular_2_applications&quot;&gt;Testing Angular 2 Applications&lt;/a&gt; I wrote in
    March.
    That tutorial was based on Angular 2.0 Beta 9. Rather than simply updating that
    tutorial and blog post for 2.0 RC1, I decided to create a new version for posterity&apos;s sake. The 2.0 Beta 9 version
    will
    remain on my blog and I&apos;ve &lt;a href=&quot;https://github.com/mraible/angular2-tutorial/releases/tag/2.0.0-beta.9&quot;&gt;tagged
        the source on GitHub&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;If you&apos;ve already read the first version of &lt;a href=&quot;//raibledesigns.com/rd/entry/testing_angular_2_applications&quot;&gt;Testing
    Angular 2 Applications&lt;/a&gt;,
    checkout the &lt;a
        href=&quot;https://github.com/mraible/angular2-tutorial/pull/2/files#diff-c5cd85f8ff52aad4b08a3dd38575dddf&quot;&gt;
        diff of the Asciidoctor version&lt;/a&gt; to see what&apos;s changed.&lt;/p&gt;
&lt;div class=&quot;sect1&quot;&gt;
    &lt;h3 id=&quot;what_you_ll_build&quot;&gt;What you&apos;ll build&lt;/h3&gt;
    &lt;div class=&quot;sectionbody&quot;&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;You&apos;ll learn to use &lt;a href=&quot;http://jasmine.github.io/&quot;&gt;Jasmine&lt;/a&gt; for unit testing controllers and &lt;a
                href=&quot;http://www.protractortest.org/&quot;&gt;Protractor&lt;/a&gt; for
                integration testing. See Angular 2&apos;s &lt;a href=&quot;https://angular.io/docs/ts/latest/guide/testing.html&quot;&gt;guide
                    to unit testing&lt;/a&gt;
                if you&apos;d like more information on testing and why it&apos;s important.&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;The best reason for writing tests is to automate your testing. Without tests, you&apos;ll likely be testing
                manually.
                This manual testing will take longer and longer as your codebase grows.&lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;sect1&quot;&gt;
    &lt;h3 id=&quot;what_you_ll_need&quot;&gt;What you&apos;ll need&lt;/h3&gt;
    &lt;div class=&quot;sectionbody&quot;&gt;
        &lt;div class=&quot;ulist&quot;&gt;
            &lt;ul&gt;
                &lt;li&gt;About 15-30 minutes.&lt;/li&gt;
                &lt;li&gt;A favorite text editor or IDE. I recommend &lt;a href=&quot;https://www.jetbrains.com/idea/&quot;&gt;IntelliJ
                    IDEA&lt;/a&gt;.
                &lt;/li&gt;
                &lt;li&gt;&lt;a href=&quot;http://git-scm.com/&quot;&gt;Git&lt;/a&gt; installed.&lt;/li&gt;
                &lt;li&gt;&lt;a href=&quot;http://nodejs.org/&quot;&gt;Node.js&lt;/a&gt; and npm installed. I recommend using &lt;a
                    href=&quot;https://github.com/creationix/nvm&quot;&gt;nvm&lt;/a&gt;.
                &lt;/li&gt;
            &lt;/ul&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;sect1&quot;&gt;
    &lt;h3 id=&quot;get_the_tutorial_project&quot;&gt;Get the tutorial project&lt;/h3&gt;
    &lt;div class=&quot;sectionbody&quot;&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;Clone the &lt;a href=&quot;https://github.com/mraible/angular2-tutorial&quot;&gt;angular2-tutorial repository&lt;/a&gt;, checkout the &lt;code&gt;testing-start&lt;/code&gt; branch, and install its dependencies.&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;listingblock&quot;&gt;
            &lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;git clone https://github.com/mraible/angular2-tutorial.git
cd angular2-tutorial
git checkout testing-start
npm install&lt;/pre&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;If you haven&apos;t completed the &lt;a href=&quot;//raibledesigns.com/rd/entry/getting_started_with_angular_2_rc1&quot;&gt;Getting
                Started with Angular 2.0 RC1&lt;/a&gt; tutorial,
                you should peruse it so you understand how this application works.
                You can also simply start the app with &lt;code&gt;npm start&lt;/code&gt; and view it in your browser at &lt;a
                    href=&quot;http://localhost:5555/&quot; class=&quot;bare&quot;&gt;http://localhost:5555/&lt;/a&gt;.
            &lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;sect1&quot;&gt;
    &lt;h3 id=&quot;unit_test_the_searchservice&quot;&gt;Unit test the SearchService&lt;/h3&gt;
    &lt;div class=&quot;sectionbody&quot;&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;Create &lt;code&gt;src/client/app/shared/search/search.service.spec.ts&lt;/code&gt; and setup the test&apos;s
                infrastructure using &lt;a
                    href=&quot;https://angular.io/docs/js/latest/api/http/testing/MockBackend-class.html&quot;&gt;MockBackend&lt;/a&gt;
                and &lt;a href=&quot;https://angular.io/docs/js/latest/api/http/BaseRequestOptions-class.html&quot;&gt;BaseRequestOptions&lt;/a&gt;.
            &lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;listingblock&quot;&gt;
            &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
import {
  beforeEachProviders,
  it,
  describe,
  expect,
  inject,
  fakeAsync,
  tick
} from &apos;@angular/core/testing&apos;;
import { MockBackend } from &apos;@angular/http/testing&apos;;
import { provide } from &apos;@angular/core&apos;;
import { Http, ConnectionBackend, BaseRequestOptions, Response, ResponseOptions } from &apos;@angular/http&apos;;
import { SearchService } from &apos;./search.service&apos;;

export function main() {
  describe(&apos;Search Service&apos;, () =&gt; {
    beforeEachProviders(() =&gt; {
      return [BaseRequestOptions, MockBackend, SearchService,
        provide(Http, {
          useFactory: (backend:ConnectionBackend, defaultOptions:BaseRequestOptions) =&gt; {
            return new Http(backend, defaultOptions);
          }, deps: [MockBackend, BaseRequestOptions]
        }),
      ];
    });
  });
}

&lt;/pre&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;If you run &lt;code&gt;npm test&lt;/code&gt;, all tests will pass, but you don&apos;t see &quot;Search Service&quot; as a listed
                test. You can fix this by adding the first test of &lt;code&gt;getAll()&lt;/code&gt;.
                This test shows how &lt;code&gt;MockBackend&lt;/code&gt; can be used to mock results and set the response.&lt;/p&gt;
        &lt;/div&gt;
        &lt;p class=&quot;alert alert-success&quot;&gt;&lt;b&gt;TIP&lt;/b&gt;: When you are testing code that returns either a Promise or an RxJS
            Observable, you can use the &lt;code&gt;fakeAsync&lt;/code&gt; helper to test that code as
            if it were synchronous.
            Promises are be fulfilled and Observables are notified immediately after you call &lt;code&gt;tick()&lt;/code&gt;.&lt;/p&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;The test below should be on the same level as &lt;code&gt;beforeEachProviders&lt;/code&gt;.&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;listingblock&quot;&gt;
            &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;it(&apos;should retrieve all search results&apos;,
  inject([SearchService, MockBackend], fakeAsync((searchService:SearchService, mockBackend:MockBackend) =&amp;gt; {
    var res:Response;
    mockBackend.connections.subscribe(c =&amp;gt; {
      expect(c.request.url).toBe(&apos;app/shared/search/data/people.json&apos;);
      let response = new ResponseOptions({body: &apos;[{&quot;name&quot;: &quot;John Elway&quot;}, {&quot;name&quot;: &quot;Gary Kubiak&quot;}]&apos;});
      c.mockRespond(new Response(response));
    });
    searchService.getAll().subscribe((response) =&amp;gt; {
      res = response;
    });
    tick();
    expect(res[0].name).toBe(&apos;John Elway&apos;);
  }))
);
&lt;/pre&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;Running &lt;code&gt;npm test&lt;/code&gt; should result in &quot;12 tests completed&quot;. Add a couple more tests for
                filtering by search term and fetching by id.&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;listingblock&quot;&gt;
            &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
it(&apos;should filter by search term&apos;,
  inject([SearchService, MockBackend], fakeAsync((searchService:SearchService, mockBackend:MockBackend) =&gt; {
    var res;
    mockBackend.connections.subscribe(c =&gt; {
      expect(c.request.url).toBe(&apos;app/shared/search/data/people.json&apos;);
      let response = new ResponseOptions({body: &apos;[{&quot;name&quot;: &quot;John Elway&quot;}, {&quot;name&quot;: &quot;Gary Kubiak&quot;}]&apos;});
      c.mockRespond(new Response(response));
    });
    searchService.search(&apos;john&apos;).subscribe((response) =&gt; {
      res = response;
    });
    tick();
    expect(res[0].name).toBe(&apos;John Elway&apos;);
  }))
);

it(&apos;should fetch by id&apos;,
  inject([SearchService, MockBackend], fakeAsync((searchService:SearchService, mockBackend:MockBackend) =&gt; {
    var res;
    mockBackend.connections.subscribe(c =&gt; {
      expect(c.request.url).toBe(&apos;app/shared/search/data/people.json&apos;);
      let response = new ResponseOptions({body: &apos;[{&quot;id&quot;: 1, &quot;name&quot;: &quot;John Elway&quot;}, {&quot;id&quot;: 2, &quot;name&quot;: &quot;Gary Kubiak&quot;}]&apos;});
      c.mockRespond(new Response(response));
    });
    searchService.search(&apos;2&apos;).subscribe((response) =&gt; {
      res = response;
    });
    tick();
    expect(res[0].name).toBe(&apos;Gary Kubiak&apos;);
  }))
);
&lt;/pre&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;If you want to have tests continually run as you add them, you can run the following commands in separate
                shell windows.&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;listingblock&quot;&gt;
            &lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;npm run build.test.watch
npm run karma.start&lt;/pre&gt;
            &lt;/div&gt;
        &lt;/div&gt;

        &lt;p&gt;NOTE: See &lt;a
            href=&quot;https://www.jetbrains.com/help/idea/15.0/running-unit-tests-on-karma.html?origin=old_help&quot;&gt;Running
            Unit Tests on Karma&lt;/a&gt; to
            learn how to run your tests from IntelliJ IDEA.&lt;/p&gt;

    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;sect1&quot;&gt;
    &lt;h3 id=&quot;unit_test_the_searchcomponent&quot;&gt;Unit test the SearchComponent&lt;/h3&gt;
    &lt;div class=&quot;sectionbody&quot;&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;To unit test the &lt;code&gt;SearchComponent&lt;/code&gt;, create a &lt;code&gt;MockSearchProvider&lt;/code&gt; that has &lt;a
                href=&quot;http://angular-tips.com/blog/2014/03/introduction-to-unit-test-spies/&quot;&gt;spies&lt;/a&gt;.
                These allow you to &lt;em&gt;spy&lt;/em&gt; on functions to check if they were called.&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;Create &lt;code&gt;src/client/app/shared/search/mocks/search.service.ts&lt;/code&gt; and populate it with spies for
                each method, as well as methods to set the response and subscribe to results.&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;listingblock&quot;&gt;
            &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
import { provide } from &apos;@angular/core&apos;;
import { SpyObject } from &apos;./helper&apos;;

import { SearchService } from &apos;../search.service&apos;;
import Spy = jasmine.Spy;

export class MockSearchService extends SpyObject {
  getAllSpy:Spy;
  getByIdSpy:Spy;
  searchSpy:Spy;
  saveSpy:Spy;
  fakeResponse:any;

  constructor() {
    super(SearchService);

    this.fakeResponse = null;
    this.getAllSpy = this.spy(&apos;getAll&apos;).andReturn(this);
    this.getByIdSpy = this.spy(&apos;get&apos;).andReturn(this);
    this.searchSpy = this.spy(&apos;search&apos;).andReturn(this);
    this.saveSpy = this.spy(&apos;save&apos;).andReturn(this);
  }

  subscribe(callback:any) {
    callback(this.fakeResponse);
  }

  setResponse(json:any):void {
    this.fakeResponse = json;
  }

  getProviders():Array&amp;lt;any&amp;gt; {
    return [provide(SearchService, {useValue: this})];
  }
}
&lt;/pre&gt;
                &lt;p&gt;
                    In this same directory, create a &lt;code&gt;helper.ts&lt;/code&gt; class to implement the
                    &lt;code&gt;SpyObject&lt;/code&gt;
                    that &lt;code&gt;MockSearchService&lt;/code&gt; extends.
                &lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
import {StringMapWrapper} from &apos;@angular/core/src/facade/collection&apos;;

export interface GuinessCompatibleSpy extends jasmine.Spy {
  /** By chaining the spy with and.returnValue, all calls to the function will return a specific
   * value. */
  andReturn(val: any): void;
  /** By chaining the spy with and.callFake, all calls to the spy will delegate to the supplied
   * function. */
  andCallFake(fn: Function): GuinessCompatibleSpy;
  /** removes all recorded calls */
  reset();
}

export class SpyObject {
  static stub(object = null, config = null, overrides = null) {
    if (!(object instanceof SpyObject)) {
      overrides = config;
      config = object;
      object = new SpyObject();
    }

    var m = StringMapWrapper.merge(config, overrides);
    StringMapWrapper.forEach(m, (value, key) =&amp;gt; { object.spy(key).andReturn(value); });
    return object;
  }

  constructor(type = null) {
    if (type) {
      for (var prop in type.prototype) {
        var m = null;
        try {
          m = type.prototype&amp;#91;prop&amp;#93;;
        } catch (e) {
          // As we are creating spys for abstract classes,
          // these classes might have getters that throw when they are accessed.
          // As we are only auto creating spys for methods, this
          // should not matter.
        }
        if (typeof m === &apos;function&apos;) {
          this.spy(prop);
        }
      }
    }
  }

  spy(name) {
    if (!this&amp;#91;name&amp;#93;) {
      this&amp;#91;name&amp;#93; = this._createGuinnessCompatibleSpy(name);
    }
    return this&amp;#91;name&amp;#93;;
  }

  prop(name, value) { this&amp;#91;name&amp;#93; = value; }

  /** @internal */
  _createGuinnessCompatibleSpy(name): GuinessCompatibleSpy {
    var newSpy: GuinessCompatibleSpy = &amp;lt;any&amp;gt;jasmine.createSpy(name);
    newSpy.andCallFake = &amp;lt;any&amp;gt;newSpy.and.callFake;
    newSpy.andReturn = &amp;lt;any&amp;gt;newSpy.and.returnValue;
    newSpy.reset = &amp;lt;any&amp;gt;newSpy.calls.reset;
    // revisit return null here (previously needed for rtts_assert).
    newSpy.and.returnValue(null);
    return newSpy;
  }
}
&lt;/pre&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;Alongside, create &lt;code&gt;routes.ts&lt;/code&gt; to mock Angular&apos;s &lt;code&gt;RouteSegment&lt;/code&gt; and passing
                parameters between components.&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;listingblock&quot;&gt;
            &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
import { RouteSegment } from &apos;@angular/router&apos;;

export class MockRouteSegment implements RouteSegment {
  urlSegments:any;
  parameters:any;
  outlet:string;
  _type:any;
  _componentFactory:any;
  type:any;
  stringifiedUrlSegments:string;

  constructor(parameters?:{ [key:string]:any; }) {
    this.parameters = parameters;
  }

  getParam(param:string) {
    return this.parameters[param];
  }
}
&lt;/pre&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;With mocks in place, you can create a spec for &lt;code&gt;SearchComponent&lt;/code&gt; that uses these as providers.
                Create a file at &lt;code&gt;src/search/components/search.component.spec.ts&lt;/code&gt; and populate it with the
                following code.
            &lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;listingblock&quot;&gt;
            &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
import { provide } from &apos;@angular/core&apos;;
import { TestComponentBuilder } from &apos;@angular/compiler/testing&apos;;
import {
  it,
  describe,
  expect,
  inject,
  beforeEachProviders,
} from &apos;@angular/core/testing&apos;;

import { RouteSegment } from &apos;@angular/router&apos;;
import { MockRouteSegment } from &apos;../shared/search/mocks/routes&apos;;
import { MockSearchService } from &apos;../shared/search/mocks/search.service&apos;;

import { SearchComponent } from &apos;./search.component&apos;;

export function main() {
  describe(&apos;Search component&apos;, () =&gt; {
    var mockSearchService:MockSearchService;

    beforeEachProviders(() =&gt; {
      mockSearchService = new MockSearchService();

      return [
        mockSearchService.getProviders(),
        provide(RouteSegment, { useValue: new MockRouteSegment({ &apos;term&apos;: &apos;peyton&apos; }) })
      ];
    });
  });
}
&lt;/pre&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;Add two tests, one to verify a search term is used when it&apos;s set on the component and a second to verify
                search is called when a term is passed in as a route
                parameter.&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;listingblock&quot;&gt;
            &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
it(&apos;should search when a term is set and search() is called&apos;, inject([TestComponentBuilder], (tcb:TestComponentBuilder) =&gt; {
  return tcb.createAsync(SearchComponent).then((fixture) =&gt; {
    let searchComponent = fixture.debugElement.componentInstance;
    searchComponent.query = &apos;M&apos;;
    searchComponent.search();
    expect(mockSearchService.searchSpy).toHaveBeenCalledWith(&apos;M&apos;);
  });
}));

it(&apos;should search automatically when a term is on the URL&apos;, inject([TestComponentBuilder], (tcb:TestComponentBuilder) =&gt; {
  return tcb.createAsync(SearchComponent).then((fixture) =&gt; {
    fixture.detectChanges();
    expect(mockSearchService.searchSpy).toHaveBeenCalledWith(&apos;peyton&apos;);
  });
}));
&lt;/pre&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;Add a spec for the &lt;code&gt;EditComponent&lt;/code&gt; as well, verifying fetching a single record works. Notice
                how you can access the component directly with
                &lt;code&gt;fixture.debugElement.componentInstance&lt;/code&gt;, or its rendered version with &lt;code&gt;fixture.debugElement.nativeElement&lt;/code&gt;.
                Create a file at &lt;code&gt;src/search/components/edit.component.spec.ts&lt;/code&gt; and populate it with the code
                below.
            &lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;listingblock&quot;&gt;
            &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
import { provide } from &apos;@angular/core&apos;;
import { TestComponentBuilder } from &apos;@angular/compiler/testing&apos;;
import {
  it,
  describe,
  expect,
  inject,
  beforeEachProviders,
} from &apos;@angular/core/testing&apos;;

import { RouteSegment } from &apos;@angular/router&apos;;
import { ROUTER_FAKE_PROVIDERS } from &apos;@angular/router/testing&apos;;
import { MockRouteSegment } from &apos;../shared/search/mocks/routes&apos;;
import { MockSearchService } from &apos;../shared/search/mocks/search.service&apos;;

import { EditComponent } from &apos;./edit.component&apos;;

export function main() {
  describe(&apos;Edit component&apos;, () =&gt; {
    var mockSearchService:MockSearchService;

    beforeEachProviders(() =&gt; {
      mockSearchService = new MockSearchService();

      return [
        mockSearchService.getProviders(),
        ROUTER_FAKE_PROVIDERS,
        provide(RouteSegment, { useValue: new MockRouteSegment({ &apos;id&apos;: &apos;1&apos; }) })
      ];
    });

    it(&apos;should fetch a single record&apos;, inject([TestComponentBuilder], (tcb:TestComponentBuilder) =&gt; {
      return tcb.createAsync(EditComponent).then((fixture) =&gt; {
        let person = {name: &apos;Emmanuel Sanders&apos;, address: {city: &apos;Denver&apos;}};
        mockSearchService.setResponse(person);

        fixture.detectChanges();
        // verify service was called
        expect(mockSearchService.getByIdSpy).toHaveBeenCalledWith(1);

        // verify data was set on component when initialized
        let editComponent = fixture.debugElement.componentInstance;
        expect(editComponent.editAddress.city).toBe(&apos;Denver&apos;);

        // verify HTML renders as expected
        var compiled = fixture.debugElement.nativeElement;
        expect(compiled.querySelector(&apos;h3&apos;)).toHaveText(&apos;Emmanuel Sanders&apos;);
      });
    }));
  });
}
&lt;/pre&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;You should see &quot;&lt;span style=&quot;color: green&quot;&gt;&amp;#10004; 22 tests completed&lt;/span&gt;&quot; in the shell window that&apos;s
                running &lt;code&gt;npm run karma.start&lt;/code&gt;. If you don&apos;t, try cancelling the command and restarting.&lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;sect1&quot;&gt;
    &lt;h3 id=&quot;integration_test_the_search_ui&quot;&gt;Integration test the search UI&lt;/h3&gt;
    &lt;div class=&quot;sectionbody&quot;&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;To test if the application works end-to-end, you can write tests with &lt;a
                href=&quot;http://angular.github.io/protractor&quot;&gt;Protractor&lt;/a&gt;. These are also known as integration tests,
                since they test the &lt;em&gt;integration&lt;/em&gt; between all layers of your application.&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;To verify end-to-end tests work in the project before you begin, run the following commands in three
                different console windows.&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;listingblock&quot;&gt;
            &lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;# npm run webdriver-update &amp;lt;- You will need to run this the first time
npm run webdriver-start
npm run serve.e2e
npm run e2e&lt;/pre&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;You should receive an error stating that the &quot;nav text for About&quot; is incorrect.&lt;/p&gt;
        &lt;/div&gt;
        &lt;div id=&quot;protractor-nav-error&quot; class=&quot;imageblock&quot;&gt;
            &lt;div style=&quot;text-align: center&quot;&gt;

                &lt;a href=&quot;https://c6.staticflickr.com/8/7393/27377971101_9a4bdfee36_c.jpg&quot;
                   title=&quot;Protractor nav test error&quot; rel=&quot;lightbox[testing-angular2.0rc1]&quot;
                   data-href=&quot;https://www.flickr.com/photos/mraible/27377971101/in/datetaken-public/&quot;&gt;&lt;img
                    src=&quot;https://c6.staticflickr.com/8/7393/27377971101_9a4bdfee36_z.jpg&quot; width=&quot;640&quot;
                    alt=&quot;Protractor nav test error&quot;&gt;&lt;/a&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;This happens because we added a Search link to the navbar and didn&apos;t update the test (in &lt;code&gt;app.component.e2e.ts&lt;/code&gt;)
                that looks for the last child. &lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;listingblock&quot;&gt;
            &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;it(&apos;should have correct nav text for About&apos;, () =&amp;gt; {
    expect(element(by.css(&apos;sd-app sd-navbar nav a:last-child&apos;)).getText()).toEqual(&apos;ABOUT&apos;);
});
&lt;/pre&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;Replace this test with the one below, and add a new one to verify the Search link is last.&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;listingblock&quot;&gt;
            &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;it(&apos;should have correct nav text for About&apos;, () =&amp;gt; {
  expect(element(by.css(&apos;sd-app sd-navbar nav a:nth-child(2)&apos;)).getText()).toEqual(&apos;ABOUT&apos;);
});

it(&apos;should have correct nav text for Search&apos;, () =&amp;gt; {
  expect(element(by.css(&apos;sd-app sd-navbar nav a:last-child&apos;)).getText()).toEqual(&apos;SEARCH&apos;);
});
&lt;/pre&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;Now when you run &lt;code&gt;npm run e2e&lt;/code&gt;, all specs should pass.&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;sect2&quot;&gt;
            &lt;h3 id=&quot;_testing_the_search_feature&quot;&gt;Testing the search feature&lt;/h3&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Create a new &lt;code&gt;search.component.e2e-spec.ts&lt;/code&gt; spec in the same directory as your &lt;code&gt;SearchComponent&lt;/code&gt;.
                    Add tests to verify elements are rendered correctly and
                    search works. At the time of this writing, Protractor&apos;s &lt;code&gt;by.model&lt;/code&gt; and
                    &lt;code&gt;by.repeater&lt;/code&gt; don&apos;t work with Angular 2. For this reason, I used &lt;code&gt;by.css&lt;/code&gt; to
                    verify the HTML renders as expected.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
describe(&apos;Search&apos;, () =&gt; {

  beforeEach(() =&gt; {
    browser.get(&apos;/search&apos;);
  });

  it(&apos;should have an input and search button&apos;, () =&gt; {
    expect(element(by.css(&apos;sd-app sd-search form input&apos;)).isPresent()).toEqual(true);
    expect(element(by.css(&apos;sd-app sd-search form button&apos;)).isPresent()).toEqual(true);
  });

  it(&apos;should allow searching&apos;, () =&gt; {
    let searchButton = element(by.css(&apos;button&apos;));
    let searchBox = element(by.css(&apos;input&apos;));
    searchBox.sendKeys(&apos;M&apos;);
    searchButton.click().then(() =&gt; {
      // doesn&apos;t work as expected - results in 0
      //expect(element.all(by.repeater(&apos;person of searchResults&apos;)).count()).toEqual(3);
      var list = element.all(by.css(&apos;sd-search table tbody tr&apos;));
      expect(list.count()).toBe(3);
    });
  });
});
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;sect2&quot;&gt;
            &lt;h3 id=&quot;_testing_the_edit_feature&quot;&gt;Testing the edit feature&lt;/h3&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Create a &lt;code&gt;edit.component.e2e-spec.ts&lt;/code&gt; spec to verify the &lt;code&gt;EditComponent&lt;/code&gt;
                    renders a person&apos;s information and that you can update their information.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
describe(&apos;Edit&apos;, () =&gt; {

  beforeEach(() =&gt; {
    browser.get(&apos;/edit/1&apos;);
  });

  let name = element(by.id(&apos;name&apos;));
  let street = element(by.id(&apos;street&apos;));
  let city = element(by.id(&apos;city&apos;));

  it(&apos;should allow viewing a person&apos;, () =&gt; {
    expect(element(by.css(&apos;h3&apos;)).getText()).toEqual(&apos;Peyton Manning&apos;);
    expect(name.getAttribute(&apos;value&apos;)).toEqual(&apos;Peyton Manning&apos;);
    expect(street.getAttribute(&apos;value&apos;)).toEqual(&apos;1234 Main Street&apos;);
    expect(city.getAttribute(&apos;value&apos;)).toEqual(&apos;Greenwood Village&apos;);
  });

  it(&apos;should allow updating a name&apos;, function () {
    let save = element(by.id(&apos;save&apos;));
    // send individual characters since sendKeys passes partial values sometimes
    // https://github.com/angular/protractor/issues/698
    &apos; Won!&apos;.split(&apos;&apos;).forEach((c) =&gt; name.sendKeys(c));
    save.click();
    // verify one element matched this change
    var list = element.all(by.css(&apos;sd-search table tbody tr&apos;));
    expect(list.count()).toBe(1);
  });
});
&lt;/pre&gt;
                    &lt;p&gt;
                        Run &lt;code&gt;npm run e2e&lt;/code&gt; to verify all your end-to-end tests pass. You might receive a
                        failure for the &quot;Home&quot; test.
                    &lt;/p&gt;
                    &lt;div style=&quot;text-align: center&quot;&gt;
                        &lt;a href=&quot;https://c1.staticflickr.com/8/7227/27173055360_80b1055f07_c.jpg&quot;
                           data-href=&quot;https://www.flickr.com/photos/mraible/27173055360/in/datetaken-public/&quot;
                           title=&quot;Protractor home error&quot; rel=&quot;lightbox[testing-angular2.0rc1]&quot;&gt;&lt;img
                            src=&quot;https://c1.staticflickr.com/8/7227/27173055360_80b1055f07_z.jpg&quot; width=&quot;640&quot;
                            alt=&quot;Protractor home error&quot;&gt;&lt;/a&gt;
                    &lt;/div&gt;
                    &lt;p&gt;If you do, open &lt;code&gt;src/client/app/+home/home.component.e2e-spec.ts&lt;/code&gt; and change line 17
                        from this:&lt;/p&gt;
&lt;pre class=&quot;brush: css&quot;&gt;
element(by.css(&apos;sd-home form input&apos;)).sendKeys(&apos;Tim Berners-Lee&apos;);
&lt;/pre&gt;
                    &lt;p&gt;To this:&lt;/p&gt;
&lt;pre class=&quot;brush: css&quot;&gt;
let input = element(by.css(&apos;sd-home form input&apos;));
&apos;Tim Berners-Lee&apos;.split(&apos;&apos;).forEach((c) =&gt; input.sendKeys(c));
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Run &lt;code&gt;npm run e2e&lt;/code&gt; again. You should see a success message similar to the one below in your
                    terminal window.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div id=&quot;protractor-success&quot; class=&quot;imageblock&quot;&gt;
                &lt;div style=&quot;text-align: center&quot;&gt;
                    &lt;a href=&quot;https://c1.staticflickr.com/8/7345/26840174984_01e6906f98_c.jpg&quot;
                       title=&quot;Protractor success&quot; rel=&quot;lightbox[testing-angular2.0rc1]&quot;
                       data-href=&quot;https://www.flickr.com/photos/mraible/26840174984/in/datetaken-public/&quot;&gt;&lt;img
                        src=&quot;https://c1.staticflickr.com/8/7345/26840174984_01e6906f98_z.jpg&quot; width=&quot;640&quot;
                        alt=&quot;Protractor success&quot;&gt;&lt;/a&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;If you made it this far and have all 13 specs passing - congratulations!
                    You&apos;re well on your way to writing quality code with Angular 2 and verifying it works.&lt;/p&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;sect1&quot;&gt;
   &lt;h3 id=&quot;_continuous_integration&quot;&gt;Continuous Integration&lt;/h3&gt;
    &lt;div class=&quot;paragraph&quot;&gt;
        &lt;p&gt;The angular2-seed project ships with a &lt;code&gt;.travis.yml&lt;/code&gt; that you can use to run continuous integration for this application
            through &lt;a href=&quot;https://travis-ci.org/&quot;&gt;Travis CI&lt;/a&gt;. To enable builds on Travis CI, login and enable builds for the
            GitHub repo you created the project in. Then trigger your first build with a &lt;code&gt;git push&lt;/code&gt;.&lt;/p&gt;
    &lt;/div&gt;
    &lt;div class=&quot;paragraph&quot;&gt;
        &lt;p&gt;When I first tried this, I received a &lt;a href=&quot;https://travis-ci.org/mraible/angular2-tutorial/jobs/135517549#L1189&quot;&gt;failure&lt;/a&gt; because
            Protractor on Travis CI is &lt;a href=&quot;https://github.com/mgechev/angular2-seed/issues/970&quot;&gt;unable to navigate directly&lt;/a&gt;
            to the search and edit components. I was able to workaround this by modifying &lt;code&gt;search.component.e2e-spec.ts&lt;/code&gt; to start
            at the top and navigate to the component.&lt;/p&gt;
    &lt;/div&gt;
    &lt;div class=&quot;listingblock&quot;&gt;
        &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;beforeEach(() =&amp;gt; {
  browser.get(&apos;/&apos;);
  element(by.linkText(&apos;SEARCH&apos;)).click();
});&lt;/pre&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;paragraph&quot;&gt;
        &lt;p&gt;I did something similar with &lt;code&gt;edit.component.e2e-spec.ts&lt;/code&gt;:&lt;/p&gt;
    &lt;/div&gt;
    &lt;div class=&quot;listingblock&quot;&gt;
        &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;beforeEach(() =&amp;gt; {
  browser.get(&apos;/&apos;);
  element(by.linkText(&apos;SEARCH&apos;)).click();
  let search = element(by.css(&apos;sd-search form input&apos;));
  &apos;Man&apos;.split(&apos;&apos;).forEach((c) =&amp;gt; search.sendKeys(c));
  element(by.css(&apos;sd-search form button&apos;)).click();
  element(by.linkText(&apos;Peyton Manning&apos;)).click();
});&lt;/pre&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;paragraph&quot;&gt;
        &lt;p&gt;After making these changes, &lt;a href=&quot;https://travis-ci.org/mraible/angular2-tutorial/builds/135598015&quot;&gt;all e2e tests passed in Travis CI&lt;/a&gt;.&lt;/p&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;sect1&quot;&gt;
    &lt;h3 id=&quot;source_code&quot;&gt;Source code&lt;/h3&gt;
    &lt;div class=&quot;sectionbody&quot;&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;A completed project with this code in it is available on GitHub at &lt;a
                href=&quot;https://github.com/mraible/angular2-tutorial&quot;&gt;https://github.com/mraible/angular2-tutorial&lt;/a&gt;.
                If you have ideas for improvements, please leave a comment or send a pull request.
            &lt;/p&gt;
            &lt;p&gt;
                I wrote this tutorial in &lt;a href=&quot;http://asciidoctor.org/&quot;&gt;Asciidoctor&lt;/a&gt; so you can also &lt;a href=&quot;https://github.com/mraible/angular2-tutorial/blob/master/TESTING.adoc&quot;&gt;read this tutorial on GitHub&lt;/a&gt; or
                &lt;a href=&quot;http://gist.asciidoctor.org/?github-mraible%2Fangular2-tutorial%2F%2FTESTING.adoc&quot;&gt;using DocGist&lt;/a&gt;.
            &lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;sect1&quot;&gt;
    &lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
    &lt;div class=&quot;sectionbody&quot;&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;I hope you&apos;ve enjoyed this quick-and-easy tutorial on testing Angular 2.0 RC1 applications. You can see
                the test coverage of your project by running
                &lt;code&gt;npm run serve.coverage&lt;/code&gt;. You&apos;ll notice that the new components and service could use some
                additional coverage. I&apos;ll leave that as a task
                for the reader.&lt;/p&gt;
        &lt;/div&gt;
        &lt;div id=&quot;test-coverage&quot; class=&quot;imageblock&quot;&gt;
            &lt;div style=&quot;text-align: center&quot;&gt;
                &lt;a href=&quot;https://c3.staticflickr.com/8/7381/27350245922_7e184fc630_c.jpg&quot; title=&quot;Test Coverage&quot;
                   rel=&quot;lightbox[testing-angular2.0rc1]&quot;
                   data-href=&quot;https://www.flickr.com/photos/mraible/27350245922/in/datetaken-public/&quot;&gt;&lt;img
                    src=&quot;https://c3.staticflickr.com/8/7381/27350245922_7e184fc630_z.jpg&quot; width=&quot;640&quot;
                    alt=&quot;Test Coverage&quot;&gt;&lt;/a&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;I learned a lot about testing from &lt;a href=&quot;https://www.ng-book.com/2/&quot;&gt;ng-book 2&lt;/a&gt; and
                its Testing chapter. If you have any Angular 2 testing tips and tricks you&apos;d like to share, I&apos;d love to
                hear about them.&lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
</description>          </item>
    <item>
    <guid isPermaLink="true">https://raibledesigns.com/rd/entry/getting_started_with_angular_2_rc1</guid>
    <title>Getting Started with Angular 2.0 RC1</title>
    <dc:creator>Matt Raible</dc:creator>
    <link>https://raibledesigns.com/rd/entry/getting_started_with_angular_2_rc1</link>
        <pubDate>Fri, 3 Jun 2016 07:16:18 -0600</pubDate>
    <category>The Web</category>
    <category>asciidoctor</category>
    <category>npm</category>
    <category>javascript</category>
    <category>node</category>
    <category>git</category>
    <category>typescript</category>
    <category>angular2</category>
    <atom:summary type="html">&lt;p&gt;A few months ago, I wrote a tutorial on &lt;a href=&quot;//raibledesigns.com/rd/entry/getting_started_with_angular_2&quot;&gt;Getting
    Started with Angular 2&lt;/a&gt;. That tutorial was based on Angular 2.0.0 Beta 9. Rather than simply updating that
    tutorial
    and blog post for 2.0.0 RC1, I decided to create a new version for posterity&apos;s sake. The 2.0 Beta 9 version will
    remain on my blog and I&apos;ve &lt;a href=&quot;https://github.com/mraible/angular2-tutorial/releases/tag/2.0.0-beta.9&quot;&gt;tagged
        the source on GitHub&lt;/a&gt;.
    This is an updated version of Getting Started with Angular 2, complete with the
    &lt;a href=&quot;https://angular.io/docs/ts/latest/guide/router.html&quot;&gt;largely undocumented component router&lt;/a&gt;,
    and lazy-loaded components.
&lt;/p&gt;
&lt;p&gt;If you&apos;d just like to see what&apos;s changed since the last release of this tutorial, you can
    &lt;a href=&quot;https://github.com/mraible/angular2-tutorial/pull/2&quot;&gt;view the pull request on GitHub&lt;/a&gt;. Note that I did
    &lt;a href=&quot;https://help.github.com/articles/syncing-a-fork/&quot;&gt;sync my angular2-tutorial project&lt;/a&gt; with &lt;a
        href=&quot;https://github.com/mgechev/angular2-seed&quot;&gt;angular2-seed&lt;/a&gt;. This made it fairly easy to upgrade,
    believe it or not. My upgrade notes are &lt;a href=&quot;&quot;&gt;in a gist&lt;/a&gt;. The best diff to read to see what changed is
    likely &lt;a href=&quot;https://github.com/mraible/angular2-tutorial/pull/2/files#diff-7bd4a925c695c2eb0eced3872b9b965f&quot;&gt;
    the diff of this tutorial&lt;/a&gt;.
&lt;/p&gt;
&lt;div class=&quot;sect1&quot;&gt;
    &lt;h3 id=&quot;what_you_ll_build&quot;&gt;What you&apos;ll build&lt;/h3&gt;
    &lt;div class=&quot;sectionbody&quot;&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;You&apos;ll build a simple web application with Angular 2 and TypeScript. You&apos;ll add search and edit features
                with mock data.&lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
    &lt;h3 id=&quot;what_you_ll_need&quot;&gt;What you&apos;ll need&lt;/h3&gt;
    &lt;div class=&quot;sectionbody&quot;&gt;
        &lt;div class=&quot;ulist&quot;&gt;
            &lt;ul&gt;
                &lt;li&gt;About 15-30 minutes.
                &lt;/li&gt;
                &lt;li&gt;A favorite text editor or IDE. I recommend &lt;a href=&quot;https://www.jetbrains.com/idea/&quot;&gt;IntelliJ
                    IDEA&lt;/a&gt;.
                &lt;/li&gt;
                &lt;li&gt;&lt;a href=&quot;http://git-scm.com/&quot;&gt;Git&lt;/a&gt; installed.
                &lt;/li&gt;
                &lt;li&gt;&lt;a href=&quot;http://nodejs.org/&quot;&gt;Node.js&lt;/a&gt; and npm installed. I recommend using &lt;a
                    href=&quot;https://github.com/creationix/nvm&quot;&gt;nvm&lt;/a&gt;.
                &lt;/li&gt;
            &lt;/ul&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
    &lt;h3 id=&quot;create_your_project&quot;&gt;Create your project&lt;/h3&gt;
    &lt;div class=&quot;sectionbody&quot;&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;Clone the &lt;a href=&quot;https://github.com/mgechev/angular2-seed&quot;&gt;angular2-seed&lt;/a&gt; repository using git:&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;listingblock&quot;&gt;
            &lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;git clone https://github.com/mgechev/angular2-seed.git angular2-tutorial
cd angular2-tutorial&lt;/pre&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;</atom:summary>        <description>&lt;p&gt;A few months ago, I wrote a tutorial on &lt;a href=&quot;//raibledesigns.com/rd/entry/getting_started_with_angular_2&quot;&gt;Getting
    Started with Angular 2&lt;/a&gt;. That tutorial was based on Angular 2.0.0 Beta 9. Rather than simply updating that
    tutorial
    and blog post for 2.0.0 RC1, I decided to create a new version for posterity&apos;s sake. The 2.0 Beta 9 version will
    remain on my blog and I&apos;ve &lt;a href=&quot;https://github.com/mraible/angular2-tutorial/releases/tag/2.0.0-beta.9&quot;&gt;tagged
        the source on GitHub&lt;/a&gt;.
    This is an updated version of Getting Started with Angular 2, complete with the
    &lt;a href=&quot;https://angular.io/docs/ts/latest/guide/router.html&quot;&gt;largely undocumented component router&lt;/a&gt;,
    and lazy-loaded components.
&lt;/p&gt;
&lt;p&gt;If you&apos;d just like to see what&apos;s changed since the last release of this tutorial, you can
    &lt;a href=&quot;https://github.com/mraible/angular2-tutorial/pull/2&quot;&gt;view the pull request on GitHub&lt;/a&gt;. Note that I did
    &lt;a href=&quot;https://help.github.com/articles/syncing-a-fork/&quot;&gt;sync my angular2-tutorial project&lt;/a&gt; with &lt;a
        href=&quot;https://github.com/mgechev/angular2-seed&quot;&gt;angular2-seed&lt;/a&gt;. This made it fairly easy to upgrade,
    believe it or not. My upgrade notes are &lt;a href=&quot;https://gist.github.com/mraible/d9754864249e1b4bfa344ce80074d73d&quot;&gt;in a gist&lt;/a&gt;. The best diff to read to see what changed is
    likely &lt;a href=&quot;https://github.com/mraible/angular2-tutorial/pull/2/files#diff-7bd4a925c695c2eb0eced3872b9b965f&quot;&gt;
    the diff of this tutorial&lt;/a&gt;.
&lt;/p&gt;
&lt;div class=&quot;sect1&quot;&gt;
    &lt;h3 id=&quot;what_you_ll_build&quot;&gt;What you&apos;ll build&lt;/h3&gt;
    &lt;div class=&quot;sectionbody&quot;&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;You&apos;ll build a simple web application with Angular 2 and TypeScript. You&apos;ll add search and edit features
                with mock data.&lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
    &lt;h3 id=&quot;what_you_ll_need&quot;&gt;What you&apos;ll need&lt;/h3&gt;
    &lt;div class=&quot;sectionbody&quot;&gt;
        &lt;div class=&quot;ulist&quot;&gt;
            &lt;ul&gt;
                &lt;li&gt;About 15-30 minutes.
                &lt;/li&gt;
                &lt;li&gt;A favorite text editor or IDE. I recommend &lt;a href=&quot;https://www.jetbrains.com/idea/&quot;&gt;IntelliJ
                    IDEA&lt;/a&gt;.
                &lt;/li&gt;
                &lt;li&gt;&lt;a href=&quot;http://git-scm.com/&quot;&gt;Git&lt;/a&gt; installed.
                &lt;/li&gt;
                &lt;li&gt;&lt;a href=&quot;http://nodejs.org/&quot;&gt;Node.js&lt;/a&gt; and npm installed. I recommend using &lt;a
                    href=&quot;https://github.com/creationix/nvm&quot;&gt;nvm&lt;/a&gt;.
                &lt;/li&gt;
            &lt;/ul&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
    &lt;h3 id=&quot;create_your_project&quot;&gt;Create your project&lt;/h3&gt;
    &lt;div class=&quot;sectionbody&quot;&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;Clone the &lt;a href=&quot;https://github.com/mgechev/angular2-seed&quot;&gt;angular2-seed&lt;/a&gt; repository using git:&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;listingblock&quot;&gt;
            &lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;git clone https://github.com/mgechev/angular2-seed.git angular2-tutorial
cd angular2-tutorial&lt;/pre&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;p&gt;
            NOTE: The angular2-seed project requires node v4.x.x or higher and npm 2.14.7. I used node v4.2.6 and npm
            3.9.3.
        &lt;/p&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;Install the project&apos;s dependencies:&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;listingblock&quot;&gt;
            &lt;div class=&quot;content&quot;&gt;
                &lt;pre&gt;npm install&lt;/pre&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
    &lt;h3 id=&quot;run_the_application&quot;&gt;Run the application&lt;/h3&gt;
    &lt;div class=&quot;sectionbody&quot;&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;The project is configured with a simple web server for development. To start it, run:&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;listingblock&quot;&gt;
            &lt;div class=&quot;content&quot;&gt;
                &lt;pre&gt;npm start&lt;/pre&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;You should see a screen like the one below at &lt;a href=&quot;http://localhost:5555&quot;&gt;http://localhost:5555&lt;/a&gt;.
            &lt;/p&gt;
        &lt;/div&gt;
        &lt;div id=&quot;default-homepage&quot; class=&quot;imageblock&quot;&gt;
            &lt;div style=&quot;text-align: center&quot;&gt;
                &lt;a href=&quot;https://farm2.staticflickr.com/1560/25886693062_4dd41acd3d_c.jpg&quot; title=&quot;Default homepage&quot;
                   rel=&quot;lightbox[getting-started-with-angular2]&quot;
                   data-href=&quot;https://www.flickr.com/photos/mraible/25886693062/in/datetaken-public/&quot;&gt;&lt;img
                    src=&quot;https://farm2.staticflickr.com/1560/25886693062_4dd41acd3d_z.jpg&quot; width=&quot;640&quot;
                    alt=&quot;Default homepage&quot;&gt;&lt;/a&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;You can see your new project&apos;s test coverage by running &lt;code&gt;npm test&lt;/code&gt;:&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;listingblock&quot;&gt;
            &lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;=============================== Coverage summary ===============================
Statements   : 96.36% ( 159/165 )
Branches     : 62.5% ( 120/192 )
Functions    : 100% ( 35/35 )
Lines        : 100% ( 129/129 )
================================================================================&lt;/pre&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
    &lt;h3 id=&quot;add_a_search_feature&quot;&gt;Add a search feature&lt;/h3&gt;
    &lt;div class=&quot;sectionbody&quot;&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;To add a search feature, open the project in an IDE or your favorite text editor. For IntelliJ IDEA, use
                File &amp;gt; New Project &amp;gt; Static Web and point to the directory you cloned angular2-seed to.&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;sect2&quot;&gt;
            &lt;h3 id=&quot;the_basics&quot;&gt;The Basics&lt;/h3&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Create a &lt;code&gt;src/client/app/+search&lt;/code&gt; folder and a &lt;code&gt;search.component.html&lt;/code&gt; file in it. The + prefix is an indicator
                    to Angular that you want to lazy-load the components in this directory. Populate the HTML file you created with the following:
                &lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: xml&quot;&gt;&amp;lt;h2&amp;gt;Search&amp;lt;/h2&amp;gt;
&amp;lt;form&amp;gt;
  &amp;lt;input type=&quot;search&quot; [(ngModel)]=&quot;query&quot; (keyup.enter)=&quot;search()&quot;&amp;gt;
  &amp;lt;button type=&quot;button&quot; (click)=&quot;search()&quot;&amp;gt;Search&amp;lt;/button&amp;gt;
&amp;lt;/form&amp;gt;
&amp;lt;div *ngIf=&quot;loading&quot;&amp;gt;loading...&amp;lt;/div&amp;gt;
&amp;lt;pre&amp;gt;{{searchResults | json}}&amp;lt;/pre&amp;gt;
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Create &lt;code&gt;src/client/app/+search/search.component.ts&lt;/code&gt; to define the
                    &lt;code&gt;SearchComponent&lt;/code&gt; and point to this template.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;import { Component } from &apos;@angular/core&apos;;
import { CORE_DIRECTIVES, FORM_DIRECTIVES } from &apos;@angular/common&apos;;
import { ROUTER_DIRECTIVES } from &apos;@angular/router&apos;;

@Component({
  selector: &apos;sd-search&apos;,
  moduleId: module.id,
  templateUrl: &apos;search.component.html&apos;,
  directives: [FORM_DIRECTIVES, CORE_DIRECTIVES, ROUTER_DIRECTIVES]
})
export class SearchComponent {
  loading: boolean;
  query: string;
  searchResults: any;

  constructor() {
    console.log(&apos;initialized search component&apos;);
  }
}
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Create &lt;code&gt;src/client/app/+search/index.ts&lt;/code&gt; to export &lt;code&gt;SearchComponent&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;/**
* This barrel file provides the export for the lazy loaded SearchComponent.
*/
export * from &apos;./search.component&apos;;
&lt;/pre&gt;
&lt;p&gt;Update &lt;code&gt;src/client/app/app.component.ts&lt;/code&gt; to import this component and include its route.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;import { SearchComponent } from &apos;./+search/index&apos;;

@Routes([
  { path: &apos;/&apos;, component: HomeComponent },
  { path: &apos;/about&apos;, component: AboutComponent },
  { path: &apos;/search&apos;, component: SearchComponent }
])
&lt;/pre&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Your browser should refresh automatically, thanks to &lt;a href=&quot;http://browsersync.io&quot;&gt;Browsersync&lt;/a&gt;.
                    Navigate to
                    &lt;a href=&quot;http://localhost:5555/search&quot;&gt;http://localhost:5555/search&lt;/a&gt; and you should see the
                    search component.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div id=&quot;search-component&quot; class=&quot;imageblock&quot;&gt;
                &lt;div style=&quot;text-align: center&quot;&gt;
                    &lt;a href=&quot;https://farm2.staticflickr.com/1552/25374766404_b0830ff897_c.jpg&quot; title=&quot;Search component&quot;
                       rel=&quot;lightbox[getting-started-with-angular2]&quot;
                       data-href=&quot;https://www.flickr.com/photos/mraible/25374766404/in/datetaken-public/&quot;&gt;&lt;img
                        src=&quot;https://farm2.staticflickr.com/1552/25374766404_b0830ff897_z.jpg&quot; width=&quot;640&quot;
                        alt=&quot;Search component&quot;&gt;&lt;/a&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;You can see it needs a bit of styling. Angular 2 allows you to provide styles specific for your
                    component using a &lt;code&gt;styleUrls&lt;/code&gt;
                    property on your component.
                    Add this property to &lt;code&gt;search.component.ts&lt;/code&gt; like you see below.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;templateUrl: &apos;search.component.html&apos;,
styleUrls: [&apos;search.component.css&apos;],
directives: [FORM_DIRECTIVES, CORE_DIRECTIVES, ROUTER_DIRECTIVES]
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Create &lt;code&gt;src/client/app/+search/search.component.css&lt;/code&gt; and add some CSS.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: css&quot;&gt;:host {
  display: block;
  padding: 0 16px;
}
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;There, that looks better!&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;imageblock&quot;&gt;
                &lt;div style=&quot;text-align: center&quot;&gt;
                    &lt;a href=&quot;https://c2.staticflickr.com/8/7433/26828737033_fd196ee3f3_c.jpg&quot;
                       title=&quot;Search component with styling&quot; rel=&quot;lightbox[getting-started-with-angular2]&quot;
                       data-href=&quot;https://www.flickr.com/photos/mraible/25886691122/in/datetaken-public/&quot;&gt;&lt;img
                        src=&quot;https://c2.staticflickr.com/8/7433/26828737033_fd196ee3f3_z.jpg&quot; width=&quot;640&quot;
                        alt=&quot;Search component with styling&quot;&gt;&lt;/a&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Finally, update &lt;code&gt;src/client/app/shared/navbar/navbar.component.html&lt;/code&gt; to include a link to the search
                    route.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: xml&quot;&gt;&amp;lt;nav&amp;gt;
  &amp;lt;a &amp;#91;routerLink&amp;#93;=&quot;&amp;#91;&apos;/&apos;&amp;#93;&quot;&amp;gt;HOME&amp;lt;/a&amp;gt;
  &amp;lt;a &amp;#91;routerLink&amp;#93;=&quot;&amp;#91;&apos;/about&apos;&amp;#93;&quot;&amp;gt;ABOUT&amp;lt;/a&amp;gt;
  &amp;lt;a &amp;#91;routerLink&amp;#93;=&quot;&amp;#91;&apos;/search&apos;&amp;#93;&quot;&amp;gt;SEARCH&amp;lt;/a&amp;gt;
&amp;lt;/nav&amp;gt;
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;This section has shown you how to add a new component to a basic Angular 2 application.
                    The next section shows you how to create a use a JSON file and &lt;code&gt;localStorage&lt;/code&gt; to create a
                    fake API.&lt;/p&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;sect2&quot;&gt;
            &lt;h3 id=&quot;the_backend&quot;&gt;The Backend&lt;/h3&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;To get search results, create a &lt;code&gt;SearchService&lt;/code&gt; that makes HTTP requests to a JSON file.
                    Start
                    by creating &lt;code&gt;src/client/app/shared/search/data/people.json&lt;/code&gt; to hold your data.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;[
  {
    &quot;id&quot;: 1,
    &quot;name&quot;: &quot;Peyton Manning&quot;,
    &quot;phone&quot;: &quot;(303) 567-8910&quot;,
    &quot;address&quot;: {
      &quot;street&quot;: &quot;1234 Main Street&quot;,
      &quot;city&quot;: &quot;Greenwood Village&quot;,
      &quot;state&quot;: &quot;CO&quot;,
      &quot;zip&quot;: &quot;80111&quot;
    }
  },
  {
    &quot;id&quot;: 2,
    &quot;name&quot;: &quot;Demaryius Thomas&quot;,
    &quot;phone&quot;: &quot;(720) 213-9876&quot;,
    &quot;address&quot;: {
      &quot;street&quot;: &quot;5555 Marion Street&quot;,
      &quot;city&quot;: &quot;Denver&quot;,
      &quot;state&quot;: &quot;CO&quot;,
      &quot;zip&quot;: &quot;80202&quot;
    }
  },
  {
    &quot;id&quot;: 3,
    &quot;name&quot;: &quot;Von Miller&quot;,
    &quot;phone&quot;: &quot;(917) 323-2333&quot;,
    &quot;address&quot;: {
      &quot;street&quot;: &quot;14 Mountain Way&quot;,
      &quot;city&quot;: &quot;Vail&quot;,
      &quot;state&quot;: &quot;CO&quot;,
      &quot;zip&quot;: &quot;81657&quot;
    }
  }
]
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Create &lt;code&gt;src/client/app/shared/search/search.service.ts&lt;/code&gt; and provide &lt;code&gt;Http&lt;/code&gt; as a
                    dependency in its constructor.
                    In this same file, define the &lt;code&gt;Address&lt;/code&gt; and &lt;code&gt;Person&lt;/code&gt; classes that JSON will be
                    marshalled to.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;import { Injectable } from &apos;@angular/core&apos;;
import { Http, Response } from &apos;@angular/http&apos;;

@Injectable()
export class SearchService {
  constructor(private http:Http) {}

  getAll() {
    return this.http.get(&apos;app/shared/search/data/people.json&apos;).map((res:Response) =&amp;gt; res.json());
  }
}

export class Address {
  street:string;
  city:string;
  state:string;
  zip:string;

  constructor(obj?:any) {
    this.street = obj &amp;amp;&amp;amp; obj.street || null;
    this.city = obj &amp;amp;&amp;amp; obj.city || null;
    this.state = obj &amp;amp;&amp;amp; obj.state || null;
    this.zip = obj &amp;amp;&amp;amp; obj.zip || null;
  }
}

export class Person {
  id:number;
  name:string;
  phone:string;
  address:Address;

  constructor(obj?:any) {
    this.id = obj &amp;amp;&amp;amp; Number(obj.id) || null;
    this.name = obj &amp;amp;&amp;amp; obj.name || null;
    this.phone = obj &amp;amp;&amp;amp; obj.phone || null;
    this.address = obj &amp;amp;&amp;amp; obj.address || null;
  }
}
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;In &lt;code&gt;search.component.ts&lt;/code&gt;, add imports for these classes.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;import { Person, SearchService } from &apos;../shared/index&apos;;
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;You can now add a type to the searchResults variable. While you&apos;re there, modify the constructor to
                    inject the
                    &lt;code&gt;SearchService&lt;/code&gt;.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;searchResults: Array&amp;lt;Person&amp;gt;;

constructor(public searchService: SearchService) {}
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Then implement the &lt;code&gt;search()&lt;/code&gt; method to call the service&apos;s &lt;code&gt;getAll()&lt;/code&gt; method.
                &lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;search(): void {
  this.searchService.getAll().subscribe(
    data =&amp;gt; {this.searchResults = data;},
    error =&amp;gt; console.log(error)
  );
}
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;At this point, you&apos;ll likely see the following message in your browser&apos;s console.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
                    &lt;pre&gt;ORIGINAL EXCEPTION: No provider for SearchService!&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;This happens because the app hasn&apos;t provided this service to components. To fix this, create
                    &lt;code&gt;src/client/app/shared/search/index.ts&lt;/code&gt; and populate it with the following.
                &lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;/**
 * This barrel file provides the export for the shared SearchService.
 */
export * from &apos;./search.service&apos;;
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;
                    Then add a reference to this file in &lt;code&gt;src/client/app/shared/index.ts&lt;/code&gt; so its
                    included in the shared export.
                &lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;/**
 * This barrel file provides the exports for the shared resources (services, components).
 */
export * from &apos;./name-list/index&apos;;
export * from &apos;./navbar/index&apos;;
export * from &apos;./toolbar/index&apos;;
export * from &apos;./search/index&apos;;&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;To fix the &quot;No provider&quot; error from above, update &lt;code&gt;app.component.ts&lt;/code&gt; to import the
                    &lt;code&gt;SearchService&lt;/code&gt; and add the service to the list of provider.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;import { NameListService, NavbarComponent, ToolbarComponent, SearchService } from &apos;./shared/index&apos;;

@Component({
  selector: &apos;sd-app&apos;,
  viewProviders: [NameListService, SearchService, HTTP_PROVIDERS]
  moduleId: module.id,
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Now clicking the search button should work. To make the results look better,
                    remove the &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; tag in &lt;code&gt;search.component.html&lt;/code&gt;
                    and replace it with a &lt;code&gt;&amp;lt;table&amp;gt;&lt;/code&gt;.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: xml&quot;&gt;&amp;lt;table *ngIf=&quot;searchResults&quot;&amp;gt;
  &amp;lt;thead&amp;gt;
  &amp;lt;tr&amp;gt;
    &amp;lt;th&amp;gt;Name&amp;lt;/th&amp;gt;
    &amp;lt;th&amp;gt;Phone&amp;lt;/th&amp;gt;
    &amp;lt;th&amp;gt;Address&amp;lt;/th&amp;gt;
  &amp;lt;/tr&amp;gt;
  &amp;lt;/thead&amp;gt;
  &amp;lt;tbody&amp;gt;
  &amp;lt;tr *ngFor=&quot;let person of searchResults; let i=index&quot;&amp;gt;
    &amp;lt;td&amp;gt;{{person.name}}&amp;lt;/td&amp;gt;
    &amp;lt;td&amp;gt;{{person.phone}}&amp;lt;/td&amp;gt;
    &amp;lt;td&amp;gt;{{person.address.street}}&amp;lt;br/&amp;gt;
      {{person.address.city}}, {{person.address.state}} {{person.address.zip}}
    &amp;lt;/td&amp;gt;
  &amp;lt;/tr&amp;gt;
  &amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Then add some additional CSS for this component in &lt;code&gt;search.component.css&lt;/code&gt;.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: css&quot;&gt;table {
  margin-top: 10px;
  border-collapse: collapse;
}

th {
  text-align: left;
  border-bottom: 2px solid #ddd;
  padding: 8px;
}

td {
  border-top: 1px solid #ddd;
  padding: 8px;
}
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Now the search results look better.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div id=&quot;search-results&quot; class=&quot;imageblock&quot;&gt;
                &lt;div style=&quot;text-align: center&quot;&gt;
                    &lt;a href=&quot;https://farm2.staticflickr.com/1677/25706917270_bf07c541e4_c.jpg&quot; title=&quot;Search results&quot;
                       rel=&quot;lightbox[getting-started-with-angular2]&quot;
                       data-href=&quot;https://www.flickr.com/photos/mraible/25706917270/in/datetaken-public/&quot;&gt;&lt;img
                        src=&quot;https://farm2.staticflickr.com/1677/25706917270_bf07c541e4_z.jpg&quot; width=&quot;640&quot;
                        alt=&quot;Search results&quot;&gt;&lt;/a&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;But wait, we still don&apos;t have search functionality! To add a search feature, add a
                    &lt;code&gt;search()&lt;/code&gt; method to
                    &lt;code&gt;search.service.ts&lt;/code&gt;.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;search(q:string) {
  if (!q || q === &apos;*&apos;) {
    q = &apos;&apos;;
  } else {
    q = q.toLowerCase();
  }
  return this.getAll().map(data =&amp;gt; {
    let results:any = [];
    data.map(item =&amp;gt; {
      if (JSON.stringify(item).toLowerCase().includes(q)) {
        results.push(item);
      }
    });
    return results;
  });
}
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Then refactor &lt;code&gt;SearchComponent&lt;/code&gt; to call this method with its &lt;code&gt;query&lt;/code&gt; variable.
                &lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;search(): void {
  this.searchService.search(this.query).subscribe(
    data =&amp;gt; {this.searchResults = data;},
    error =&amp;gt; console.log(error)
  );
}
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Now search results will be filtered by the query value you type in.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;This section showed you how to fetch and display search results. The next section builds on this and
                    shows how to edit and save a record.&lt;/p&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;sect2&quot;&gt;
            &lt;h3 id=&quot;add_an_edit_feature&quot;&gt;Add an edit feature&lt;/h3&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Modify &lt;code&gt;search.component.html&lt;/code&gt; to add a link for editing a person.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: xml&quot;&gt;&amp;lt;td&amp;gt;&amp;lt;a &amp;#91;routerLink&amp;#93;=&quot;&amp;#91;&apos;/edit&apos;, person.id&amp;#93;&quot;&amp;gt;{{person.name}}&amp;lt;/a&amp;gt;&amp;lt;/td&amp;gt;&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Create &lt;code&gt;src/client/app/+edit/edit.component.html&lt;/code&gt; to display an editable form. You might
                    notice I&apos;ve added &lt;code&gt;id&lt;/code&gt;
                    attributes to most elements. This is to
                    make things easier when writing integration tests with Protractor.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: xml&quot;&gt;&amp;lt;div *ngIf=&quot;person&quot;&amp;gt;
  &amp;lt;h3&amp;gt;{{editName}}&amp;lt;/h3&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;label&amp;gt;Id:&amp;lt;/label&amp;gt;
    {{person.id}}
  &amp;lt;/div&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;label&amp;gt;Name:&amp;lt;/label&amp;gt;
    &amp;lt;input [(ngModel)]=&quot;editName&quot; id=&quot;name&quot; placeholder=&quot;name&quot;/&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;label&amp;gt;Phone:&amp;lt;/label&amp;gt;
    &amp;lt;input [(ngModel)]=&quot;editPhone&quot; id=&quot;phone&quot; placeholder=&quot;Phone&quot;/&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;fieldset&amp;gt;
    &amp;lt;legend&amp;gt;Address:&amp;lt;/legend&amp;gt;
    &amp;lt;address&amp;gt;
      &amp;lt;input [(ngModel)]=&quot;editAddress.street&quot; id=&quot;street&quot;&amp;gt;&amp;lt;br/&amp;gt;
      &amp;lt;input [(ngModel)]=&quot;editAddress.city&quot; id=&quot;city&quot;&amp;gt;,
      &amp;lt;input [(ngModel)]=&quot;editAddress.state&quot; id=&quot;state&quot; size=&quot;2&quot;&amp;gt;
      &amp;lt;input [(ngModel)]=&quot;editAddress.zip&quot; id=&quot;zip&quot; size=&quot;5&quot;&amp;gt;
    &amp;lt;/address&amp;gt;
  &amp;lt;/fieldset&amp;gt;
  &amp;lt;button (click)=&quot;save()&quot; id=&quot;save&quot;&amp;gt;Save&amp;lt;/button&amp;gt;
  &amp;lt;button (click)=&quot;cancel()&quot; id=&quot;cancel&quot;&amp;gt;Cancel&amp;lt;/button&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Create an &lt;code&gt;EditComponent&lt;/code&gt; in &lt;code&gt;src/search/components/edit.component.ts&lt;/code&gt; that
                    references this template and handles communication with the &lt;code&gt;SearchService&lt;/code&gt;.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;import { Component, OnInit } from &apos;@angular/core&apos;;
import { Person, Address, SearchService } from &apos;../shared/index&apos;;
import { RouteSegment, Router } from &apos;@angular/router&apos;;

@Component({
  selector: &apos;sd-edit&apos;,
  moduleId: module.id,
  templateUrl: &apos;edit.component.html&apos;,
  styleUrls: [&apos;edit.component.css&apos;]
})
export class EditComponent implements OnInit {

  person: Person;
  editName: string;
  editPhone: string;
  editAddress: Address;

  constructor(
    private _service: SearchService,
    private _router: Router,
    private _routeSegment: RouteSegment
  ) { }

  ngOnInit() {
    let id = +this._routeSegment.getParam(&apos;id&apos;);
    this._service.get(id).subscribe(person =&gt; {
      if (person) {
        this.editName = person.name;
        this.editPhone = person.phone;
        this.editAddress = person.address;
        this.person = person;
      } else {
        this.gotoList();
      }
    });
  }

  cancel() {
    this._router.navigate([&apos;/search&apos;]);
  }

  save() {
    this.person.name = this.editName;
    this.person.phone = this.editPhone;
    this.person.address = this.editAddress;
    this._service.save(this.person);
    this.gotoList();
  }

  gotoList() {
    if (this.person) {
      this._router.navigate([&apos;/search&apos;, {term: this.person.name} ]);
    } else {
      this._router.navigate([&apos;/search&apos;]);
    }
  }
}
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Create &lt;code&gt;src/client/app/+edit/index.ts&lt;/code&gt; to export &lt;code&gt;EditComponent&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;/**
 * This barrel file provides the export for the lazy loaded EditComponent.
 */
export * from &apos;./edit.component&apos;;
&lt;/pre&gt;
                &lt;p&gt;Modify &lt;code&gt;SearchService&lt;/code&gt; to contain functions for finding a person by their id, and saving
                    them. While you&apos;re in there, modify
                    the &lt;code&gt;search()&lt;/code&gt; method to
                    be aware of updated objects in &lt;code&gt;localStorage&lt;/code&gt;.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;search(q:string) {
  if (!q || q === &apos;*&apos;) {
    q = &apos;&apos;;
  } else {
    q = q.toLowerCase();
  }
  return this.getAll().map(data =&amp;gt; {
    let results = [];
    data.map(item =&amp;gt; {
      // check for item in localStorage
      if (localStorage[&apos;person&apos; + item.id]) {
        item = JSON.parse(localStorage[&apos;person&apos; + item.id]);
      }
      if (JSON.stringify(item).toLowerCase().includes(q)) {
        results.push(item);
      }
    });
    return results;
  });
}

get(id: number) {
  return this.getAll().map(all =&amp;gt; {
    if (localStorage[&apos;person&apos; + id]) {
      return JSON.parse(localStorage[&apos;person&apos; + id]);
    }
    return all.find(e =&amp;gt; e.id === id);
  });
}

save(person: Person) {
  localStorage[&apos;person&apos; + person.id] = JSON.stringify(person);
}
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;To make the app aware of this new component, add an import and route configuration in &lt;code&gt;app.component.ts&lt;/code&gt;.
                &lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;import { EditComponent } from &apos;../+edit/index&apos;;

@Routes([
  { path: &apos;/&apos;, component: HomeComponent },
  { path: &apos;/about&apos;, component: AboutComponent },
  { path: &apos;/search&apos;, component: SearchComponent },
  { path: &apos;/edit/:id&apos;, component: EditComponent }
])
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Then create &lt;code&gt;src/client/app/+edit/edit.component.css&lt;/code&gt; to make the form look a bit better.
                &lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: css&quot;&gt;:host {
  display: block;
  padding: 0 16px;
}

button {
  margin-top: 10px;
}
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;At this point, you should be able to search for a person and update their information.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div id=&quot;edit-form&quot; class=&quot;imageblock&quot;&gt;
                &lt;div style=&quot;text-align: center&quot;&gt;
                    &lt;a href=&quot;https://farm2.staticflickr.com/1603/25886692692_42abb78ef6_c.jpg&quot; title=&quot;Edit component&quot;
                       rel=&quot;lightbox[getting-started-with-angular2]&quot;
                       data-href=&quot;https://www.flickr.com/photos/mraible/25886692692/in/datetaken-public/&quot;&gt;&lt;img
                        src=&quot;https://farm2.staticflickr.com/1603/25886692692_42abb78ef6_z.jpg&quot; width=&quot;640&quot;
                        alt=&quot;Edit component&quot;&gt;&lt;/a&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;The &amp;lt;form&amp;gt; in &lt;code&gt;src/client/app/+edit/edit.component.html&lt;/code&gt; calls a
                    &lt;code&gt;save()&lt;/code&gt; function to update a person&apos;s
                    data. You already implemented this above.
                    The function calls a &lt;code&gt;gotoList()&lt;/code&gt; function that appends the person&apos;s name to the URL when
                    sending the user back to the
                    search screen.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;gotoList() {
  if (this.person) {
    this._router.navigate([&apos;/search&apos;, {term: this.person.name} ]);
  } else {
    this._router.navigate([&apos;/search&apos;]);
  }
}
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Since the &lt;code&gt;SearchComponent&lt;/code&gt; doesn&apos;t execute a search automatically when you execute this
                    URL, add the following logic to do
                    so in its constructor.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;constructor(public searchService: SearchService, routeSegment: RouteSegment) {
  if (routeSegment.getParam(&apos;term&apos;)) {
    this.query = decodeURIComponent(routeSegment.getParam(&apos;term&apos;));
    this.search();
  }
}
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;You&apos;ll need to import &lt;code&gt;RouteSegment&lt;/code&gt; in order for everything to compile.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
                    &lt;pre class=&quot;brush: js&quot;&gt;import { ROUTER_DIRECTIVES, RouteSegment } from &apos;@angular/router&apos;;&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;After making all these changes, you should be able to search/edit/update a person&apos;s information. If
                    it works - nice job!&lt;/p&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
    &lt;h3 id=&quot;source_code&quot;&gt;Source code&lt;/h3&gt;
    &lt;div class=&quot;sectionbody&quot;&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;A completed project with this code in it is available on GitHub at &lt;a
                href=&quot;https://github.com/mraible/angular2-tutorial&quot;&gt;https://github.com/mraible/angular2-tutorial&lt;/a&gt;.
                If you have ideas for improvements, please leave a comment or send a pull request.
            &lt;/p&gt;
            &lt;p&gt;
                I originally wrote this tutorial in &lt;a href=&quot;http://asciidoctor.org/&quot;&gt;Asciidoctor&lt;/a&gt; because it has a
                slick feature where you
                can include the source code from files rather than copying and pasting. Since GitHub
                &lt;a href=&quot;https://github.com/github/markup/issues/172&quot;&gt;doesn&apos;t support includes&lt;/a&gt;, I changed things so the
                code is now embedded in the document. You can now &lt;a href=&quot;https://github.com/mraible/angular2-tutorial/blob/master/README.adoc&quot;&gt;read this tutorial on GitHub&lt;/a&gt; or
                &lt;a href=&quot;http://gist.asciidoctor.org/?github-mraible%2Fangular2-tutorial%2F%2FREADME.adoc&quot;&gt;using DocGist&lt;/a&gt;.
            &lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
    &lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
    &lt;div class=&quot;sectionbody&quot;&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;I hope you&apos;ve enjoyed this quick-and-easy tutorial on how to get started with Angular 2.0 RC1. In a future
                tutorial, I&apos;ll show you &lt;a href=&quot;//raibledesigns.com/rd/entry/testing_angular_2_0_rc1&quot;&gt;how to write unit tests and integration tests&lt;/a&gt; for this application.&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;sect2&quot;&gt;
            &lt;h3 id=&quot;resources&quot;&gt;Resources&lt;/h3&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;I used a number of resources while creating this application. &lt;a href=&quot;https://www.ng-book.com/2/&quot;&gt;ng-book
                    2&lt;/a&gt; was an invaluable resource
                    and I highly recommend it if you&apos;re learning Angular 2. I found Chariot Solution&apos;s article on
                    &lt;a href=&quot;http://chariotsolutions.com/blog/post/angular2-observables-http-separating-services-components/&quot;&gt;Angular2
                        Observables, Http, and
                        separating services and components&lt;/a&gt;
                    to be quite helpful. Finally, the &lt;a href=&quot;https://github.com/angular/angular-cli&quot;&gt;angular-cli&lt;/a&gt;
                    project was a big help, especially its
                    &lt;code&gt;ng generate route &amp;lt;object&amp;gt;&lt;/code&gt; feature.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Kudos to all the pioneers in Angular 2 land that&apos;ve been using it and writing about it on blogs and
                    Stack Overflow.
                    Getting started with Angular 2 would&apos;ve been a real pain without your trailblazing.&lt;/p&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;</description>          </item>
    <item>
    <guid isPermaLink="true">https://raibledesigns.com/rd/entry/devoxx_france_2016_springtime_in</guid>
    <title>Devoxx France 2016: Springtime in Paris</title>
    <dc:creator>Matt Raible</dc:creator>
    <link>https://raibledesigns.com/rd/entry/devoxx_france_2016_springtime_in</link>
        <pubDate>Tue, 26 Apr 2016 07:13:18 -0600</pubDate>
    <category>Java</category>
    <category>infoq</category>
    <category>javascript</category>
    <category>java</category>
    <category>devoxxfr</category>
    <category>paris</category>
    <category>asciidoctor</category>
    <category>devoxx</category>
    <category>jhipster</category>
    <category>travel</category>
    <category>france</category>
    <category>angular2</category>
    <category>angularjs</category>
    <atom:summary type="html">&lt;p&gt;I had the good fortune to visit Paris last week for &lt;a href=&quot;http://www.devoxx.fr/&quot;&gt;Devoxx France&lt;/a&gt;. When traveling
    to conferences in exotic locations,
    I like to bring a travel partner. This time, I asked my daughter, Abbie, to join me. She gladly accepted. Springtime
    in Paris can be a beautiful event. The grass is green, the flowers are blooming and the sun&apos;s rays blanket the city.
&lt;/p&gt;
&lt;p&gt;We arrived in Paris on Tuesday, April 19 and quickly found our way to our &lt;a href=&quot;http://www.lemeridienetoile.com/&quot;&gt;hotel&lt;/a&gt;.
    Its location was ideal: across the street from Le Palais des Congr&#232;s de Paris convention center and mall. Since the
    conference
    was at the convention center, it made logistics for my talks very convenient. We grabbed a quick bite after settling
    in,
    then took a 15-minute stroll to the Arc de Triomphe.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1650/26377054130_d1d6561024_c.jpg&quot; title=&quot;Obligatory Arc de Triomphe selfie&quot;
       rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26377054130/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1650/26377054130_d1d6561024_m.jpg&quot; width=&quot;240&quot;
        alt=&quot;Obligatory Arc de Triomphe selfie&quot; style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1548/26377063160_2cc22299cf_c.jpg&quot; title=&quot;Abbie and Eiffel Tower&quot;
       rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26377063160/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1548/26377063160_2cc22299cf_m.jpg&quot; width=&quot;240&quot; alt=&quot;Abbie and Eiffel Tower&quot;
        style=&quot;border: 1px solid black; margin-left: 15px;&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;
    That evening, we joined Ippon developers and friends at a
    &lt;a href=&quot;http://blog.ippon.fr/2016/04/07/le-before-du-devoxx-avec-matt-raible/&quot;&gt;special event for Java Hipsters&lt;/a&gt;.
    Their
    &lt;a href=&quot;http://rooftop-work.paris/&quot;&gt;rooftop location&lt;/a&gt; had great views, cold &quot;Java&quot; beer and I met a lot of
    enthusiastic
    developers. I especially enjoyed talking with the original Java Hipster and founder of
    &lt;a href=&quot;http://jhipster.github.io/&quot;&gt;JHipster&lt;/a&gt;, &lt;a href=&quot;http://www.julien-dubois.com/&quot;&gt;Julien Dubois&lt;/a&gt;.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1697/26046785153_7fdd931724_c.jpg&quot; title=&quot;Java Beer!&quot;
       rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26046785153/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1697/26046785153_7fdd931724_q.jpg&quot; width=&quot;150&quot; alt=&quot;Java Beer!&quot;
        style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1718/26046789653_ac527f73ec_c.jpg&quot;
       title=&quot;The original Java Hipster, Julien Dubious&quot; rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26046789653/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1718/26046789653_ac527f73ec_q.jpg&quot; width=&quot;150&quot;
        alt=&quot;The original Java Hipster, Julien Dubious&quot; style=&quot;border: 1px solid black; margin-left: 15px;&quot;&gt;&lt;/a&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1588/26046794363_3a057b8e6e_c.jpg&quot; title=&quot;Fun event!&quot;
       rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26046794363/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1588/26046794363_3a057b8e6e_q.jpg&quot; width=&quot;150&quot; alt=&quot;Fun event!&quot;
        style=&quot;border: 1px solid black; margin-left: 15px;&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;The sunset over Paris provided a splendid backdrop for the festivities.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1591/26046797633_60beba62be_c.jpg&quot; title=&quot;Sunset over Paris&quot;
       rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26046797633/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1591/26046797633_60beba62be.jpg&quot; width=&quot;500&quot; alt=&quot;Sunset over Paris&quot;
        style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
&lt;/p&gt;</atom:summary>        <description>&lt;p&gt;I had the good fortune to visit Paris last week for &lt;a href=&quot;http://www.devoxx.fr/&quot;&gt;Devoxx France&lt;/a&gt;. When traveling
    to conferences in exotic locations,
    I like to bring a travel partner. This time, I asked my daughter, Abbie, to join me. She gladly accepted. Springtime
    in Paris can be a beautiful event. The grass is green, the flowers are blooming and the sun&apos;s rays blanket the city.
&lt;/p&gt;
&lt;p&gt;We arrived in Paris on Tuesday, April 19 and quickly found our way to our &lt;a href=&quot;http://www.lemeridienetoile.com/&quot;&gt;hotel&lt;/a&gt;.
    Its location was ideal: across the street from Le Palais des Congr&#232;s de Paris convention center and mall. Since the
    conference
    was at the convention center, it made logistics for my talks very convenient. We grabbed a quick bite after settling
    in,
    then took a 15-minute stroll to the Arc de Triomphe.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1650/26377054130_d1d6561024_c.jpg&quot; title=&quot;Obligatory Arc de Triomphe selfie&quot;
       rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26377054130/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1650/26377054130_d1d6561024_m.jpg&quot; width=&quot;240&quot;
        alt=&quot;Obligatory Arc de Triomphe selfie&quot; style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1548/26377063160_2cc22299cf_c.jpg&quot; title=&quot;Abbie and Eiffel Tower&quot;
       rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26377063160/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1548/26377063160_2cc22299cf_m.jpg&quot; width=&quot;240&quot; alt=&quot;Abbie and Eiffel Tower&quot;
        style=&quot;border: 1px solid black; margin-left: 15px;&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1634/26046780663_83b2de9696_c.jpg&quot; title=&quot;The Arc is massive!&quot;
       rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26046780663/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1634/26046780663_83b2de9696.jpg&quot; width=&quot;500&quot; alt=&quot;The Arc is massive!&quot;
        style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;
    That evening, we joined Ippon developers and friends at a
    &lt;a href=&quot;http://blog.ippon.fr/2016/04/07/le-before-du-devoxx-avec-matt-raible/&quot;&gt;special event for Java Hipsters&lt;/a&gt;.
    Their
    &lt;a href=&quot;http://rooftop-work.paris/&quot;&gt;rooftop location&lt;/a&gt; had great views, cold &quot;Java&quot; beer and I met a lot of
    enthusiastic
    developers. I especially enjoyed talking with the original Java Hipster and founder of
    &lt;a href=&quot;http://jhipster.github.io/&quot;&gt;JHipster&lt;/a&gt;, &lt;a href=&quot;http://www.julien-dubois.com/&quot;&gt;Julien Dubois&lt;/a&gt;.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1697/26046785153_7fdd931724_c.jpg&quot; title=&quot;Java Beer!&quot;
       rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26046785153/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1697/26046785153_7fdd931724_q.jpg&quot; width=&quot;150&quot; alt=&quot;Java Beer!&quot;
        style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1718/26046789653_ac527f73ec_c.jpg&quot;
       title=&quot;The original Java Hipster, Julien Dubious&quot; rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26046789653/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1718/26046789653_ac527f73ec_q.jpg&quot; width=&quot;150&quot;
        alt=&quot;The original Java Hipster, Julien Dubious&quot; style=&quot;border: 1px solid black; margin-left: 15px;&quot;&gt;&lt;/a&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1588/26046794363_3a057b8e6e_c.jpg&quot; title=&quot;Fun event!&quot;
       rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26046794363/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1588/26046794363_3a057b8e6e_q.jpg&quot; width=&quot;150&quot; alt=&quot;Fun event!&quot;
        style=&quot;border: 1px solid black; margin-left: 15px;&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;The sunset over Paris provided a splendid backdrop for the festivities.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1591/26046797633_60beba62be_c.jpg&quot; title=&quot;Sunset over Paris&quot;
       rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26046797633/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1591/26046797633_60beba62be.jpg&quot; width=&quot;500&quot; alt=&quot;Sunset over Paris&quot;
        style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;On Wednesday, Abbie and I got up early and headed to Versailles. We toured Ch&#226;teau de Versailles, the Gardens and
    &lt;a href=&quot;http://en.chateauversailles.fr/marie-antoinettes-estate&quot;&gt;Marie-Antoinette&apos;s estate&lt;/a&gt;. I&apos;d never visited
    this
    area of Versailles and never realized what I was missing. We rented a boat and practiced rowing on the Grand Canal
    to get
    ready for rafting season.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1592/26046801963_95dafdd5b7_c.jpg&quot; title=&quot;Abbie and Louis&quot;
       rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26046801963/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1592/26046801963_95dafdd5b7_n.jpg&quot; width=&quot;240&quot; alt=&quot;Abbie and Louis&quot;
        style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1717/26584955241_4b69591dea_c.jpg&quot; title=&quot;Lots of gold at Versailles!&quot;
       rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26584955241/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1717/26584955241_4b69591dea_n.jpg&quot; width=&quot;240&quot;
        alt=&quot;Lots of gold at Versailles!&quot; style=&quot;border: 1px solid black; margin-left: 15px;&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1583/26584961021_29d12506dd_c.jpg&quot; title=&quot;The Gardens&quot;
       rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26584961021/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1583/26584961021_29d12506dd.jpg&quot; width=&quot;500&quot; alt=&quot;The Gardens&quot;
        style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1490/26044647774_97f6749313_c.jpg&quot; title=&quot;Spring in Paris is beautiful!&quot;
       rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26044647774/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1490/26044647774_97f6749313.jpg&quot; width=&quot;500&quot;
        alt=&quot;Spring in Paris is beautiful!&quot; style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1531/26377129570_b72406b68e_c.jpg&quot; title=&quot;Hameau de la Reine&quot;
       rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26377129570/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1531/26377129570_b72406b68e_q.jpg&quot; width=&quot;150&quot; alt=&quot;Hameau de la Reine&quot;
        style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1441/26377132120_90c70026c8_c.jpg&quot;
       title=&quot;The Queen&apos;s house and billiard room&quot; rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26377132120/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1441/26377132120_90c70026c8_q.jpg&quot; width=&quot;150&quot;
        alt=&quot;The Queen&apos;s house and billiard room&quot; style=&quot;border: 1px solid black; margin-left: 15px;&quot;&gt;&lt;/a&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1644/26624011386_06b02e4cee_c.jpg&quot; title=&quot;The Apollo Fountain&quot;
       rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26624011386/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1644/26624011386_06b02e4cee_q.jpg&quot; width=&quot;150&quot; alt=&quot;The Apollo Fountain&quot;
        style=&quot;border: 1px solid black; margin-left: 15px;&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;
    That evening, we stopped by the &lt;a href=&quot;http://uk.le-sud-restaurant.com/&quot;&gt;Restaurant Le Sud&lt;/a&gt; for the speaker&apos;s
    dinner. It was
    fun seeing familiar faces and meeting new folks.
&lt;/p&gt;
&lt;p&gt;Thursday was my first talk, but we had the morning free to explore. We headed for the Eiffel Tower and rode its north
    elevator
    straight to the top. The views where spectacular and Abbie got goosebumps from the gentle sway.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1445/26650599775_547452b330_c.jpg&quot; title=&quot;Great view from the top&quot;
       rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26650599775/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1445/26650599775_547452b330_m.jpg&quot; width=&quot;240&quot; alt=&quot;Great view from the top&quot;
        style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1588/26650603845_9df85f8e47_c.jpg&quot; title=&quot;It&apos;s a long way down&quot;
       rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26650603845/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1588/26650603845_9df85f8e47_m.jpg&quot; width=&quot;240&quot; alt=&quot;It&apos;s a long way down&quot;
        style=&quot;border: 1px solid black; margin-left: 15px;&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1441/26650605665_a3a0a2e5a9_c.jpg&quot; title=&quot;Happiness in Paris&quot;
       rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26650605665/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1441/26650605665_a3a0a2e5a9_m.jpg&quot; width=&quot;240&quot; alt=&quot;Happiness in Paris&quot;
        style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1710/26624042686_d3004caf1d_c.jpg&quot; title=&quot;Tour Eiffel&quot;
       rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26624042686/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1710/26624042686_d3004caf1d_m.jpg&quot; width=&quot;240&quot; alt=&quot;Tour Eiffel&quot;
        style=&quot;border: 1px solid black; margin-left: 15px;&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;
    I transformed from an old-fashioned, whiskey-drinking Java developer to a Java Hipster
    &lt;a href=&quot;https://cfp.devoxx.fr/2016/talk/OJD-3590/Get_Hip_with_JHipster:_Spring_Boot_+_AngularJS_+_Bootstrap&quot;&gt;a few
        hours later&lt;/a&gt;. You can see
    the slides from my &quot;Get Hip with JHipster&quot; presentation below, or &lt;a
    href=&quot;http://www.slideshare.net/mraible/get-hip-with-jhipster-spring-boot-angularjs-bootstrap-devoxx-france-2016&quot;&gt;on
    SlideShare&lt;/a&gt;.
&lt;/p&gt;
&lt;div style=&quot;text-align: center&quot;&gt;
&lt;iframe src=&quot;//www.slideshare.net/slideshow/embed_code/key/DICuqemFX1Sjx7&quot; width=&quot;595&quot; height=&quot;373&quot; frameborder=&quot;0&quot;
        marginwidth=&quot;0&quot; marginheight=&quot;0&quot; scrolling=&quot;no&quot;
        style=&quot;border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;We wanted to see the &lt;a href=&quot;https://en.wikipedia.org/wiki/Catacombs_of_Paris&quot;&gt;Catacombs of Paris&lt;/a&gt; that night,
    and made it just minutes before it closed. Seeing the remains of millions of people&apos;s bones stacked on top of each other
    frightened Abbie more than standing on the glass floor in the Eiffel Tower. I experienced more heebie jeebies from
    the floor.
    We popped out of the Catacombs near the excellent
    &lt;a href=&quot;https://www.tripadvisor.com/Restaurant_Review-g187147-d5562763-Reviews-Thai_paragon-Paris_Ile_de_France.html&quot;&gt;Thai
        Paragon&lt;/a&gt; and stopped for a delicious meal.
    Abbie tried duck for the first time and loved it.
&lt;/p&gt;
&lt;p&gt;
    I had two talks on Friday, a quickie on &lt;a
    href=&quot;https://cfp.devoxx.fr/2016/talk/PGF-2414/Writing_an_InfoQ_Mini_Book_with_Asciidoctor&quot;&gt;how to write an InfoQ
    Mini-Book with Asciidoctor&lt;/a&gt;
    and a 45-minute session on &lt;a href=&quot;https://cfp.devoxx.fr/2016/talk/LUI-4351/The_Art_of_Angular_in_2016&quot;&gt;The Art of
    Angular in 2016&lt;/a&gt;.
    I wrote the InfoQ Mini-Book presentation using &lt;a href=&quot;https://github.com/opendevise/bespoke-emulating-shower&quot;&gt;Asciidoctor&apos;s
    Bespoke support&lt;/a&gt; and really enjoyed
    the experience. Thanks to &lt;a href=&quot;https://twitter.com/mojavelinux&quot;&gt;Dan Allen&lt;/a&gt; for assembling this easy to use
    starter template!
    Dan was also a great help in getting the JHipster Book printed for the first time and I was pumped to have a copy
    with me to show off.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1485/26046771893_34ea82fccc_c.jpg&quot; data-href=&quot;https://www.flickr.com/photos/mraible/26046771893/in/album-72157667022214770/&quot; title=&quot;JHipster Book in print!&quot; rel=&quot;lightbox[devoxxfr2016]&quot;&gt;
        &lt;img src=&quot;https://farm2.staticflickr.com/1485/26046771893_34ea82fccc_n.jpg&quot; width=&quot;240&quot; alt=&quot;JHipster Book in print!&quot; style=&quot;border: 1px solid black&quot;&gt;&lt;/a&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1584/26046773243_b3c3a8f4b2_c.jpg&quot; data-href=&quot;https://www.flickr.com/photos/mraible/26046773243/in/album-72157667022214770/&quot; title=&quot;heroku deploy:jar&quot; rel=&quot;lightbox[devoxxfr2016]&quot;&gt;
        &lt;img src=&quot;https://farm2.staticflickr.com/1584/26046773243_b3c3a8f4b2_n.jpg&quot; width=&quot;240&quot; alt=&quot;heroku deploy:jar&quot; style=&quot;border: 1px solid black; margin-left: 15px&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;
    You can &lt;a href=&quot;http://mraible.github.io/infoq-mini-book-presentation/&quot;&gt;view the presentation online&lt;/a&gt; and
    checkout
    &lt;a href=&quot;https://github.com/mraible/infoq-mini-book-presentation&quot;&gt;its repository on GitHub&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;For my Angular presentation, I invited Abbie to kick things off, so she could experience what it&apos;s like to speak at a
    conference.&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1619/26044723184_ee99bd81d9_c.jpg&quot;
       title=&quot;Moments before Abbie and I spoke about the Art of #Angular in 2016.&quot; rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26044723184/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1619/26044723184_ee99bd81d9.jpg&quot; width=&quot;500&quot;
        alt=&quot;Moments before Abbie and I spoke about the Art of #Angular in 2016.&quot; style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;She did great and I followed her intro with my presentation on working with Angular 2. You can see my presentation
    below or &lt;a href=&quot;http://www.slideshare.net/mraible/the-art-of-angular-in-2016-devoxx-france-2016&quot;&gt;check it out on
        SlideShare&lt;/a&gt;.
&lt;/p&gt;
&lt;div style=&quot;text-align: center&quot;&gt;
&lt;iframe src=&quot;//www.slideshare.net/slideshow/embed_code/key/f4qsdZ0gkbnbKN&quot; width=&quot;595&quot; height=&quot;373&quot; frameborder=&quot;0&quot;
        marginwidth=&quot;0&quot; marginheight=&quot;0&quot; scrolling=&quot;no&quot;
        style=&quot;border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;
    At the end of the conference, we attended the &lt;a href=&quot;https://lescastcodeurs.com/&quot;&gt;Les Cast Codeurs Podcast&lt;/a&gt;. It
    was all in French, but you
    could tell everyone was having a good time from the smiles and laughter in the audience. During the session, the
    Devoxx Crew surprised me
    with a &lt;a href=&quot;https://java-champions.java.net/&quot;&gt;Java Champion&lt;/a&gt; award. I was very &lt;a href=&quot;https://twitter.com/mraible/status/723565855821443072&quot;&gt;surprised and humbled to
    receive this recognition&lt;/a&gt;. It
    was pretty cool having Abbie with me for such an honor.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1495/26044726404_91272a2bae_c.jpg&quot; data-href=&quot;https://www.flickr.com/photos/mraible/26044726404/in/album-72157667022214770/&quot; title=&quot;Les Cast Codeurs&quot; rel=&quot;lightbox[devoxxfr2016]&quot;&gt;
        &lt;img src=&quot;https://farm2.staticflickr.com/1495/26044726404_91272a2bae_m.jpg&quot; width=&quot;240&quot; alt=&quot;Les Cast Codeurs&quot; style=&quot;border: 1px solid black&quot;&gt;&lt;/a&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1465/26044729304_080e658df0_c.jpg&quot; data-href=&quot;https://www.flickr.com/photos/mraible/26044729304/in/album-72157667022214770/&quot; title=&quot;I&amp;#x27;m a Java Champion! :)&quot; rel=&quot;lightbox[devoxxfr2016]&quot;&gt;
        &lt;img src=&quot;https://farm2.staticflickr.com/1465/26044729304_080e658df0_m.jpg&quot; width=&quot;240&quot; alt=&quot;I&amp;#x27;m a Java Champion! :)&quot; style=&quot;border: 1px solid black; margin-left: 15px&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;div style=&quot;margin: 0 auto; text-align: right; margin-top: -10px; max-width: 500px; font-size: .9em&quot;&gt;
    More on Flickr &amp;rarr; &lt;a href=&quot;https://www.flickr.com/photos/mraible/albums/72157667022214770&quot;&gt;Devoxx France 2016&lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;
    Thanks to Devoxx France and Ippon Technologies for providing us with the opportunity for such a fun adventure. We
    had a blast!&lt;/p&gt;
</description>          </item>
    <item>
    <guid isPermaLink="true">https://raibledesigns.com/rd/entry/testing_angular_2_applications</guid>
    <title>Testing Angular 2 Applications</title>
    <dc:creator>Matt Raible</dc:creator>
    <link>https://raibledesigns.com/rd/entry/testing_angular_2_applications</link>
        <pubDate>Tue, 29 Mar 2016 08:08:58 -0600</pubDate>
    <category>The Web</category>
    <category>node</category>
    <category>javascript</category>
    <category>angular2</category>
    <category>karma</category>
    <category>npm</category>
    <category>jasmine</category>
    <category>protractor</category>
    <category>asciidoctor</category>
    <category>git</category>
    <atom:summary type="html">&lt;p class=&quot;alert alert-info&quot;&gt;&lt;a href=&quot;//raibledesigns.com/rd/entry/testing_angular_2_0_rc1&quot;&gt;Click here&lt;/a&gt; to see an updated version of this tutorial that&apos;s been upgraded for Angular 2.0 RC1.&lt;/p&gt;
&lt;p&gt;This article is the second in a series about learning &lt;a href=&quot;https://angular.io/&quot;&gt;Angular 2&lt;/a&gt;. It describes
    how to test a simple Angular 2 application. In a previous article, &lt;a
        href=&quot;//raibledesigns.com/rd/entry/getting_started_with_angular_2&quot;&gt;Getting Started with Angular 2&lt;/a&gt;, I
    showed how to develop a simple search and edit feature.
    In this tutorial, I did my best to keep the tests similar to last year&apos;s &lt;a
        href=&quot;//raibledesigns.com/rd/entry/testing_angularjs_applications&quot;&gt;
        Testing AngularJS Applications&lt;/a&gt; so you can compare &lt;a
        href=&quot;https://github.com/mraible/angular-tutorial/tree/testing&quot;&gt;
        the code between AngularJS&lt;/a&gt; and Angular 2.
&lt;/p&gt;


&lt;div class=&quot;sect1&quot;&gt;
    &lt;h3 id=&quot;what_you_ll_build&quot;&gt;What you&apos;ll build&lt;/h3&gt;
    &lt;div class=&quot;sectionbody&quot;&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;You&apos;ll learn to use &lt;a href=&quot;http://jasmine.github.io/&quot;&gt;Jasmine&lt;/a&gt; for unit testing controllers and &lt;a
                href=&quot;https://angular.github.io/protractor/&quot;&gt;Protractor&lt;/a&gt; for
                integration testing. Angular&apos;s documentation has a good &lt;a
                    href=&quot;https://angular.io/docs/ts/latest/guide/testing.html&quot;&gt;guide to unit testing&lt;/a&gt;
                if you&apos;d
                like more information on testing and why it&apos;s important.&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;The best reason for writing tests is to automate your testing. Without tests, you&apos;ll likely be testing
                manually.
                This manual testing will take longer and longer as your codebase grows.&lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;sect1&quot;&gt;
    &lt;h3 id=&quot;what_you_ll_need&quot;&gt;What you&apos;ll need&lt;/h3&gt;
    &lt;div class=&quot;sectionbody&quot;&gt;
        &lt;div class=&quot;ulist&quot;&gt;
            &lt;ul&gt;
                &lt;li&gt;About 15-30 minutes.&lt;/li&gt;
                &lt;li&gt;A favorite text editor or IDE. I recommend &lt;a href=&quot;https://www.jetbrains.com/idea/&quot;&gt;IntelliJ
                    IDEA&lt;/a&gt;.
                &lt;/li&gt;
                &lt;li&gt;&lt;a href=&quot;http://git-scm.com/&quot;&gt;Git&lt;/a&gt; installed.&lt;/li&gt;
                &lt;li&gt;&lt;a href=&quot;http://nodejs.org/&quot;&gt;Node.js&lt;/a&gt; and npm installed. I recommend using &lt;a
                    href=&quot;https://github.com/creationix/nvm&quot;&gt;nvm&lt;/a&gt;.
                &lt;/li&gt;
            &lt;/ul&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;sect1&quot;&gt;
    &lt;h3 id=&quot;get_the_tutorial_project&quot;&gt;Get the tutorial project&lt;/h3&gt;
    &lt;div class=&quot;sectionbody&quot;&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;Clone the &lt;a href=&quot;https://github.com/mraible/angular2-tutorial&quot;&gt;angular2-tutorial repository&lt;/a&gt; using
                git and install its dependencies.&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;listingblock&quot;&gt;
            &lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;git clone https://github.com/mraible/angular2-tutorial.git
cd angular2-tutorial
npm install&lt;/pre&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;If you haven&apos;t completed the &lt;a href=&quot;//raibledesigns.com/rd/entry/getting_started_with_angular_2&quot;&gt;Getting
                Started with Angular 2&lt;/a&gt; tutorial,
                you should peruse it so you understand how this application works.
                You can also simply start the app with &lt;code&gt;npm start&lt;/code&gt; and view it in your browser at &lt;a
                    href=&quot;http://localhost:5555/&quot; class=&quot;bare&quot;&gt;http://localhost:5555/&lt;/a&gt;.
            &lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;</atom:summary>        <description>&lt;p class=&quot;alert alert-info&quot;&gt;&lt;a href=&quot;//raibledesigns.com/rd/entry/testing_angular_2_0_rc1&quot;&gt;Click here&lt;/a&gt; to see an updated version of this tutorial that&apos;s been upgraded for Angular 2.0 RC1.&lt;/p&gt;
&lt;p&gt;This article is the second in a series about learning &lt;a href=&quot;https://angular.io/&quot;&gt;Angular 2&lt;/a&gt;. It describes
    how to test a simple Angular 2 application. In a previous article, &lt;a
        href=&quot;//raibledesigns.com/rd/entry/getting_started_with_angular_2&quot;&gt;Getting Started with Angular 2&lt;/a&gt;, I
    showed how to develop a simple search and edit feature.
    In this tutorial, I did my best to keep the tests similar to last year&apos;s &lt;a
        href=&quot;//raibledesigns.com/rd/entry/testing_angularjs_applications&quot;&gt;
        Testing AngularJS Applications&lt;/a&gt; so you can compare &lt;a
        href=&quot;https://github.com/mraible/angular-tutorial/tree/testing&quot;&gt;
        the code between AngularJS&lt;/a&gt; and Angular 2.
&lt;/p&gt;


&lt;div class=&quot;sect1&quot;&gt;
    &lt;h3 id=&quot;what_you_ll_build&quot;&gt;What you&apos;ll build&lt;/h3&gt;
    &lt;div class=&quot;sectionbody&quot;&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;You&apos;ll learn to use &lt;a href=&quot;http://jasmine.github.io/&quot;&gt;Jasmine&lt;/a&gt; for unit testing controllers and &lt;a
                href=&quot;https://angular.github.io/protractor/&quot;&gt;Protractor&lt;/a&gt; for
                integration testing. Angular&apos;s documentation has a good &lt;a
                    href=&quot;https://angular.io/docs/ts/latest/guide/testing.html&quot;&gt;guide to unit testing&lt;/a&gt;
                if you&apos;d
                like more information on testing and why it&apos;s important.&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;The best reason for writing tests is to automate your testing. Without tests, you&apos;ll likely be testing
                manually.
                This manual testing will take longer and longer as your codebase grows.&lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;sect1&quot;&gt;
    &lt;h3 id=&quot;what_you_ll_need&quot;&gt;What you&apos;ll need&lt;/h3&gt;
    &lt;div class=&quot;sectionbody&quot;&gt;
        &lt;div class=&quot;ulist&quot;&gt;
            &lt;ul&gt;
                &lt;li&gt;About 15-30 minutes.&lt;/li&gt;
                &lt;li&gt;A favorite text editor or IDE. I recommend &lt;a href=&quot;https://www.jetbrains.com/idea/&quot;&gt;IntelliJ
                    IDEA&lt;/a&gt;.
                &lt;/li&gt;
                &lt;li&gt;&lt;a href=&quot;http://git-scm.com/&quot;&gt;Git&lt;/a&gt; installed.&lt;/li&gt;
                &lt;li&gt;&lt;a href=&quot;http://nodejs.org/&quot;&gt;Node.js&lt;/a&gt; and npm installed. I recommend using &lt;a
                    href=&quot;https://github.com/creationix/nvm&quot;&gt;nvm&lt;/a&gt;.
                &lt;/li&gt;
            &lt;/ul&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;sect1&quot;&gt;
    &lt;h3 id=&quot;get_the_tutorial_project&quot;&gt;Get the tutorial project&lt;/h3&gt;
    &lt;div class=&quot;sectionbody&quot;&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;Clone the &lt;a href=&quot;https://github.com/mraible/angular2-tutorial&quot;&gt;angular2-tutorial repository&lt;/a&gt; using
                git and install its dependencies.&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;listingblock&quot;&gt;
            &lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;git clone https://github.com/mraible/angular2-tutorial.git
cd angular2-tutorial
npm install&lt;/pre&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;If you haven&apos;t completed the &lt;a href=&quot;//raibledesigns.com/rd/entry/getting_started_with_angular_2&quot;&gt;Getting
                Started with Angular 2&lt;/a&gt; tutorial,
                you should peruse it so you understand how this application works.
                You can also simply start the app with &lt;code&gt;npm start&lt;/code&gt; and view it in your browser at &lt;a
                    href=&quot;http://localhost:5555/&quot; class=&quot;bare&quot;&gt;http://localhost:5555/&lt;/a&gt;.
            &lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;sect1&quot;&gt;
    &lt;h3 id=&quot;unit_test_the_searchservice&quot;&gt;Unit test the SearchService&lt;/h3&gt;
    &lt;div class=&quot;sectionbody&quot;&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;Create &lt;code&gt;src/shared/services/search.service.spec.ts&lt;/code&gt; and setup the test&apos;s infrastructure using
                &lt;a href=&quot;https://angular.io/docs/js/latest/api/http/testing/MockBackend-class.html&quot;&gt;MockBackend&lt;/a&gt;
                and &lt;a href=&quot;https://angular.io/docs/js/latest/api/http/BaseRequestOptions-class.html&quot;&gt;BaseRequestOptions&lt;/a&gt;.
            &lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;listingblock&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
import {it, describe, expect, inject, fakeAsync, beforeEachProviders, tick} from &apos;angular2/testing&apos;;
import {MockBackend} from &apos;angular2/http/testing&apos;;
import {provide} from &apos;angular2/core&apos;;
import &apos;rxjs/add/operator/map&apos;;
import {Http, ConnectionBackend, BaseRequestOptions, Response, ResponseOptions} from &apos;angular2/http&apos;;

import {SearchService} from &apos;./search.service&apos;;

export function main() {
  describe(&apos;Search Service&apos;, () =&gt; {
    beforeEachProviders(() =&gt; {
      return [BaseRequestOptions, MockBackend, SearchService,
        provide(Http, {
          useFactory: (backend:ConnectionBackend, defaultOptions:BaseRequestOptions) =&gt; {
            return new Http(backend, defaultOptions);
          }, deps: [MockBackend, BaseRequestOptions]
        }),
      ];
    });
  });
}
&lt;/pre&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;If you run &lt;code&gt;npm test&lt;/code&gt;, you&apos;ll get a failed build from a number of unused imports. You can fix
                those by adding the first test of &lt;code&gt;getAll()&lt;/code&gt;.
                This test shows how &lt;code&gt;MockBackend&lt;/code&gt; can be used to mock results and set the response.&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;When you are testing code that returns either a Promise or an RxJS Observable, you can use the &lt;code&gt;fakeAsync&lt;/code&gt;
                helper to test that code as
                if it were synchronous.
                Promises are be fulfilled and Observables are notified immediately after you call &lt;code&gt;tick()&lt;/code&gt;.
            &lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;The test below should be on the same level as &lt;code&gt;beforeEachProviders&lt;/code&gt;.&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;listingblock&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
it(&apos;should retrieve all search results&apos;,
  inject([SearchService, MockBackend], fakeAsync((searchService:SearchService, mockBackend:MockBackend) =&gt; {
    var res:Response;
    mockBackend.connections.subscribe(c =&gt; {
      expect(c.request.url).toBe(&apos;shared/data/people.json&apos;);
      let response = new ResponseOptions({body: &apos;[{&quot;name&quot;: &quot;John Elway&quot;}, {&quot;name&quot;: &quot;Gary Kubiak&quot;}]&apos;});
      c.mockRespond(new Response(response));
    });
    searchService.getAll().subscribe((response) =&gt; {
      res = response;
    });
    tick();
    expect(res[0].name).toBe(&apos;John Elway&apos;);
  }))
);
&lt;/pre&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;Running &lt;code&gt;npm test&lt;/code&gt; should result in &quot;10 tests completed&quot;. Add a couple more tests for
                filtering by search term and fetching by id.&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;listingblock&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
it(&apos;should filter by search term&apos;,
  inject([SearchService, MockBackend], fakeAsync((searchService:SearchService, mockBackend:MockBackend) =&gt; {
    var res:Response;
    mockBackend.connections.subscribe(c =&gt; {
      expect(c.request.url).toBe(&apos;shared/data/people.json&apos;);
      let response = new ResponseOptions({body: &apos;[{&quot;name&quot;: &quot;John Elway&quot;}, {&quot;name&quot;: &quot;Gary Kubiak&quot;}]&apos;});
      c.mockRespond(new Response(response));
    });
    searchService.search(&apos;john&apos;).subscribe((response) =&gt; {
      res = response;
    });
    tick();
    expect(res[0].name).toBe(&apos;John Elway&apos;);
  }))
);

it(&apos;should fetch by id&apos;,
  inject([SearchService, MockBackend], fakeAsync((searchService:SearchService, mockBackend:MockBackend) =&gt; {
    var res:Response;
    mockBackend.connections.subscribe(c =&gt; {
      expect(c.request.url).toBe(&apos;shared/data/people.json&apos;);
      let response = new ResponseOptions({body: &apos;[{&quot;id&quot;: 1, &quot;name&quot;: &quot;John Elway&quot;}, {&quot;id&quot;: 2, &quot;name&quot;: &quot;Gary Kubiak&quot;}]&apos;});
      c.mockRespond(new Response(response));
    });
    searchService.search(&apos;2&apos;).subscribe((response) =&gt; {
      res = response;
    });
    tick();
    expect(res[0].name).toBe(&apos;Gary Kubiak&apos;);
  }))
);
&lt;/pre&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;If you want to have tests continually run as you add them, you can run the following commands in separate
                shell windows.&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;listingblock&quot;&gt;
            &lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;npm run build.test.watch
npm run karma.start&lt;/pre&gt;
            &lt;/div&gt;
        &lt;/div&gt;

        &lt;p&gt;NOTE: See &lt;a
            href=&quot;https://www.jetbrains.com/help/idea/15.0/running-unit-tests-on-karma.html?origin=old_help&quot;&gt;Running
            Unit Tests on Karma&lt;/a&gt; to
            learn how to run your tests from IntelliJ IDEA.&lt;/p&gt;

    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;sect1&quot;&gt;
    &lt;h3 id=&quot;unit_test_the_searchcomponent&quot;&gt;Unit test the SearchComponent&lt;/h3&gt;
    &lt;div class=&quot;sectionbody&quot;&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;To unit test the &lt;code&gt;SearchComponent&lt;/code&gt;, create a &lt;code&gt;MockSearchProvider&lt;/code&gt; that has &lt;a
                href=&quot;http://angular-tips.com/blog/2014/03/introduction-to-unit-test-spies/&quot;&gt;spies&lt;/a&gt;.
                These allow you to &lt;em&gt;spy&lt;/em&gt; on functions to check if they were called.&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;Create &lt;code&gt;src/shared/services/mocks/search.service.ts&lt;/code&gt; and populate it with spies for each
                method, as well as methods to set the response and subscribe to results.&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;listingblock&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
import {provide} from &apos;angular2/core&apos;;
import {SpyObject} from &apos;angular2/testing_internal&apos;;

import {SearchService} from &apos;../search.service&apos;;
import Spy = jasmine.Spy;

export class MockSearchService extends SpyObject {
  getAllSpy:Spy;
  getByIdSpy:Spy;
  searchSpy:Spy;
  saveSpy:Spy;
  fakeResponse;

  constructor() {
    super(SearchService);

    this.fakeResponse = null;
    this.getAllSpy = this.spy(&apos;getAll&apos;).andReturn(this);
    this.getByIdSpy = this.spy(&apos;get&apos;).andReturn(this);
    this.searchSpy = this.spy(&apos;search&apos;).andReturn(this);
    this.saveSpy = this.spy(&apos;save&apos;).andReturn(this);
  }

  subscribe(callback) {
    callback(this.fakeResponse);
  }

  setResponse(json: any): void {
    this.fakeResponse = json;
  }

  getProviders(): Array&amp;lt;any&amp;gt; {
    return [provide(SearchService, {useValue: this})];
  }
}
&lt;/pre&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;In this same directory, create &lt;code&gt;routes.ts&lt;/code&gt; to mock Angular&apos;s &lt;code&gt;Router&lt;/code&gt;, &lt;code&gt;RouteParams&lt;/code&gt;
                and &lt;code&gt;RouterProvider&lt;/code&gt;.&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;listingblock&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
import {provide} from &apos;angular2/core&apos;;
import {
  ComponentInstruction,
  Router,
  RouteParams
} from &apos;angular2/router&apos;;
import {ResolvedInstruction} from &apos;angular2/src/router/instruction&apos;;
import {SpyObject} from &apos;angular2/testing_internal&apos;;

export class MockRouteParams extends SpyObject {
  private ROUTE_PARAMS = {};

  constructor() { super(RouteParams); }

  set(key: string, value: string) {
    this.ROUTE_PARAMS[key] = value;
  }

  get(key: string) {
    return this.ROUTE_PARAMS[key];
  }
}

export class MockRouter extends SpyObject {
  constructor() { super(Router); }
  isRouteActive(s) { return true; }
  generate(s) {
    return new ResolvedInstruction(new ComponentInstruction(&apos;detail&apos;, [], null, null, true, &apos;0&apos;), null, {});
  }
}

export class MockRouterProvider {
  mockRouter: MockRouter = new MockRouter();
  mockRouteParams: MockRouteParams = new MockRouteParams();

  setRouteParam(key: string, value: any) {
    this.mockRouteParams.set(key, value);
  }

  getProviders(): Array&amp;lt;any&gt; {
    return [
      provide(Router, {useValue: this.mockRouter}),
      provide(RouteParams, {useValue: this.mockRouteParams}),
    ];
  }
}
&lt;/pre&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;With mocks in place, you can create a spec for &lt;code&gt;SearchComponent&lt;/code&gt; that uses these as providers.
                Create a file at &lt;code&gt;src/search/components/search.component.spec.ts&lt;/code&gt; and populate it with the
                following code.
            &lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;listingblock&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
import {
  it,
  describe,
  expect,
  injectAsync,
  beforeEachProviders,
  TestComponentBuilder,
} from &apos;angular2/testing&apos;;

import {MockRouterProvider} from &apos;../../shared/services/mocks/routes&apos;;
import {MockSearchService} from &apos;../../shared/services/mocks/search.service&apos;;

import {SearchComponent} from &apos;./search.component&apos;;

export function main() {
  describe(&apos;Search component&apos;, () =&gt; {
    var mockSearchService:MockSearchService;
    var mockRouterProvider:MockRouterProvider;

    beforeEachProviders(() =&gt; {
      mockSearchService = new MockSearchService();
      mockRouterProvider = new MockRouterProvider();

      return [
        mockSearchService.getProviders(), mockRouterProvider.getProviders()
      ];
    });
  });
}
&lt;/pre&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;Add two tests, one to verify a search term is used when it&apos;s set on the component and a second to verify
                search is called when a term is passed in as a route
                parameter.&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;listingblock&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
it(&apos;should search when a term is set and search() is called&apos;, injectAsync([TestComponentBuilder], (tcb:TestComponentBuilder) =&gt; {
  return tcb.createAsync(SearchComponent).then((fixture) =&gt; {
    let searchComponent = fixture.debugElement.componentInstance;
    searchComponent.query = &apos;M&apos;;
    searchComponent.search();
    expect(mockSearchService.searchSpy).toHaveBeenCalledWith(&apos;M&apos;);
  });
}));

it(&apos;should search automatically when a term is on the URL&apos;, injectAsync([TestComponentBuilder], (tcb:TestComponentBuilder) =&gt; {
  mockRouterProvider.setRouteParam(&apos;term&apos;, &apos;peyton&apos;);
  return tcb.createAsync(SearchComponent).then((fixture) =&gt; {
    fixture.detectChanges();
    expect(mockSearchService.searchSpy).toHaveBeenCalledWith(&apos;peyton&apos;);
  });
}));
&lt;/pre&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;Add a spec for the &lt;code&gt;EditComponent&lt;/code&gt; as well, verifying fetching a single record works. Notice
                how you can access the component directly with
                &lt;code&gt;fixture.debugElement.componentInstance&lt;/code&gt;, or its rendered version with &lt;code&gt;fixture.debugElement.nativeElement&lt;/code&gt;.
                Create a file at &lt;code&gt;src/search/components/edit.component.spec.ts&lt;/code&gt; and populate it with the code
                below.
            &lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;listingblock&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
import {
  it,
  describe,
  expect,
  injectAsync,
  beforeEachProviders,
  TestComponentBuilder,
} from &apos;angular2/testing&apos;;

import {MockRouterProvider} from &apos;../../shared/services/mocks/routes&apos;;
import {MockSearchService} from &apos;../../shared/services/mocks/search.service&apos;;

import {EditComponent} from &apos;./edit.component&apos;;

export function main() {
  describe(&apos;Edit component&apos;, () =&gt; {
    var mockSearchService:MockSearchService;
    var mockRouterProvider:MockRouterProvider;

    beforeEachProviders(() =&gt; {
      mockSearchService = new MockSearchService();
      mockRouterProvider = new MockRouterProvider();

      return [
        mockSearchService.getProviders(), mockRouterProvider.getProviders()
      ];
    });

    it(&apos;should fetch a single record&apos;, injectAsync([TestComponentBuilder], (tcb:TestComponentBuilder) =&gt; {
      mockRouterProvider.setRouteParam(&apos;id&apos;, &apos;1&apos;);
      return tcb.createAsync(EditComponent).then((fixture) =&gt; {
        let person = {name: &apos;Emmanuel Sanders&apos;, address: {city: &apos;Denver&apos;}};
        mockSearchService.setResponse(person);

        fixture.detectChanges();
        // verify service was called
        expect(mockSearchService.getByIdSpy).toHaveBeenCalledWith(1);

        // verify data was set on component when initialized
        let editComponent = fixture.debugElement.componentInstance;
        expect(editComponent.editAddress.city).toBe(&apos;Denver&apos;);

        // verify HTML renders as expected
        var compiled = fixture.debugElement.nativeElement;
        expect(compiled.querySelector(&apos;h3&apos;)).toHaveText(&apos;Emmanuel Sanders&apos;);
      });
    }));
  });
}
&lt;/pre&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;You should see &quot;&lt;span style=&quot;color: green&quot;&gt;? 20 tests completed&lt;/span&gt;&quot; in the shell window that&apos;s
                running &lt;code&gt;npm run karma.start&lt;/code&gt;. If you don&apos;t, try cancelling the command and restarting.&lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;sect1&quot;&gt;
    &lt;h3 id=&quot;integration_test_the_search_ui&quot;&gt;Integration test the search UI&lt;/h3&gt;
    &lt;div class=&quot;sectionbody&quot;&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;To test if the application works end-to-end, you can write tests with &lt;a
                href=&quot;http://angular.github.io/protractor&quot;&gt;Protractor&lt;/a&gt;. These are also known as integration tests,
                since they test the &lt;em&gt;integration&lt;/em&gt; between all layers of your application.&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;To verify end-to-end tests work in the project before you begin, run the following commands in three
                different console windows.&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;listingblock&quot;&gt;
            &lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;# npm run webdriver-update &amp;lt;- You will need to run this the first time
npm run webdriver-start
npm run serve.e2e
npm run e2e&lt;/pre&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;You should receive an error stating that the &quot;nav text for About&quot; is incorrect.&lt;/p&gt;
        &lt;/div&gt;
        &lt;div id=&quot;protractor-nav-error&quot; class=&quot;imageblock&quot;&gt;
            &lt;div style=&quot;text-align: center&quot;&gt;
                &lt;a href=&quot;https://farm2.staticflickr.com/1614/25706918470_49c4b4fd22_c.jpg&quot;
                   title=&quot;Protractor nav test error&quot; rel=&quot;lightbox[testing-angular2-applications]&quot;
                   data-href=&quot;https://www.flickr.com/photos/mraible/25706918470/in/album-72157666324227025/&quot;&gt;&lt;img
                    src=&quot;https://farm2.staticflickr.com/1614/25706918470_49c4b4fd22_z.jpg&quot; width=&quot;640&quot;
                    alt=&quot;Protractor nav test error&quot;&gt;&lt;/a&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;This happens because we added a Search link to the navbar and didn&apos;t update the test (in &lt;code&gt;app.component.e2e.ts&lt;/code&gt;)
                that looks for the last child. &lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;listingblock&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
it(&apos;should have correct nav text for About&apos;, () =&gt; {
    expect(element(by.css(&apos;sd-app sd-navbar nav a:last-child&apos;)).getText()).toEqual(&apos;ABOUT&apos;);
});
&lt;/pre&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;Replace this test with the one below, and add a new one to verify the Search link is last.&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;listingblock&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
it(&apos;should have correct nav text for About&apos;, () =&gt; {
  expect(element(by.css(&apos;sd-app sd-navbar nav a:nth-child(2)&apos;)).getText()).toEqual(&apos;ABOUT&apos;);
});

it(&apos;should have correct nav text for Search&apos;, () =&gt; {
  expect(element(by.css(&apos;sd-app sd-navbar nav a:last-child&apos;)).getText()).toEqual(&apos;SEARCH&apos;);
});
&lt;/pre&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;Now when you run &lt;code&gt;npm run e2e&lt;/code&gt;, all specs should pass.&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;sect2&quot;&gt;
            &lt;h3 id=&quot;_testing_the_search_feature&quot;&gt;Testing the search feature&lt;/h3&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Create a new &lt;code&gt;search.component.e2e.ts&lt;/code&gt; spec in the same directory as your &lt;code&gt;SearchComponent&lt;/code&gt;.
                    Add tests to verify elements are rendered correctly and
                    search works. At the time of this writing, Protractor&apos;s &lt;code&gt;by.model&lt;/code&gt; and
                    &lt;code&gt;by.repeater&lt;/code&gt; don&apos;t work with Angular 2. For this reason, I used &lt;code&gt;by.css&lt;/code&gt; to
                    verify the HTML renders as expected.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
describe(&apos;Search&apos;, () =&gt; {

  beforeEach(() =&gt; {
    browser.get(&apos;/search&apos;);
  });

  it(&apos;should have an input and search button&apos;, () =&gt; {
    expect(element(by.css(&apos;sd-app sd-search form input&apos;)).isPresent()).toEqual(true);
    expect(element(by.css(&apos;sd-app sd-search form button&apos;)).isPresent()).toEqual(true);
  });

  it(&apos;should allow searching&apos;, () =&gt; {
    let searchButton = element(by.css(&apos;button&apos;));
    let searchBox = element(by.css(&apos;input&apos;));
    searchBox.sendKeys(&apos;M&apos;);
    searchButton.click().then(() =&gt; {
      // doesn&apos;t work as expected - results in 0
      //expect(element.all(by.repeater(&apos;person of searchResults&apos;)).count()).toEqual(3);
      var list = element.all(by.css(&apos;sd-search table tbody tr&apos;));
      expect(list.count()).toBe(3);
    });
  });
});
&lt;/pre&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;sect2&quot;&gt;
            &lt;h3 id=&quot;_testing_the_edit_feature&quot;&gt;Testing the edit feature&lt;/h3&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Create a &lt;code&gt;edit.component.e2e.ts&lt;/code&gt; spec to verify the &lt;code&gt;EditComponent&lt;/code&gt; renders a
                    person&apos;s information and that you can update their information.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
describe(&apos;Edit&apos;, () =&gt; {
 
  beforeEach(() =&gt; {
    browser.get(&apos;/edit/1&apos;);
  });
 
  let name = element(by.id(&apos;name&apos;));
  let street = element(by.id(&apos;street&apos;));
  let city = element(by.id(&apos;city&apos;));
 
  it(&apos;should allow viewing a person&apos;, () =&gt; {
    expect(element(by.css(&apos;h3&apos;)).getText()).toEqual(&apos;Peyton Manning&apos;);
    expect(name.getAttribute(&apos;value&apos;)).toEqual(&apos;Peyton Manning&apos;);
    expect(street.getAttribute(&apos;value&apos;)).toEqual(&apos;1234 Main Street&apos;);
    expect(city.getAttribute(&apos;value&apos;)).toEqual(&apos;Greenwood Village&apos;);
  });
 
  it(&apos;should allow updating a name&apos;, function () {
    let save = element(by.id(&apos;save&apos;));
    // send individual characters since sendKeys passes partial values sometimes
    // https://github.com/angular/protractor/issues/698
    &apos; Won!&apos;.split(&apos;&apos;).forEach((c) =&gt; name.sendKeys(c));
    save.click();
    // verify one element matched this change
    var list = element.all(by.css(&apos;sd-search table tbody tr&apos;));
    expect(list.count()).toBe(1);
  });
});
&lt;/pre&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Run &lt;code&gt;npm run e2e&lt;/code&gt; to verify all your end-to-end tests pass. You should see a success
                    message similar to the one below in your terminal window.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div id=&quot;protractor-success&quot; class=&quot;imageblock&quot;&gt;
                &lt;div style=&quot;text-align: center&quot;&gt;
                    &lt;a href=&quot;https://farm2.staticflickr.com/1558/25706917370_e88e4a6d0f_c.jpg&quot;
                       title=&quot;Protractor success&quot; rel=&quot;lightbox[testing-angular2-applications]&quot;
                       data-href=&quot;https://www.flickr.com/photos/mraible/25706917370/in/album-72157666324227025/&quot;&gt;&lt;img
                        src=&quot;https://farm2.staticflickr.com/1558/25706917370_e88e4a6d0f_z.jpg&quot; width=&quot;640&quot;
                        alt=&quot;Protractor success&quot;&gt;&lt;/a&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;If you made it this far and have all 13 specs passing - congratulations!
                    You&apos;re well on your way to writing quality code with Angular 2 and verifying it works.&lt;/p&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;sect1&quot;&gt;
    &lt;h3 id=&quot;source_code&quot;&gt;Source code&lt;/h3&gt;
    &lt;div class=&quot;sectionbody&quot;&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;A completed project with this code in it is available on GitHub at &lt;a
                href=&quot;https://github.com/mraible/angular2-tutorial&quot;&gt;https://github.com/mraible/angular2-tutorial&lt;/a&gt;.
                If you have ideas for improvements, please leave a comment or send a pull request.
            &lt;/p&gt;
            &lt;p&gt;
                I originally wrote this tutorial in &lt;a href=&quot;http://asciidoctor.org/&quot;&gt;Asciidoctor&lt;/a&gt; because it has a
                slick feature where you
                can include the source code from files rather than copying and pasting. Unfortunately, GitHub
                &lt;a href=&quot;https://github.com/github/markup/issues/172&quot;&gt;doesn&apos;t support includes&lt;/a&gt;. You can
                &lt;a href=&quot;http://gist.asciidoctor.org/?github-mraible%2Fangular2-tutorial%2F%2FTESTING.adoc&quot;&gt;use DocGist
                    to view this tutorial&lt;/a&gt;,
                but &lt;a href=&quot;https://github.com/asciidoctor/docgist/issues/11&quot;&gt;includes don&apos;t work&lt;/a&gt; there either.
            &lt;/p&gt;
            &lt;p&gt;If you&apos;d like to see the Asciidoctor-generated version of this tutorial, you can install the gem,
                checkout the project from GitHub,
                and then run &lt;code&gt;asciidoctor TESTING.adoc&lt;/code&gt; to produce a &lt;code&gt;TESTING.html&lt;/code&gt; file.
            &lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;sect1&quot;&gt;
    &lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
    &lt;div class=&quot;sectionbody&quot;&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;I hope you&apos;ve enjoyed this quick-and-easy tutorial on testing Angular 2 applications. You can see the
                test coverage of your project by running
                &lt;code&gt;npm run serve.coverage&lt;/code&gt;. You&apos;ll notice that the new components and service could use some
                additional coverage. I&apos;ll leave that as a task
                for the reader.&lt;/p&gt;
        &lt;/div&gt;
        &lt;div id=&quot;test-coverage&quot; class=&quot;imageblock&quot;&gt;
            &lt;div style=&quot;text-align: center&quot;&gt;
                &lt;a href=&quot;https://farm2.staticflickr.com/1547/25706915830_f764f63717_c.jpg&quot; title=&quot;Test Coverage&quot;
                   rel=&quot;lightbox[testing-angular2-applications]&quot;
                   data-href=&quot;https://www.flickr.com/photos/mraible/25706915830/in/album-72157666324227025/&quot;&gt;&lt;img
                    src=&quot;https://farm2.staticflickr.com/1547/25706915830_f764f63717_z.jpg&quot; width=&quot;640&quot;
                    alt=&quot;Test Coverage&quot;&gt;&lt;/a&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;I learned a lot about testing from &lt;a href=&quot;https://www.ng-book.com/2/&quot;&gt;ng-book 2&lt;/a&gt; and
                its Testing chapter. If you have any Angular 2 testing tips and tricks you&apos;d like to share, I&apos;d love to
                hear about them.&lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
</description>          </item>
    <item>
    <guid isPermaLink="true">https://raibledesigns.com/rd/entry/getting_started_with_angular_2</guid>
    <title>Getting Started with Angular 2</title>
    <dc:creator>Matt Raible</dc:creator>
    <link>https://raibledesigns.com/rd/entry/getting_started_with_angular_2</link>
        <pubDate>Wed, 23 Mar 2016 09:23:53 -0600</pubDate>
    <category>The Web</category>
    <category>javascript</category>
    <category>angular2</category>
    <category>typescript</category>
    <category>node</category>
    <category>git</category>
    <category>asciidoctor</category>
    <category>npm</category>
    <atom:summary type="html">&lt;p class=&quot;alert alert-info&quot;&gt;&lt;a href=&quot;//raibledesigns.com/rd/entry/getting_started_with_angular_2_rc1&quot;&gt;Click here&lt;/a&gt; to see an updated version of this tutorial that&apos;s been upgraded for Angular 2.0 RC1.&lt;/p&gt;
&lt;p&gt;I was hired by one of my current clients in November to help them develop a project management
    application with AngularJS. I&apos;m proud to say we&apos;ve built the application, it looks great, and it&apos;s
    scheduled to be released next month. The team had lots of experience with ExtJS, but was new to AngularJS.
    While using AngularJS worked, they&apos;re keen on moving to Angular 2 shortly after it&apos;s released.
&lt;/p&gt;
&lt;p&gt;
    To help them learn Angular 2, I decided to write a couple tutorials similar to the AngularJS tutorials
    I wrote last year. In this tutorial, I did my best to keep the functionality and features similar
    to &lt;a href=&quot;http://raibledesigns.com/rd/entry/getting_started_with_angularjs&quot;&gt;Getting Started with AngularJS&lt;/a&gt;
    so you can compare &lt;a href=&quot;https://github.com/mraible/angular-tutorial&quot;&gt;the code&lt;/a&gt; between the two.
&lt;/p&gt;
&lt;div class=&quot;sect1&quot;&gt;
    &lt;h3 id=&quot;what_you_ll_build&quot;&gt;What you&apos;ll build&lt;/h3&gt;
    &lt;div class=&quot;sectionbody&quot;&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;You&apos;ll build a simple web application with Angular 2 and TypeScript. You&apos;ll add search and edit features with mock data.&lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
    &lt;h3 id=&quot;what_you_ll_need&quot;&gt;What you&apos;ll need&lt;/h3&gt;
    &lt;div class=&quot;sectionbody&quot;&gt;
        &lt;div class=&quot;ulist&quot;&gt;
            &lt;ul&gt;
                &lt;li&gt;About 15-30 minutes.
                &lt;/li&gt;
                &lt;li&gt;A favorite text editor or IDE. I recommend &lt;a href=&quot;https://www.jetbrains.com/idea/&quot;&gt;IntelliJ IDEA&lt;/a&gt;.
                &lt;/li&gt;
                &lt;li&gt;&lt;a href=&quot;http://git-scm.com/&quot;&gt;Git&lt;/a&gt; installed.
                &lt;/li&gt;
                &lt;li&gt;&lt;a href=&quot;http://nodejs.org/&quot;&gt;Node.js&lt;/a&gt; and npm installed. I recommend using &lt;a href=&quot;https://github.com/creationix/nvm&quot;&gt;nvm&lt;/a&gt;.
                &lt;/li&gt;
            &lt;/ul&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;h3 id=&quot;create_your_project&quot;&gt;Create your project&lt;/h3&gt;
&lt;p&gt;Clone the &lt;a href=&quot;https://github.com/mgechev/angular2-seed&quot;&gt;angular2-seed&lt;/a&gt; repository using git:&lt;/p&gt;
&lt;pre&gt;git clone https://github.com/mgechev/angular2-seed.git angular2-tutorial
cd angular2-tutorial&lt;/pre&gt;</atom:summary>        <description>&lt;p class=&quot;alert alert-info&quot;&gt;&lt;a href=&quot;//raibledesigns.com/rd/entry/getting_started_with_angular_2_rc1&quot;&gt;Click here&lt;/a&gt; to see an updated version of this tutorial that&apos;s been upgraded for Angular 2.0 RC1.&lt;/p&gt;
&lt;p&gt;I was hired by one of my current clients in November to help them develop a project management
    application with AngularJS. I&apos;m proud to say we&apos;ve built the application, it looks great, and it&apos;s
    scheduled to be released next month. The team had lots of experience with ExtJS, but was new to AngularJS.
    While using AngularJS worked, they&apos;re keen on moving to Angular 2 shortly after it&apos;s released.
&lt;/p&gt;
&lt;p&gt;
    To help them learn Angular 2, I decided to write a couple tutorials similar to the AngularJS tutorials
    I wrote last year. In this tutorial, I did my best to keep the functionality and features similar
    to &lt;a href=&quot;http://raibledesigns.com/rd/entry/getting_started_with_angularjs&quot;&gt;Getting Started with AngularJS&lt;/a&gt;
    so you can compare &lt;a href=&quot;https://github.com/mraible/angular-tutorial&quot;&gt;the code&lt;/a&gt; between the two.
&lt;/p&gt;
&lt;div class=&quot;sect1&quot;&gt;
    &lt;h3 id=&quot;what_you_ll_build&quot;&gt;What you&apos;ll build&lt;/h3&gt;
    &lt;div class=&quot;sectionbody&quot;&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;You&apos;ll build a simple web application with Angular 2 and TypeScript. You&apos;ll add search and edit features with mock data.&lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
    &lt;h3 id=&quot;what_you_ll_need&quot;&gt;What you&apos;ll need&lt;/h3&gt;
    &lt;div class=&quot;sectionbody&quot;&gt;
        &lt;div class=&quot;ulist&quot;&gt;
            &lt;ul&gt;
                &lt;li&gt;About 15-30 minutes.
                &lt;/li&gt;
                &lt;li&gt;A favorite text editor or IDE. I recommend &lt;a href=&quot;https://www.jetbrains.com/idea/&quot;&gt;IntelliJ IDEA&lt;/a&gt;.
                &lt;/li&gt;
                &lt;li&gt;&lt;a href=&quot;http://git-scm.com/&quot;&gt;Git&lt;/a&gt; installed.
                &lt;/li&gt;
                &lt;li&gt;&lt;a href=&quot;http://nodejs.org/&quot;&gt;Node.js&lt;/a&gt; and npm installed. I recommend using &lt;a href=&quot;https://github.com/creationix/nvm&quot;&gt;nvm&lt;/a&gt;.
                &lt;/li&gt;
            &lt;/ul&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
    &lt;h3 id=&quot;create_your_project&quot;&gt;Create your project&lt;/h3&gt;
    &lt;div class=&quot;sectionbody&quot;&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;Clone the &lt;a href=&quot;https://github.com/mgechev/angular2-seed&quot;&gt;angular2-seed&lt;/a&gt; repository using git:&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;listingblock&quot;&gt;
            &lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;git clone https://github.com/mgechev/angular2-seed.git angular2-tutorial
cd angular2-tutorial&lt;/pre&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;p&gt;
            NOTE: The angular2-seed project requires node v4.x.x or higher and npm 2.14.7. I used node v4.2.6 and npm 3.6.0.
        &lt;/p&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;Install &lt;code&gt;ts-node&lt;/code&gt; for TypeScript:&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;listingblock&quot;&gt;
            &lt;div class=&quot;content&quot;&gt;
                &lt;pre&gt;npm install -g ts-node&lt;/pre&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;Install the project&apos;s dependencies:&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;listingblock&quot;&gt;
            &lt;div class=&quot;content&quot;&gt;
                &lt;pre&gt;npm install&lt;/pre&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
    &lt;h3 id=&quot;run_the_application&quot;&gt;Run the application&lt;/h3&gt;
    &lt;div class=&quot;sectionbody&quot;&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;The project is configured with a simple web server for development. To start it, run:&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;listingblock&quot;&gt;
            &lt;div class=&quot;content&quot;&gt;
                &lt;pre&gt;npm start&lt;/pre&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;You should see a screen like the one below at &lt;a href=&quot;http://localhost:5555&quot;&gt;http://localhost:5555&lt;/a&gt;.&lt;/p&gt;
        &lt;/div&gt;
        &lt;div id=&quot;default-homepage&quot; class=&quot;imageblock&quot;&gt;
            &lt;div style=&quot;text-align: center&quot;&gt;
                &lt;a href=&quot;https://farm2.staticflickr.com/1560/25886693062_4dd41acd3d_c.jpg&quot; title=&quot;Default homepage&quot; rel=&quot;lightbox[getting-started-with-angular2]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/25886693062/in/datetaken-public/&quot;&gt;&lt;img src=&quot;https://farm2.staticflickr.com/1560/25886693062_4dd41acd3d_z.jpg&quot; width=&quot;640&quot; alt=&quot;Default homepage&quot;&gt;&lt;/a&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;You can see your new project&apos;s test coverage by running &lt;code&gt;npm test&lt;/code&gt;:&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;listingblock&quot;&gt;
            &lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;=============================== Coverage summary ===============================
Statements : 86.11% ( 93/108 )
Branches : 48.28% ( 70/145 )
Functions : 100% ( 25/25 )
Lines : 94.32% ( 83/88 )
================================================================================&lt;/pre&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
    &lt;h3 id=&quot;add_a_search_feature&quot;&gt;Add a search feature&lt;/h3&gt;
    &lt;div class=&quot;sectionbody&quot;&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;To add a search feature, open the project in an IDE or your favorite text editor. For IntelliJ IDEA, use
                File &amp;gt; New Project &amp;gt; Static Web and point to the directory you cloned angular2-seed to.&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;sect2&quot;&gt;
            &lt;h3 id=&quot;the_basics&quot;&gt;The Basics&lt;/h3&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Create a file at &lt;code&gt;src/search/components/search.component.html&lt;/code&gt; with the following HTML:&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: xml&quot;&gt;&amp;lt;h2&amp;gt;Search&amp;lt;/h2&amp;gt;
&amp;lt;form&amp;gt;
  &amp;lt;input type=&quot;search&quot; [(ngModel)]=&quot;query&quot; (keyup.enter)=&quot;search()&quot;&amp;gt;
  &amp;lt;button type=&quot;button&quot; (click)=&quot;search()&quot;&amp;gt;Search&amp;lt;/button&amp;gt;
&amp;lt;/form&amp;gt;
&amp;lt;div *ngIf=&quot;loading&quot;&amp;gt;loading...&amp;lt;/div&amp;gt;
&amp;lt;pre&amp;gt;{{searchResults | json}}&amp;lt;/pre&amp;gt;
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Create &lt;code&gt;src/search/components/search.component.ts&lt;/code&gt; to define the &lt;code&gt;SearchComponent&lt;/code&gt; and point to this template.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;import {Component} from &apos;angular2/core&apos;;
import {CORE_DIRECTIVES, FORM_DIRECTIVES} from &apos;angular2/common&apos;;
import {ROUTER_DIRECTIVES} from &apos;angular2/router&apos;;

@Component({
  selector: &apos;sd-search&apos;,
  moduleId: module.id,
  templateUrl: &apos;./search.component.html&apos;,
  directives: [FORM_DIRECTIVES, CORE_DIRECTIVES, ROUTER_DIRECTIVES]
})
export class SearchComponent {
  loading: boolean;
  query: string;
  searchResults: any;

  constructor() {
    console.log(&apos;initialized search component&apos;);
  }
}
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Update &lt;code&gt;src/app/components/app.component.ts&lt;/code&gt; to import this component and include its route.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;import {SearchComponent} from &apos;../../search/components/search.component&apos;;

@RouteConfig([
  { path: &apos;/&apos;,      name: &apos;Home&apos;,  component: HomeComponent  },
  { path: &apos;/about&apos;, name: &apos;About&apos;, component: AboutComponent },
  { path: &apos;/search&apos;, name: &apos;Search&apos;, component: SearchComponent }
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Your browser should refresh automatically, thanks to &lt;a href=&quot;http://browsersync.io&quot;&gt;Browsersync&lt;/a&gt;. Navigate to
                    &lt;a href=&quot;http://localhost:5555/search&quot;&gt;http://localhost:5555/search&lt;/a&gt; and you should see the search component.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div id=&quot;search-component&quot; class=&quot;imageblock&quot;&gt;
                &lt;div style=&quot;text-align: center&quot;&gt;
                    &lt;a href=&quot;https://farm2.staticflickr.com/1552/25374766404_b0830ff897_c.jpg&quot; title=&quot;Search component&quot; rel=&quot;lightbox[getting-started-with-angular2]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/25374766404/in/datetaken-public/&quot;&gt;&lt;img src=&quot;https://farm2.staticflickr.com/1552/25374766404_b0830ff897_z.jpg&quot; width=&quot;640&quot; alt=&quot;Search component&quot;&gt;&lt;/a&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;You can see it needs a bit of styling. Angular 2 allows you to provide styles specific for your component using a &lt;code&gt;styleUrls&lt;/code&gt;
                    property on your component.
                    Add this property to &lt;code&gt;search.component.ts&lt;/code&gt; you see below.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;templateUrl: &apos;./search.component.html&apos;,
styleUrls: [&apos;./search.component.css&apos;],
directives: [FORM_DIRECTIVES, CORE_DIRECTIVES, ROUTER_DIRECTIVES]
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Create &lt;code&gt;src/search/components/search.component.css&lt;/code&gt; and add some CSS.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: css&quot;&gt;:host {
  display: block;
  padding: 0 16px;
}
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;There, that looks better!&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;imageblock&quot;&gt;
                &lt;div style=&quot;text-align: center&quot;&gt;
                    &lt;a href=&quot;https://farm2.staticflickr.com/1563/25886691122_c209b90e33_c.jpg&quot; title=&quot;Search component with styling&quot; rel=&quot;lightbox[getting-started-with-angular2]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/25886691122/in/datetaken-public/&quot;&gt;&lt;img src=&quot;https://farm2.staticflickr.com/1563/25886691122_c209b90e33_z.jpg&quot; width=&quot;640&quot; alt=&quot;Search component with styling&quot;&gt;&lt;/a&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Finally, update &lt;code&gt;src/app/components/navbar.component.html&lt;/code&gt; to include a link to the search route.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: xml&quot;&gt;&amp;lt;nav&amp;gt;
  &amp;lt;a [routerLink]=&quot;[&apos;Home&apos;]&quot;&amp;gt;HOME&amp;lt;/a&amp;gt;
  &amp;lt;a [routerLink]=&quot;[&apos;About&apos;]&quot;&amp;gt;ABOUT&amp;lt;/a&amp;gt;
  &amp;lt;a [routerLink]=&quot;[&apos;Search&apos;]&quot;&amp;gt;SEARCH&amp;lt;/a&amp;gt;
&amp;lt;/nav&amp;gt;
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;This section has shown you how to add a new component to a basic Angular 2 application.
                    The next section shows you how to create a use a JSON file and &lt;code&gt;localStorage&lt;/code&gt; to create a fake API.&lt;/p&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;sect2&quot;&gt;
            &lt;h3 id=&quot;the_backend&quot;&gt;The Backend&lt;/h3&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;To get search results, create a &lt;code&gt;SearchService&lt;/code&gt; that makes HTTP requests to a JSON file. Start
                    by creating &lt;code&gt;src/shared/data/people.json&lt;/code&gt; to hold your data.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;[
  {
    &quot;id&quot;: 1,
    &quot;name&quot;: &quot;Peyton Manning&quot;,
    &quot;phone&quot;: &quot;(303) 567-8910&quot;,
    &quot;address&quot;: {
      &quot;street&quot;: &quot;1234 Main Street&quot;,
      &quot;city&quot;: &quot;Greenwood Village&quot;,
      &quot;state&quot;: &quot;CO&quot;,
      &quot;zip&quot;: &quot;80111&quot;
    }
  },
  {
    &quot;id&quot;: 2,
    &quot;name&quot;: &quot;Demaryius Thomas&quot;,
    &quot;phone&quot;: &quot;(720) 213-9876&quot;,
    &quot;address&quot;: {
      &quot;street&quot;: &quot;5555 Marion Street&quot;,
      &quot;city&quot;: &quot;Denver&quot;,
      &quot;state&quot;: &quot;CO&quot;,
      &quot;zip&quot;: &quot;80202&quot;
    }
  },
  {
    &quot;id&quot;: 3,
    &quot;name&quot;: &quot;Von Miller&quot;,
    &quot;phone&quot;: &quot;(917) 323-2333&quot;,
    &quot;address&quot;: {
      &quot;street&quot;: &quot;14 Mountain Way&quot;,
      &quot;city&quot;: &quot;Vail&quot;,
      &quot;state&quot;: &quot;CO&quot;,
      &quot;zip&quot;: &quot;81657&quot;
    }
  }
]
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Create &lt;code&gt;src/shared/services/search.service.ts&lt;/code&gt; and provide &lt;code&gt;Http&lt;/code&gt; as a dependency in its constructor.
                    In this same file, define the &lt;code&gt;Address&lt;/code&gt; and &lt;code&gt;Person&lt;/code&gt; classes that JSON will be marshalled to.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;import {Injectable} from &apos;angular2/core&apos;;
import {Http, Response} from &apos;angular2/http&apos;;

@Injectable()
export class SearchService {
  constructor(private http:Http) {}

  getAll() {
    return this.http.get(&apos;shared/data/people.json&apos;).map((res:Response) =&amp;gt; res.json());
  }
}

export class Address {
  street:string;
  city:string;
  state:string;
  zip:string;

  constructor(obj?:any) {
    this.street = obj &amp;amp;&amp;amp; obj.street || null;
    this.city = obj &amp;amp;&amp;amp; obj.city || null;
    this.state = obj &amp;amp;&amp;amp; obj.state || null;
    this.zip = obj &amp;amp;&amp;amp; obj.zip || null;
  }
}

export class Person {
  id:number;
  name:string;
  phone:string;
  address:Address;

  constructor(obj?:any) {
    this.id = obj &amp;amp;&amp;amp; Number(obj.id) || null;
    this.name = obj &amp;amp;&amp;amp; obj.name || null;
    this.phone = obj &amp;amp;&amp;amp; obj.phone || null;
    this.address = obj &amp;amp;&amp;amp; obj.address || null;
  }
}
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;In &lt;code&gt;search.component.ts&lt;/code&gt;, add imports for these classes.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;import {Person, SearchService} from &apos;../../shared/services/search.service&apos;;
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;You can now add a type to the searchResults variable. While you&apos;re there, modify the constructor to inject the
                    &lt;code&gt;SearchService&lt;/code&gt;.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;searchResults: Array&amp;lt;Person&amp;gt;;

constructor(public searchService: SearchService) {}
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Then implement the &lt;code&gt;search()&lt;/code&gt; method to call the service&apos;s &lt;code&gt;getAll()&lt;/code&gt; method.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;search(): void {
  this.searchService.getAll().subscribe(
    data =&amp;gt; {this.searchResults = data;},
    error =&amp;gt; console.log(error)
  );
}
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;At this point, you&apos;ll likely see the following message in your browser&apos;s console.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
                    &lt;pre&gt;EXCEPTION: No provider for SearchService! (SearchComponent -&amp;gt; SearchService)&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;This happens because the app hasn&apos;t provided this service to components. To fix this, modify
                    &lt;code&gt;app.component.ts&lt;/code&gt; to import this component and add the service to the list of providers.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;import {NameListService} from &apos;../../shared/services/name-list.service&apos;;
import {SearchService} from &apos;../../shared/services/search.service&apos;;

@Component({
  selector: &apos;sd-app&apos;,
  viewProviders: [NameListService, SearchService],
  moduleId: module.id,
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Next, you&apos;ll likely get an error about the &lt;code&gt;Http&lt;/code&gt; dependency in &lt;code&gt;SearchService&lt;/code&gt;.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
                    &lt;pre&gt;EXCEPTION: No provider for Http! (SearchComponent -&amp;gt; SearchService -&amp;gt; Http)&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;To solve this problem, modify &lt;code&gt;src/main.ts&lt;/code&gt; to import the &lt;code&gt;Http&lt;/code&gt; service and make it
                    available to the app.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;import {HTTP_PROVIDERS} from &apos;angular2/http&apos;;

bootstrap(AppComponent, [
  HTTP_PROVIDERS, ROUTER_PROVIDERS,
  provide(APP_BASE_HREF, { useValue: &apos;&amp;lt;%= APP_BASE %&amp;gt;&apos; })
]);
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Now the page will load without errors. However, when you click on the button, you&apos;ll see the following error.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
                    &lt;pre&gt;ORIGINAL EXCEPTION: TypeError: this.http.get(...).map is not a function&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;I was stuck here for quite some time when I first encountered this issue. I was able to solve it
                    with a simple import in &lt;code&gt;main.ts&lt;/code&gt;.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
                    &lt;pre class=&quot;brush: js&quot;&gt;import &apos;rxjs/add/operator/map&apos;;&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Now clicking the search button should work. To make the results look better,
                    remove the &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; tag in &lt;code&gt;search.component.html&lt;/code&gt;
                    and replace it with a &lt;code&gt;&amp;lt;table&amp;gt;&lt;/code&gt;.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: xml&quot;&gt;&amp;lt;table *ngIf=&quot;searchResults&quot;&amp;gt;
  &amp;lt;thead&amp;gt;
  &amp;lt;tr&amp;gt;
    &amp;lt;th&amp;gt;Name&amp;lt;/th&amp;gt;
    &amp;lt;th&amp;gt;Phone&amp;lt;/th&amp;gt;
    &amp;lt;th&amp;gt;Address&amp;lt;/th&amp;gt;
  &amp;lt;/tr&amp;gt;
  &amp;lt;/thead&amp;gt;
  &amp;lt;tbody&amp;gt;
  &amp;lt;tr *ngFor=&quot;#person of searchResults; #i=index&quot;&amp;gt;
    &amp;lt;td&amp;gt;{{person.name}}&amp;lt;/td&amp;gt;
    &amp;lt;td&amp;gt;{{person.phone}}&amp;lt;/td&amp;gt;
    &amp;lt;td&amp;gt;{{person.address.street}}&amp;lt;br/&amp;gt;
      {{person.address.city}}, {{person.address.state}} {{person.address.zip}}
    &amp;lt;/td&amp;gt;
  &amp;lt;/tr&amp;gt;
  &amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Then add some additional CSS for this component in &lt;code&gt;search.component.css&lt;/code&gt;.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: css&quot;&gt;table {
  margin-top: 10px;
  border-collapse: collapse;
}

th {
  text-align: left;
  border-bottom: 2px solid #ddd;
  padding: 8px;
}

td {
  border-top: 1px solid #ddd;
  padding: 8px;
}
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Now the search results look better.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div id=&quot;search-results&quot; class=&quot;imageblock&quot;&gt;
                &lt;div style=&quot;text-align: center&quot;&gt;
                    &lt;a href=&quot;https://farm2.staticflickr.com/1677/25706917270_bf07c541e4_c.jpg&quot; title=&quot;Search results&quot; rel=&quot;lightbox[getting-started-with-angular2]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/25706917270/in/datetaken-public/&quot;&gt;&lt;img src=&quot;https://farm2.staticflickr.com/1677/25706917270_bf07c541e4_z.jpg&quot; width=&quot;640&quot; alt=&quot;Search results&quot;&gt;&lt;/a&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;But wait, we still don&apos;t have search functionality! To add a search feature, add a &lt;code&gt;search()&lt;/code&gt; method to
                    &lt;code&gt;search.service.ts&lt;/code&gt;.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;search(q:string) {
  if (!q || q === &apos;*&apos;) {
    q = &apos;&apos;;
  } else {
    q = q.toLowerCase();
  }
  return this.getAll().map(data =&amp;gt; {
    let results = [];
    data.map(item =&amp;gt; {
      if (JSON.stringify(item).toLowerCase().includes(q)) {
        results.push(item);
      }
    });
    return results;
  });
}
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Then refactor &lt;code&gt;SearchComponent&lt;/code&gt; to call this method with its &lt;code&gt;query&lt;/code&gt; variable.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;search(): void {
  this.searchService.search(this.query).subscribe(
    data =&amp;gt; {this.searchResults = data;},
    error =&amp;gt; console.log(error)
  );
}
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Now search results will be filtered by the query value you type in.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;This section showed you how to fetch and display search results. The next section builds on this and shows how to edit and save a record.&lt;/p&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;sect2&quot;&gt;
            &lt;h3 id=&quot;add_an_edit_feature&quot;&gt;Add an edit feature&lt;/h3&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Modify &lt;code&gt;search.component.html&lt;/code&gt; to add a link for editing a person.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: xml&quot;&gt;&amp;lt;td&amp;gt;&amp;lt;a [routerLink]=&quot;[&apos;Edit&apos;, { id: person.id }]&quot;&amp;gt;{{person.name}}&amp;lt;/a&amp;gt;&amp;lt;/td&amp;gt;
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Create &lt;code&gt;src/search/components/edit.component.html&lt;/code&gt; to display an editable form. You might notice I&apos;ve added &lt;code&gt;id&lt;/code&gt;
                    attributes to most elements. This is to
                    make things easier when writing integration tests with Protractor.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: xml&quot;&gt;&amp;lt;div *ngIf=&quot;person&quot;&amp;gt;
  &amp;lt;h3&amp;gt;{{editName}}&amp;lt;/h3&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;label&amp;gt;Id:&amp;lt;/label&amp;gt;
    {{person.id}}
  &amp;lt;/div&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;label&amp;gt;Name:&amp;lt;/label&amp;gt;
    &amp;lt;input [(ngModel)]=&quot;editName&quot; id=&quot;name&quot; placeholder=&quot;name&quot;/&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;label&amp;gt;Phone:&amp;lt;/label&amp;gt;
    &amp;lt;input [(ngModel)]=&quot;editPhone&quot; id=&quot;phone&quot; placeholder=&quot;Phone&quot;/&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;fieldset&amp;gt;
    &amp;lt;legend&amp;gt;Address:&amp;lt;/legend&amp;gt;
    &amp;lt;address&amp;gt;
      &amp;lt;input [(ngModel)]=&quot;editAddress.street&quot; id=&quot;street&quot;&amp;gt;&amp;lt;br/&amp;gt;
      &amp;lt;input [(ngModel)]=&quot;editAddress.city&quot; id=&quot;city&quot;&amp;gt;,
      &amp;lt;input [(ngModel)]=&quot;editAddress.state&quot; id=&quot;state&quot; size=&quot;2&quot;&amp;gt;
      &amp;lt;input [(ngModel)]=&quot;editAddress.zip&quot; id=&quot;zip&quot; size=&quot;5&quot;&amp;gt;
    &amp;lt;/address&amp;gt;
  &amp;lt;/fieldset&amp;gt;
  &amp;lt;button (click)=&quot;save()&quot; id=&quot;save&quot;&amp;gt;Save&amp;lt;/button&amp;gt;
  &amp;lt;button (click)=&quot;cancel()&quot; id=&quot;cancel&quot;&amp;gt;Cancel&amp;lt;/button&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Create an &lt;code&gt;EditComponent&lt;/code&gt; in &lt;code&gt;src/search/components/edit.component.ts&lt;/code&gt; that references this template and handles communication with the &lt;code&gt;SearchService&lt;/code&gt;.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;import {Component, OnInit} from &apos;angular2/core&apos;;
import {Person, Address, SearchService} from &apos;../../shared/services/search.service&apos;;
import {RouteParams, Router} from &apos;angular2/router&apos;;
import {CanDeactivate, ComponentInstruction} from &apos;angular2/router&apos;;

@Component({
  selector: &apos;sd-edit&apos;,
  moduleId: module.id,
  templateUrl: &apos;./edit.component.html&apos;,
  styleUrls: [&apos;./edit.component.css&apos;]
})
export class EditComponent implements OnInit, CanDeactivate {

  person: Person;
  editName: string;
  editPhone: string;
  editAddress: Address;

  constructor(
    private _service: SearchService,
    private _router: Router,
    private _routeParams: RouteParams
  ) { }

  ngOnInit() {
    let id = +this._routeParams.get(&apos;id&apos;);
    this._service.get(id).subscribe(person =&amp;gt; {
      if (person) {
        this.editName = person.name;
        this.editPhone = person.phone;
        this.editAddress = person.address;
        this.person = person;
      } else {
        this.gotoList();
      }
    });
  }

  routerCanDeactivate(next: ComponentInstruction, prev: ComponentInstruction): any {
    if (!this.person || this.person.name === this.editName || this.person.phone === this.editPhone
      || this.person.address === this.editAddress) {
      return true;
    }

    return new Promise&amp;lt;boolean&amp;gt;((resolve, reject) =&amp;gt; resolve(window.confirm(&apos;Discard changes?&apos;)));
  }

  cancel() {
    this._router.navigate([&apos;Search&apos;]);
  }

  save() {
    this.person.name = this.editName;
    this.person.phone = this.editPhone;
    this.person.address = this.editAddress;
    this._service.save(this.person);
    this.gotoList();
  }

  gotoList() {
    if (this.person) {
      this._router.navigate([&apos;Search&apos;, { term: this.person.name }]);
    } else {
      this._router.navigate([&apos;Search&apos;]);
    }
  }
}
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Modify &lt;code&gt;SearchService&lt;/code&gt; to contain functions for finding a person by their id, and saving them. While you&apos;re in there, modify
                    the &lt;code&gt;search()&lt;/code&gt; method to
                    be aware of updated objects in &lt;code&gt;localStorage&lt;/code&gt;.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;search(q:string) {
  if (!q || q === &apos;*&apos;) {
    q = &apos;&apos;;
  } else {
    q = q.toLowerCase();
  }
  return this.getAll().map(data =&amp;gt; {
    let results = [];
    data.map(item =&amp;gt; {
      // check for item in localStorage
      if (localStorage[&apos;person&apos; + item.id]) {
        item = JSON.parse(localStorage[&apos;person&apos; + item.id]);
      }
      if (JSON.stringify(item).toLowerCase().includes(q)) {
        results.push(item);
      }
    });
    return results;
  });
}

get(id: number) {
  return this.getAll().map(all =&amp;gt; {
    if (localStorage[&apos;person&apos; + id]) {
      return JSON.parse(localStorage[&apos;person&apos; + id]);
    }
    return all.find(e =&amp;gt; e.id === id);
  });
}

save(person: Person) {
  localStorage[&apos;person&apos; + person.id] = JSON.stringify(person);
}
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;To make the app are of this new component, add an import and route configuration in &lt;code&gt;app.component.ts&lt;/code&gt;.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;import {EditComponent} from &apos;../../search/components/edit.component&apos;;

@RouteConfig([
  { path: &apos;/&apos;,      name: &apos;Home&apos;,  component: HomeComponent  },
  { path: &apos;/about&apos;, name: &apos;About&apos;, component: AboutComponent },
  { path: &apos;/search&apos;, name: &apos;Search&apos;, component: SearchComponent },
  { path: &apos;/edit/:id&apos;, name: &apos;Edit&apos;, component: EditComponent }
])
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Then create &lt;code&gt;src/search/components/edit.component.css&lt;/code&gt; to make the form look a bit better.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: css&quot;&gt;:host {
  display: block;
  padding: 0 16px;
}

button {
  margin-top: 10px;
}
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;At this point, you should be able to search for a person and update their information.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div id=&quot;edit-form&quot; class=&quot;imageblock&quot;&gt;
                &lt;div style=&quot;text-align: center&quot;&gt;
                    &lt;a href=&quot;https://farm2.staticflickr.com/1603/25886692692_42abb78ef6_c.jpg&quot; title=&quot;Edit component&quot; rel=&quot;lightbox[getting-started-with-angular2]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/25886692692/in/datetaken-public/&quot;&gt;&lt;img src=&quot;https://farm2.staticflickr.com/1603/25886692692_42abb78ef6_z.jpg&quot; width=&quot;640&quot; alt=&quot;Edit component&quot;&gt;&lt;/a&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;The &amp;lt;form&amp;gt; in &lt;code&gt;src/search/components/edit.component.html&lt;/code&gt; calls a &lt;code&gt;save()&lt;/code&gt; function to update a person&apos;s
                    data. You already implemented this above.
                    The function calls a &lt;code&gt;gotoList()&lt;/code&gt; function that appends the person&apos;s name to the URL when sending the user back to the
                    search screen.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;gotoList() {
  if (this.person) {
    this._router.navigate([&apos;Search&apos;, { term: this.person.name }]);
  } else {
    this._router.navigate([&apos;Search&apos;]);
  }
}
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Since the &lt;code&gt;SearchComponent&lt;/code&gt; doesn&apos;t execute a search automatically when you execute this URL, add the following logic to do
                    so in its constructor.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;brush: js&quot;&gt;constructor(public searchService: SearchService, params: RouteParams) {
  if (params.get(&apos;term&apos;)) {
    this.query = decodeURIComponent(params.get(&apos;term&apos;));
    this.search();
  }
}
&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;You&apos;ll need to import &lt;code&gt;RouteParams&lt;/code&gt; in order for everything to compile.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;listingblock&quot;&gt;
                &lt;div class=&quot;content&quot;&gt;
                    &lt;pre class=&quot;brush: js&quot;&gt;import {ROUTER_DIRECTIVES, RouteParams} from &apos;angular2/router&apos;;&lt;/pre&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;After making all these changes, you should be able to search/edit/update a person&apos;s information. If it works - nice job!&lt;/p&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
    &lt;h3 id=&quot;source_code&quot;&gt;Source code&lt;/h3&gt;
    &lt;div class=&quot;sectionbody&quot;&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;A completed project with this code in it is available on GitHub at &lt;a href=&quot;https://github.com/mraible/angular2-tutorial&quot;&gt;https://github.com/mraible/angular2-tutorial&lt;/a&gt;.
                  If you have ideas for improvements, please leave a comment or send a pull request. 
            &lt;/p&gt;
            &lt;p&gt;
                I originally wrote this tutorial in &lt;a href=&quot;http://asciidoctor.org/&quot;&gt;Asciidoctor&lt;/a&gt; because it has a slick feature where you
                can include the source code from files rather than copying and pasting. Unfortunately, GitHub
                &lt;a href=&quot;https://github.com/github/markup/issues/172&quot;&gt;doesn&apos;t support includes&lt;/a&gt;. You can
                &lt;a href=&quot;http://gist.asciidoctor.org/?github-mraible%2Fangular2-tutorial%2F%2FREADME.adoc&quot;&gt;use DocGist to view this tutorial&lt;/a&gt;,
                but &lt;a href=&quot;https://github.com/asciidoctor/docgist/issues/11&quot;&gt;includes don&apos;t work&lt;/a&gt; there either.
            &lt;/p&gt;
            &lt;p&gt;If you&apos;d like to see the Asciidoctor-generated version of this tutorial, you can install the gem, checkout the project from GitHub,
                and then run &lt;code&gt;asciidoctor README.adoc&lt;/code&gt; to produce a &lt;code&gt;README.html&lt;/code&gt; file.
            &lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
    &lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
    &lt;div class=&quot;sectionbody&quot;&gt;
        &lt;div class=&quot;paragraph&quot;&gt;
            &lt;p&gt;I hope you&apos;ve enjoyed this quick-and-easy tutorial on how to get started with Angular 2. In a future tutorial,
                I&apos;ll show you &lt;a href=&quot;//raibledesigns.com/rd/entry/testing_angular_2_applications&quot;&gt;how to write unit tests and integration tests for this application&lt;/a&gt;. I&apos;ve also started looking into creating an ES6 version of this tutorial using So&#243;s G&#225;bor&apos;s &lt;a href=&quot;https://github.com/blacksonic/angular2-es6-starter&quot;&gt;angular2-es6-starter&lt;/a&gt;. If you know of a better starter for Angular 2 and ES6, please let me know.&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;sect2&quot;&gt;
            &lt;h3 id=&quot;resources&quot;&gt;Resources&lt;/h3&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;I used a number of resources while creating this application. &lt;a href=&quot;https://www.ng-book.com/2/&quot;&gt;ng-book 2&lt;/a&gt; was an invaluable resource
                    and I highly recommend it if you&apos;re learning Angular 2. I found Chariot Solution&apos;s article on
                    &lt;a href=&quot;http://chariotsolutions.com/blog/post/angular2-observables-http-separating-services-components/&quot;&gt;Angular2 Observables, Http, and
                        separating services and components&lt;/a&gt;
                    to be quite helpful. Finally, the &lt;a href=&quot;https://github.com/angular/angular-cli&quot;&gt;angular-cli&lt;/a&gt; project was a big help, especially its
                    &lt;code&gt;ng generate route &amp;lt;object&amp;gt;&lt;/code&gt; feature.&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;paragraph&quot;&gt;
                &lt;p&gt;Kudos to all the pioneers in Angular 2 land that&apos;ve been using it and writing about it on blogs and Stack Overflow.
                    Getting started with Angular 2 would&apos;ve been a real pain without your trailblazing.&lt;/p&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;</description>          </item>
    <item>
    <guid isPermaLink="true">https://raibledesigns.com/rd/entry/how_to_implement_a_smart</guid>
    <title>How to Implement a Smart Chunking Bootstrap Carousel with AngularJS</title>
    <dc:creator>Matt Raible</dc:creator>
    <link>https://raibledesigns.com/rd/entry/how_to_implement_a_smart</link>
        <pubDate>Tue, 15 Mar 2016 09:47:30 -0600</pubDate>
    <category>The Web</category>
    <category>bootstrap</category>
    <category>angularjs</category>
    <category>javascript</category>
    <category>ui-bootstrap</category>
    <category>carousel</category>
    <category>css</category>
    <category>responsive</category>
    <atom:summary type="html">&lt;p&gt;
    I&apos;ve been helping a client develop a project management application for the last several months. One of the features
    I implemented uses &lt;a href=&quot;https://angular-ui.github.io/bootstrap/#/carousel&quot;&gt;UI Bootstrap&apos;s carousel directive&lt;/a&gt; to display a list of project templates to choose from when creating a new project. Rather than displaying
    one at a time, we wanted to display as many as the user&apos;s screen would allow. That is, if they were on a large monitor,
    we wanted to display five templates, a medium size monitor would display three and so on. This is a story of how I implemented a
    &lt;em&gt;smart chunking carousel&lt;/em&gt;.
&lt;/p&gt;</atom:summary>        <description>&lt;p&gt;
    I&apos;ve been helping a client develop a project management application for the last several months. One of the features
    I implemented uses &lt;a href=&quot;https://angular-ui.github.io/bootstrap/#/carousel&quot;&gt;UI Bootstrap&apos;s carousel directive&lt;/a&gt; to display a list of project templates to choose from when creating a new project. Rather than displaying
    one at a time, we wanted to display as many as the user&apos;s screen would allow. That is, if they were on a large monitor,
    we wanted to display five templates, a medium size monitor would display three and so on. This is a story of how I implemented a
    &lt;em&gt;smart chunking carousel&lt;/em&gt;.
&lt;/p&gt;
&lt;p&gt;
    To begin, I made it possible to show groups of items in the carousel using &lt;a href=&quot;http://stackoverflow.com/a/21653981/65681&quot;&gt;array chunking&lt;/a&gt;.
&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
  function chunk(arr, size) {
    var newArr = [];
    var arrayLength = arr.length;
    for (var i = 0; i &lt; arrayLength; i += size) {
      newArr.push(arr.slice(i, i + size));
    }
    return newArr;
  }
&lt;/pre&gt;
&lt;p&gt;Using UI Bootstrap&apos;s &lt;a href=&quot;https://angular-ui.github.io/bootstrap/#/carousel&quot;&gt;example code&lt;/a&gt;, I created &lt;code&gt;$scope.chunkedSlides&lt;/code&gt;
from &lt;code&gt;$scope.slides&lt;/code&gt;.
&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
$scope.chunkSize = 5;

// chunk slides so there&apos;s two per chunk by default
$scope.chunkedSlides = chunk($scope.slides, $scope.chunkSize);
&lt;/pre&gt;
&lt;p&gt;Next, I changed the HTML template to read the grouped slides, and show each one.&lt;/p&gt;
&lt;pre class=&quot;brush: xml&quot;&gt;
&amp;lt;uib-carousel active=&quot;active&quot; interval=&quot;0&quot; no-wrap=&quot;true&quot;&amp;gt;
  &amp;lt;uib-slide ng-repeat=&quot;row in chunkedSlides&quot;&amp;gt;
    &amp;lt;div class=&quot;row&quot;&amp;gt;
      &amp;lt;div ng-repeat=&quot;slide in row track by $index&quot; class=&quot;slide&quot;&amp;gt;
        &amp;lt;img ng-src=&quot;{{slide.image}}&quot;&amp;gt;
        &amp;lt;div class=&quot;carousel-caption&quot;&amp;gt;
          &amp;lt;h4&amp;gt;Slide {{slide.id}}&amp;lt;/h4&amp;gt;
          &amp;lt;p&amp;gt;{{slide.text}}&amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/uib-slide&amp;gt;
&amp;lt;/uib-carousel&amp;gt;
&lt;/pre&gt;
&lt;p&gt;This was enough to get five squares on a large monitor. 
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
&lt;img src=&quot;https://farm2.staticflickr.com/1704/25673191306_39ee95e5a0_z.jpg&quot; width=&quot;640&quot; alt=&quot;Carousel Diagram&quot;&gt;
&lt;/p&gt;
&lt;p&gt;
However, I wanted to go further and reduce the number per group on smaller monitors. I created a &lt;code&gt;SmartChunking&lt;/code&gt;
    service that defined how many per group for each possible width.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
angular.module(&apos;app&apos;).service(&apos;SmartChunking&apos;, function() {
  var large = 1600;
  var medium = 1200;
  var small = 1024;
  var xsmall = 800;

  this.getChunkSize = function(width) {
    var chunkSize;
    if (width &gt;= large) {
      chunkSize = 5;
    } else if (width &gt;= medium) {
      chunkSize = 4;
    } else if (width &gt;= small) {
      chunkSize = 3;
    } else if (width &gt;= xsmall) {
      chunkSize = 2;
    } else {
      chunkSize = 1;
    }
    return chunkSize;
  }
});
&lt;/pre&gt;
&lt;p&gt;
    I wrote a &lt;code&gt;smart-chunking&lt;/code&gt; directive to fire an event with the chunk size.
&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
angular.module(&apos;app&apos;).directive(&apos;smartChunking&apos;, function($window, SmartChunking) {
  return {
    restrict: &apos;A&apos;,
    link: function($scope) {
      var w = angular.element($window);

      // window.outerWidth works on desktop, screen.height on iPad (width returns 768)
      var width = ($window.outerWidth &gt; 0) ? $window.outerWidth : screen.height;
      var chunkSize = SmartChunking.getChunkSize(width);
      if (chunkSize !== 5) {
        $scope.$emit(&apos;change-chunk-size&apos;, chunkSize);
      }

      $scope.getWidth = function() {
        return ($window.outerWidth &gt; 0) ? $window.outerWidth : screen.width;
      };

      $scope.$watch($scope.getWidth, function(newValue, oldValue) {
        if (newValue !== oldValue) {
          var chunkSize = SmartChunking.getChunkSize(newValue);
          $scope.$emit(&apos;change-chunk-size&apos;, chunkSize);
        }
      });

      w.bind(&apos;resize&apos;, function() {
        $scope.$apply();
      });
    }
  }
});
&lt;/pre&gt;
&lt;p&gt;Then I added a listener for this in the controller that populated the carousel.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
$scope.$on(&apos;change-chunk-size&apos;, function(event, data) {
  if (data !== $scope.chunkSize) {
    $scope.chunkedSlides = chunk($scope.slides, data);
    $scope.chunkSize = data;
  }
});
&lt;/pre&gt;
&lt;p&gt;
    The final step was adding the &lt;code&gt;smark-chunking&lt;/code&gt; directive to each slide and dynamically determining its &lt;code&gt;col-sm-*&lt;/code&gt; class.
&lt;/p&gt;
&lt;pre class=&quot;brush: xml&quot;&gt;
&amp;lt;div ng-repeat=&quot;slide in row track by $index&quot; class=&quot;slide&quot; ng-class=&quot;getSlideClass(chunkSize)&quot; smart-chunking&amp;gt;
&lt;/pre&gt;
&lt;p&gt;The controller contains a map of classes that map to chunk sizes:&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
var classMap = {
  5: &apos;col-sm-2&apos;,
  4: &apos;col-sm-3&apos;,
  3: &apos;col-sm-4&apos;,
  2: &apos;col-sm-5&apos;,
};

$scope.getSlideClass = function(chunkSize) {
  if (classMap[chunkSize]) {
    return classMap[chunkSize];
  } else {
    return &apos;col-sm-10&apos;;
  }
}
&lt;/pre&gt;
&lt;p&gt;I did find that adding some CSS made things look quite a bit better.&lt;/p&gt;
&lt;pre class=&quot;brush: css&quot;&gt;
.carousel-caption {
  padding-bottom: 0;
}

.carousel-control.left,
.carousel-control.right {
  background-image: none;
}

.carousel-indicators {
  display: none;
}

.carousel-inner {
  padding-left: 10%;
  overflow: visible;
}

.carousel-control .glyphicon-chevron-left,
.carousel-control .glyphicon-chevron-right {
  font-size: 100px;
  margin-top: -60px;
  font-style: normal;
  font-weight: 100;
}

.carousel-control .glyphicon-chevron-left {
  margin-left: -100px;
}

.carousel-control .glyphicon-chevron-right {
  margin-right: -40px;
}

/* make slide widths more responsive */
@media only screen and (min-width: 1600px) {
  .col-sm-2 {
    width: 18%;
  }
}

@media only screen and (min-width: 1200px) {
  .col-sm-3 {
    width: 22%;
  }
  .carousel-control .glyphicon-chevron-left {
    margin-left: -70px;
  }
  .carousel-control .glyphicon-chevron-right {
    margin-right: -20px;
  }
}

@media only screen and (max-width: 1200px) {
  .col-sm-4 {
    width: 29%;
  }
  .carousel-control .glyphicon-chevron-left {
    margin-left: -70px;
  }
  .carousel-control .glyphicon-chevron-right {
    margin-right: -20px;
  }
}

@media only screen and (max-width: 800px) {
  .col-sm-10 {
    width: 90%;
  }
}
&lt;/pre&gt;
&lt;p&gt;I hope this tip helps you if you need to implement a similar feature. I&apos;ve published a &lt;a href=&quot;https://plnkr.co/edit/BQkTiIUTbiLuOQ03QV8z?p=preview&quot;&gt;demo on Plunkr&lt;/a&gt; (best experienced in embedded view).&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
&lt;a href=&quot;https://farm2.staticflickr.com/1504/25663696696_2517b5f33d_b.jpg&quot; data-href=&quot;https://www.flickr.com/photos/mraible/25663696696/in/datetaken-public/&quot; title=&quot;Smark Chunking Carousel&quot; rel=&quot;lightbox[smart-chunking-carousel]&quot;&gt;&lt;img src=&quot;https://farm2.staticflickr.com/1504/25663696696_2517b5f33d_z.jpg&quot; width=&quot;640&quot;  alt=&quot;Smark Chunking Carousel&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
</description>          </item>
    <item>
    <guid isPermaLink="true">https://raibledesigns.com/rd/entry/angular_summit_2015</guid>
    <title>Angular Summit 2015</title>
    <dc:creator>Matt Raible</dc:creator>
    <link>https://raibledesigns.com/rd/entry/angular_summit_2015</link>
        <pubDate>Thu, 1 Oct 2015 10:29:31 -0600</pubDate>
    <category>The Web</category>
    <category>aurelia</category>
    <category>javascript</category>
    <category>bootstrap</category>
    <category>angular2</category>
    <category>angularjs</category>
    <category>jhipster</category>
    <category>spring-boot</category>
    <category>java</category>
    <category>meteor</category>
    <category>es6</category>
            <description>&lt;p&gt;
  I was in Boston this week, speaking and attending the very first &lt;a href=&quot;http://angularsummit.com&quot;&gt;Angular Summit&lt;/a&gt;. I had the privilege of delivering the opening keynote on Monday. I spoke about the Art of Angular
  and used a slide deck similar to &lt;a href=&quot;//raibledesigns.com/rd/entry/the_art_of_angularjs_in&quot;&gt;last time&lt;/a&gt;. I did
  update the presentation to show the astronomical growth of AngularJS in terms of candidate skills (on LinkedIn) and job opportunities (on Dice.com)&lt;sup&gt;1&lt;/sup&gt;.
&lt;/p&gt;

&lt;p style=&quot;text-align: center&quot;&gt;
&lt;a href=&quot;https://farm1.staticflickr.com/691/21198424124_e9b9b37afb_c.jpg&quot; title=&quot;LinkedIn Skills Growth for JavaScript MVC Frameworks&quot; rel=&quot;lightbox[angularsummit2015]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/21198424124/in/dateposted-public/&quot;&gt;&lt;img src=&quot;https://farm1.staticflickr.com/691/21198424124_e9b9b37afb_c.jpg&quot; width=&quot;300&quot; alt=&quot;LinkedIn Skills Growth for JavaScript MVC Frameworks&quot; style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
&lt;a href=&quot;https://farm6.staticflickr.com/5808/21633143850_9aef93d361_c.jpg&quot; title=&quot;Dice.com Job Growth for JavaScript MVC Frameworks&quot; rel=&quot;lightbox[angularsummit2015]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/21633143850/in/dateposted-public/&quot;&gt;&lt;img src=&quot;https://farm6.staticflickr.com/5808/21633143850_9aef93d361_c.jpg&quot; width=&quot;300&quot; alt=&quot;Dice.com Job Growth for JavaScript MVC Frameworks&quot; style=&quot;border: 1px solid black; margin-left: 15px&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I mentioned the recently announced &lt;a href=&quot;http://angularjs.blogspot.com/2015/08/angular-1-and-angular-2-coexistence.html&quot;&gt;good news for Angular 2&lt;/a&gt;:
  &lt;/p&gt;&lt;ul&gt;
  &lt;li&gt;We&apos;re enabling mixing of Angular 1 and Angular 2 in the same application.&lt;/li&gt;
  &lt;li&gt;You can mix Angular 1 and Angular 2 components in the same view.&lt;/li&gt;
  &lt;li&gt;Angular 1 and Angular 2 can inject services across frameworks.&lt;/li&gt;
  &lt;li&gt;Data binding works across frameworks.&lt;/li&gt;
  &lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;In related news, &lt;a href=&quot;https://twitter.com/cdoremus&quot;&gt;Craig Doremus&lt;/a&gt; recently posted a &lt;a href=&quot;https://github.com/cdoremus/state-geo-angular&quot;&gt;state-geo-angular&lt;/a&gt; project
  that shows how you can develop an Angular 1.x application that will be easy to upgrade to Angular 2.x.
  Thanks Craig!
&lt;/p&gt;

&lt;p style=&quot;text-align: center&quot;&gt;
&lt;iframe src=&quot;//www.slideshare.net/slideshow/embed_code/key/vaTKWLA8oVDr8z&quot; width=&quot;600&quot; height=&quot;377&quot; frameborder=&quot;0&quot; marginwidth=&quot;0&quot; marginheight=&quot;0&quot; scrolling=&quot;no&quot; style=&quot;border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;&quot; allowfullscreen=&quot;&quot;&gt; &lt;/iframe&gt;
&lt;/p&gt;

&lt;div style=&quot;text-align: right; max-width: 600px; margin: -20px auto 10px auto&quot;&gt;
    &lt;a href=&quot;//raibledesigns.com/rd/page/publications&quot;&gt;Download&lt;/a&gt; | &lt;a href=&quot;//www.slideshare.net/mraible/the-art-of-angularjs-in-2015-angular-summit-2015&quot;&gt;SlideShare&lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;After my keynote, I attended &lt;a href=&quot;https://twitter.com/prpatel&quot;&gt;Pratik Patel&lt;/a&gt;&apos;s session on &lt;a href=&quot;https://angularsummit.com/conference/boston/2015/09/session?id=34208&quot;&gt;High Performance JavaScript Web Apps&lt;/a&gt;.
  Pratik pointed out &lt;a href=&quot;http://mobitest.akamai.com&quot;&gt;mobitest.akamai.com&lt;/a&gt; for testing an app&apos;s performance and seeing its blocking resources. He also mentioned
  &lt;a href=&quot;http://speedgun.io/&quot;&gt;speedgun.io&lt;/a&gt; (currently unavailable) for capturing performance numbers as part of a continuous integration process. Finally,
  he recommended &lt;a href=&quot;http://addyosmani.com/blog/video-javascript-memory-management-masterclass/&quot;&gt;Addy Somani&apos;s JavaScript Memory Management Masterclass&lt;/a&gt;.
&lt;p&gt;
My second presentation was about &lt;a href=&quot;http://jhipster.github.io/&quot;&gt;JHipster&lt;/a&gt;. Near the end of the presentation,
I mentioned that I hope to finish the &lt;a href=&quot;http://www.jhipster-book.com/&quot;&gt;JHipster Book&lt;/a&gt; this month. Writing presentations for
&lt;a href=&quot;//raibledesigns.com/rd/entry/springone_2gx_2015_my_presentations&quot;&gt;SpringOne 2GX&lt;/a&gt; and the Angular Summit occupied a lot of my free time in September. Now that it&apos;s October, I&apos;ll be dedicating my free time to finishing the book. In fact, I think I can finish the rough draft this week!
&lt;/p&gt;&lt;p style=&quot;text-align: center&quot;&gt;
&lt;iframe src=&quot;//www.slideshare.net/slideshow/embed_code/key/769Ne9avDiEeWl&quot; width=&quot;600&quot; height=&quot;377&quot; frameborder=&quot;0&quot; marginwidth=&quot;0&quot; marginheight=&quot;0&quot; scrolling=&quot;no&quot; style=&quot;border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;&quot; allowfullscreen=&quot;&quot;&gt; &lt;/iframe&gt;
&lt;/p&gt;

&lt;div style=&quot;text-align: right; max-width: 600px; margin: -20px auto 10px auto&quot;&gt;
&lt;a href=&quot;//raibledesigns.com/rd/page/publications&quot;&gt;Download&lt;/a&gt; | &lt;a href=&quot;//www.slideshare.net/mraible/get-hip-with-jhipster-spring-boot-angularjs-bootstrap-angular-summit-2015&quot;&gt;SlideShare&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;
  For the last session of the day, I attended &lt;a href=&quot;https://twitter.com/johnlindquist&quot;&gt;John Lindquist&apos;s&lt;/a&gt; session on &lt;a href=&quot;http://angularsummit.com/conference/boston/2015/09/session?id=34230&quot;&gt;Angular 2 Components&lt;/a&gt;. John showed us
  how &lt;em&gt;everything is a component in Angular 2&lt;/em&gt;. He also said &quot;now is the time to learn ES6&quot; and built an
  &lt;a href=&quot;https://github.com/johnlindquist/angular-2-quickstart&quot;&gt;Angular 2 ToDo App&lt;/a&gt; using ES6 and a bit of TypeScript. You might recognize John&apos;s name; he&apos;s the founder of &lt;a href=&quot;http://egghead.io/&quot;&gt;egghead.io&lt;/a&gt;, an excellent
  site for &lt;a href=&quot;https://egghead.io/playlists/new-to-angular-start-here&quot;&gt;learning Angular&lt;/a&gt; with bite-sized videos.
&lt;/p&gt;
&lt;p&gt;Tuesday morning started with a &lt;a href=&quot;http://angularsummit.com/conference/boston/2015/09/session?id=34187&quot;&gt;
  Angular 2.0 keynote&lt;/a&gt; from &lt;a href=&quot;https://twitter.com/ppavlovich&quot;&gt;Peter Pavlovich&lt;/a&gt;. I really enjoyed
  this session and received lots of good tips about getting ready for Angular 2. The tweet below from
  Ksenia Dmitrieva shows his advice.
&lt;/p&gt;
&lt;div style=&quot;margin: 0 auto; max-width: 500px;&quot;&gt;
&lt;blockquote class=&quot;twitter-tweet&quot; lang=&quot;en&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Best Practices for &lt;a href=&quot;https://twitter.com/hashtag/angularjs?src=hash&quot;&gt;#angularjs&lt;/a&gt; 1.X if you plan to switch to 2.0 by &lt;a href=&quot;https://twitter.com/ppavlovich&quot;&gt;@ppavlovich&lt;/a&gt; &lt;a href=&quot;https://twitter.com/hashtag/AngularSummit?src=hash&quot;&gt;#AngularSummit&lt;/a&gt; &lt;a href=&quot;http://t.co/9nobqDc9G9&quot;&gt;pic.twitter.com/9nobqDc9G9&lt;/a&gt;&lt;/p&gt;&amp;mdash; Ksenia Dmitrieva (@KseniaDmitrieva) &lt;a href=&quot;https://twitter.com/KseniaDmitrieva/status/648865784152915968&quot;&gt;September 29, 2015&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&quot;//platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;/div&gt;
&lt;p&gt;My biggest takeaway was to start following &lt;a href=&quot;https://github.com/johnpapa/angular-styleguide&quot;&gt;John Papa&apos;s Angular Style Guide&lt;/a&gt; &lt;abbr title=&quot;As Soon As Possible&quot;&gt;ASAP&lt;/abbr&gt;.
&lt;p&gt;The first session I attended on Tuesday was &lt;a href=&quot;https://twitter.com/JuddFlamm&quot;&gt;Judd Flamm&lt;/a&gt;&apos;s &lt;a href=&quot;https://angularsummit.com/conference/boston/2015/09/session?id=34298&quot;&gt;Google Material Design &amp;amp; Angular&lt;/a&gt;.
  I&apos;m using &lt;a href=&quot;https://fezvrasta.github.io/bootstrap-material-design/&quot;&gt;Material Design for Bootstrap&lt;/a&gt; on a side project, so I was interested in learning more about its inspiration.
  We learned that &lt;a href=&quot;https://design.google.com/&quot;&gt;Google Design&lt;/a&gt; has everything you need to know about why Material Design exists. We also
  learned about &lt;a href=&quot;https://material.angularjs.org&quot;&gt;Angular Material&lt;/a&gt; and spent most of the session looking at its components. Judd
  recommended &lt;a href=&quot;https://github.com/angular/material-start&quot;&gt;Angular Material-Start&lt;/a&gt; for those looking to get started quickly with both frameworks.
  Judd was a very entertaining speaker; I highly recommend you attend one of his talks if you get the opportunity.
&lt;/p&gt;
&lt;p&gt;After being dazzled by Peter&apos;s knowledge of Angular 2 in Tuesday&apos;s keynote, I attended two more of his talks: one on &lt;a href=&quot;https://www.meteor.com/&quot;&gt;Meteor&lt;/a&gt; and
  another on &lt;a href=&quot;http://aurelia.io/&quot;&gt;Aurelia&lt;/a&gt;. I&apos;ve known about Meteor for a while, but have become more intrigued by it lately with its
  &lt;a href=&quot;http://www.infoq.com/news/2015/09/meteor-12-ecmascript&quot;&gt;1.2 release&lt;/a&gt; and &lt;a href=&quot;http://info.meteor.com/blog/official-angular-support-with-angular-meteor-1.0.0&quot;&gt;Angular support&lt;/a&gt;. Meteor&apos;s
  command line tools that auto-inject CSS and JS demoed very well, as did it&apos;s installable features like a LESS support and Facebook authentication.
&lt;/p&gt;
&lt;p&gt;After hearing all the good things about Angular 2 from Peter, it was interesting to hear him downplay it in his Aurelia talk later that day. When he started showing code,
  it was pretty obvious that Aurelia is doing a great job of simplifying JavaScript MVC syntax for developers. You can develop components with almost half the
    code that Angular 2 requires, and it uses ES6, &lt;a href=&quot;http://jspm.io/&quot;&gt;jspm&lt;/a&gt; and &lt;a href=&quot;https://github.com/systemjs/systemjs&quot;&gt;SystemJS&lt;/a&gt;.
    If you&apos;re developing JavaScript, learning these tools will help prepare you for the future. It&apos;s cool that Aurelia encourages learning things you should learn anyway.
  &lt;/p&gt;
  &lt;p&gt;Aurelia and Angular 2 are both still in Alpha, so I&apos;m not sure it makes sense to use them on a project this year. However, I do think it&apos;s important to track
    them both. I especially think it&apos;s interesting that the founder of Aurelia, &lt;a href=&quot;http://twitter.com/EisenbergEffect&quot;&gt;Rob Eisenberg&lt;/a&gt;,
    &lt;a href=&quot;http://eisenbergeffect.bluespire.com/leaving-angular/&quot;&gt;left the Angular Team&lt;/a&gt; in November 2014 and &lt;a href=&quot;http://blog.durandal.io/2015/01/26/introducing-aurelia/&quot;&gt;
      announced Aurelia&lt;/a&gt; in January 2015 (&lt;a href=&quot;https://news.ycombinator.com/item?id=8948665&quot;&gt;Hacker News thread&lt;/a&gt;). Peter mentioned several times that Aurelia wants to help developers write apps,
      while AngularJS is more tied to helping Google write apps.
  &lt;/p&gt;
&lt;p&gt;
  There were around 400 people at Angular Summit, which I think is pretty good for a first-run conference. As with most No Fluff Just Stuff shows, it ran smoothly, had
    plenty of time between sessions and was filled with knowledgeable, entertaining speakers. It was fun doing my first keynote and I look forward to speaking again in November
    (at &lt;a href=&quot;http://www.devoxx.be/&quot;&gt;Devoxx&lt;/a&gt;) and December (at
    &lt;a href=&quot;http://www.therichwebexperience.com/conference/fort_lauderdale/2015/12/home&quot;&gt;The Rich Web Experience&lt;/a&gt;).
  &lt;/head&gt;
&lt;/p&gt;
&lt;p style=&quot;font-size: .9em&quot;&gt;1. I know Dice.com is probably not a great site, but it makes sense to use it since I&apos;ve
been tracking JavaScript MVC framework job stats on it since February 2014.&lt;/p&gt;
</description>          </item>
    <item>
    <guid isPermaLink="true">https://raibledesigns.com/rd/entry/springone_2gx_2015_my_presentations</guid>
    <title>SpringOne 2GX 2015: My Presentations on Comparing Hot JavaScript Frameworks and NoXML </title>
    <dc:creator>Matt Raible</dc:creator>
    <link>https://raibledesigns.com/rd/entry/springone_2gx_2015_my_presentations</link>
        <pubDate>Sun, 20 Sep 2015 12:29:00 -0600</pubDate>
    <category>Java</category>
    <category>xml</category>
    <category>springframework</category>
    <category>javascript</category>
    <category>s2gx</category>
    <category>emberjs</category>
    <category>angularjs</category>
    <category>noxml</category>
    <category>springone2gx</category>
    <category>reactjs</category>
            <description>Last week, I had the pleasure of traveling to Washington, DC to speak at the annual &lt;a href=&quot;http://www.springone2gx.com/&quot;&gt;SpringOne 2GX conference&lt;/a&gt;. I was pretty stressed for the last few weeks because I had to create two new presentations from scratch, and both had to be 90 minutes long. I was also hoping to finish the JHipster Book before the conference started. I was able to finish both presentations in the nick of time, but did not find the time to write the last chapter in the JHipster Book.&lt;/p&gt;
&lt;p&gt;The first presentation was titled &lt;a href=&quot;https://2015.event.springone2gx.com/schedule/sessions/comparing_hot_javascript_frameworks_angularjs_ember_js_and_react_js.html&quot;&gt;Comparing Hot JavaScript Frameworks: AngularJS, Ember.js and React.js&lt;/a&gt;. I started by revisiting the &lt;a href=&quot;//raibledesigns.com/rd/entry/comparing_jvm_web_frameworks_at&quot;&gt;Comparing JVM Web Frameworks talk I did at vJUG&lt;/a&gt; last February. I explained how I think traditional web frameworks are no longer relevant in 2015, but I do believe server-side rendering is still &lt;em&gt;very&lt;/em&gt; relevant. From there, I used &lt;a href=&quot;http://www.ybrikman.com/&quot;&gt;Yevgeniy Brikman&#8217;s&lt;/a&gt; framework scorecard (from his &lt;a href=&quot;http://www.slideshare.net/brikis98/nodejs-vs-play-framework&quot;&gt;Node.js vs. Play Framework presentation&lt;/a&gt;) to rank each framework by a number of different criteria. You can see the final results on &lt;a href=&quot;http://www.slideshare.net/mraible/comparing-hot-javascript-frameworks-angularjs-emberjs-and-reactjs-springone-2gx-2015/160&quot;&gt;slide 160&lt;/a&gt;. Since the scores were so close, I believe you could tweak some scores a bit (or add weights to the different criteria) and make any of the frameworks come out on top.
&lt;/p&gt;
&lt;p&gt;
You can click through the presentation below, download it from &lt;a href=&quot;//raibledesigns.com/rd/page/publications&quot;&gt;my presentations page&lt;/a&gt;, or &lt;a href=&quot;//www.slideshare.net/mraible/comparing-hot-javascript-frameworks-angularjs-emberjs-and-reactjs-springone-2gx-2015&quot;&gt;see it on SlideShare&lt;/a&gt;.
&lt;/p&gt;
&lt;div style=&quot;text-align: center&quot;&gt;
&lt;iframe src=&quot;//www.slideshare.net/slideshow/embed_code/key/NGLRPcZiLF0pBo&quot; width=&quot;512&quot; height=&quot;325&quot; frameborder=&quot;0&quot; marginwidth=&quot;0&quot; marginheight=&quot;0&quot; scrolling=&quot;no&quot; style=&quot;border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;&quot; allowfullscreen&gt; &lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;I started writing the second presentation a week before I had to deliver it. On Thursday, September 10th, I stayed up late, trying to figure out how to create a good presentation on NoXML &lt;em&gt;and&lt;/em&gt; finish the last part of the JHipster Book. Then it came to me, I needed to &lt;em&gt;parallelize&lt;/em&gt; and do them both at the same time. I decided to compare AppFuse (which is similar to a legacy Spring application with lots of XML) to JHipster (which hardly contains any XML). 
&lt;/p&gt;
&lt;p&gt;
I wrote a 10-page Google Doc on how I planned to do this, then went rafting and camping with my family for the weekend. I finished most of the presentation on Monday night, but then realized the presentation wouldn&apos;t be long enough to fill 90 minutes. So I hunkered down at midnight, created a new AppFuse application and removed a bunch of its XML. This took me until 3:30am, and I was able to accomplish the following tasks:
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Spring XML to Java&lt;/li&gt;
&lt;li&gt;Spring Security Configuration to Java&lt;/li&gt;
&lt;li&gt;web.xml to WebApplicationInitializer&lt;/li&gt;
&lt;li&gt;Spring MVC to Java&lt;/li&gt;
&lt;li&gt;Migrated to Spring Boot&lt;/li&gt;
&lt;li&gt;Maven to Groovy&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
&lt;p&gt;I was pretty pumped when I completed my final goal: converting to Spring Boot and getting a test to pass. I made commits to an &lt;a href=&quot;https://github.com/mraible/appfuse-noxml/commits/master&quot;&gt;appfuse-noxml project on GitHub&lt;/a&gt; as I accomplished each step. You can see all the changes in &lt;a href=&quot;https://github.com/mraible/appfuse-noxml/commits/master&quot;&gt;the project&apos;s commit log&lt;/a&gt;. While I&apos;d figured everything out, I still needed to complete the presentation. Luckily, I found time to do this the night before, the morning of, and in the final hour before I had to deliver the talk. You can imagine my relief when I was done delivering both talks. 
&lt;/p&gt;
&lt;p&gt;
You can click through the presentation below, download it from &lt;a href=&quot;//raibledesigns.com/rd/page/publications&quot;&gt;my presentations page&lt;/a&gt;, or &lt;a href=&quot;//www.slideshare.net/mraible/noxml-eliminating-xml-in-spring-projects-springone-2gx-2015&quot;&gt;view it on SlideShare&lt;/a&gt;.
&lt;/p&gt;
&lt;div style=&quot;text-align: center&quot;&gt;
&lt;iframe src=&quot;//www.slideshare.net/slideshow/embed_code/key/4V9V7NSsNC2rd7&quot; width=&quot;512&quot; height=&quot;325&quot; frameborder=&quot;0&quot; marginwidth=&quot;0&quot; marginheight=&quot;0&quot; scrolling=&quot;no&quot; style=&quot;border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;&quot; allowfullscreen&gt; &lt;/iframe&gt; 
&lt;/div&gt;
&lt;p&gt;While I didn&apos;t get to spend much time at the conference, I did have a lot of fun while I was there. I got to meet some new folks, reconnect with old friends, and enjoy beers and dinner with a smiling crew on Thursday night. The Broncos victory late that night was the icing on the cake. &lt;img src=&quot;//raibledesigns.com/images/smileys/smile.gif&quot; class=&quot;smiley&quot; alt=&quot;:)&quot; title=&quot;:)&quot;&gt;&lt;/p&gt;
</description>          </item>
    <item>
    <guid isPermaLink="true">https://raibledesigns.com/rd/entry/best_practices_for_using_foundation1</guid>
    <title>Best Practices for using Foundation with AngularJS Revisited</title>
    <dc:creator>Matt Raible</dc:creator>
    <link>https://raibledesigns.com/rd/entry/best_practices_for_using_foundation1</link>
        <pubDate>Thu, 19 Feb 2015 09:38:42 -0700</pubDate>
    <category>The Web</category>
    <category>foundation</category>
    <category>css</category>
    <category>javascript</category>
    <category>foundationforapps</category>
    <category>angularjs</category>
            <description>&lt;p&gt;
&lt;a href=&quot;http://foundation.zurb.com&quot;&gt;&lt;img src=&quot;//raibledesigns.com/repository/images/angular-foundation.png&quot; width=&quot;200&quot; class=&quot;picture&quot; alt=&quot;Angular Foundation&quot; style=&quot;margin-top: -12px&quot;&gt;&lt;/a&gt;
A couple weeks ago I wrote about &lt;a href=&quot;//raibledesigns.com/rd/entry/best_practices_for_using_foundation&quot;&gt;using Foundation with AngularJS&lt;/a&gt;. Based on research I&apos;d done, I concluded that it was best to use &lt;a href=&quot;http://foundation.zurb.com/apps/&quot;&gt;Foundation for Apps&lt;/a&gt; for any webapps my client created and &lt;a href=&quot;http://foundation.zurb.com/&quot;&gt;Foundation for Sites&lt;/a&gt; for any websites (e.g. a WordPress-based intranet).&lt;/p&gt;
&lt;p&gt;After doing my initial research, I did some prototyping with Foundation for Apps (F4A). What I discovered is that F4A does &lt;em&gt;not&lt;/em&gt; include all the same components as Foundation for Sites (F5). For example, the top-bar and dropdown functionality are missing. I &lt;a href=&quot;http://foundation.zurb.com/forum/posts/22587-dropdowns-in-foundation-for-apps&quot;&gt;posted my issues to the Foundation Forums&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The response I received:&lt;/p&gt;
&lt;blockquote class=&quot;quote&quot;&gt;
It should work. You would need to copy over all the Scss and global mixins that you used in top-bar or at least all the output CSS from it. Otherwise there is no reason the components won&apos;t fit into the grid.&lt;/blockquote&gt;
&lt;p&gt;I was able to import Foundation for Sites into my project by adding it to bower.json:&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;  &quot;dependencies&quot;: {
    &quot;foundation-apps&quot;: &quot;~1.0.2&quot;,
    &quot;foundation&quot;: &quot;~5.5.1&quot;
  }
&lt;/pre&gt;
&lt;p&gt;After doing this, I added the new path to Gulpfile.js:&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;var sassPaths = [
  &apos;client/assets/scss&apos;,
  &apos;bower_components/foundation/scss&apos;,
  &apos;bower_components/foundation-apps/scss&apos;
];
&lt;/pre&gt;
&lt;p&gt;
After making this change, the top-bar rendered and my dropdowns worked. Since there was no jQuery in the page, I thought this might be a viable option. However, &lt;a href=&quot;http://foundation.zurb.com/forum/12220-jason-demitri&quot;&gt;Jason Demitri&lt;/a&gt; quickly pointed out it probably wouldn&apos;t work with mobile. He was right.&lt;/p&gt;
&lt;p&gt;While using F4A, I noticed that its components, and much of its look-n-feel, was different than F5. If you look at its &lt;a href=&quot;http://foundation.zurb.com/apps/app-templates/email/#!/&quot;&gt;Email App template&lt;/a&gt;, you&apos;ll see it looks kinda like a mobile app, even in a desktop browser. After trying F4A myself, I decided that F4A wasn&apos;t for us.  First of all, it doesn&apos;t seem to provide a consistent look and feel with a website that&apos;s written using F5. Furthermore, F4A only supports IE10+. In the healthcare industry, there&apos;s a lot of older browsers out there, so my client needs to support IE9 as a minimum.&lt;/p&gt;
&lt;p&gt;For these reasons, I decided to try &lt;a href=&quot;http://pineconellc.github.io/angular-foundation/&quot;&gt;Angular directives for Foundation&lt;/a&gt;. I took a prototype I&apos;d written with F5, removed its JavaScript, added Angular Foundation + Foundation dependencies to bower.json, added references to the respective scripts in index.html and added &apos;mm.foundation&apos; as a dependency in app.js. The experiment worked beautifully and I was quite happy with the results. I shared my findings with the team and we decided &lt;strong&gt;Angular Foundation is the best way to integrate Foundation and AngularJS&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;F4A is pretty new and I imagine it&apos;ll add more of F5&apos;s features as it evolves. However, I don&apos;t know if the two will ever be so similar that they can live side-by-side and allow a seamless experience for users. If you&apos;re interested in mixing F4A and F5, you might want to watch Jason Demitri&apos;s &lt;a href=&quot;https://github.com/RelutionDev/foundationUltra&quot;&gt;foundationUltra&lt;/a&gt;. This project combines Angular Foundation, Foundation for Sites, Foundation for Apps and Font Awesome. You can see a demo at &lt;a href=&quot;http://relutiondev.github.io/foundationUltra/&quot;&gt;http://relutiondev.github.io/foundationUltra/&lt;/a&gt;.</description>          </item>
    <item>
    <guid isPermaLink="true">https://raibledesigns.com/rd/entry/best_practices_for_using_foundation</guid>
    <title>Best Practices for using Foundation with AngularJS</title>
    <dc:creator>Matt Raible</dc:creator>
    <link>https://raibledesigns.com/rd/entry/best_practices_for_using_foundation</link>
        <pubDate>Thu, 5 Feb 2015 09:21:50 -0700</pubDate>
    <category>The Web</category>
    <category>foundation</category>
    <category>css</category>
    <category>javascript</category>
    <category>angularjs</category>
    <atom:summary type="html">&lt;p&gt;
&lt;a href=&quot;http://www.htmlxprs.com/post/46/what-you-need-to-know-about-zurb-foundation-for-apps&quot;&gt;&lt;img src=&quot;/repository/images/foundation-for-apps-angularjs.jpg&quot; width=&quot;200&quot; alt=&quot;What You Need To Know About Zurb Foundation for Apps&quot; class=&quot;picture&quot;&gt;&lt;/a&gt;
I was recently tasked with doing some research to figure out the best way to use &lt;a
    href=&quot;http://foundation.zurb.com/&quot;&gt;Foundation&lt;/a&gt; with &lt;a href=&quot;https://angularjs.org/&quot;&gt;AngularJS&lt;/a&gt;. Goals for
    this research included:&lt;/p&gt;
&lt;ol&gt;
    &lt;li&gt;Identify use cases of Foundation for Sites vs Foundation for Apps and recommend when to use each.&lt;/li&gt;
    &lt;li&gt;Look at pros and cons of using AngularJS with Foundation for Sites.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I&apos;m writing this blog post to get feedback from you, fellow web developers, on your experience with Foundation. Have
    you tried using Foundation for Sites with AngularJS? If so, did you experience any pain?&lt;/p&gt;
&lt;p&gt;From what I can tell, it looks like Foundation for Apps (FA) was created because folks had issues making AngularJS
    and Foundation 5 play nicely together. &lt;a href=&quot;http://zurb.com/article/1312/the-next-foundation&quot;&gt;The Next
        Foundation&lt;/a&gt; explains why FA was created. Reddit&apos;s web_design zone has quite a few &lt;a
        href=&quot;http://www.reddit.com/r/web_design/comments/26btc4/zurb_the_next_foundation_foundation_built_with/&quot;&gt;comments
        related to this article&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;From there, I found a few ZURB blog posts that describe FA&apos;s three main advantages over Foundation for Sites
    (FS):&lt;/p&gt;

&lt;ol&gt;
    &lt;li&gt;&lt;a href=&quot;http://zurb.com/article/1333/foundation-a-new-grid&quot;&gt;A New Grid&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://zurb.com/article/1340/foundation-for-apps-motion-ui-is-the-new-&quot;&gt;Motion UI&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://zurb.com/article/1345/design-amazing-single-page-apps-with-the-&quot;&gt;AngularJS Integration&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href=&quot;http://foundation.zurb.com/forum/posts/21134-foundation-5-and-foundation-apps-differences&quot;&gt;This thread
    on the Foundation forums&lt;/a&gt; seems to indicate that FA would be good for developing applications while FS would be
    good for an intranet built on WordPress (since it&apos;s more of a website than a webapp). &lt;/p&gt;</atom:summary>        <description>&lt;p&gt;
&lt;a href=&quot;http://www.htmlxprs.com/post/46/what-you-need-to-know-about-zurb-foundation-for-apps&quot;&gt;&lt;img src=&quot;/repository/images/foundation-for-apps-angularjs.jpg&quot; width=&quot;200&quot; alt=&quot;What You Need To Know About Zurb Foundation for Apps&quot; class=&quot;picture&quot;&gt;&lt;/a&gt;
I was recently tasked with doing some research to figure out the best way to use &lt;a
    href=&quot;http://foundation.zurb.com/&quot;&gt;Foundation&lt;/a&gt; with &lt;a href=&quot;https://angularjs.org/&quot;&gt;AngularJS&lt;/a&gt;. Goals for
    this research included:&lt;/p&gt;
&lt;ol&gt;
    &lt;li&gt;Identify use cases of Foundation for Sites vs Foundation for Apps and recommend when to use each.&lt;/li&gt;
    &lt;li&gt;Look at pros and cons of using AngularJS with Foundation for Sites.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I&apos;m writing this blog post to get feedback from you, fellow web developers, on your experience with Foundation. Have
    you tried using Foundation for Sites with AngularJS? If so, did you experience any pain?&lt;/p&gt;
&lt;p&gt;From what I can tell, it looks like Foundation for Apps (FA) was created because folks had issues making AngularJS
    and Foundation 5 play nicely together. &lt;a href=&quot;http://zurb.com/article/1312/the-next-foundation&quot;&gt;The Next
        Foundation&lt;/a&gt; explains why FA was created. Reddit&apos;s web_design zone has quite a few &lt;a
        href=&quot;http://www.reddit.com/r/web_design/comments/26btc4/zurb_the_next_foundation_foundation_built_with/&quot;&gt;comments
        related to this article&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;From there, I found a few ZURB blog posts that describe FA&apos;s three main advantages over Foundation for Sites
    (FS):&lt;/p&gt;

&lt;ol&gt;
    &lt;li&gt;&lt;a href=&quot;http://zurb.com/article/1333/foundation-a-new-grid&quot;&gt;A New Grid&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://zurb.com/article/1340/foundation-for-apps-motion-ui-is-the-new-&quot;&gt;Motion UI&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://zurb.com/article/1345/design-amazing-single-page-apps-with-the-&quot;&gt;AngularJS Integration&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href=&quot;http://foundation.zurb.com/forum/posts/21134-foundation-5-and-foundation-apps-differences&quot;&gt;This thread
    on the Foundation forums&lt;/a&gt; seems to indicate that FA would be good for developing applications while FS would be
    good for an intranet built on WordPress (since it&apos;s more of a website than a webapp). &lt;/p&gt;

&lt;blockquote class=&quot;quote&quot;&gt;
    Foundation for apps is for making responsive web apps vs responsive web sites. The difference is in the structure
    of an app. They usually take up the screen and instead of the page scrolling, content in the app scrolls. Apps
    employ stateful views, so a view can change without reloading the app, creating a better user experience.
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href=&quot;http://foundation.zurb.com/forum/posts/16594-foundation-for-sites-to-foundation-for-apps-preparation&quot;&gt;This
    other thread&lt;/a&gt; backs up that notion.&lt;/p&gt;

&lt;blockquote class=&quot;quote&quot;&gt;
    &lt;p style=&quot;margin-top: 0&quot;&gt;Foundation for Apps will be an additional version of Foundation - not a replacement.&lt;/p&gt;

    &lt;p&gt;This means Foundation for Sites will continue on along side the new Apps version. Ink will be Foundation for
        Emails.&lt;/p&gt;

    &lt;p&gt;Having said that, choosing which one to use simply depends on the type of site you need to build. The Apps grid
        is better suited to make web apps or apps that can be wrapped for native.&lt;/p&gt;

    &lt;p&gt;The syntax is different (on purpose) and we are taking great care to make it easy to get.&lt;/p&gt;

    &lt;p style=&quot;margin-bottom: 0&quot;&gt;You should continue using Foundation for sites as long as you need traditional scrolling websites. We don&apos;t
        expect people to convert an existing site to an app unless they need to re-do their site anyways.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I also found &lt;a href=&quot;https://github.com/pineconellc/angular-foundation&quot;&gt;Angular Foundation&lt;/a&gt; which is a &quot;a port of
    the AngularUI team&apos;s excellent angular-bootstrap project for use in the Foundation framework&quot;.&lt;/p&gt;

&lt;p&gt;The problem I see with using Angular Foundation over Foundation for Apps is that it&apos;s maintained by folks that aren&apos;t
    developing Foundation. It&apos;s more of a &quot;here&apos;s some Bootstrap widgets as Foundation widgets&quot;.&lt;/p&gt;

&lt;p&gt;It could also come down to IE support. Angular Foundation &lt;a href=&quot;http://foundation.zurb.com/docs/compatibility.html&quot;&gt;supports IE9+&lt;/a&gt; while Foundation for Apps is IE10+. From &lt;a
    href=&quot;http://www.htmlxprs.com/post/46/what-you-need-to-know-about-zurb-foundation-for-apps&quot;&gt;What You Need To Know
    About Zurb Foundation for Apps&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote class=&quot;quote&quot;&gt;
    &lt;p style=&quot;margin-top: 0&quot;&gt;Foundation for Apps works with all of the modern browsers including Internet Explorer 10. It doesn&apos;t support IE9
        and other older browsers because of the new CSS3 features and issues of AngularJS in older browsers. Below is
        the snapshot of the compatibility list grabbed from Zurb website.&lt;/p&gt;

    &lt;p style=&quot;margin-bottom: 0&quot;&gt;&lt;img src=&quot;//raibledesigns.com/repository/images/compatibility-foundation-for-apps.jpg&quot; alt=&quot;Foundation for Apps compatibility&quot; width=&quot;100%&quot;&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I wrote this article in order to publish my research findings about Foundation for Apps vs. Foundation for Sites. It
seems like the natural thing to do is to use FA for any webapps we create and FS for any websites (e.g. WordPress sites). Do you agree with these findings?
Any other &lt;em&gt;recommended practices&lt;/em&gt; for integrating Foundation with AngularJS?
&lt;/p&gt;
&lt;p class=&quot;alert alert-info&quot;&gt;&lt;strong&gt;Sidenote&lt;/strong&gt;: For the project I&apos;m working on, we haven&apos;t chose a backend framework yet. We&apos;ve chosen &lt;a href=&quot;https://www.heroku.com/&quot;&gt;Heroku&lt;/a&gt; as our deployment
    platform, so there&apos;s a plethora of languages and frameworks to choose from.
    It&apos;s too bad &lt;a href=&quot;https://github.com/jhipster/generator-jhipster/issues/1039&quot;&gt;JHipster doesn&apos;t support
    Foundation&lt;/a&gt;. I could probably sell the team on Java + Spring Boot pretty easily if it did.&lt;/p&gt;
</description>          </item>
    <item>
    <guid isPermaLink="true">https://raibledesigns.com/rd/entry/the_art_of_angularjs_in</guid>
    <title>The Art of AngularJS in 2015</title>
    <dc:creator>Matt Raible</dc:creator>
    <link>https://raibledesigns.com/rd/entry/the_art_of_angularjs_in</link>
        <pubDate>Wed, 4 Feb 2015 09:14:57 -0700</pubDate>
    <category>The Web</category>
    <category>denver</category>
    <category>angularjs</category>
    <category>grunt</category>
    <category>dosug</category>
    <category>http2</category>
    <category>coffeescript</category>
    <category>protractor</category>
    <category>javascript</category>
    <category>html5</category>
    <category>jasmine</category>
    <atom:summary type="html">&lt;p&gt;I&apos;ve been tracking statistics on jobs and skills for JavaScript MVC frameworks ever since I &lt;a href=&quot;//raibledesigns.com/rd/entry/devoxx_france_a_great_conference&quot;&gt;Compared JVM Web Frameworks at Devoxx
    France in 2013&lt;/a&gt;. At that time, Backbone was the dominant framework.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm8.staticflickr.com/7452/16255644670_e426fb455f_c.jpg&quot; title=&quot;2013 Dice Jobs for JavaScript MVC Frameworks by Matt Raible, on Flickr&quot; rel=&quot;lightbox[artofangular2015]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/16255644670&quot;&gt;&lt;img src=&quot;https://farm8.staticflickr.com/7452/16255644670_e426fb455f_m.jpg&quot; width=&quot;240&quot; alt=&quot;2013 Dice Jobs for JavaScript MVC Frameworks&quot; style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;

    &lt;a href=&quot;https://farm8.staticflickr.com/7411/16255384478_67712c17dd_c.jpg&quot; title=&quot;2013 LinkedIn Skills for JavaScript MVC Frameworks by Matt Raible, on Flickr&quot; rel=&quot;lightbox[artofangular2015]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/16255384478&quot;&gt;&lt;img src=&quot;https://farm8.staticflickr.com/7411/16255384478_67712c17dd_m.jpg&quot; width=&quot;240&quot; alt=&quot;2013 LinkedIn Skills for JavaScript MVC Frameworks&quot; style=&quot;border: 1px solid black; margin-left: 15px&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;Last year, I updated those statistics for a &lt;a href=&quot;//raibledesigns.com/rd/entry/the_art_of_angularjs&quot;&gt;presentation
    on AngularJS&lt;/a&gt; at Denver&apos;s Derailed. Angular had a similar amount of jobs as Backbone and a lot of people added it
    to their LinkedIn profiles. I found that Ember had grown around 300%, Backbone 200% and Angular 1000%!&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm8.staticflickr.com/7381/16256817639_b1ea05213a_c.jpg&quot; title=&quot;2014 Dice Jobs for JavaScript MVC Frameworks by Matt Raible, on Flickr&quot; rel=&quot;lightbox[artofangular2015]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/16256817639&quot;&gt;&lt;img src=&quot;https://farm8.staticflickr.com/7381/16256817639_b1ea05213a_m.jpg&quot; width=&quot;240&quot; alt=&quot;2014 Dice Jobs for JavaScript MVC Frameworks&quot; style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
    &lt;a href=&quot;https://farm8.staticflickr.com/7381/16443061465_e89eda261c_c.jpg&quot; title=&quot;2014 LinkedIn Skills for JavaScript MVC Frameworks by Matt Raible, on Flickr&quot; rel=&quot;lightbox[artofangular2015]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/16443061465&quot;&gt;&lt;img src=&quot;https://farm8.staticflickr.com/7381/16443061465_e89eda261c_m.jpg&quot; width=&quot;240&quot; alt=&quot;2014 LinkedIn Skills for JavaScript MVC Frameworks&quot; style=&quot;border: 1px solid black; margin-left: 15px&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;Before presenting on AngularJS at &lt;a href=&quot;http://www.meetup.com/DOSUG1/events/219099019/&quot;&gt;last night&apos;s Denver Open
    Source Users Group&lt;/a&gt;, I updated these statistics once again. The charts below show how the number of jobs for
    Angular has doubled in the last year, while jobs for Ember and Backbone have fallen slightly. As far as skills,
    developers learning Ember and Backbone has increased 200%, while skilled Angular folks has risen 400%.&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm9.staticflickr.com/8637/16443001655_fb8593e2f3_c.jpg&quot; title=&quot;2015 Dice Jobs for JavaScript MVC Frameworks by Matt Raible, on Flickr&quot; rel=&quot;lightbox[artofangular2015]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/16443001655&quot;&gt;&lt;img src=&quot;https://farm9.staticflickr.com/8637/16443001655_fb8593e2f3_m.jpg&quot; width=&quot;240&quot; alt=&quot;2015 Dice Jobs for JavaScript MVC Frameworks&quot; style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
    &lt;a href=&quot;https://farm9.staticflickr.com/8628/16257092107_c4a9735b65_c.jpg&quot; title=&quot;2015 LinkedIn Skills for JavaScript MVC Frameworks by Matt Raible, on Flickr&quot; rel=&quot;lightbox[artofangular2015]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/16257092107&quot;&gt;&lt;img src=&quot;https://farm9.staticflickr.com/8628/16257092107_c4a9735b65_m.jpg&quot; width=&quot;240&quot; alt=&quot;2015 LinkedIn Skills for JavaScript MVC Frameworks&quot; style=&quot;border: 1px solid black; margin-left: 15px&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;Yes, AngularJS has experienced &lt;em&gt;huge&lt;/em&gt; growth in the last couple of years. You might even say it&apos;s the &lt;em&gt;Struts
    of the JavaScript world&lt;/em&gt;.
&lt;/p&gt;&lt;p&gt;For the presentation I delivered last night, I made a number of improvements over last year&apos;s. I added a live coding
    demo based on my &lt;a href=&quot;&quot;&gt;Getting Started with AngularJS&lt;/a&gt; tutorial. I used IntelliJ&apos;s &lt;a href=&quot;https://www.jetbrains.com/idea/help/live-templates.html&quot;&gt;live templates&lt;/a&gt; to make it look easy. However,
    since the audience was quiet, and some were falling asleep, I skipped over the &lt;a href=&quot;//raibledesigns.com/rd/entry/testing_angularjs_applications&quot;&gt;testing&lt;/a&gt; demo.&lt;/p&gt;
</atom:summary>        <description>&lt;p&gt;I&apos;ve been tracking statistics on jobs and skills for JavaScript MVC frameworks ever since I &lt;a href=&quot;//raibledesigns.com/rd/entry/devoxx_france_a_great_conference&quot;&gt;Compared JVM Web Frameworks at Devoxx
    France in 2013&lt;/a&gt;. At that time, Backbone was the dominant framework.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm8.staticflickr.com/7452/16255644670_e426fb455f_c.jpg&quot; title=&quot;2013 Dice Jobs for JavaScript MVC Frameworks by Matt Raible, on Flickr&quot; rel=&quot;lightbox[artofangular2015]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/16255644670&quot;&gt;&lt;img src=&quot;https://farm8.staticflickr.com/7452/16255644670_e426fb455f.jpg&quot; width=&quot;500&quot; alt=&quot;2013 Dice Jobs for JavaScript MVC Frameworks&quot; style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm8.staticflickr.com/7411/16255384478_67712c17dd_c.jpg&quot; title=&quot;2013 LinkedIn Skills for JavaScript MVC Frameworks by Matt Raible, on Flickr&quot; rel=&quot;lightbox[artofangular2015]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/16255384478&quot;&gt;&lt;img src=&quot;https://farm8.staticflickr.com/7411/16255384478_67712c17dd.jpg&quot; width=&quot;500&quot; alt=&quot;2013 LinkedIn Skills for JavaScript MVC Frameworks&quot; style=&quot;border: 1px solid black&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;Last year, I updated those statistics for a &lt;a href=&quot;//raibledesigns.com/rd/entry/the_art_of_angularjs&quot;&gt;presentation
    on AngularJS&lt;/a&gt; at Denver&apos;s Derailed. Angular had a similar amount of jobs as Backbone and a lot of people added it
    to their LinkedIn profiles. I found that Ember had grown around 300%, Backbone 200% and Angular 1000%!&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm8.staticflickr.com/7381/16256817639_b1ea05213a_c.jpg&quot; title=&quot;2014 Dice Jobs for JavaScript MVC Frameworks by Matt Raible, on Flickr&quot; rel=&quot;lightbox[artofangular2015]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/16256817639&quot;&gt;&lt;img src=&quot;https://farm8.staticflickr.com/7381/16256817639_b1ea05213a.jpg&quot; width=&quot;500&quot; alt=&quot;2014 Dice Jobs for JavaScript MVC Frameworks&quot; style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm8.staticflickr.com/7381/16443061465_e89eda261c_c.jpg&quot; title=&quot;2014 LinkedIn Skills for JavaScript MVC Frameworks by Matt Raible, on Flickr&quot; rel=&quot;lightbox[artofangular2015]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/16443061465&quot;&gt;&lt;img src=&quot;https://farm8.staticflickr.com/7381/16443061465_e89eda261c.jpg&quot; width=&quot;500&quot; alt=&quot;2014 LinkedIn Skills for JavaScript MVC Frameworks&quot; style=&quot;border: 1px solid black&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;Before presenting on AngularJS at &lt;a href=&quot;http://www.meetup.com/DOSUG1/events/219099019/&quot;&gt;last night&apos;s Denver Open
    Source Users Group&lt;/a&gt;, I updated these statistics once again. The charts below show how the number of jobs for
    Angular has doubled in the last year, while jobs for Ember and Backbone have fallen slightly. As far as skills,
    developers learning Ember and Backbone has increased 200%, while skilled Angular folks has risen 400%.&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm9.staticflickr.com/8637/16443001655_fb8593e2f3_c.jpg&quot; title=&quot;2015 Dice Jobs for JavaScript MVC Frameworks by Matt Raible, on Flickr&quot; rel=&quot;lightbox[artofangular2015]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/16443001655&quot;&gt;&lt;img src=&quot;https://farm9.staticflickr.com/8637/16443001655_fb8593e2f3.jpg&quot; width=&quot;500&quot; alt=&quot;2015 Dice Jobs for JavaScript MVC Frameworks&quot; style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm9.staticflickr.com/8628/16257092107_c4a9735b65_c.jpg&quot; title=&quot;2015 LinkedIn Skills for JavaScript MVC Frameworks by Matt Raible, on Flickr&quot; rel=&quot;lightbox[artofangular2015]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/16257092107&quot;&gt;&lt;img src=&quot;https://farm9.staticflickr.com/8628/16257092107_c4a9735b65.jpg&quot; width=&quot;500&quot; alt=&quot;2015 LinkedIn Skills for JavaScript MVC Frameworks&quot; style=&quot;border: 1px solid black&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;Yes, AngularJS has experienced &lt;em&gt;huge&lt;/em&gt; growth in the last couple of years. You might even say it&apos;s the &lt;em&gt;Struts
    of the JavaScript world&lt;/em&gt;. I like to say that &lt;a href=&quot;http://www.infoq.com/news/2013/04/struts1-eol&quot;&gt;Struts 1.x was the &apos;Killer App&apos; for J2EE&lt;/a&gt; back in the day.
&lt;/p&gt;&lt;p&gt;For the presentation I delivered last night, I made a number of improvements over last year&apos;s. I added a live coding
    demo based on my &lt;a href=&quot;&quot;&gt;Getting Started with AngularJS&lt;/a&gt; tutorial. I used IntelliJ&apos;s &lt;a href=&quot;https://www.jetbrains.com/idea/help/live-templates.html&quot;&gt;live templates&lt;/a&gt; to make it look easy. However,
    since the audience was quiet, and some were falling asleep, I skipped over the &lt;a href=&quot;//raibledesigns.com/rd/entry/testing_angularjs_applications&quot;&gt;testing&lt;/a&gt; demo.&lt;/p&gt;
&lt;p&gt;I added a few slides on &lt;a href=&quot;http://foundation.zurb.com/apps/&quot;&gt;Foundation for Apps&lt;/a&gt; (FA). We&apos;ve selected
    AngularJS and Foundation on my current project, and I&apos;ve been researching how to integrate the two lately. FA is one
    solution I&apos;ve found, &lt;a href=&quot;http://pineconellc.github.io/angular-foundation/&quot;&gt;Angular Foundation&lt;/a&gt; is
    another. If you know of others, please let me know.&lt;/p&gt;
&lt;p&gt;For Java developers getting started with Angular, I recommended &lt;a href=&quot;http://jhipster.github.io/&quot;&gt;JHipster&lt;/a&gt;. I
    talked about its foundational frameworks and project options when creating your project. I included screenshots of
    its slick metrics UI and code generation features.&lt;/p&gt;
&lt;p&gt;I also added a slide for Dave Syer&apos;s excellent five-part series on Spring and AngularJS:&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;&lt;a href=&quot;http://spring.io/blog/2015/01/12/spring-and-angular-js-a-secure-single-page-application&quot;&gt;Spring and
        Angular JS: A Secure Single Page Application&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://spring.io/blog/2015/01/12/the-login-page-angular-js-and-spring-security-part-ii&quot;&gt;The Login Page:
        Angular JS and Spring Security Part II&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://spring.io/blog/2015/01/20/the-resource-server-angular-js-and-spring-security-part-iii&quot;&gt;The
        Resource Server: Angular JS and Spring Security Part III&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://spring.io/blog/2015/01/28/the-api-gateway-pattern-angular-js-and-spring-security-part-iv&quot;&gt;The
        API Gateway Pattern: Angular JS and Spring Security Part IV&lt;/a&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://spring.io/blog/2015/02/03/sso-with-oauth2-angular-js-and-spring-security-part-v&quot;&gt;SSO with
        OAuth2: Angular JS and Spring Security Part V&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Finally, I added a number of slides on &lt;a href=&quot;http://www.infoq.com/news/2014/10/angular-2-atscript&quot;&gt;Angular 2.0&lt;/a&gt;.
    I encouraged folks to checkout &lt;a href=&quot;http://12factor.net/&quot;&gt;The Twelve-Factor App&lt;/a&gt; and James Ward&apos;s
    &lt;a href=&quot;http://www.jamesward.com/2014/12/03/java-doesnt-suck-youre-just-using-it-wrong&quot;&gt;Java Doesn&#8217;t Suck &#8211; You&#8217;re Just Using it Wrong&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;The discussion with the audience was great, particularly around HTTP/2 and minification/concatenation of assets.
    Thanks to all who attended, I really enjoyed having the opportunity to share what I&apos;ve learned.&lt;/p&gt;
&lt;p&gt;You can click through my presentation below, download it from &lt;a href=&quot;//raibledesigns.com/rd/page/publications&quot;&gt;my
    presentations page&lt;/a&gt;, or view it &lt;a href=&quot;http://www.slideshare.net/mraible/the-art-of-angularjs-in-2015&quot;&gt;on SlideShare&lt;/a&gt;.
&lt;/p&gt;
&lt;div style=&quot;text-align: center&quot;&gt;
    &lt;iframe src=&quot;//www.slideshare.net/slideshow/embed_code/44244006&quot; width=&quot;512&quot; height=&quot;325&quot; frameborder=&quot;0&quot; marginwidth=&quot;0&quot; marginheight=&quot;0&quot; scrolling=&quot;no&quot; style=&quot;border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;If you live in Denver, there&apos;s a number of interesting meetups happening in the next couple months. &lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;Monday, February 16: &lt;a href=&quot;http://www.meetup.com/HTML5-Denver-Users-Group/events/220053261/&quot;&gt;Introduction to ReactJS&lt;/a&gt; at HTML5 Denver&lt;/li&gt;
    &lt;li&gt;Tuesday &amp;amp; Wednesday, March 3rd and 4th: &lt;a href=&quot;http://thingmonk.com/&quot;&gt;ThingMonk&lt;/a&gt; at &lt;a href=&quot;http://www.drinkmilehighspirits.com/&quot;&gt;&lt;em&gt;a distillery!&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;Wednesday, April 8: I&apos;ll be speaking about JHipster at Denver Java User Group&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&apos;m also looking for speakers to teach programming to kids at &lt;a href=&quot;http://www.meetup.com/Devoxx4Kids-Denver/&quot;&gt;Devoxx4Kids Denver&lt;/a&gt;.
    Let me know if you have a fun topic you&apos;d like to present.&lt;/p&gt;
</description>          </item>
    <item>
    <guid isPermaLink="true">https://raibledesigns.com/rd/entry/testing_angularjs_applications</guid>
    <title>Testing AngularJS Applications</title>
    <dc:creator>Matt Raible</dc:creator>
    <link>https://raibledesigns.com/rd/entry/testing_angularjs_applications</link>
        <pubDate>Mon, 2 Feb 2015 10:11:56 -0700</pubDate>
    <category>The Web</category>
    <category>angularjs</category>
    <category>karma</category>
    <category>javascript</category>
    <category>node</category>
    <category>jasmine</category>
    <category>git</category>
    <category>protractor</category>
    <category>npm</category>
    <atom:summary type="html">&lt;p&gt;This article is the second in a series about learning &lt;a href=&quot;https://angularjs.org/&quot;&gt;AngularJS&lt;/a&gt;. It describes
    how to test a simple AngularJS application. In a previous article, &lt;a
            href=&quot;//raibledesigns.com/rd/entry/getting_started_with_angularjs&quot;&gt;Getting Started with AngularJS&lt;/a&gt;, I
    showed how to develop a simple search and edit feature.&lt;/p&gt;
&lt;h3&gt;What you&apos;ll learn&lt;/h3&gt;
&lt;p&gt;You&apos;ll learn to use &lt;a href=&quot;http://jasmine.github.io/&quot;&gt;Jasmine&lt;/a&gt; for unit testing controllers and &lt;a
        href=&quot;http://angular.github.io/protractor/#/&quot;&gt;Protractor&lt;/a&gt; for integration testing. Angular&apos;s documentation
    has a
    good &lt;a href=&quot;https://docs.angularjs.org/guide/unit-testing&quot;&gt;developer&apos;s guide to unit testing&lt;/a&gt; if you&apos;d like
    more information on testing and why it&apos;s important. &lt;/p&gt;
&lt;p&gt;The best reason for writing tests is to automate your testing. Without tests, you&apos;ll likely be testing manually. This
    manual testing will take longer and longer as your codebase grows. &lt;/p&gt;
&lt;h3&gt;What you&apos;ll need&lt;/h3&gt;
&lt;ul&gt;
    &lt;li&gt;About 15-30 minutes&lt;/li&gt;
    &lt;li&gt;A favorite text editor or IDE. We recommend &lt;a href=&quot;https://www.jetbrains.com/idea/&quot;&gt;IntelliJ IDEA&lt;/a&gt;.
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://git-scm.com/&quot;&gt;Git&lt;/a&gt; installed.&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://nodejs.org/&quot;&gt;Node.js&lt;/a&gt; and NPM installed.&lt;/li&gt;
&lt;/ul&gt;</atom:summary>        <description>&lt;p&gt;This article is the second in a series about learning &lt;a href=&quot;https://angularjs.org/&quot;&gt;AngularJS&lt;/a&gt;. It describes
    how to test a simple AngularJS application. In a previous article, &lt;a
            href=&quot;//raibledesigns.com/rd/entry/getting_started_with_angularjs&quot;&gt;Getting Started with AngularJS&lt;/a&gt;, I
    showed how to develop a simple search and edit feature.&lt;/p&gt;
&lt;h3&gt;What you&apos;ll learn&lt;/h3&gt;
&lt;p&gt;You&apos;ll learn to use &lt;a href=&quot;http://jasmine.github.io/&quot;&gt;Jasmine&lt;/a&gt; for unit testing controllers and &lt;a
        href=&quot;http://angular.github.io/protractor/#/&quot;&gt;Protractor&lt;/a&gt; for integration testing. Angular&apos;s documentation
    has a
    good &lt;a href=&quot;https://docs.angularjs.org/guide/unit-testing&quot;&gt;developer&apos;s guide to unit testing&lt;/a&gt; if you&apos;d like
    more information on testing and why it&apos;s important. &lt;/p&gt;
&lt;p&gt;The best reason for writing tests is to automate your testing. Without tests, you&apos;ll likely be testing manually. This
    manual testing will take longer and longer as your codebase grows. &lt;/p&gt;
&lt;h3&gt;What you&apos;ll need&lt;/h3&gt;
&lt;ul&gt;
    &lt;li&gt;About 15-30 minutes&lt;/li&gt;
    &lt;li&gt;A favorite text editor or IDE. We recommend &lt;a href=&quot;https://www.jetbrains.com/idea/&quot;&gt;IntelliJ IDEA&lt;/a&gt;.
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://git-scm.com/&quot;&gt;Git&lt;/a&gt; installed.&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://nodejs.org/&quot;&gt;Node.js&lt;/a&gt; and NPM installed.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Get the tutorial project&lt;/h3&gt;
&lt;p&gt;Clone the angular-tutorial repository using &lt;a href=&quot;http://git-scm.com/&quot;&gt;git&lt;/a&gt; and
    install the dependencies. &lt;/p&gt;

&lt;pre&gt;
git clone https://github.com/mraible/angular-tutorial.git
cd angular-tutorial
npm install&lt;/pre&gt;
    &lt;p&gt;If you haven&apos;t completed the &lt;a href=&quot;//raibledesigns.com/rd/entry/getting_started_with_angularjs&quot;&gt;Getting
        Started with AngularJS&lt;/a&gt; tutorial, you should peruse it so you
        understand how this application works. You can also simply start the app with &amp;quot;npm start&amp;quot; and view it
        in your browser at &lt;a href=&quot;http://localhost:8000/app/&quot;&gt;http://localhost:8000/app/&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;Test the SearchController&lt;/h3&gt;
&lt;ol&gt;
    &lt;li&gt;
        &lt;p&gt;Create &lt;code&gt;app/search/search_test.js&lt;/code&gt; and populate it with the basic test infrastructure. This code
            sets up a mock &lt;code&gt;SearchService&lt;/code&gt; that has the first function we want to test:
            &lt;code&gt;query(term)&lt;/code&gt;. It uses &lt;code&gt;$provide&lt;/code&gt; to override the default &lt;code&gt;SearchService&lt;/code&gt;.
            &lt;a href=&quot;http://stackoverflow.com/questions/20828179/angular-unit-test-controllers-mocking-service-inside-controller&quot;&gt;Angular
                unit-test controllers - mocking service inside controller&lt;/a&gt; was a useful question on Stack Overflow
            for figuring out how to mock services in controllers.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;&apos;use strict&apos;;

describe(&apos;myApp.search module&apos;, function() {
    var mockSearchService;

    beforeEach(module(&apos;myApp.search&apos;, function($provide) {
        mockSearchService = {query: function(term) {}};
        $provide.value(&quot;SearchService&quot;, mockSearchService);
    }));
});&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Modify &lt;code&gt;karma.conf.js&lt;/code&gt; (in the root directory) to add the search implementation and test.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;files : [
  ...
  &apos;app/components/**/*.js&apos;,
  &apos;app/search/search.js&apos;,
  &apos;app/search/search_test.js&apos;,
  &apos;app/view*/**/*.js&apos;
],&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Add the first test to &lt;code&gt;search_test.js&lt;/code&gt;. This test verifies that setting a search term and
            executing the &lt;code&gt;search()&lt;/code&gt; function will call the service and return results. You can see the results returned
            from the service are mocked with
            &lt;code&gt;deferred.resolve()&lt;/code&gt;. The &lt;code&gt;deferred.resolve()&lt;/code&gt; call is &lt;a
                    href=&quot;http://entwicklertagebuch.com/blog/2013/10/how-to-handle-angularjs-promises-in-jasmine-unit-tests/&quot;&gt;how
                to handle promises in unit tests&lt;/a&gt;.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;describe(&apos;search by term&apos;, function() {
    var scope, rootScope, controller, deferred;

	// setup the controller with dependencies.
    beforeEach(inject(function($rootScope, $controller, $q) {
        rootScope = $rootScope;
        scope = $rootScope.$new();
        controller = $controller(&apos;SearchController&apos;, {$scope: scope, SearchService: mockSearchService });
        deferred = $q.defer();
    }));

    it(&apos;should search when a term is set and search() is called&apos;, function() {
        spyOn(mockSearchService, &apos;query&apos;).andReturn(deferred.promise);
        scope.term = &apos;M&apos;;
        scope.search();
        deferred.resolve({data: {name: &quot;Peyton Manning&quot;}});
        rootScope.$apply();
        expect(scope.searchResults).toEqual({name: &quot;Peyton Manning&quot;});
    });
});&lt;/pre&gt;
        &lt;p&gt;Related: &lt;a href=&quot;http://angular-tips.com/blog/2014/03/introduction-to-unit-test-spies/&quot;&gt;Introduction to
            Unit Test: Spies&lt;/a&gt; is a good introduction to using spies in unit tests.&lt;br/&gt;&lt;/p&gt;&lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Run the following command on the command line to start the Karma test runner. You can leave this process
            running and new tests will be run automatically. You can change the mocked data and expectation to see your
            test fail.&lt;/p&gt;
        &lt;pre&gt;npm test&lt;/pre&gt;
        &lt;div class=&quot;alert alert-success&quot;&gt;&lt;strong&gt;Running Tests from IntelliJ IDEA&lt;/strong&gt;&lt;br&gt;
            See &lt;a href=&quot;https://www.jetbrains.com/idea/help/running-unit-tests-on-karma.html&quot;&gt;Running Unit Tests on
                Karma&lt;/a&gt; to learn how to run your tests from IntelliJ IDEA.
        &lt;/div&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Add a test to verify a search occurs automatically when the term is in the URL. Notice how the code structure had
            to change a bit to handle &lt;code&gt;$routeParams&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;describe(&apos;search by term automatically&apos;, function() {
    var scope, rootScope, controller, location, deferred;

    beforeEach(inject(function($rootScope, $controller, $q) {
        rootScope = $rootScope;
        scope = $rootScope.$new();

        // in this case, expectations need to be setup before controller is initialized
        var routeParams = {&quot;term&quot;: &quot;peyton&quot;};
        deferred = $q.defer();
        spyOn(mockSearchService, &apos;query&apos;).andReturn(deferred.promise);
        deferred.resolve({data: {name: &quot;Peyton Manning&quot;}});

        controller = $controller(&apos;SearchController&apos;, {$scope: scope, $routeParams: routeParams, SearchService: mockSearchService });
    }));

    it(&apos;should search automatically when a term is on the URL&apos;, function() {
        rootScope.$apply();
        expect(scope.searchResults).toEqual({name: &quot;Peyton Manning&quot;});
    });
});&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Add a test to verify the &lt;code&gt;EditController&lt;/code&gt; works as expected.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;describe(&apos;edit person&apos;, function() {
    var scope, rootScope, controller, location, deferred;

    beforeEach(inject(function($rootScope, $controller, $q) {
        rootScope = $rootScope;
        scope = $rootScope.$new();

        // expectations need to be setup before controller is initialized
        var routeParams = {&quot;id&quot;: &quot;1&quot;};
        deferred = $q.defer();
        spyOn(mockSearchService, &apos;fetch&apos;).andReturn(deferred.promise);
        deferred.resolve({data: {name: &quot;Peyton Manning&quot;, address: {street: &quot;12345 Blake Street&quot;, city: &quot;Denver&quot;}}});

        controller = $controller(&apos;EditController&apos;, {$scope: scope, $routeParams: routeParams, SearchService: mockSearchService });
    }));

    it(&apos;should fetch a single record&apos;, function() {
        rootScope.$apply();
        expect(scope.person.name).toBe(&quot;Peyton Manning&quot;);
        expect(scope.person.address.street).toBe(&quot;12345 Blake Street&quot;);
    });
});&lt;/pre&gt;
&lt;p&gt;After adding this test, you&apos;ll likely see the following error in your terminal.&lt;/p&gt;
&lt;pre class=&quot;brush: shell&quot;&gt;
Chrome 40.0.2214 (Mac OS X 10.10.2) myApp.search module edit person should fetch a single record FAILED
	fetch() method does not exist
	TypeError: Cannot read property &apos;name&apos; of undefined
&lt;/pre&gt;
        &lt;p&gt;This happens because the &lt;code&gt;mockSearchService&lt;/code&gt; does not have a fetch method defined. Modify the &lt;code&gt;beforeEach()&lt;/code&gt; on line 7 to add this function.&lt;/p&gt;
    &lt;pre class=&quot;brush: js&quot;&gt;mockSearchService = {query: function(term) {}, fetch: function(id) {}};&lt;/pre&gt;
    &lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Extra Credit&lt;/h3&gt;
&lt;p&gt;Create a test for saving a person. &lt;a
        href=&quot;http://stackoverflow.com/questions/13664144/how-to-unit-test-angularjs-controller-with-location-service&quot;&gt;Here&apos;s
    a question on Stack Overflow&lt;/a&gt; that might help you verify the location after a save has been performed.&lt;/p&gt;
&lt;h3&gt;Test the Search Feature&lt;/h3&gt;
&lt;p&gt;To test if the application works end-to-end, you can write scenarios with &lt;a
        href=&quot;http://angular.github.io/protractor/&quot;&gt;Protractor&lt;/a&gt;. These are also known as integration tests, as they
    test
    the &lt;em&gt;integration&lt;/em&gt; between all layers of your application.&lt;/p&gt;
&lt;p&gt;To verify end-to-end tests work in the project before you begin, run the following command in one terminal
    window:&lt;/p&gt;
&lt;pre&gt;npm start&lt;/pre&gt;
&lt;p&gt;Then in another window, run the following to execute the tests:&lt;/p&gt;
&lt;pre&gt;npm run protractor&lt;/pre&gt;
&lt;ol&gt;
    &lt;li&gt;
        &lt;p&gt;Write your first integration test to verify you can navigate to /search and enter a search term to see
            results. Add the following to &lt;code&gt;e2e-tests/scenarios.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;describe(&apos;search&apos;, function() {
  var searchTerm = element(by.model(&apos;term&apos;));
  var searchButton = element(by.id(&apos;search&apos;));

  beforeEach(function() {
    browser.get(&apos;index.html#/search&apos;);
  });

  it(&apos;should allow searching at /search&apos;, function() {
    searchTerm.sendKeys(&quot;M&quot;);
    searchButton.click().then(function() {
      expect(element.all(by.repeater(&apos;person in searchResults&apos;)).count()).toEqual(3);
    });
  });
});&lt;/pre&gt;
        &lt;p&gt;The &amp;quot;searchTerm&amp;quot; variable represents the input field. The &lt;code&gt;by.model()&lt;/code&gt; syntax binds to
            the &amp;quot;ng-model&amp;quot; attribute you defined in the HTML. &lt;/p&gt;&lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Run &amp;quot;npm run protractor&amp;quot;. This should fail with the following error.&lt;/p&gt;
&lt;pre class=&quot;brush: bash&quot;&gt;[launcher] Running 1 instances of WebDriver
Selenium standalone server started at http://172.16.6.39:64230/wd/hub
...F
Failures:
  1) my app search should allow searching at /search
   Message:
     NoSuchElementError: No element found using locator: By.id(&quot;search&quot;)&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;To fix, you need to add an &amp;quot;id&amp;quot; attribute to the Search button in
            &lt;code&gt;app/search/index.html&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;brush: xml&quot;&gt;
&amp;lt;form ng-submit=&quot;search()&quot;&amp;gt;
    &amp;lt;input type=&quot;search&quot; name=&quot;search&quot; ng-model=&quot;term&quot;&amp;gt;
    &amp;lt;button id=&quot;search&quot;&amp;gt;Search&amp;lt;/button&amp;gt;
&amp;lt;/form&amp;gt;
&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;Run the tests again using &amp;quot;npm run protractor&amp;quot;. They should all pass this time.&lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Write another test to verify editing a user displays their information.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;describe(&apos;edit person&apos;, function() {
  var name = element(by.model(&apos;person.name&apos;));
  var street = element(by.model(&apos;person.address.street&apos;));
  var city = element(by.model(&apos;person.address.city&apos;));

  beforeEach(function() {
    browser.get(&apos;index.html#/edit/1&apos;);
  });

  it(&apos;should allow viewing a person&apos;, function() {
    // getText() doesn&apos;t work with input elements, see the following for more information:
    // https://github.com/angular/protractor/blob/master/docs/faq.md#the-result-of-gettext-from-an-input-element-is-always-empty
    expect(name.getAttribute(&apos;value&apos;)).toEqual(&quot;Peyton Manning&quot;);
    expect(street.getAttribute(&apos;value&apos;)).toEqual(&quot;1234 Main Street&quot;);
    expect(city.getAttribute(&apos;value&apos;)).toEqual(&quot;Greenwood Village&quot;);
  });
});&lt;/pre&gt;
        &lt;p&gt;Verify it works with &amp;quot;npm run protractor&amp;quot;.&lt;/p&gt;&lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Finally, write a test to verify you can save a person and their details are updated. Figuring out how to
            verify the URL after it changed was assisted by &lt;a href=&quot;https://github.com/angular/protractor/issues/610&quot;&gt;this
                issue&lt;/a&gt;.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;describe(&apos;save person&apos;, function() {
  var name = element(by.model(&apos;person.name&apos;));
  var save = element(by.id(&apos;save&apos;));

  beforeEach(function() {
    browser.get(&apos;index.html#/edit/1&apos;);
  });

  it(&apos;should allow updating a name&apos;, function() {
    name.sendKeys(&quot; Updated&quot;);
    save.click().then(function() {
      // verify url set back to search results
      browser.driver.wait(function() {
        return browser.driver.getCurrentUrl().then(function(url) {
          expect(url).toContain(&apos;/search/Peyton%20Manning%20Updated&apos;);
          return url;
        });
      });
      // verify one element matched this change
      expect(element.all(by.repeater(&apos;person in searchResults&apos;)).count()).toEqual(1);
    });
  });
});&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;When you run this test with &amp;quot;npm run protractor&amp;quot;, it should fail because there&apos;s no element with
            &lt;code&gt;id=&amp;quot;save&amp;quot;&lt;/code&gt; in &lt;code&gt;app/search/edit.html&lt;/code&gt;. Add it to the Save button in this
            file and try again. You should see something similar to the following:&lt;/p&gt;
&lt;pre class=&quot;brush: bash&quot;&gt;Finished in 4.478 seconds
6 tests, 9 assertions, 0 failures&lt;/pre&gt;
    &lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Source Code&lt;/h3&gt;
&lt;p&gt;A completed project with this code in it is available on GitHub at &lt;a
        href=&quot;https://github.com/mraible/angular-tutorial/&quot;&gt;https://github.com/mraible/angular-tutorial&lt;/a&gt; on the &lt;strong&gt;testing&lt;/strong&gt; branch.
&lt;/p&gt;
&lt;p&gt;There are two commits that make the changes for the two main steps in this tutorial:&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;&lt;a href=&quot;https://github.com/mraible/angular-tutorial/commit/f3551cc84bd2f0fb35b4dd9ac38cbf6f417c106f&quot;&gt;Unit Tests&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://github.com/mraible/angular-tutorial/commit/12e37eb7ad8cde82c6ec92c8562700a7fb6952cc&quot;&gt;Integration Tests&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Summary&lt;/h3&gt;
&lt;p&gt;I hope you&apos;ve enjoyed this quick-and-easy tutorial on testing AngularJS applications. The first couple AngularJS applications
I developed didn&apos;t have tests and required a lot of manual testing to verify their quality. After learning that testing AngularJS
apps is pretty easy, I now do it on all my projects. Hopefully this tutorial motivates you to do do the same.
&lt;/p&gt;</description>          </item>
    <item>
    <guid isPermaLink="true">https://raibledesigns.com/rd/entry/getting_started_with_angularjs</guid>
    <title>Getting Started with AngularJS</title>
    <dc:creator>Matt Raible</dc:creator>
    <link>https://raibledesigns.com/rd/entry/getting_started_with_angularjs</link>
        <pubDate>Thu, 29 Jan 2015 11:12:38 -0700</pubDate>
    <category>The Web</category>
    <category>npm</category>
    <category>javascript</category>
    <category>git</category>
    <category>angularjs</category>
    <category>node</category>
    <atom:summary type="html">&lt;p&gt;I was hired by my current client in November to help them choose a technology stack for developing modern web applications.
   In our first sprint, we decided to look at JavaScript MVC frameworks. I suggested &lt;a href=&quot;https://angularjs.org/&quot;&gt;AngularJS&lt;/a&gt;, &lt;a href=&quot;http://emberjs.com/&quot;&gt;Ember.js&lt;/a&gt; and &lt;a href=&quot;http://facebook.github.io/react/&quot;&gt;React&lt;/a&gt;. Since
    most of the team was new to JavaScript MVC, I decided to create a tutorial for them. I tried to make it easy so they
    could learn how to write a simple web application with AngularJS. I thought others could benefit from this article as well,
so I asked (and received) permission from my client to publish it here.&lt;/p&gt;
&lt;h3&gt;What you&apos;ll build&lt;/h3&gt;
&lt;p&gt;You&apos;ll build a simple web application with AngularJS. You&apos;ll also add search and edit features with mock data.&lt;/p&gt;
&lt;h3&gt;What you&apos;ll need&lt;/h3&gt;
&lt;ul&gt;
    &lt;li&gt;About 15-30 minutes&lt;/li&gt;
    &lt;li&gt;A favorite text editor or IDE. I recommend &lt;a href=&quot;https://www.jetbrains.com/idea/&quot;&gt;IntelliJ IDEA&lt;/a&gt;.&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://git-scm.com/&quot;&gt;Git&lt;/a&gt; installed.&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://nodejs.org/&quot;&gt;Node.js&lt;/a&gt; and NPM installed.&lt;/li&gt;
&lt;/ul&gt;</atom:summary>        <description>&lt;p&gt;I was hired by my current client in November to help them choose a technology stack for developing modern web applications.
   In our first sprint, we decided to look at JavaScript MVC frameworks. I suggested &lt;a href=&quot;https://angularjs.org/&quot;&gt;AngularJS&lt;/a&gt;, &lt;a href=&quot;http://emberjs.com/&quot;&gt;Ember.js&lt;/a&gt; and &lt;a href=&quot;http://facebook.github.io/react/&quot;&gt;React&lt;/a&gt;. Since
    most of the team was new to JavaScript MVC, I decided to create a tutorial for them. I tried to make it easy so they
    could learn how to write a simple web application with AngularJS. I thought others could benefit from this article as well,
so I asked (and received) permission from my client to publish it here.&lt;/p&gt;
&lt;h3&gt;What you&apos;ll build&lt;/h3&gt;
&lt;p&gt;You&apos;ll build a simple web application with AngularJS. You&apos;ll also add search and edit features with mock data.&lt;/p&gt;
&lt;h3&gt;What you&apos;ll need&lt;/h3&gt;
&lt;ul&gt;
    &lt;li&gt;About 15-30 minutes&lt;/li&gt;
    &lt;li&gt;A favorite text editor or IDE. I recommend &lt;a href=&quot;https://www.jetbrains.com/idea/&quot;&gt;IntelliJ IDEA&lt;/a&gt;.&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://git-scm.com/&quot;&gt;Git&lt;/a&gt; installed.&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://nodejs.org/&quot;&gt;Node.js&lt;/a&gt; and NPM installed.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Create a simple web application&lt;/h3&gt;
&lt;ol&gt;
    &lt;li&gt;
        &lt;p&gt;Clone the angular-seed repository using &lt;a href=&quot;http://git-scm.com/&quot;&gt;git&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;
git clone https://github.com/angular/angular-seed.git angular-tutorial
cd angular-tutorial&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;There are two kinds of dependencies in this project: tools and angular framework code. The tools help manage
            and test the application.&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;To get the tools that depend upon via &lt;code&gt;npm&lt;/code&gt;, the &lt;a href=&quot;https://www.npmjs.org/&quot;&gt;node
                package manager&lt;/a&gt;.
            &lt;/li&gt;
            &lt;li&gt;To get the angular code via &lt;code&gt;bower&lt;/code&gt;, a &lt;a href=&quot;http://bower.io/&quot;&gt;client-side code package
                manager&lt;/a&gt;.
            &lt;/li&gt;
        &lt;/ul&gt;
        &lt;p&gt;The project has preconfigured &lt;code&gt;npm&lt;/code&gt;&amp;nbsp;to automatically run &lt;code&gt;bower&lt;/code&gt;&amp;nbsp;so you can
            simply do:&lt;/p&gt;
        &lt;pre&gt;npm install&lt;/pre&gt;
    &lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Run the application&lt;/h3&gt;
&lt;p&gt;The project is configured with a simple development web server. The simplest way to start this server is:&lt;/p&gt;
&lt;pre&gt;npm start&lt;/pre&gt;
&lt;p&gt;Now browse to the app at &lt;code&gt;&lt;a href=&quot;http://localhost:8000/app/index.html&quot;&gt;http://localhost:8000/app/&lt;/a&gt;&lt;/code&gt;.
&lt;/p&gt;
&lt;h3&gt;Add a search feature&lt;/h3&gt;
&lt;p&gt;To add a search feature, open the project in an IDE or your favorite text editor. For IntelliJ IDEA, use File &amp;gt;
    New Project &amp;gt; Static Web and point to the directory you cloned angular-seed to.&lt;/p&gt;
&lt;h3&gt;The Basics&lt;/h3&gt;
&lt;ol&gt;
    &lt;li&gt;
        &lt;p&gt;Create an &lt;code&gt;app/search/index.html&lt;/code&gt; file with the following HTML:&lt;/p&gt;
&lt;pre class=&quot;brush: xml&quot;&gt;
&amp;lt;form ng-submit=&quot;search()&quot;&amp;gt;
    &amp;lt;input type=&quot;search&quot; name=&quot;search&quot; ng-model=&quot;term&quot;&amp;gt;
    &amp;lt;button&amp;gt;Search&amp;lt;/button&amp;gt;
&amp;lt;/form&amp;gt;
&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Create &lt;code&gt;app/search/search.js&lt;/code&gt; and define the routes (URLs) and controller for the search feature.
        &lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
angular.module(&apos;myApp.search&apos;, [&apos;ngRoute&apos;])

    .config([&apos;$routeProvider&apos;, function ($routeProvider) {
        $routeProvider
            .when(&apos;/search&apos;, {
                templateUrl: &apos;search/index.html&apos;,
                controller: &apos;SearchController&apos;
            })
    }])

    .controller(&apos;SearchController&apos;, function () {
        console.log(&quot;In Search Controller...&quot;);
    });
&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Modify &lt;code&gt;app/app.js&lt;/code&gt; and add the &amp;quot;myApp.search&amp;quot; module you created above.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;angular.module(&apos;myApp&apos;, [
  &apos;ngRoute&apos;,
  &apos;myApp.view1&apos;,
  &apos;myApp.view2&apos;,
  &apos;myApp.version&apos;,
  &apos;myApp.search&apos;
])&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Modify &lt;code&gt;app/index.html&lt;/code&gt; and add a link to the search.js file.&lt;/p&gt;
&lt;pre class=&quot;brush: xml&quot;&gt;
&amp;lt;script src=&quot;view2/view2.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;search/search.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;components/version/version.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Refresh your browser and navigate to &lt;a href=&quot;http://localhost:8000/app/#/search&quot;&gt;http://localhost:8000/app/#/search.&lt;/a&gt;
            You should see an input field and search button. You should also see a log message printed in your browser&apos;s
            console.
            In Chrome, you can view the console using View &amp;gt; Developer &amp;gt; JavaScript Console. You can make it
            easier to navigate to this page by adding a menu item in &lt;code&gt;app/index.html&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;brush: xml&quot;&gt;
&amp;lt;ul class=&quot;menu&quot;&amp;gt;
  &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#/view1&quot;&amp;gt;view1&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
  &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#/view2&quot;&amp;gt;view2&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
  &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#/search&quot;&amp;gt;search&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&lt;/pre&gt;
    &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This section has shown you how to add a new controller and view to a basic AngularJS application. This was fairly simple to create.
    The next section shows you how to create a fake backend API. &lt;/p&gt;
&lt;h3&gt;The Backend&lt;/h3&gt;
&lt;p&gt;To get search results, you&apos;re going to create a SearchService
    that makes HTTP requests. These HTTP requests will be handled by a mock backend using some of Angular&apos;s built-in
    mocking tools. The backend implementation was created using &lt;a
        href=&quot;http://www.jeremyzerr.com/angularjs-backend-less-development-using-httpbackend-mock&quot;&gt;AngularJS Backend-less Development Using a $httpBackend Mock&lt;/a&gt;.&lt;/p&gt;
&lt;ol&gt;
    &lt;li&gt;
        &lt;p&gt;Add a &lt;code&gt;SearchService&lt;/code&gt; to &lt;code&gt;app/search/search.js&lt;/code&gt;. This is done in Angular using its &lt;a
            href=&quot;https://docs.angularjs.org/guide/providers#factory-recipe&quot;&gt;Factory Recipe&lt;/a&gt;. Make sure to remove the
            semicolon from the &lt;code&gt;.controller&lt;/code&gt; code block.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;.controller(&apos;SearchController&apos;, function () {
    console.log(&quot;In Search Controller...&quot;);
})

.factory(&apos;SearchService&apos;, function ($http) {
    var service = {
        query: function (term) {
            return $http.get(&apos;/search/&apos; + term);
        }
    };
    return service;
});
&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Inject the &lt;code&gt;SearchService&lt;/code&gt; into the &lt;code&gt;SearchController&lt;/code&gt; and use it to get results from
            the backend. The form in &lt;code&gt;app/search/index.html&lt;/code&gt; calls the &lt;code&gt;search()&lt;/code&gt; function. The
            &amp;quot;term&amp;quot; is defined by the input field in this page with
            &lt;span style=&quot;color: rgb(0,0,255);&quot;&gt;ng-model=&lt;/span&gt;&lt;span
                style=&quot;color: rgb(0,128,0);&quot;&gt;&amp;quot;term&amp;quot;.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;.controller(&apos;SearchController&apos;, function ($scope, SearchService) {
    $scope.search = function () {
        console.log(&quot;Search term is: &quot; + $scope.term);
        SearchService.query($scope.term).then(function (response) {
            $scope.searchResults = response.data;
        });
    };
})&lt;/pre&gt;
        &lt;p&gt;If you try to search for a &quot;foo&quot; term now, you&apos;ll see the following error in your console.&lt;/p&gt;
        &lt;pre&gt;Search term is: foo&lt;br/&gt;GET &lt;a href=&quot;http://localhost:8000/search/foo&quot;&gt;http://localhost:8000/search/foo&lt;/a&gt; 404 (Not Found)&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Create &lt;code&gt;app/search/mock-api.js&lt;/code&gt; for the fake backend. Populate it with the following JavaScript.
        &lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;// We will be using backend-less development
// $http uses $httpBackend to make its calls to the server
// $resource uses $http, so it uses $httpBackend too
// We will mock $httpBackend, capturing routes and returning data
angular.module(&apos;myApp&apos;)
    .service(&apos;ServerDataModel&apos;, function ServerDataModel() {
        this.data = [
            {
                id: 1,
                name: &quot;Peyton Manning&quot;,
                phone: &quot;(303) 567-8910&quot;,
                address: {
                    street: &quot;1234 Main Street&quot;,
                    city: &quot;Greenwood Village&quot;,
                    state: &quot;CO&quot;,
                    zip: &quot;80111&quot;
                }
            },
            {
                id: 2,
                name: &quot;Demaryius Thomas&quot;,
                phone: &quot;(720) 213-9876&quot;,
                address: {
                    street: &quot;5555 Marion Street&quot;,
                    city: &quot;Denver&quot;,
                    state: &quot;CO&quot;,
                    zip: &quot;80202&quot;
                }
            },
            {
                id: 3,
                name: &quot;Von Miller&quot;,
                phone: &quot;(917) 323-2333&quot;,
                address: {
                    street: &quot;14 Mountain Way&quot;,
                    city: &quot;Vail&quot;,
                    state: &quot;CO&quot;,
                    zip: &quot;81657&quot;
                }
            }
        ];

        this.getData = function () {
            return this.data;
        };

        this.search = function (term) {
            if (term == &quot;&quot; || term == &quot;*&quot;) {
                return this.getData();
            }
            // find the name that matches the term
            var list = $.grep(this.getData(), function (element, index) {
                term = term.toLowerCase();
                return (element.name.toLowerCase().match(term));
            });

            if (list.length === 0) {
                return [];
            } else {
                return list;
            }
        };
    })
    .run(function ($httpBackend, ServerDataModel) {

        $httpBackend.whenGET(/\/search\/\w+/).respond(function (method, url, data) {
            // parse the matching URL to pull out the term (/search/:term)
            var term = url.split(&apos;/&apos;)[2];

            var results = ServerDataModel.search(term);

            return [200, results, {Location: &apos;/search/&apos; + term}];
        });

        $httpBackend.whenGET(/search\/index.html/).passThrough();
        $httpBackend.whenGET(/view/).passThrough();
    });&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;This file uses &lt;a href=&quot;http://api.jquery.com/jquery.grep/&quot;&gt;jQuery.grep()&lt;/a&gt;, so you&apos;ll need to install
            jQuery. Modify &lt;code&gt;bower.json&lt;/code&gt; and add jQuery to the list of dependencies. &lt;pre
        class=&quot;brush: js&quot;&gt;&quot;dependencies&quot;: {
  ...
  &quot;html5-boilerplate&quot;: &quot;~4.3.0&quot;,
  &quot;jquery&quot;: &quot;~1.10.x&quot;
}&lt;/pre&gt;
        &lt;p&gt;Stop the npm process, run
            &amp;quot;npm install&amp;quot; to download and install jQuery, then &amp;quot;npm start&amp;quot; again.&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Modify &lt;code&gt;app/app.js&lt;/code&gt;&amp;nbsp;and add a dependency on &lt;a
            href=&quot;https://docs.angularjs.org/api/ngMockE2E&quot;&gt;ngMockE2E&lt;/a&gt;.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;angular.module(&apos;myApp&apos;, [&apos;ngMockE2E&apos;,
  &apos;ngRoute&apos;,
  ...&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Modify &lt;code&gt;app/index.html&lt;/code&gt; to include references to &lt;code&gt;jquery.js&lt;/code&gt;,
            &lt;code&gt;angular-mocks.js&lt;/code&gt; and &lt;code&gt;mock-api.js&lt;/code&gt;.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
&amp;lt;script src=&quot;bower_components/angular-route/angular-route.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;bower_components/angular-mocks/angular-mocks.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;bower_components/jquery/jquery.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
...
&amp;lt;script src=&quot;search/mock-api.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;components/version/version.js&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Add the following HTML to &lt;code&gt;app/search/index.html&lt;/code&gt; to display the search results.&lt;/p&gt;
&lt;pre class=&quot;brush: xml&quot;&gt;
&amp;lt;div&amp;gt;
    &amp;lt;pre&amp;gt;{{ searchResults | json}}&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/pre&gt;
        &lt;p&gt;After making this change, you should be able to search for &amp;quot;p&amp;quot;, &amp;quot;d&amp;quot; or &amp;quot;v&amp;quot; and
            see results as JSON.&lt;/p&gt;&lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;To make the results more readable, change the above HTML to use a &lt;code&gt;&amp;lt;table&amp;gt;&lt;/code&gt; and Angular&apos;s &lt;a
            href=&quot;https://docs.angularjs.org/api/ng/directive/ngRepeat&quot;&gt;ng-repeat&lt;/a&gt; directive.&lt;/p&gt;
&lt;pre class=&quot;brush: xml&quot;&gt;
&amp;lt;table ng-show=&quot;searchResults.length&quot; style=&quot;width: 100%&quot;&amp;gt;
    &amp;lt;thead&amp;gt;
    &amp;lt;tr&amp;gt;
        &amp;lt;th&amp;gt;Name&amp;lt;/th&amp;gt;
        &amp;lt;th&amp;gt;Phone&amp;lt;/th&amp;gt;
        &amp;lt;th&amp;gt;Address&amp;lt;/th&amp;gt;
    &amp;lt;/tr&amp;gt;
    &amp;lt;/thead&amp;gt;
    &amp;lt;tbody&amp;gt;
    &amp;lt;tr ng-repeat=&quot;person in searchResults&quot;&amp;gt;
        &amp;lt;td&amp;gt;{{person.name}}&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;{{person.phone}}&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;{{person.address.street}}&amp;lt;br/&amp;gt;
            {{person.address.city}}, {{person.address.state}} {{person.address.zip}}
        &amp;lt;/td&amp;gt;
    &amp;lt;/tr&amp;gt;
    &amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&lt;/pre&gt;
    &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This section has shown you how to fetch and display search results. The next section builds on this and shows how to edit and save a record.&lt;/p&gt;
&lt;h3&gt;Add an Edit Feature&lt;/h3&gt;
&lt;ol&gt;
    &lt;li&gt;
        &lt;p&gt;Modify &lt;code&gt;app/search/index.html&lt;/code&gt;&amp;nbsp;to add a link for editing a person.&lt;/p&gt;
&lt;pre class=&quot;brush: xml&quot;&gt;
&amp;lt;table ng-show=&quot;searchResults.length&quot; style=&quot;width: 100%&quot;&amp;gt;
    ...
    &amp;lt;tr ng-repeat=&quot;person in searchResults&quot;&amp;gt;
        &amp;lt;td&amp;gt;&amp;lt;a href=&quot;&quot; ng-click=&quot;edit(person)&quot;&amp;gt;{{person.name}}&amp;lt;/a&amp;gt;&amp;lt;/td&amp;gt;
        ...
    &amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Add an &lt;code&gt;edit()&lt;/code&gt; function to &lt;code&gt;SearchController&lt;/code&gt;.
           Notice that the &lt;a href=&quot;https://docs.angularjs.org/api/ng/service/$location&quot;&gt;$location service&lt;/a&gt;
            dependency has been added to the controller&apos;s initialization function. &lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
.controller(&apos;SearchController&apos;, function ($scope, $location, SearchService) {
    ...

    $scope.edit = function (person) {
        $location.path(&quot;/edit/&quot; + person.id);
    }
})&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Create a route to handle the edit URL in &lt;code&gt;app/search/search.js&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
.config([&apos;$routeProvider&apos;, function ($routeProvider) {
    $routeProvider
        .when(&apos;/search&apos;, {
            templateUrl: &apos;search/index.html&apos;,
            controller: &apos;SearchController&apos;
        })
        .when(&apos;/edit/:id&apos;, {
            templateUrl: &apos;search/edit.html&apos;,
            controller: &apos;EditController&apos;
        });
}])&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Create &lt;code&gt;app/search/edit.html&lt;/code&gt; to display an editable form. The HTML below shows how you can use &lt;a
            href=&quot;https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Using_data_attributes&quot;&gt;HTML5&apos;s data
            attributes&lt;/a&gt; to have valid attributes instead of ng-*.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
&amp;lt;form ng-submit=&quot;save()&quot;&amp;gt;
    &amp;lt;div&amp;gt;
        &amp;lt;label for=&quot;name&quot;&amp;gt;Name:&amp;lt;/label&amp;gt;
        &amp;lt;input type=&quot;text&quot; data-ng-model=&quot;person.name&quot; id=&quot;name&quot;&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div&amp;gt;
        &amp;lt;label for=&quot;phone&quot;&amp;gt;Phone:&amp;lt;/label&amp;gt;
        &amp;lt;input type=&quot;text&quot; data-ng-model=&quot;person.phone&quot; id=&quot;phone&quot;&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;fieldset&amp;gt;
        &amp;lt;legend&amp;gt;Address:&amp;lt;/legend&amp;gt;
        &amp;lt;address style=&quot;margin-left: 50px&quot;&amp;gt;
            &amp;lt;input type=&quot;text&quot; data-ng-model=&quot;person.address.street&quot;&amp;gt;&amp;lt;br/&amp;gt;
            &amp;lt;input type=&quot;text&quot; data-ng-model=&quot;person.address.city&quot;&amp;gt;,
            &amp;lt;input type=&quot;text&quot; data-ng-model=&quot;person.address.state&quot; size=&quot;2&quot;&amp;gt;
            &amp;lt;input type=&quot;text&quot; data-ng-model=&quot;person.address.zip&quot; size=&quot;5&quot;&amp;gt;
        &amp;lt;/address&amp;gt;
    &amp;lt;/fieldset&amp;gt;
    &amp;lt;div&amp;gt;
        &amp;lt;button type=&quot;submit&quot;&amp;gt;Save&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/form&amp;gt;
&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Modify &lt;code&gt;SearchService&lt;/code&gt;&amp;nbsp;to contain functions for finding a person by their id, and saving
            them.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
.factory(&apos;SearchService&apos;, function ($http) {
    var service = {
        query: function (term) {
            return $http.get(&apos;/search/&apos; + term);
        },
        fetch: function (id) {
            return $http.get(&apos;/edit/&apos; + id);
        },
        save: function(data) {
            return $http.post(&apos;/edit/&apos; + data.id, data);
        }
    };
    return service;
});&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Create &lt;code&gt;EditController&lt;/code&gt; in &lt;code&gt;app/search/search.js&lt;/code&gt;. &lt;a
            href=&quot;https://docs.angularjs.org/api/ngRoute/service/$routeParams&quot;&gt;$routeParams&lt;/a&gt; is an Angular service
            that allows you to grab parameters from a URL.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
.controller(&apos;EditController&apos;, function ($scope, $location, $routeParams, SearchService) {
    SearchService.fetch($routeParams.id).then(function (response) {
        $scope.person = response.data;
    });

    $scope.save = function() {
        SearchService.save($scope.person).then(function(response) {
            $location.path(&quot;/search/&quot; + $scope.person.name);
        });
    }
})&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;At the bottom of &lt;code&gt;app/search/mock-api.js&lt;/code&gt;, in its &lt;code&gt;run()&lt;/code&gt; function, add the following:
        &lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;$httpBackend.whenGET(/\/search/).respond(function (method, url, data) {
    var results = ServerDataModel.search(&quot;&quot;);

    return [200, results];
});

$httpBackend.whenGET(/\/edit\/\d+/).respond(function (method, url, data) {
    // parse the matching URL to pull out the id (/edit/:id)
    var id = url.split(&apos;/&apos;)[2];

    var results = ServerDataModel.find(id);

    return [200, results, {Location: &apos;/edit/&apos; + id}];
});

$httpBackend.whenPOST(/\/edit\/\d+/).respond(function(method, url, data) {
    var params = angular.fromJson(data);

    // parse the matching URL to pull out the id (/edit/:id)
    var id = url.split(&apos;/&apos;)[2];

    var person = ServerDataModel.update(id, params);

    return [201, person, { Location: &apos;/edit/&apos; + id }];
});

$httpBackend.whenGET(/search\/edit.html/).passThrough();&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Further up in the same file, add &lt;code&gt;find()&lt;/code&gt; and &lt;code&gt;update()&lt;/code&gt; methods to &lt;code&gt;ServerDataModel&lt;/code&gt;.
        &lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;this.getData = function () {
    return this.data;
};

this.search = function (term) {
    ...
};

this.find = function (id) {
    // find the game that matches that id
    var list = $.grep(this.getData(), function (element, index) {
        return (element.id == id);
    });
    if (list.length === 0) {
        return {};
    }
    // even if list contains multiple items, just return first one
    return list[0];
};

this.update = function (id, dataItem) {
    // find the game that matches that id
    var people = this.getData();
    var match = null;
    for (var i = 0; i &lt; people.length; i++) {
        if (people[i].id == id) {
            match = people[i];
            break;
        }
    }
    if (!angular.isObject(match)) {
        return {};
    }
    angular.extend(match, dataItem);
    return match;
};&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;The &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; in &lt;code&gt;app/search/edit.html&lt;/code&gt;&amp;nbsp;calls a &lt;code&gt;save()&lt;/code&gt; function to update a
            person&apos;s data. You already implemented this above. The function executes the logic below.&amp;nbsp;&lt;/p&gt;
        &lt;pre class=&quot;brush: js&quot;&gt;$location.path(&quot;/search/&quot; + $scope.person.name);&lt;/pre&gt;
        &lt;p&gt;Since the SearchController doesn&apos;t execute a search automatically when you execute this URL, add the
            following logic to do so in &lt;code&gt;app/search/search.js&lt;/code&gt;. Note that
            &lt;code&gt;$routeParams&lt;/code&gt; is added to the list of injected dependencies.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
.controller(&apos;SearchController&apos;, function ($scope, $location, $routeParams, SearchService) {
    if ($routeParams.term) {
        SearchService.query($routeParams.term).then(function (response) {
            $scope.term = $routeParams.term;
            $scope.searchResults = response.data;
        });
    }
    ...
})&lt;/pre&gt;
        &lt;p&gt;You&apos;ll also need to add a new route so search/term is recognized.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;.config([&apos;$routeProvider&apos;, function ($routeProvider) {
    $routeProvider
        .when(&apos;/search&apos;, {
            templateUrl: &apos;search/index.html&apos;,
            controller: &apos;SearchController&apos;
        })
        .when(&apos;/search/:term&apos;, {
            templateUrl: &apos;search/index.html&apos;,
            controller: &apos;SearchController&apos;
        })
        ...
}])&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;After making all these changes, you should be able to refresh your browser and search/edit/update a person&apos;s
        information. If it works - nice job!
    &lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Source Code&lt;/h3&gt;
&lt;p&gt;A completed project with this code in it is available on GitHub at &lt;a
    href=&quot;https://github.com/mraible/angular-tutorial&quot;&gt;https://github.com/mraible/angular-tutorial&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;There are three commits that make the changes for the three main steps in this tutorial:&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;&lt;a
        href=&quot;https://github.com/mraible/angular-tutorial/commit/9792a0fbf1c2a5f1171498de7666e6f13cdd0537&quot;&gt;The
        Basics&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a
        href=&quot;https://github.com/mraible/angular-tutorial/commit/e9c277a50606d7ebaf9bcefa46f5942e2edf7ecf&quot;&gt;The
        Backend&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a
        href=&quot;https://github.com/mraible/angular-tutorial/commit/56be9decd0242dd60f06ef7232db723d6595ed0c&quot;&gt;Add
        an Edit Feature&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Extra Credit&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;http://linqed.eu/2014/10/07/deploying-angular-seed-to-heroku/&quot;&gt;Deploy your completed app to Heroku&lt;/a&gt;. See
    running version of this tutorial at &lt;a
        href=&quot;https://angular-123.herokuapp.com&quot;&gt;https://angular-123.herokuapp.com&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;Summary&lt;/h3&gt;
&lt;p&gt;I hope you&apos;ve enjoyed this quick-and-easy tutorial on how to get started with AngularJS. In a future tutorial, I&apos;ll show
you &lt;a href=&quot;//raibledesigns.com/rd/entry/testing_angularjs_applications&quot;&gt;how to write unit tests and integration tests for this application&lt;/a&gt;. If you&apos;re in Denver next Tuesday, I&apos;ll be &lt;a href=&quot;http://www.meetup.com/DOSUG1/events/219099019/&quot;&gt;speaking about AngularJS at Denver&apos;s Open Source Users Group&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you&apos;re a Java developer that&apos;s interested in developing with AngularJS and Spring Boot, you might want to checkout
the &lt;a href=&quot;http://www.infoq.com/news/2015/01/jhipster-2.0&quot;&gt;recently released JHipster 2.0&lt;/a&gt;.&lt;/p&gt;</description>          </item>
    <item>
    <guid isPermaLink="true">https://raibledesigns.com/rd/entry/the_art_of_angularjs</guid>
    <title>The Art of AngularJS</title>
    <dc:creator>Matt Raible</dc:creator>
    <link>https://raibledesigns.com/rd/entry/the_art_of_angularjs</link>
        <pubDate>Thu, 27 Feb 2014 09:44:29 -0700</pubDate>
    <category>The Web</category>
    <category>angularjs</category>
    <category>coffeescript</category>
    <category>http2</category>
    <category>javascript</category>
    <category>protractor</category>
    <category>html5</category>
    <category>jasmine</category>
    <category>grunt</category>
    <category>derailed</category>
            <description>Last night, I had the pleasure of &lt;a href=&quot;http://www.meetup.com/DeRailed/events/164446322/&quot;&gt;speaking at Denver&apos;s DeRailed about AngularJS&lt;/a&gt;. &lt;a href=&quot;https://twitter.com/kitesurfer&quot;&gt;Fernand&lt;/a&gt; (the group&apos;s leader) asked me to speak in December, just after I&apos;d finished a &lt;a href=&quot;http://raibledesigns.com/rd/entry/devoxx_2013_a_nordic_countries&quot;&gt;European speaking tour&lt;/a&gt;. The Modern Java Web Developer talk I created for that tour included a &lt;a href=&quot;https://vimeo.com/80314102&quot;&gt;20-minute AngularJS Deep Dive&lt;/a&gt; screencast. I figured it wouldn&apos;t be much work to augment the screencast and create an hour long talk, so I agreed.
&lt;/p&gt;
&lt;p&gt;When I started creating the presentation last week, I decided I didn&apos;t want to make the audience watch my screencast as part of the presentation. They could easily do that on their own time. So I wrote, from scratch, a brand new presentation on AngularJS. I tried to include all the things about Angular that I thought were important and useful for me in my learning process. The result is a presentation I&apos;m proud of and enjoyed delivering. &lt;/p&gt;
&lt;p&gt;

&lt;p&gt;You can click through it below, download it from &lt;a href=&quot;http://raibledesigns.com/rd/page/publications&quot;&gt;my
    presentations page&lt;/a&gt;, or view it &lt;a
        href=&quot;http://www.slideshare.net/mraible/the-art-of-angularjs&quot;&gt;on SlideShare&lt;/a&gt;.
&lt;/p&gt;

&lt;p style=&quot;text-align: center&quot;&gt;
&lt;iframe src=&quot;//www.slideshare.net/slideshow/embed_code/31721908?rel=0&quot; width=&quot;512&quot; height=&quot;325&quot; frameborder=&quot;0&quot; marginwidth=&quot;0&quot; marginheight=&quot;0&quot; scrolling=&quot;no&quot; style=&quot;border:1px solid #CCC;border-width:1px 1px 0;margin-bottom:5px&quot; allowfullscreen webkitallowfullscreen mozallowfullscreen&gt; &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;You might notice the presentation has a whole lot of code in it. Normally, when I copy/paste code into a presentation, I use IntelliJ IDEA and everything works. This time, there was something amiss between IDEA 13 and Keynote 6. I tried using IDEA&apos;s plugins (namely &lt;a href=&quot;http://plugins.jetbrains.com/plugin/7198&quot;&gt;Copy on steroids&lt;/a&gt; and &lt;a href=&quot;http://plugins.jetbrains.com/plugin/190&quot;&gt;Copy as HTML&lt;/a&gt;), but none of them worked. IDEA 12 resulted in the same problem. Then I turned to other solutions. I &lt;a href=&quot;https://gist.github.com/jimbojsb/1630790&quot;&gt;installed highlight&lt;/a&gt; and copied code from the command line. This worked, but the fonts and colors weren&apos;t to my liking. Finally, I decided to try another editor: &lt;a href=&quot;http://www.sublimetext.com/&quot;&gt;Sublime Text&lt;/a&gt; with &lt;a href=&quot;https://github.com/n1k0/SublimeHighlight&quot;&gt;SublimeHighlight&lt;/a&gt;. This worked &lt;em&gt;great&lt;/em&gt; and I&apos;m very happy with the results.
&lt;/p&gt;
&lt;p&gt;Most of my presentations end with a Questions/Contact slide. For this one, I added a few more: people to follow on Twitter, resources to learn from and projects with useful code. Below are a handful of links that greatly enhanced my AngularJS knowledge in the last year.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.parleys.com/play/5148922b0364bc17fc56c91b&quot;&gt;Devoxx 2012 - Re-imagining the browser with AngularJS&lt;/a&gt;. This is the original video I watched about AngularJS. I learned enough from this one video to start developing my first app for a client. I wrote about it in a four-part series on &lt;a href=&quot;http://raibledesigns.com/rd/entry/developing_with_angularjs_part_iv&quot;&gt;Developing with AngularJS&lt;/a&gt;. &lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.ng-book.com/&quot;&gt;ng-book: The Complete Book on AngularJS&lt;/a&gt;. A great book with all the nitty-gritty Angular details you ever wanted to know.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=UYVcY9EJcRs&quot;&gt;David Mosher&apos;s Testing Strategies for AngularJS&lt;/a&gt;. I stumbled upon this a week ago and it&apos;s greatly enhanced my knowledge of how to test AngularJS apps. It also introduced me to &lt;a href=&quot;http://linemanjs.com/&quot;&gt;Lineman&lt;/a&gt;, which I&apos;m thankful for.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://egghead.io/&quot;&gt;Egghead.io&lt;/a&gt; - bit-sized videos of AngularJS knowledge. Very useful for when you want to learn how to do a specific thing quickly.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/philipsorst/angular-rest-springsecurity&quot;&gt;AngularJS + REST + Spring Security&lt;/a&gt;. This is a sample application from &lt;a href=&quot;http://sorst.net/&quot;&gt;Philip Sorst&lt;/a&gt; (&lt;a href=&quot;https://github.com/joshlong/boot-examples/tree/master/x-auth-security&quot;&gt;ported to Spring Boot&lt;/a&gt; by my good friend &lt;a href=&quot;http://www.joshlong.com/&quot;&gt;Josh Long&lt;/a&gt;) that shows how to integrate AngularJS with Spring Security for stateless, token-based authentication.
&lt;/ul&gt;
&lt;p&gt;One of the audience members at DeRailed recommended &lt;a href=&quot;http://www.thinkster.io/&quot;&gt;thinkster.io&lt;/a&gt; as a good resource too.&lt;/p&gt;
&lt;p&gt;Thanks to Fernand for inviting me to speak and causing me to write this presentation. Creating it greatly improved my AngularJS knowledge and I learned about some new tools in the process. If you&apos;d like to tap into my wealth of knowledge, I&apos;m available for a new gig in April. &lt;img src=&quot;//raibledesigns.com/images/smileys/wink.gif&quot; class=&quot;smiley&quot; alt=&quot;;)&quot; title=&quot;;)&quot;&gt;</description>          </item>
    <item>
    <guid isPermaLink="true">https://raibledesigns.com/rd/entry/devoxx_2013_a_nordic_countries</guid>
    <title>Devoxx 2013 + a Nordic Countries Speaking Tour</title>
    <dc:creator>Matt Raible</dc:creator>
    <link>https://raibledesigns.com/rd/entry/devoxx_2013_a_nordic_countries</link>
        <pubDate>Thu, 28 Nov 2013 12:07:26 -0700</pubDate>
    <category>Java</category>
    <category>css</category>
    <category>devoxx</category>
    <category>nordea</category>
    <category>javascript</category>
    <category>bootstrap</category>
    <category>angularjs</category>
    <category>avegagroup</category>
    <category>java</category>
    <category>jvm</category>
    <category>webdevelopment</category>
    <atom:summary type="html">&lt;p&gt;
    &lt;a href=&quot;http://farm8.staticflickr.com/7452/11089788834_b7541d335d_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mraible/11089788834/&quot;
       title=&quot;Trish at Pelgrom by mraible, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;http://farm8.staticflickr.com/7452/11089788834_b7541d335d_t.jpg&quot; width=&quot;100&quot;
            alt=&quot;Trish at Pelgrom&quot; class=&quot;picture&quot;&gt;&lt;/a&gt;
    Two weeks ago, Trish and I boarded a flight for one of our favorite conferences: &lt;a
        href=&quot;http://devoxx.be/&quot;&gt;Devoxx&lt;/a&gt;. After a brief layover in Frankfurt, we arrived in Amsterdam and took a train to Antwerp. Within hours, we&apos;d settled into our hotel near the center of Antwerp and strolled over to the
    dungeonous, yet cozy, &lt;a href=&quot;http://www.pelgrom.be/&quot;&gt;Pelgrom restaurant&lt;/a&gt;. We were hoping for a delicious
    dinner, but found much more. We
    ran into James Ward, Dick Wall and a number of other enthusiastic speakers from the conference. Since I had to speak
    the next day, we didn&apos;t stay long, but we did share a number of laughs with some great people.
&lt;/p&gt;
&lt;p&gt;
    Tuesday (November 12), was a University Day at Devoxx, and I had my talk that afternoon. I spent a couple hours
    finishing up my talk
    that morning, then grabbed a taxi to head to the conference. I was honored with the opportunity to speak in Room 8,
    which is a huge theater that holds several hundred people.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;http://farm3.staticflickr.com/2809/11089790274_ac70261260_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mraible/11089790274/&quot;
       title=&quot;Devoxx: A Speaker&apos;s Perspective by mraible, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;http://farm3.staticflickr.com/2809/11089790274_ac70261260_q.jpg&quot; width=&quot;150&quot;
            alt=&quot;Devoxx: A Speaker&apos;s Perspective&quot; style=&quot;border: 1px solid black&quot;&gt;&lt;/a&gt;

    &lt;a href=&quot;http://farm3.staticflickr.com/2805/11102110783_8c0ea95e1d_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102110783/&quot;
       title=&quot;The Modern JVM Web Developer by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;http://farm3.staticflickr.com/2805/11102110783_8c0ea95e1d_q.jpg&quot; width=&quot;150&quot;
            alt=&quot;The Modern JVM Web Developer&quot; style=&quot;border: 1px solid black; margin-left: 15px&quot;&gt;&lt;/a&gt;

    &lt;a href=&quot;http://farm4.staticflickr.com/3727/11102183263_7fc28918f6_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102183263/&quot;
       title=&quot;AngularJS Deep Dive by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;http://farm4.staticflickr.com/3727/11102183263_7fc28918f6_q.jpg&quot; width=&quot;150&quot;
            alt=&quot;AngularJS Deep Dive&quot; style=&quot;border: 1px solid black; margin-left: 15px&quot;&gt;&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;
    I presented a lengthened version of The Modern Java Web Developer presentation I did early this year (at &lt;a
        href=&quot;http://raibledesigns.com/rd/entry/the_modern_java_web_developer&quot;&gt;Denver&apos;s JUG&lt;/a&gt; and &lt;a
        href=&quot;http://raibledesigns.com/rd/entry/javaone_2013_my_presentations&quot;&gt;JavaOne&lt;/a&gt;). Based on &lt;a
        href=&quot;http://raibledesigns.com/rd/entry/the_modern_java_web_developer1&quot;&gt;your feedback&lt;/a&gt;, I chose to do deep
    dives on AngularJS, Bootstrap and Page Speed. I&apos;ve always enjoyed speaking at Devoxx because attendees are so
    enthusiastic and passionate about the conference. I received an immense amount of feedback, both in praises and
    criticisms. The critics indicated there were &lt;a
        href=&quot;http://steveschols.wordpress.com/2013/11/21/devoxx-2013-a-retrospective/&quot;&gt;too many buzzwords&lt;/a&gt; and not
    enough substance. Others complained that the AngularJS &lt;a
        href=&quot;http://www.informit.com/articles/article.aspx?p=1930512&amp;amp;seqNum=3&quot;&gt;Lipsync&lt;/a&gt; that I did was &lt;em&gt;too
    deep&lt;/em&gt;.
&lt;/p&gt;
&lt;p&gt;
    I made sure to review and process everyone&apos;s comments, and then used them to improve the presentation throughout the
    following week. I learned to elaborate on the fact that many of the technologies were important to know about, but
    not important to know through-and-through. I made sure to mention that the use of CoffeeScript and LESS is often
    limited (or embraced) by team members and their willingness to try new things. If you&apos;re not writing thousands of
    lines of JavaScript or CSS, it probably doesn&apos;t make sense to use these languages. Furthermore, if your team members
    are struggling to write JavaScript or CSS, introducing a new language is probably not the best thing. I also
    reminded people to be skeptical of new technology, but also to be open-minded and give everything a chance. The
    10-minute, download-and-try test, is a great way to do that.
&lt;/p&gt;
&lt;p&gt;You can find my presentation below, download it from &lt;a href=&quot;http://raibledesigns.com/rd/page/publications&quot;&gt;my
    presentations page&lt;/a&gt;, or view it &lt;a
        href=&quot;http://www.slideshare.net/mraible/the-modern-java-web-developer-bootcamp-devoxx-2013&quot;&gt;on SlideShare&lt;/a&gt;.
&lt;/p&gt;
&lt;div style=&quot;text-align: center&quot;&gt;
    &lt;iframe src=&quot;http://www.slideshare.net/slideshow/embed_code/28649243?rel=0&quot; width=&quot;600&quot; height=&quot;375&quot; frameborder=&quot;0&quot;
            marginwidth=&quot;0&quot; marginheight=&quot;0&quot; scrolling=&quot;no&quot;
            style=&quot;border:1px solid #ccc;border-width:1px 1px 0;margin-bottom:5px&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;
    Within this presentation, there are links to each of the deep dives. The last two are screencasts that I added
    audio to a few days ago.&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
&lt;a href=&quot;http://static.raibledesigns.com/bootstrap3&quot;&gt;Bootstrap 3&lt;/a&gt; | 
&lt;a href=&quot;https://vimeo.com/mraible/angularjs-deep-dive&quot;&gt;AngularJS Deep Dive&lt;/a&gt; | 
&lt;a href=&quot;https://vimeo.com/mraible/page-speed-demo&quot;&gt;Page Speed Demo&lt;/a&gt;
&lt;/p&gt;</atom:summary>        <description>&lt;p&gt;
    &lt;a href=&quot;http://farm8.staticflickr.com/7452/11089788834_b7541d335d_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mraible/11089788834/&quot;
       title=&quot;Trish at Pelgrom by mraible, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm8.staticflickr.com/7452/11089788834_b7541d335d_t.jpg&quot; width=&quot;100&quot;
            alt=&quot;Trish at Pelgrom&quot; class=&quot;picture&quot;&gt;&lt;/a&gt;
    Two weeks ago, Trish and I boarded a flight for one of our favorite conferences: &lt;a
        href=&quot;http://devoxx.be/&quot;&gt;Devoxx&lt;/a&gt;. After a brief layover in Frankfurt, we arrived in Amsterdam and took a train to Antwerp. Within hours, we&apos;d settled into our hotel near the center of Antwerp and strolled over to the
    dungeonous, yet cozy, &lt;a href=&quot;http://www.pelgrom.be/&quot;&gt;Pelgrom restaurant&lt;/a&gt;. We were hoping for a delicious
    dinner, but found much more. We
    ran into James Ward, Dick Wall and a number of other enthusiastic speakers from the conference. Since I had to speak
    the next day, we didn&apos;t stay long, but we did share a number of laughs with some great people.
&lt;/p&gt;
&lt;p&gt;
    Tuesday (November 12), was a University Day at Devoxx, and I had my talk that afternoon. I spent a couple hours
    finishing up my talk
    that morning, then grabbed a taxi to head to the conference. I was honored with the opportunity to speak in Room 8,
    which is a huge theater that holds several hundred people.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;http://farm3.staticflickr.com/2809/11089790274_ac70261260_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mraible/11089790274/&quot;
       title=&quot;Devoxx: A Speaker&apos;s Perspective by mraible, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm3.staticflickr.com/2809/11089790274_ac70261260_q.jpg&quot; width=&quot;150&quot;
            alt=&quot;Devoxx: A Speaker&apos;s Perspective&quot; style=&quot;border: 1px solid black&quot;&gt;&lt;/a&gt;

    &lt;a href=&quot;http://farm3.staticflickr.com/2805/11102110783_8c0ea95e1d_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102110783/&quot;
       title=&quot;The Modern JVM Web Developer by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm3.staticflickr.com/2805/11102110783_8c0ea95e1d_q.jpg&quot; width=&quot;150&quot;
            alt=&quot;The Modern JVM Web Developer&quot; style=&quot;border: 1px solid black; margin-left: 15px&quot;&gt;&lt;/a&gt;

    &lt;a href=&quot;http://farm4.staticflickr.com/3727/11102183263_7fc28918f6_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102183263/&quot;
       title=&quot;AngularJS Deep Dive by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm4.staticflickr.com/3727/11102183263_7fc28918f6_q.jpg&quot; width=&quot;150&quot;
            alt=&quot;AngularJS Deep Dive&quot; style=&quot;border: 1px solid black; margin-left: 15px&quot;&gt;&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;
    I presented a lengthened version of The Modern Java Web Developer presentation I did early this year (at &lt;a
        href=&quot;http://raibledesigns.com/rd/entry/the_modern_java_web_developer&quot;&gt;Denver&apos;s JUG&lt;/a&gt; and &lt;a
        href=&quot;http://raibledesigns.com/rd/entry/javaone_2013_my_presentations&quot;&gt;JavaOne&lt;/a&gt;). Based on &lt;a
        href=&quot;http://raibledesigns.com/rd/entry/the_modern_java_web_developer1&quot;&gt;your feedback&lt;/a&gt;, I chose to do deep
    dives on AngularJS, Bootstrap and Page Speed. I&apos;ve always enjoyed speaking at Devoxx because attendees are so
    enthusiastic and passionate about the conference. I received an immense amount of feedback, both in praises and
    criticisms. The critics indicated there were &lt;a
        href=&quot;http://steveschols.wordpress.com/2013/11/21/devoxx-2013-a-retrospective/&quot;&gt;too many buzzwords&lt;/a&gt; and not
    enough substance. Others complained that the AngularJS &lt;a
        href=&quot;http://www.informit.com/articles/article.aspx?p=1930512&amp;amp;seqNum=3&quot;&gt;Lipsync&lt;/a&gt; that I did was &lt;em&gt;too
    deep&lt;/em&gt;.
&lt;/p&gt;
&lt;p&gt;
    I made sure to review and process everyone&apos;s comments, and then used them to improve the presentation throughout the
    following week. I learned to elaborate on the fact that many of the technologies were important to know about, but
    not important to know through-and-through. I made sure to mention that the use of CoffeeScript and LESS is often
    limited (or embraced) by team members and their willingness to try new things. If you&apos;re not writing thousands of
    lines of JavaScript or CSS, it probably doesn&apos;t make sense to use these languages. Furthermore, if your team members
    are struggling to write JavaScript or CSS, introducing a new language is probably not the best thing. I also
    reminded people to be skeptical of new technology, but also to be open-minded and give everything a chance. The
    10-minute, download-and-try test, is a great way to do that.
&lt;/p&gt;
&lt;p&gt;You can find my presentation below, download it from &lt;a href=&quot;http://raibledesigns.com/rd/page/publications&quot;&gt;my
    presentations page&lt;/a&gt;, or view it &lt;a
        href=&quot;http://www.slideshare.net/mraible/the-modern-java-web-developer-bootcamp-devoxx-2013&quot;&gt;on SlideShare&lt;/a&gt;.
&lt;/p&gt;
&lt;div style=&quot;text-align: center&quot;&gt;
    &lt;iframe src=&quot;//www.slideshare.net/slideshow/embed_code/28649243?rel=0&quot; width=&quot;600&quot; height=&quot;375&quot; frameborder=&quot;0&quot;
            marginwidth=&quot;0&quot; marginheight=&quot;0&quot; scrolling=&quot;no&quot;
            style=&quot;border:1px solid #ccc;border-width:1px 1px 0;margin-bottom:5px&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;
    Within this presentation, there are links to each of the deep dives. The last two are screencasts that I added
    audio to a few days ago.&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
&lt;a href=&quot;http://static.raibledesigns.com/bootstrap3&quot;&gt;Bootstrap 3&lt;/a&gt; | 
&lt;a href=&quot;https://vimeo.com/mraible/angularjs-deep-dive&quot;&gt;AngularJS Deep Dive&lt;/a&gt; | 
&lt;a href=&quot;https://vimeo.com/mraible/page-speed-demo&quot;&gt;Page Speed Demo&lt;/a&gt;
&lt;/p&gt;
&lt;div style=&quot;width: 575px; margin: auto;&quot;&gt;
    &lt;div style=&quot;display: inline;&quot;&gt;
        &lt;iframe src=&quot;//player.vimeo.com/video/80314102&quot; width=&quot;280&quot; height=&quot;175&quot; frameborder=&quot;0&quot; webkitallowfullscreen=&quot;&quot; mozallowfullscreen=&quot;&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
    &lt;/div&gt;
    &lt;div style=&quot;display: inline; float: right;&quot;&gt;
        &lt;iframe src=&quot;//player.vimeo.com/video/80391343&quot; width=&quot;280&quot; height=&quot;175&quot; frameborder=&quot;0&quot; webkitallowfullscreen=&quot;&quot; mozallowfullscreen=&quot;&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
    We stayed in Antwerp until Friday, attending the conference, taking pictures, networking over beers and having a
    fabulous time with everyone attending Devoxx.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;http://farm8.staticflickr.com/7350/11102130224_8b2d9d1109_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102130224/&quot;
       title=&quot;Street Shadows by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm8.staticflickr.com/7350/11102130224_8b2d9d1109_s.jpg&quot; width=&quot;75&quot;
            alt=&quot;Street Shadows&quot; style=&quot;border: 1px solid black&quot;&gt;&lt;/a&gt;

    &lt;a href=&quot;http://farm4.staticflickr.com/3706/11102238913_441d2fb728_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102238913/&quot;
       title=&quot;Devoxx posse at the Antwerp Town Hall by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm4.staticflickr.com/3706/11102238913_441d2fb728_s.jpg&quot; width=&quot;75&quot;
            alt=&quot;Devoxx posse at the Antwerp Town Hall&quot; style=&quot;border: 1px solid black; margin-left: 10px&quot;&gt;&lt;/a&gt;

    &lt;a href=&quot;http://farm6.staticflickr.com/5533/11102264113_6747b92a61_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102264113/&quot;
       title=&quot;Matt and Josh next to the Cathedral Antwerp by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm6.staticflickr.com/5533/11102264113_6747b92a61_s.jpg&quot; width=&quot;75&quot;
            alt=&quot;Matt and Josh next to the Cathedral Antwerp&quot; style=&quot;border: 1px solid black; margin-left: 10px&quot;&gt;&lt;/a&gt;

    &lt;a href=&quot;http://farm8.staticflickr.com/7371/11089691035_34e95b21d0_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mraible/11089691035/&quot;
       title=&quot;Devoxx Late Night by mraible, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm8.staticflickr.com/7371/11089691035_34e95b21d0_s.jpg&quot; width=&quot;75&quot;
            alt=&quot;Devoxx Late Night&quot; style=&quot;border: 1px solid black; margin-left: 10px&quot;&gt;&lt;/a&gt;

    &lt;a href=&quot;http://farm4.staticflickr.com/3775/11102166205_fe4c769a30_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102166205/&quot;
       title=&quot;Trish and Amelia by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm4.staticflickr.com/3775/11102166205_fe4c769a30_s.jpg&quot; width=&quot;75&quot;
            alt=&quot;Trish and Amelia&quot; style=&quot;border: 1px solid black; margin-left: 10px&quot;&gt;&lt;/a&gt;

&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;http://farm4.staticflickr.com/3801/11102112776_b8b7c90b9a_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102112776/&quot;
       title=&quot;Town Hall Antwerp by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm4.staticflickr.com/3801/11102112776_b8b7c90b9a.jpg&quot; width=&quot;500&quot;
            alt=&quot;Town Hall Antwerp&quot; style=&quot;border: 1px solid black&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;http://farm8.staticflickr.com/7399/11102078745_37f02cdc32_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102078745/&quot;
       title=&quot;Antwerp square by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm8.staticflickr.com/7399/11102078745_37f02cdc32.jpg&quot; width=&quot;500&quot;
            alt=&quot;Antwerp square&quot; style=&quot;border: 1px solid black&quot;&gt;&lt;/a&gt;

&lt;/p&gt;

&lt;p&gt;
    Thursday night, we dined at &lt;a
        href=&quot;http://www.tripadvisor.com/Restaurant_Review-g188636-d2536032-Reviews-Matty-Antwerp_Antwerp_Province.html&quot;&gt;Matty&lt;/a&gt;,
    one of the best restaurants in Antwerp.
    The food was excellent and provided a nice start for a night that included the Devoxx Party at Noxx and a journey to
    &lt;a href=&quot;http://biercentral.be/&quot;&gt;Bier Central&lt;/a&gt;.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;http://farm3.staticflickr.com/2813/11102230866_8b45e6d1e5_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102230866/&quot;
       title=&quot;Matty Restaurant Antwerp by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm3.staticflickr.com/2813/11102230866_8b45e6d1e5_q.jpg&quot; width=&quot;150&quot;
            alt=&quot;Matty Restaurant Antwerp&quot; style=&quot;border: 1px solid black&quot;&gt;&lt;/a&gt;
    &lt;a href=&quot;http://farm3.staticflickr.com/2872/11102238026_29af90ac67_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102238026/&quot;
       title=&quot;Steak at Matty by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm3.staticflickr.com/2872/11102238026_29af90ac67_q.jpg&quot; width=&quot;150&quot;
            alt=&quot;Steak at Matty&quot; style=&quot;border: 1px solid black; margin-left: 15px&quot;&gt;&lt;/a&gt;
    &lt;a href=&quot;http://farm8.staticflickr.com/7291/11102264004_71837f43e7_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102264004/&quot;
       title=&quot;Dessert by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm8.staticflickr.com/7291/11102264004_71837f43e7_q.jpg&quot; width=&quot;150&quot;
            alt=&quot;Dessert&quot; style=&quot;border: 1px solid black; margin-left: 15px&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;
    On Friday, we traveled to Brussels for a weekend in one of the &lt;a
        href=&quot;http://www.usatoday.com/story/travel/destinations/2012/10/02/10-best-beer-cities-in-the-world/1608885/&quot;&gt;best
    beer cities in the world&lt;/a&gt;. Trish booked us a room at the &lt;a href=&quot;http://www.hotel-saint-michel.be/&quot;&gt;Hotel Saint
    Michel&lt;/a&gt;, which was right on the &lt;a href=&quot;http://www.europeish.com/25-amazingly-stunning-european-squares/&quot;&gt;most
    beautiful square in Europe&lt;/a&gt;. Their pre-Christmas light show was spectacular. The beer was delicious, the location
    was magnificent and we thoroughly enjoyed ourselves, especially the &lt;a
        href=&quot;http://farm8.staticflickr.com/7342/11102281806_49f4a65516_c.jpg&quot;
        data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102281806/&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;mussels in
    Brussels&lt;/a&gt;. Thanks in particular to &lt;a href=&quot;https://twitter.com/snicoll&quot;&gt;St&#233;phane&lt;/a&gt; and &lt;a
        href=&quot;https://twitter.com/philipluppens&quot;&gt;Philip&lt;/a&gt; for your recommendations.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;http://farm3.staticflickr.com/2830/11102444343_3005a06b72_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102444343/&quot;
       title=&quot;Grand Place Brussels by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm3.staticflickr.com/2830/11102444343_3005a06b72.jpg&quot; width=&quot;500&quot;
            alt=&quot;Grand Place Brussels&quot; style=&quot;border: 1px solid black&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p id=&quot;nordic-speaking-tour&quot;&gt;
    &lt;strong&gt;Nordic Countries Speaking Tour&lt;/strong&gt;&lt;br/&gt;
    On Sunday (November 17), we flew to Stockholm to being the second half our trip. Nordea hired me to deliver my
    Devoxx presentation as part their Java Competence Network. Nordea&apos;s Jonny Berggren first contacted me in March 2010
    about this opportunity, so it was fun to see it finally happen. We agreed that I&apos;d speak at their four main
    locations: Stockholm, Helsinki, Oslo and Copenhagen. 
&lt;/p&gt;
&lt;p&gt;
    Mattias Karlsson (of &lt;a href=&quot;http://www.jfokus.se/&quot;&gt;Jfokus&lt;/a&gt; fame) also presented me with an opportunity to speak at his
    company while I was in Sweden.
&lt;/p&gt;
&lt;p&gt;
    I started the week delivering my talk on Monday afternoon at Nordea. Then we met up with Mattias, walked to his
    company and I delivered it again 45 minutes later. It was exhausting to talk for six hours in one day, but it all seemed
    to go well. I especially enjoyed the enthusiasm of Mattias&apos;s Avega Group.&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;http://farm4.staticflickr.com/3817/11102348876_9d52cb9c3e_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102348876/&quot;
       title=&quot;Speaking at Avega Group by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm4.staticflickr.com/3817/11102348876_9d52cb9c3e_n.jpg&quot; width=&quot;320&quot;
            alt=&quot;Speaking at Avega Group&quot; style=&quot;border: 1px solid black&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;
    That evening, we took the train to the airport, boarded a flight to Helsinki and arrived just after midnight. While
    on the train, we sat next to a Finlander, Karol, that gave us all kinds of great advice on what to do. Tuesday in
    Helsinki was cold and dreary; perfect sauna weather. We walked around a bit in the rain that morning and visited
    Senate Square on Karol&apos;s recommendation. I mentioned to the developers there that my Mom&apos;s grandparents were from
    Finland (Oulu and Hamina), and that I&apos;d grown up in a rustic cabin built by my Finish grandfather, Matti Hill.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;http://farm4.staticflickr.com/3749/11102390024_179d50d342_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102390024/&quot;
       title=&quot;Matt in Helsinki Capitol by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm4.staticflickr.com/3749/11102390024_179d50d342_m.jpg&quot; width=&quot;240&quot;
            alt=&quot;Matt in Helsinki Capitol&quot; style=&quot;border: 1px solid black&quot;&gt;&lt;/a&gt;
    &lt;a href=&quot;http://farm3.staticflickr.com/2824/11102401054_357a5665f8_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102401054/&quot;
       title=&quot;Downtown Helsinki by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm3.staticflickr.com/2824/11102401054_357a5665f8_m.jpg&quot; width=&quot;240&quot;
            alt=&quot;Downtown Helsinki&quot; style=&quot;border: 1px solid black; margin-left: 15px&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;
    We flew to Oslo Tuesday evening, rode the super-fast train from the airport and got to bed just before midnight.
    Wednesday morning, we walked around the Vigelandsparken Sculpture Park, and then I headed to the Nordea office while
    Trish did a walkabout and rode a Viking ship around the bay. We met up afterwards at the wonderful &lt;a
        href=&quot;http://www.beerpalace.no/&quot;&gt;Beer Palace&lt;/a&gt; for some pizza and delicious German/Belgian beer.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;http://farm4.staticflickr.com/3716/11102323505_758f0b2480_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102323505/&quot;
       title=&quot;Oslo Opera by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm4.staticflickr.com/3716/11102323505_758f0b2480.jpg&quot; width=&quot;500&quot;
            alt=&quot;Oslo Opera&quot; style=&quot;border: 1px solid black&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;http://farm8.staticflickr.com/7445/11102326065_c9807c37d5_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102326065/&quot;
       title=&quot;Ship and Oslo Opera by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm8.staticflickr.com/7445/11102326065_c9807c37d5_m.jpg&quot; width=&quot;240&quot;
            alt=&quot;Ship and Oslo Opera&quot; style=&quot;border: 1px solid black&quot;&gt;&lt;/a&gt;
    &lt;a href=&quot;http://farm4.staticflickr.com/3816/11102531063_e63b78e22e_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102531063/&quot;
       title=&quot;Crew&apos;s Sunset Oslo by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm4.staticflickr.com/3816/11102531063_e63b78e22e_m.jpg&quot; width=&quot;240&quot;
            alt=&quot;Crew&apos;s Sunset Oslo&quot; style=&quot;border: 1px solid black; margin-left: 15px&quot;&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p style=&quot;text-align: center&quot;&gt;

    &lt;a href=&quot;http://farm6.staticflickr.com/5512/11102364565_d44c5bf9dc_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102364565/&quot;
       title=&quot;Oslo ship in the Harbor by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm6.staticflickr.com/5512/11102364565_d44c5bf9dc.jpg&quot; width=&quot;500&quot;
            alt=&quot;Oslo ship in the Harbor&quot; style=&quot;border: 1px solid black&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;
    &lt;a href=&quot;http://farm8.staticflickr.com/7317/11102552963_caa2a844cd_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102552963/&quot;
       title=&quot;Danish Welcome by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm8.staticflickr.com/7317/11102552963_caa2a844cd_t.jpg&quot; width=&quot;100&quot;
            alt=&quot;Danish Welcome&quot; class=&quot;picture&quot; style=&quot;border: 1px solid black&quot;&gt;&lt;/a&gt;
    Copenhagen was our last stop, a city which neither of us have ever traveled to. After arriving, we quickly got a
    hint that Copenhagen was a special place. We did a bit of research on &lt;a
        href=&quot;http://en.wikipedia.org/wiki/Copenhagen&quot;&gt;Wikipedia&lt;/a&gt; and learned it was The City of Bikes and their
    craft brewing industry has blossomed in the last decade, now sporting over 100 microbreweries. My last talk on
    Thursday morning went very well, especially since my presentation and advice was well polished by that point.
&lt;/p&gt;
&lt;p&gt;
    That afternoon, we rented bikes from our hotel, slowly ate &lt;a
        href=&quot;http://www.tripadvisor.com/Restaurant_Review-g189541-d3454699-Reviews-Sticks_n_Sushi_Tivoli_Hotel_Congress_Center-Copenhagen_Zealand.html&quot;&gt;sushi&lt;/a&gt;
    on the top of the Tivoli Hotel, and then rode to &lt;a href=&quot;http://www.tivoli.dk/&quot;&gt;Tivoli Gardens&lt;/a&gt;. Tivoli Gardens
    was decorated as a Christmas wonderland and their amusement park made us smile and giggle with glee. We stayed there
    for hours before riding home. Biking around town with hundreds of other cyclists was really cool and fun. I hope
    Denver gets &lt;em&gt;Copenhagenized&lt;/em&gt; someday, the abundance of bike-only roads is simply awesome.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;

&lt;a href=&quot;http://farm4.staticflickr.com/3778/11102417765_08ccdbdc77_c.jpg&quot; data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102417765/&quot; title=&quot;Happy Couple at Tivoli by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img src=&quot;//farm4.staticflickr.com/3778/11102417765_08ccdbdc77_q.jpg&quot; width=&quot;150&quot; alt=&quot;Happy Couple at Tivoli&quot; style=&quot;border: 1px solid black&quot;&gt;&lt;/a&gt;

&lt;a href=&quot;http://farm4.staticflickr.com/3821/11102492616_cc34d454e9_c.jpg&quot; data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102492616/&quot; title=&quot;Tivoli by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img src=&quot;//farm4.staticflickr.com/3821/11102492616_cc34d454e9_q.jpg&quot; width=&quot;150&quot; alt=&quot;Tivoli&quot; style=&quot;border: 1px solid black; margin-left: 15px&quot;&gt;&lt;/a&gt;

&lt;a href=&quot;http://farm4.staticflickr.com/3689/11102535804_660b10428a_c.jpg&quot; data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102535804/&quot; title=&quot;Tivoli by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img src=&quot;//farm4.staticflickr.com/3689/11102535804_660b10428a_q.jpg&quot; width=&quot;150&quot; alt=&quot;Tivoli&quot; style=&quot;border: 1px solid black; margin-left: 15px&quot;&gt;&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;
    Our Nordic countries tour was especially pleasant because Christmas decorations were everywhere. In the US, many
    folks look down upon Christmas decorations before Thanksgiving, but since Europeans don&apos;t celebrate Thanksgiving - there&apos;s no reason not to ease into the Christmas spirit. We figured Abbie and Jack would have a blast at Tivoli in a future November or December. Yes, it was
    a bit chilly (20-30&amp;deg;F) in most of the countries, but we were well dressed for it. Unfortunately, we didn&apos;t get
    to see any snow.
&lt;/p&gt;
&lt;p&gt;
    Many thanks to Devoxx, Nordea, Mattias&apos;s Avega Group and the hundreds of developers who listened to me talk about being a
    modern web developer. We had a wonderful time speaking, laughing, photographing, drinking your delicious beer and
    seeing all your smiling faces.&lt;/p&gt;
&lt;p style=&quot;border-top: 1px dotted silver; color: #999; padding-top: 5px&quot;&gt;
    For more photos of this whirlwind trip, see Trish&apos;s &lt;a href=&quot;http://www.flickr.com/photos/mcginityphoto/sets/72157638119009136/&quot;&gt;
    EU and Scandanavian Speaking Tour 2013&lt;/a&gt;, while mine are in
    &lt;a href=&quot;http://www.flickr.com/photos/mraible/sets/72157638123386015/&quot;&gt;Devoxx 2013 and Nordic Speaking Tour&lt;/a&gt;.
&lt;/p&gt;</description>          </item>
    <item>
    <guid isPermaLink="true">https://raibledesigns.com/rd/entry/developing_with_angularjs_part_iv</guid>
    <title>Developing with AngularJS - Part IV: Making it Pop</title>
    <dc:creator>Matt Raible</dc:creator>
    <link>https://raibledesigns.com/rd/entry/developing_with_angularjs_part_iv</link>
        <pubDate>Thu, 12 Sep 2013 10:54:29 -0600</pubDate>
    <category>The Web</category>
    <category>design</category>
    <category>css3</category>
    <category>responsivedesign</category>
    <category>javascript</category>
    <category>taleo</category>
    <category>angularjs</category>
    <atom:summary type="html">&lt;p&gt;
Welcome to the final article in a series on my experience developing with &lt;a href=&quot;http://angularjs.org/&quot;&gt;AngularJS&lt;/a&gt;. I learned its concepts, beat my head against-the-wall with and finally tamed it enough to create a &quot;My Dashboard&quot; feature for a client. For previous articles, please see the following:
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://raibledesigns.com/rd/entry/developing_with_angularjs_part_i&quot;&gt;Part I: The Basics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://raibledesigns.com/rd/entry/developing_with_angularjs_part_ii&quot;&gt;Part II: Dialogs and Data&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://raibledesigns.com/rd/entry/developing_with_angularjs_part_iii&quot;&gt;Part III: Services&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The last mile of development for the My Dashboard feature was to spice things up a bit and make it look better. We hired a design company to come up a new look and feel and they went to work. Within a week, we had a meeting with them and they presented a few different options. We picked the one we liked the best and went to work. Below are screenshots that I used to implement the new design.&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;

&lt;a data-href=&quot;http://www.flickr.com/photos/mraible/8904971086/&quot; href=&quot;http://farm6.staticflickr.com/5447/8904971086_e24b89fa7e_c.jpg&quot; title=&quot;My Dashboard - New Design&quot; rel=&quot;lightbox[makingitpop]&quot;&gt;&lt;img src=&quot;http://farm6.staticflickr.com/5447/8904971086_e24b89fa7e_m.jpg&quot; width=&quot;240&quot; height=&quot;229&quot; alt=&quot;My Dashboard - New Design&quot; style=&quot;border: 1px solid silver&quot;&gt;&lt;/a&gt;

&lt;a data-href=&quot;http://www.flickr.com/photos/mraible/8904971332/&quot; href=&quot;http://farm3.staticflickr.com/2866/8904971332_9e919549b8_c.jpg&quot; title=&quot;My Dashboard with Show More&quot; rel=&quot;lightbox[makingitpop]&quot;&gt;&lt;img src=&quot;http://farm3.staticflickr.com/2866/8904971332_9e919549b8_m.jpg&quot; width=&quot;240&quot; height=&quot;229&quot; alt=&quot;My Dashboard with Show More&quot; style=&quot;border: 1px solid silver; margin-left: 20px&quot;&gt;&lt;/a&gt;

&lt;/p&gt;</atom:summary>        <description>&lt;p&gt;
Welcome to the final article in a series on my experience developing with &lt;a href=&quot;http://angularjs.org/&quot;&gt;AngularJS&lt;/a&gt;. I learned its concepts, beat my head against-the-wall with and finally tamed it enough to create a &quot;My Dashboard&quot; feature for a client. For previous articles, please see the following:
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://raibledesigns.com/rd/entry/developing_with_angularjs_part_i&quot;&gt;Part I: The Basics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://raibledesigns.com/rd/entry/developing_with_angularjs_part_ii&quot;&gt;Part II: Dialogs and Data&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://raibledesigns.com/rd/entry/developing_with_angularjs_part_iii&quot;&gt;Part III: Services&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The last mile of development for the My Dashboard feature was to spice things up a bit and make it look better. We hired a design company to come up a new look and feel and they went to work. Within a week, we had a meeting with them and they presented a few different options. We picked the one we liked the best and went to work. Below are screenshots that I used to implement the new design.&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;

&lt;a data-href=&quot;http://www.flickr.com/photos/mraible/8904971086/&quot; href=&quot;http://farm6.staticflickr.com/5447/8904971086_e24b89fa7e_c.jpg&quot; title=&quot;My Dashboard - New Design&quot; rel=&quot;lightbox[makingitpop]&quot;&gt;&lt;img src=&quot;//farm6.staticflickr.com/5447/8904971086_e24b89fa7e_m.jpg&quot; width=&quot;240&quot; height=&quot;229&quot; alt=&quot;My Dashboard - New Design&quot; style=&quot;border: 1px solid silver&quot;&gt;&lt;/a&gt;

&lt;a data-href=&quot;http://www.flickr.com/photos/mraible/8904971332/&quot; href=&quot;http://farm3.staticflickr.com/2866/8904971332_9e919549b8_c.jpg&quot; title=&quot;My Dashboard with Show More&quot; rel=&quot;lightbox[makingitpop]&quot;&gt;&lt;img src=&quot;//farm3.staticflickr.com/2866/8904971332_9e919549b8_m.jpg&quot; width=&quot;240&quot; height=&quot;229&quot; alt=&quot;My Dashboard with Show More&quot; style=&quot;border: 1px solid silver; margin-left: 20px&quot;&gt;&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;At first, I thought implementing this design might take quite a bit of effort, since it looked like it used custom fonts. It&apos;s true we could use CSS3&apos;s &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face&quot;&gt;@font-face&lt;/a&gt;, but I knew it might take awhile to find the right fonts with the appropriate licenses. When I received the screenshot below, I was pleased to see that all fonts were web-safe.&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
&lt;a data-href=&quot;http://www.flickr.com/photos/mraible/8904971378/&quot; href=&quot;http://farm3.staticflickr.com/2839/8904971378_a0ac267cc0_c.jpg&quot; title=&quot;My Dashboard Fonts&quot; rel=&quot;lightbox[makingitpop]&quot;&gt;&lt;img src=&quot;//farm3.staticflickr.com/2839/8904971378_a0ac267cc0.jpg&quot; width=&quot;500&quot; height=&quot;430&quot; alt=&quot;My Dashboard Fonts&quot; style=&quot;border: 1px solid silver&quot;&gt;&lt;/a&gt;

&lt;/p&gt;
&lt;h3 id=&quot;design-elements&quot;&gt;Design Elements&lt;/h3&gt;
&lt;p&gt;There are a number of elements in this new design that I had to create. For example, if numbers were only 1 digit, we had to add a leading zero to them in the summary band. Other design elements we needed to implement are listed below:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;A background image that filled the page&lt;/li&gt;&lt;li&gt;Fade to white on summary widget titles&lt;/li&gt;&lt;li&gt;A responsive grid for summary widgets&lt;/li&gt;&lt;li&gt;Provide a colored background for odd rows in the summary grid&lt;/li&gt;&lt;li&gt;Add a &quot;Show More&quot; band at the bottom of Tasks, Summary and Reports when there&apos;s more items to display&lt;/li&gt;&lt;/ul&gt;
&lt;p class=&quot;nolink&quot;&gt;In addition to these elements, there was quite a bit of work to conform to the new colors, fonts and drop-shadows. I implemented all of these using CSS3 (border-radius, box-shadow, box-sizing, linear-gradient), and lots of trial-and-error. To use the best fonts across various devices, I used CSS-Trick&apos;s &lt;a href=&quot;http://css-tricks.com/snippets/css/font-stacks/&quot;&gt;Font Stacks&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;new-background&quot;&gt;New Background&lt;/h3&gt;
&lt;p&gt;The new background shown in the screenshots above has a light source in the middle of it. Therefore, it&apos;s impossible to tile/repeat it across the page since it&apos;s not uniform. To make it work, I used a 1024 x 768 image and CSS3&apos;s &lt;code&gt;background-size: cover&lt;/code&gt;. For more information on background-size, see SitePoint&apos;s &lt;a href=&quot;http://www.sitepoint.com/css3-background-size-property/&quot;&gt;How to Resize Background Images with CSS3&lt;/a&gt;. This worked great on smaller screens, but we noticed some issues on 30&quot; monitors. Therefore, we ended up getting a new repeatable background and stopped using background-size.&lt;/p&gt;
&lt;h3 id=&quot;leadingzero-filter&quot;&gt;LeadingZero filter&lt;/h3&gt;
&lt;p&gt;For the first leading zero feature, I wrote an Angular filter. I put the code for this in &lt;em&gt;filters.js&lt;/em&gt;:&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
filter(&apos;leadingZero&apos;, function() {
    return function(input) {
        if (input.length === 1) {
            return &quot;0&quot; + input;
        } else if (input.length &amp;gt; 2) {
            return &quot;+99&quot;;
        } else {
            return input;
        }
    }
});
&lt;/pre&gt;
&lt;p&gt;This filter is used in the HTML template as follows:&lt;/p&gt;
&lt;pre class=&quot;brush: html&quot;&gt;
&amp;lt;div class=&quot;summary-value&quot;&amp;gt;{{widget.value | leadingZero}}&amp;lt;/div&amp;gt;
&lt;/pre&gt;
&lt;h3 id=&quot;text-fadeout&quot;&gt;Text Fade Out&lt;/h3&gt;
&lt;p&gt;To implement the fade-to-white text in summary titles, I started with &lt;a href=&quot;http://css-tricks.com/text-fade-read-more/&quot;&gt;this tutorial&lt;/a&gt;. I quickly discovered that it worked best for vertical text blocks and not for horizontal text. Then I found &lt;a href=&quot;http://xion.org.pl/2011/12/26/text-ellipsis-with-gradient-fade-in-pure-css/&quot;&gt;Text Ellipsis with Gradient Fade in Pure CSS&lt;/a&gt;, which uses :after to position a block over the text that fades to white. Since the title is not the right-most element (the numbers are), I had to figure out best positioning that worked cross-browser. Below is the CSS I used to implement this feature.&lt;/p&gt;
&lt;pre class=&quot;brush: css&quot;&gt;
.dashboard .summary-title:after {
    display: block;
    position: absolute;
    right: 66px;
    top: 5px;
    bottom: 5px;
    width: 30px;
    background: -moz-linear-gradient(left,  rgba(255,255,255,0) 0%, #fff 20px); /* FF3.6+ */
    background: -webkit-linear-gradient(left,  rgba(255,255,255,0) 0%, #fff 20px); /* Chrome10+,Safari5.1+ */
    background: -o-linear-gradient(left,  rgba(255,255,255,0) 0%, #fff 20px); /* Opera 11.10+ */
    background: -ms-linear-gradient(left,  rgba(255,255,255,0) 0%, #fff 20px); /* IE10+ */
    background: linear-gradient(to right,  rgba(255,255,255,0) 0%, #fff 20px); /* W3C */
    filter: progid:DXImageTransform.Microsoft.gradient( startColorstr=&apos;#00ffffff&apos;, endColorstr=&apos;#ffffff&apos;,GradientType=1 ); /* IE6-9 */
    content: &quot;&quot;;
}
&lt;/pre&gt;
&lt;h3 id=&quot;responsive-grid&quot;&gt;Responsive Grid&lt;/h3&gt;
&lt;p&gt;To implement the responsive grid of summary widgets, I started with Codrops&apos; &lt;a href=&quot;http://tympanus.net/codrops/2013/04/17/responsive-full-width-grid/&quot;&gt;Responsive Full Width Grid Tutorial&lt;/a&gt;. This proved to be a great model and I used the following CSS to position all the &amp;lt;li&amp;gt;&apos;s appropriately. In the code below, &lt;code&gt;.summary-item&lt;/code&gt; is the class on the &amp;lt;li&amp;gt; elements.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
.dashboard .summary-item {
    border-right: 1px solid #d1d1d1;
    border-bottom: 1px solid #d1d1d1;
    /* put the border on the inside of the box */
    -webkit-box-sizing: border-box;
    -moz-box-sizing: border-box;
    -ms-box-sizing: border-box;
    box-sizing: border-box;
    font-family: Constantia, &quot;Lucida Bright&quot;, Lucidabright, &quot;Lucida Serif&quot;, Lucida, &quot;DejaVu Serif&quot;, &quot;Bitstream Vera Serif&quot;, &quot;Liberation Serif&quot;, Georgia, serif;
    font-size: 14px;
    color: #666;
    height: 50px;
    box-shadow: inset 0 0 6px rgba(0,0,0, 0.25);
    /* responsive grid */
    position: relative;
    float: left;
    overflow: hidden;
    width: 25% /* Fallback */
    width: -webkit-calc(100% / 4);
    width: calc(100% / 4);
}

@media screen and (max-width: 1400px) {
    .dashboard .summary-item {
        width: 33.33333333333333%; /* Fallback */
        width: -webkit-calc(100% / 3);
        width: calc(100% / 3);
    }
}

@media screen and (max-width: 1000px) {
    .dashboard .summary-item {
        width: 50%; /* Fallback */
        width: -webkit-calc(100% / 2);
        width: calc(100% / 2);
    }
}
&lt;/pre&gt;
&lt;p&gt;This worked great in most browsers, but we did find an issue with IE9. When squishing/expanding the browser window, sometimes there would be a blank column on the right side. To fix this, I changed the width on the default &lt;code&gt;.summary-item&lt;/code&gt; to be 25%, and removed the lines with &lt;code&gt;calc&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;brush: css&quot;&gt;
.dashboard .summary-item {
    ...
    width: 25%
}
&lt;/pre&gt;
&lt;h3 id=&quot;coloring-oddrows&quot;&gt;Coloring Odd Rows&lt;/h3&gt;
&lt;p&gt;Coloring odd rows in a table is easy, but when the rows are in a responsive grid, that&apos;s a whole different story. For tables, the CSS rules are extremely simple:&lt;/p&gt;
&lt;pre class=&quot;brush: css&quot;&gt;
tr:nth-child(even) {background: #CCC}
tr:nth-child(odd) {background: #FFF}
&lt;/pre&gt;
&lt;p&gt;Via Twitter, &lt;a href=&quot;http://twitter.com/tomaslin&quot;&gt;@tomaslin&lt;/a&gt; advised me that the nth-child selector could probably be used for this, but it&apos;d likely require some JavaScript to make it responsive. I found the excellent &lt;a href=&quot;http://nthmaster.com/&quot;&gt;Master of the :nth-child&lt;/a&gt; and began trying to figure it out. The following function is what we now use to color odd rows in the Summary Bar.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
function colorRows() {
    var lisInRow = 0;
    var items = $(&apos;.summary-items li&apos;);
    items.each(function() {
        if($(this).prev().length &amp;gt; 0) {
            if($(this).position().top != $(this).prev().position().top) return false;
            lisInRow++;
        }
        else {
            lisInRow++;
        }
    });
    var rows = items.length / lisInRow;
    for (var i = 0; i &amp;lt; rows; i++) {
        var selector = &quot;nth-child(n+{x}):nth-child(-n+{y})&quot;;
        var x = (lisInRow * i) + 1;
        var y = x + (lisInRow - 1);
        selector = selector.replace(&apos;{x}&apos;, &apos;&apos; + x);
        selector = selector.replace(&apos;{y}&apos;, &apos;&apos; + y);
        if (i % 2) {
            $(&apos;.summary-items li:&apos; + selector).addClass(&apos;odd&apos;);
        } else {
            $(&apos;.summary-items li:&apos; + selector).removeClass(&apos;odd&apos;);
        }
    }
}
&lt;/pre&gt;
&lt;p&gt;The above code is in &lt;em&gt;dashboard.js&lt;/em&gt; and is called anytime the browser window is resized (to adapt to the responsive grid).&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
$(window).resize(colorRows);
&lt;/pre&gt;
&lt;p&gt;It&apos;s also called when summary widgets are re-ordered, in the &lt;code&gt;updateOrder()&lt;/code&gt; function of &lt;code&gt;WidgetController&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
$scope.updateOrder = function(event, ui) {
    ...
    Preferences.saveWidgetOrder(type, {items: items});
    if (type === &apos;summary&apos;) {
        colorRows();
    }
};
&lt;/pre&gt;
&lt;p&gt;I&apos;d like to figure out how to make this more Angular-esque, but all the &quot;how to hook into window.resize&quot; articles I found make it seem harder than this.&lt;/p&gt;
&lt;h3 id=&quot;show-more&quot;&gt;Show More&lt;/h3&gt;
&lt;p&gt;The last feature I had to implement was the &quot;Show More&quot; bar that appears when widgets are hidden. This was the most difficult thing to implement and I tried many different things before arriving at a solution that works. First of all, the widgets bars that can be expanded are put into their original (collapsed) state using &lt;code&gt;max-height&lt;/code&gt; and &lt;code&gt;overflow: hidden&lt;/code&gt;. From there, I look at the list inside the bar and compare the height&apos;s of the two elements. If the list is taller than the bar, the Show More bar is added.&lt;/p&gt;
&lt;div class=&quot;alert alert-info&quot;&gt;
I originally looked at the list&apos;s &lt;code&gt;:last-child&lt;/code&gt; to see if it was visible, but jQuery&apos;s &lt;a href=&quot;http://api.jquery.com/hidden-selector/&quot;&gt;:hidden selector&lt;/a&gt; only works on items that are hidden by &lt;code&gt;display: none&lt;/code&gt; rather than ones that are hidden by &lt;code&gt;overflow&lt;/code&gt;.
&lt;/div&gt;
&lt;p&gt;As you can see from the code below, there&apos;s special logic needed to expand the min-height of the Summary Bar since it doesn&apos;t have enough room at the bottom to add the bar in its collapsed state.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
function showMore(element) {
    var bar = element.parent().parent();
    var list = element.parent();
    var barId = bar.attr(&apos;id&apos;);
    var listHeight = list.height();
    var barHeight = bar.height();
    var isSummaryBar = (barId.indexOf(&apos;summary&apos;) &amp;gt; -1);
    var summaryBarMinHeight = 260;
    var showMoreShouldBeVisible = (isSummaryBar &amp;amp;&amp;amp; element.position().top &amp;gt;= 200) ? true : listHeight &amp;gt; barHeight;
    if (showMoreShouldBeVisible) {
        var messages = {};
        // the variables below are defined in the host page, before this file is loaded
        messages.more = showMoreText;
        messages.less = showLessText;

        var showMore = $(&apos;&amp;lt;div class=&quot;show-more&quot;/&amp;gt;&apos;).html(messages.more + &quot; &amp;lt;b class=&apos;caret&apos;&amp;gt;&amp;lt;/b&amp;gt;&quot;);
        showMore.appendTo(bar);
        // summary bar doesn&apos;t have enough room for the Show More bar in its collapsed state,
        // so change it from 242 to 260
        if (isSummaryBar) {
            bar.css({&apos;min-height&apos;: summaryBarMinHeight + &apos;px&apos;, &apos;max-height&apos;: &apos;&apos;});
        }

        showMore.bind(&apos;click&apos;, function (e) {
            var element = $(this);
            var parent = element.parent();

            if (element.hasClass(&apos;less&apos;)) {
                parent.css({&quot;max-height&quot;: &apos;&apos;});
                if (isSummaryBar) {
                    parent.css({&quot;min-height&quot;: summaryBarMinHeight + &apos;px&apos;}).animate(200);
                } else {
                    parent.css({&quot;min-height&quot;: &apos;&apos;}).animate(200);
                }
                element.removeClass(&apos;less&apos;);
                element.html(messages.more + &apos; &amp;lt;b class=&quot;caret&quot;&amp;gt;&amp;lt;/b&amp;gt;&apos;);
            } else {
                parent.css({
                    &quot;max-height&quot;: 9999,
                    &quot;min-height&quot;: &apos;auto&apos;
                }).animate({
                        &quot;min-height&quot;: parent.height() + 19
                    }, 200);

                element.addClass(&apos;less&apos;);
                element.html(messages.less + &apos; &amp;lt;b class=&quot;caret caret-up&quot;&amp;gt;&amp;lt;/b&amp;gt;&apos;);
            }

            // prevent jump-down
            return false;
        });
    } else {
        // Remove show-more in case it was previously added
        if (bar.find(&apos;.show-more&apos;).length &amp;gt; 0) {
            if (isSummaryBar) {
                bar.css(&apos;min-height&apos;, summaryBarMinHeight - 18)
            } else {
                bar.attr(&apos;style&apos;, &apos;&apos;);
            }

            bar.find(&apos;.show-more&apos;).remove();
        }
    }
}

function showMoreOnResize() {
    var dataItems = $(&apos;.task-items,.summary-items,.report-items&apos;);
    dataItems.each(function() {
        var lastItem = $(this).find(&apos;li:last-child&apos;);
        if (lastItem.length &amp;gt; 0) {
            showMore(lastItem);
        }
    });
}
&lt;/pre&gt;
&lt;p&gt;At first, I wrote this logic as a directive, but when I needed it for responsiveness, I moved it into &lt;em&gt;dashboard.js&lt;/em&gt;. The &lt;code&gt;showMoreOnResize()&lt;/code&gt; function is called on window resize.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
$(window).resize(showMoreOnResize);
&lt;/pre&gt;
&lt;p&gt;I also found that I had to add it to the Preferences service after widgets were saved (since the number displayed could change).&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
factory(&apos;Preferences&apos;, function ($filter) {
    return {
        ...
        // Save hidden and visible (and order) widgets from config dialog
        saveWidgetPreferences: function (type, widgets) {
            ...
            DWRFacade.saveDashboardWidgetPreference(type, preferences, {
                callback: function() {
                    // recalculate show more bar
                    showMoreOnResize();
                },
                errorHandler: function (errorString) {
                    alert(errorString);
                }
            });
        }
    }
});
&lt;/pre&gt;
&lt;p&gt;To implement the .caret-up (the .caret class is from Bootstrap), I found &lt;a href=&quot;https://github.com/twitter/bootstrap/issues/2902&quot;&gt;a caret-right howto&lt;/a&gt; and used it to create &lt;code&gt;.caret-up&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;brush: css&quot;&gt;
.caret-up {
    border-left: 4px solid transparent;
    border-right: 4px solid transparent;
    border-top: 4px solid transparent;
    border-bottom: 4px solid black;
}
&lt;/pre&gt;
&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;
The final My Dashboard feature is something that I&apos;m quite proud of. A fellow developer, Vlad, did an excellent job of implementing the backend and admin portions. The Product Team&apos;s vision and desire to make it &lt;em&gt;Pop!&lt;/em&gt; created something great. The fact that we didn&apos;t have to support IE8 helped a lot in the implementation. Below is a screenshot of how My Dashboard looked when we completed the project.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
&lt;a data-href=&quot;http://www.flickr.com/photos/mraible/8904354347/&quot; href=&quot;http://farm8.staticflickr.com/7380/8904354347_0b38a0bf2d_c.jpg&quot; title=&quot;My Dashboard&quot; rel=&quot;lightbox[makingitpop]&quot;&gt;&lt;img src=&quot;//farm8.staticflickr.com/7380/8904354347_0b38a0bf2d.jpg&quot; width=&quot;500&quot; height=&quot;338&quot; alt=&quot;My Dashboard&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;Angular isn&apos;t mentioned much in this article. That&apos;s because we didn&apos;t have to do much to the existing Angular code to implement the new design. It was just a matter of writing/modifying some CSS as well as introducing some JavaScript for colored rows and show more. If you know how these features could be written in a more Angular Way, I&apos;d love to hear about it.&lt;/p&gt;
&lt;p&gt;If you&apos;d still like to learn more about Angular and why it&apos;s good to integrate it little by little, I encourage you to read &lt;a href=&quot;http://oscarvillarreal.com/2013/05/07/5-reasons-to-use-angularjs-in-the-corporate-app-world/&quot;&gt;5 reasons to use AngularJS in the corporate app world&lt;/a&gt;.&lt;/p&gt;</description>          </item>
    <item>
    <guid isPermaLink="true">https://raibledesigns.com/rd/entry/developing_with_angularjs_part_iii</guid>
    <title>Developing with AngularJS - Part III: Services</title>
    <dc:creator>Matt Raible</dc:creator>
    <link>https://raibledesigns.com/rd/entry/developing_with_angularjs_part_iii</link>
        <pubDate>Tue, 25 Jun 2013 07:03:26 -0600</pubDate>
    <category>The Web</category>
    <category>rest</category>
    <category>angularjs</category>
    <category>taleo</category>
    <category>javascript</category>
    <category>dwr</category>
    <atom:summary type="html">&lt;p&gt;This is the 3rd article in a series on my experience developing with &lt;a href=&quot;http://angularjs.org/&quot;&gt;AngularJS&lt;/a&gt;. I used AngularJS for several months to create a &quot;My Dashboard&quot; feature for a client and learned a whole bunch of Angular goodness along the way. For previous articles, please see &lt;a href=&quot;http://raibledesigns.com/rd/entry/developing_with_angularjs_part_i&quot;&gt;Part I: The Basics&lt;/a&gt; and &lt;a href=&quot;http://raibledesigns.com/rd/entry/developing_with_angularjs_part_ii&quot;&gt;Part II: Dialogs and Data&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Angular offers several ways to interact with data from the server. The easiest way is to use the &lt;a href=&quot;http://docs.angularjs.org/api/ngResource.$resource&quot;&gt;$resource factory&lt;/a&gt;, which lets you interact with &lt;a href=&quot;http://en.wikipedia.org/wiki/Representational_State_Transfer&quot;&gt;RESTful&lt;/a&gt; server-side data sources. When we started the My Dashboard project, we were hoping to interact with a REST API, but soon found out that it didn&apos;t have all the data we needed. Rather than loading the page and then making another request to get its data, we decided to embed the JSON in the page. For communication back to the server, we used our tried-and-true Ajax solution: &lt;a href=&quot;http://directwebremoting.org/&quot;&gt;DWR&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In Angular-speak, &lt;em&gt;services&lt;/em&gt; are &lt;a href=&quot;http://docs.angularjs.org/guide/dev_guide.services.understanding_services&quot;&gt;singletons that carry out specific tasks common to web apps&lt;/a&gt;. In other words, they&apos;re any &lt;em&gt;$name&lt;/em&gt; object that can be injected into a controller or directive. However, as a Java Developer, I tend to think of services as objects that communicate with the server. Angular&apos;s documentation on &lt;a href=&quot;http://docs.angularjs.org/guide/dev_guide.services.creating_services&quot;&gt;Creating Services&lt;/a&gt; shows you various options for registering services. I used the angular.Module api method.&lt;/p&gt;</atom:summary>        <description>&lt;p&gt;
&lt;a href=&quot;http://angularjs.org/&quot; title=&quot;AngularJS&quot;&gt;&lt;img src=&quot;//farm8.staticflickr.com/7445/9137074888_9d3bb13d32_s.jpg&quot; width=&quot;75&quot; height=&quot;75&quot; alt=&quot;AngularJS&quot; class=&quot;picture&quot;&gt;&lt;/a&gt;
This is the 3rd article in a series on my experience developing with &lt;a href=&quot;http://angularjs.org/&quot;&gt;AngularJS&lt;/a&gt;. I used AngularJS for several months to create a &quot;My Dashboard&quot; feature for a client and learned a whole bunch of Angular goodness along the way. For previous articles, please see &lt;a href=&quot;http://raibledesigns.com/rd/entry/developing_with_angularjs_part_i&quot;&gt;Part I: The Basics&lt;/a&gt; and &lt;a href=&quot;http://raibledesigns.com/rd/entry/developing_with_angularjs_part_ii&quot;&gt;Part II: Dialogs and Data&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Angular offers several ways to interact with data from the server. The easiest way is to use the &lt;a href=&quot;http://docs.angularjs.org/api/ngResource.$resource&quot;&gt;$resource factory&lt;/a&gt;, which lets you interact with &lt;a href=&quot;http://en.wikipedia.org/wiki/Representational_State_Transfer&quot;&gt;RESTful&lt;/a&gt; server-side data sources. When we started the My Dashboard project, we were hoping to interact with a REST API, but soon found out that it didn&apos;t have all the data we needed. Rather than loading the page and then making another request to get its data, we decided to embed the JSON in the page. For communication back to the server, we used our tried-and-true Ajax solution: &lt;a href=&quot;http://directwebremoting.org/&quot;&gt;DWR&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In Angular-speak, &lt;em&gt;services&lt;/em&gt; are &lt;a href=&quot;http://docs.angularjs.org/guide/dev_guide.services.understanding_services&quot;&gt;singletons that carry out specific tasks common to web apps&lt;/a&gt;. In other words, they&apos;re any &lt;em&gt;$name&lt;/em&gt; object that can be injected into a controller or directive. However, as a Java Developer, I tend to think of services as objects that communicate with the server. Angular&apos;s documentation on &lt;a href=&quot;http://docs.angularjs.org/guide/dev_guide.services.creating_services&quot;&gt;Creating Services&lt;/a&gt; shows you various options for registering services. I used the angular.Module api method.&lt;/p&gt;

&lt;p&gt;When I last worked on the project, there were only two services in My Dashboard: Widget and Preferences.&lt;/p&gt;

&lt;h3 id=&quot;widget&quot;&gt;Widget Service&lt;/h3&gt;
&lt;p&gt;The Widget service is used to retrieve the visible widgets for the user. It has two functions that are exposed to controllers: &lt;code&gt;getUserWidgets(type)&lt;/code&gt; and &lt;code&gt;getHiddenWidgets(type)&lt;/code&gt;. The former function is used at the top of &lt;code&gt;WidgetController&lt;/code&gt;, while the latter is used for the configuration dialog mentioned in the &lt;a href=&quot;http://raibledesigns.com/rd/entry/developing_with_angularjs_part_ii&quot;&gt;previous article&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The code for this service is in &lt;em&gt;services.js&lt;/em&gt;. The bulk of the logic is in its &lt;code&gt;filterData()&lt;/code&gt; function, where it goes through a 4-step process:&lt;/p&gt;
&lt;ol&gt;&lt;li&gt;Get all the widgets by type, ensuring they&apos;re unique.&lt;/li&gt;&lt;li&gt;Remove the widgets that are &lt;em&gt;hidden&lt;/em&gt; by the user&apos;s preferences.&lt;/li&gt;&lt;li&gt;Build an array that&apos;s &lt;em&gt;ordered&lt;/em&gt; by user&apos;s preferences.&lt;/li&gt;&lt;li&gt;Add any new widgets that aren&apos;t &lt;em&gt;hidden&lt;/em&gt; or &lt;em&gt;ordered&lt;/em&gt;.&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;The code for the Widget object is as follows:&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
angular.module(&apos;dashboard.services&apos;, &amp;#91;&amp;#93;).
    factory(&apos;Widget&apos;,function ($filter, Preferences) {
        var filter = $filter(&apos;filter&apos;);
        var unique = $filter(&apos;unique&apos;);

        function filterData(array, query) {
            // get all possible widgets for a particular type
            var data = filter(array, query);
            data = unique(data);

            // remove widgets that are hidden by users preference
            var hidden = Preferences.getHiddenWidgets(query.type);
            for (var i = 0; i &amp;lt; hidden.length; i++) {
                var w = filter(data, {id: hidden&amp;#91;i&amp;#93;});
                $.each(w, function (index, item) {
                    var itemId = item.id;
                    if (hidden.indexOf(itemId) &amp;gt; -1) {
                        data.splice(data.indexOf(item), 1);
                    }
                });
            }

            // build an array that&apos;s ordered by users preference
            var ordered = &amp;#91;&amp;#93;;
            var visible = Preferences.getUserWidgets(query.type);
            for (var j = 0; j &amp;lt; visible.length; j++) {
                var v = filter(data, {id: visible&amp;#91;j&amp;#93;});
                $.each(v, function (index, item) {
                    var itemId = item.id;
                    if (visible.indexOf(itemId) &amp;gt; -1) {
                        ordered.push(item)
                    }
                });
            }

            // loop through data again and add any new widgets not in ordered
            $.each(data, function (index, item) {
                if (ordered.indexOf(item) === -1) {
                    ordered.push(item);
                }
            });

            return ordered;
        }

        return {
            getUserWidgets: function (type) {
                return filterData(widgetData, {type: type})
            },

            getHiddenWidgets: function (type) {
                var hidden = Preferences.getHiddenWidgets(type);
                var widgetsForType = filter(widgetData, {type: type});
                widgetsForType = unique(widgetsForType);
                var widgets = &amp;#91;&amp;#93;;
                for (var j = 0; j &amp;lt; hidden.length; j++) {
                    var v = filter(widgetsForType, {id: hidden&amp;#91;j&amp;#93;});
                    $.each(v, function (index, item) {
                        if (widgetsForType.indexOf(item) &amp;gt; -1) {
                            widgets.push(item)
                        }
                    });
                }
                return widgets;
            }
        }
    })
&lt;/pre&gt;
&lt;p&gt;Once you have a service configured like this, you can inject it by name. For example, &lt;code&gt;WidgetController&lt;/code&gt; has &lt;code&gt;Widget&lt;/code&gt; injected into its constructor:&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
function WidgetController($dialog, $scope, Widget, Preferences) {
&lt;/pre&gt;
&lt;h3 id=&quot;preferences&quot;&gt;Preferences Service&lt;/h3&gt;
&lt;p&gt;The Preferences service is used to get and save user preferences. It&apos;s pretty straightforward and the bulk of its code is interacting with DWR. This service has 5 methods:&lt;/p&gt;
&lt;ol&gt;&lt;li&gt;getHiddenWidgets(type) - used by Widget service&lt;/li&gt;&lt;li&gt;getUserWidgets(type) - used by Widget service&lt;/li&gt;&lt;li&gt;saveBarOrder(bars) - called from WidgetController&lt;/li&gt;&lt;li&gt;saveWidgetOrder(type, widgets) - called from WidgetController&lt;/li&gt;&lt;li&gt;saveWidgetPreferences(type, widgets) - called from WidgetController&lt;/li&gt;&lt;/ol&gt;

&lt;p&gt;First, let&apos;s take a look at the &lt;code&gt;save*Order()&lt;/code&gt; functions. There are two parts of the page that use the &lt;em&gt;ui-sortable&lt;/em&gt; directive to initialize drag-and-drop functionality. The first is on the main &amp;lt;ul&amp;gt; that holds the 3 bars on the left.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
&amp;lt;ul class=&quot;widgets&quot; ui-sortable=&quot;{handle:&apos;.heading&apos;, update: updateBars}&quot;&amp;gt;
&lt;/pre&gt;
&lt;p&gt;The &quot;update&quot; property in the configuration JSON indicates which method to call in the controller. Similarly, the tasks and summary items call an &lt;code&gt;updateOrder&lt;/code&gt; function.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
&amp;lt;ul class=&quot;summary-items&quot; ng-model=&quot;summaryWidgets&quot; ui-sortable=&quot;{update: updateOrder}&quot;&amp;gt;
...
&amp;lt;ul class=&quot;task-items&quot; ng-model=&quot;taskWidgets&quot; ui-sortable=&quot;{update: updateOrder}&quot;&amp;gt;
&lt;/pre&gt;
&lt;p&gt;These functions are in &lt;code&gt;WidgetController&lt;/code&gt; and build an array of widget ids to pass to the Preferences service.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
$scope.updateBars = function(event, ui) {
    var bars = &amp;#91;&amp;#93;;
    $.each($(ui.item).parent().children(), function (index, item) {
        bars.push(item.id.substring(0, item.id.indexOf(&apos;-&apos;)))
    });
    Preferences.saveBarOrder(bars);
};

$scope.updateOrder = function(event, ui) {
    var parentId = $(ui.item).parent().parent().attr(&apos;id&apos;);
    var type = parentId.substring(0, parentId.indexOf(&apos;-&apos;));
    var items = &amp;#91;&amp;#93;;
    $.each($(ui.item).parent().children(), function (index, item) {
        items.push(item.id.substring(item.id.indexOf(&apos;-&apos;) + 1))
    });
    Preferences.saveWidgetOrder(type, {items: items});
};
&lt;/pre&gt;
&lt;p&gt;The bar order is used when the page is loaded. The following scriptlet code exists at the bottom of the app&apos;s page, in its $(document).ready:&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
&amp;lt;%  String barOrder = user.getDashboardBarSortOrder();
    if (barOrder != null) { %&amp;gt;
    sortBars(&amp;#91;&apos;&amp;lt;%= barOrder %&amp;gt;&apos;&amp;#93;);
&amp;lt;% } %&amp;gt;
&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;sortBars()&lt;/code&gt; function is in a &lt;em&gt;dashboard.js&lt;/em&gt; file (where we put all non-Angular functions):&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
function sortBars(barOrder) {
    // Sort bars according to user preferences
    $.each(barOrder, function(index, item) {
        var bar = $(&apos;#&apos; + item + &apos;-bar&apos;);
        if (bar.index() !== index) {
            if (index === 0) {
                bar.insertBefore($(&apos;.widgets&amp;gt;li:first-child&apos;));
            } else if (index === (barOrder.length - 1)) {
                bar.insertAfter($(&apos;.widgets&amp;gt;li:last-child&apos;));
            } else {
                bar.insertBefore($(&apos;.widgets&amp;gt;li:eq(&apos; + index + &apos;)&apos;));
            }
        }
    });
}
&lt;/pre&gt;
&lt;p&gt;Now that you&apos;ve seen where Preferences is called from, let&apos;s take a look at the code for the service.&lt;/p&gt;
&lt;div class=&quot;alert alert-info&quot;&gt;
The checks for undefined and uniqueness in the code below shouldn&apos;t be necessary, but I prefer defensive coding.
&lt;/div&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
factory(&apos;Preferences&apos;, function ($filter) {
    var unique = $filter(&apos;unique&apos;);

    return {
        // Get in-page variable: hiddenWidgets
        getHiddenWidgets: function (type) {
            var items = hiddenWidgets&amp;#91;type&amp;#93;;
            return (angular.isUndefined(items) ? &amp;#91;&amp;#93; : unique(items));
        },

        // Get in-page variable: userWidgets
        getUserWidgets: function (type) {
            var items = userWidgets&amp;#91;type&amp;#93;;
            return (angular.isUndefined(items) ? &amp;#91;&amp;#93; : unique(items));
        },

        // Save main bar (task, summary, chart) order
        saveBarOrder: function (bars) {
            DWRFacade.saveDashboardBarSortOrder(bars, {
                errorHandler: function (errorString) {
                    alert(errorString);
                }
            })
        },

        // Save order of widgets from sortable
        saveWidgetOrder: function (type, widgets) {
            userWidgets&amp;#91;type&amp;#93; = widgets.items;
            DWRFacade.saveDashboardWidgetPreference(type, widgets, {
                errorHandler: function (errorString) {
                    alert(errorString);
                }
            });
        },

        // Save hidden and visible (and order) widgets from config dialog
        saveWidgetPreferences: function (type, widgets) {
            // widgets is a map of hidden and visible
            var hiddenIds = &amp;#91;&amp;#93;;
            $.each(widgets.hidden, function (index, item) {
                hiddenIds.push(item.id);
            });
            var visibleIds = &amp;#91;&amp;#93;;
            $.each(widgets.items, function (index, item) {
                visibleIds.push(item.id);
            });
            var preferences = {
                hidden: hiddenIds,
                items: visibleIds
            };
            // reset local variables in page
            hiddenWidgets&amp;#91;type&amp;#93; = hiddenIds;
            userWidgets&amp;#91;type&amp;#93; = visibleIds;
            DWRFacade.saveDashboardWidgetPreference(type, preferences, {
                errorHandler: function (errorString) {
                    alert(errorString);
                }
            });
        }
    }
})
&lt;/pre&gt;
&lt;h3 id=&quot;http&quot;&gt;Using $http and Receiving Data&lt;/h3&gt;
&lt;p&gt;In this particular application, we didn&apos;t do any reading from the server with Angular. We simply wrote preferences to the server, and updated embedded variables when data changed. Real-time functionality of the app wouldn&apos;t be noticeable if a write failed. 
&lt;/p&gt;
&lt;p&gt;
In my current Angular project, it&apos;s more of a full-blown application that does as much reading as writing. For this, I&apos;ve found it useful to either 1) pass in callbacks to services or 2) use Angular&apos;s event system to publish/subscribe to events.
&lt;/p&gt;
&lt;p&gt;The first method is the easiest, and likely the most familiar to JavaScript developers. For example, here&apos;s the controller code to remove a profile picture:
&lt;/p&gt;

&lt;pre class=&quot;brush: js&quot;&gt;
Profile.removePhoto($scope.user, function (data) {
    // close the dialog
    $scope.close(&apos;avatar&apos;);
    // success message using toastr: http://bit.ly/14Uisgm
    Flash.pop({type: &apos;success&apos;, body: &apos;Your profile picture was removed.&apos;});
})
&lt;/pre&gt;
&lt;p&gt;And the &lt;code&gt;Profile.removePhoto()&lt;/code&gt; method:
&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
removePhoto: function (user, callback) {
    $http.post(&apos;/profile/removePhoto&apos;, user).success(function (response) {
        return callback(response);
    });
}
&lt;/pre&gt;
&lt;p&gt;The second, event-driven method works equally as well, but can easily suffer from typos in event names.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
// controller calling code
Profile.getUser();

// service code
getUser: function () {
    $http.get(&apos;/profile&apos;).success(function (data) {
        if (data.username) {
            $log.info(&apos;Profile for &apos; + data.username + &apos; retrieved!&apos;);
            $rootScope.$broadcast(&apos;event:profile&apos;, data);
        }
    });
}

// controller receiving code
$rootScope.$on(&apos;event:profile&apos;, function (event, data) {
    $scope.user = data;
});
&lt;/pre&gt;
&lt;p&gt;I like both methods, but the event-driven one seems like it could offer more extensibility in the future.&lt;/p&gt;

&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;Using in-page variables and DWR doesn&apos;t seem to be recommended by the Angular Team. However, it worked well for us and seems like a good way to construct Angular services. Even if a REST API becomes available to get all the data, I think using in-page variables to minimize requests is a good idea. &lt;/p&gt;
&lt;p&gt;When retrieving data, you can use callbacks or Angular&apos;s pub/sub event system ($broadcast and $on) to get data in your controllers. If you want to learn more about this technique, see Eric Terpstra&apos;s &lt;a href=&quot;http://ericterpstra.com/2012/09/angular-cats-part-3-communicating-with-broadcast/&quot;&gt;Communicating with $broadcast&lt;/a&gt;. In his article, Eric mentions &lt;a href=&quot;https://groups.google.com/d/msg/angular/M0SHItdgBqg/R1t_17cR0pYJ&quot;&gt;Thomas Burleson&apos;s pub/sub module&lt;/a&gt; that acts as a message queue. If you&apos;ve used Thomas&apos;s MessageQueue (or something similar) with Angular, I&apos;d love to hear about your experience. 
&lt;/p&gt;
&lt;p&gt;In the &lt;a href=&quot;http://raibledesigns.com/rd/entry/developing_with_angularjs_part_iv&quot;&gt;next article&lt;/a&gt;, I&apos;ll talk about how we redesigned My Dashboard and used CSS3 and JavaScript to implement new ideas.&lt;/p&gt;</description>          </item>
    <item>
    <guid isPermaLink="true">https://raibledesigns.com/rd/entry/developing_with_angularjs_part_ii</guid>
    <title>Developing with AngularJS - Part II: Dialogs and Data</title>
    <dc:creator>Matt Raible</dc:creator>
    <link>https://raibledesigns.com/rd/entry/developing_with_angularjs_part_ii</link>
        <pubDate>Thu, 20 Jun 2013 08:45:13 -0600</pubDate>
    <category>The Web</category>
    <category>angularui</category>
    <category>jqueryui</category>
    <category>jquery</category>
    <category>angularjs</category>
    <category>javascript</category>
    <category>taleo</category>
    <atom:summary type="html">&lt;p&gt;A couple of days ago, I wrote an article on &lt;a href=&quot;http://raibledesigns.com/rd/entry/developing_with_angularjs_part_i&quot;&gt;how I started developing with AngularJS&lt;/a&gt;. I used AngularJS for several months to develop a &quot;My Dashboard&quot; feature for a client&apos;s product and learned a whole bunch of stuff along the way. &lt;/p&gt;
&lt;p&gt;This article provides an overview of how I changed some of My Dashboard&apos;s features to use Angular instead of jQuery. After finishing the prototype work in January, we started moving bits and pieces into the main application. We kept the same file names for our Angular-related files and copied them into the project.
&lt;/p&gt;
&lt;p&gt;
&lt;a href=&quot;http://www.flickr.com/photos/mraible/8904352343/&quot; title=&quot;Directory Structure&quot;&gt;&lt;img src=&quot;http://farm3.staticflickr.com/2856/8904352343_20beb87da8_o.png&quot; width=&quot;280&quot; height=&quot;268&quot; alt=&quot;Directory Structure&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;All these files are packaged up into a &lt;code&gt;dashboard.js&lt;/code&gt; file that&apos;s included at the bottom of our Dashboard page. While our prototype used jQuery 1.9 and jQuery UI 1.10, the application&apos;s codebase used jQuery 1.7.1 and jQuery UI 1.8.3. Luckily, this didn&apos;t present a problem as everything continued to work as expected.&lt;/p&gt;
&lt;p&gt;Around this time, we also had many discussions with the Product Team about charts. Since Highcharts required we purchase a license, we took at look at &lt;a href=&quot;http://www.anychart.com/&quot;&gt;AnyChart&lt;/a&gt;, which we were already using. We were able to get AnyChart to work with our existing &lt;em&gt;chart&lt;/em&gt; directive with minimal changes. Most changes were in the JSON itself. &lt;/p&gt;
&lt;p&gt;We committed the first pass (with sample data still hard-coded) in mid-February.&lt;/p&gt;</atom:summary>        <description>&lt;p&gt;A couple of days ago, I wrote an article on &lt;a href=&quot;http://raibledesigns.com/rd/entry/developing_with_angularjs_part_i&quot;&gt;how I started developing with AngularJS&lt;/a&gt;. I used AngularJS for several months to develop a &quot;My Dashboard&quot; feature for a client&apos;s product and learned a whole bunch of stuff along the way. &lt;/p&gt;
&lt;p&gt;This article provides an overview of how I changed some of My Dashboard&apos;s features to use Angular instead of jQuery. After finishing the prototype work in January, we started moving bits and pieces into the main application. We kept the same file names for our Angular-related files and copied them into the project.
&lt;/p&gt;
&lt;p&gt;
&lt;a href=&quot;http://www.flickr.com/photos/mraible/8904352343/&quot; title=&quot;Directory Structure&quot;&gt;&lt;img src=&quot;//farm3.staticflickr.com/2856/8904352343_20beb87da8_o.png&quot; width=&quot;280&quot; height=&quot;268&quot; alt=&quot;Directory Structure&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;All these files are packaged up into a &lt;code&gt;dashboard.js&lt;/code&gt; file that&apos;s included at the bottom of our Dashboard page. While our prototype used jQuery 1.9 and jQuery UI 1.10, the application&apos;s codebase used jQuery 1.7.1 and jQuery UI 1.8.3. Luckily, this didn&apos;t present a problem as everything continued to work as expected.&lt;/p&gt;
&lt;p&gt;Around this time, we also had many discussions with the Product Team about charts. Since Highcharts required we purchase a license, we took at look at &lt;a href=&quot;http://www.anychart.com/&quot;&gt;AnyChart&lt;/a&gt;, which we were already using. We were able to get AnyChart to work with our existing &lt;em&gt;chart&lt;/em&gt; directive with minimal changes. Most changes were in the JSON itself. &lt;/p&gt;
&lt;p&gt;We committed the first pass (with sample data still hard-coded) in mid-February.&lt;/p&gt;
&lt;h3 id=&quot;angular-ui-carousel&quot;&gt;Angular UI&apos;s Carousel&lt;/h3&gt;
&lt;p&gt;While finishing our initial prototype, I learned about Angular UI&apos;s &lt;a href=&quot;https://github.com/angular-ui/bootstrap/tree/master/src/carousel&quot;&gt;Carousel&lt;/a&gt;, an implementation of Bootstrap&apos;s Carousel. It required a 70% less HTML and is quite a bit easier to read. Below is the refactored carousel section.&lt;/p&gt;
&lt;pre class=&quot;brush: html&quot;&gt;
&amp;lt;carousel interval=&quot;-1&quot; class=&quot;oneup&quot;&amp;gt;
    &amp;lt;slide ng-repeat=&quot;widget in widgets | filter: {type: &apos;chart&apos;} | orderBy: &apos;order&apos;&quot; active=&quot;slide.active&quot; class=&quot;chart&quot;&amp;gt;
        &amp;lt;chart class=&quot;widget&quot; value=&quot;{{widget}}&quot; type=&quot;{{widget.chartType}}&quot;&amp;gt;&amp;lt;/chart&amp;gt;
    &amp;lt;/slide&amp;gt;
&amp;lt;/carousel&amp;gt;
&amp;lt;carousel interval=&quot;-1&quot; class=&quot;twoup&quot;&amp;gt;
    &amp;lt;slide ng-repeat=&quot;widget in widgets | filter: {type: &apos;chart&apos;} | chunk: 2 | orderBy: &apos;order&apos;&quot; active=&quot;slide.active&quot; class=&quot;chart&quot;&amp;gt;
        &amp;lt;chart class=&quot;widget&quot; value=&quot;{{widget&amp;#91;0&amp;#93;}}&quot; type=&quot;{{widget&amp;#91;0&amp;#93;.chartType}}&quot;&amp;gt;&amp;lt;/chart&amp;gt;
        &amp;lt;chart class=&quot;widget&quot; value=&quot;{{widget&amp;#91;1&amp;#93;}}&quot; type=&quot;{{widget&amp;#91;1&amp;#93;.chartType}}&quot;&amp;gt;&amp;lt;/chart&amp;gt;
    &amp;lt;/slide&amp;gt;
&amp;lt;/carousel&amp;gt;
&lt;/pre&gt;
&lt;p&gt;I was also able to remove the JavaScript that once initialized the carousel.&lt;/p&gt;
&lt;pre class=&quot;brush: diff&quot;&gt;
-    var carousel = $(&apos;.carousel&apos;);
-    $(carousel).carousel({
-        interval: 0
-    });
&lt;/pre&gt;
&lt;p&gt;While integrating this directive, I found a way to improve it by &lt;a href=&quot;https://github.com/angular-ui/bootstrap/commit/aedc05654b19342d96eabc4fc08d6a090765a48b&quot;&gt;hiding navigation indicators if there&apos;s only one slide&lt;/a&gt;. And thus my first contribution to Angular UI was born.&lt;/p&gt;
&lt;h3 id=&quot;angular-ui-sortable&quot;&gt;Angular UI&apos;s Sortable&lt;/h3&gt;
&lt;p&gt;Next, I switched from using JavaScript to Angular UI&apos;s &lt;a href=&quot;https://github.com/angular-ui/ui-sortable&quot;&gt;&lt;em&gt;sortable&lt;/em&gt;&lt;/a&gt;&lt;em&gt; &lt;/em&gt;to initialize drag-and-drop functionality. This was as simple as removing 5 lines of JavaScript and adding &quot;ui-sortable&quot; as an attribute to HTML tags.&lt;/p&gt;
&lt;pre class=&quot;brush: diff&quot;&gt;
&amp;lt;div class=&quot;container-widgets&quot; ng-controller=&quot;WidgetController&quot; ng-cloak&amp;gt;
     &amp;lt;div class=&quot;row-fluid&quot;&amp;gt;
         &amp;lt;div class=&quot;span9&quot;&amp;gt;
-                &amp;lt;ul class=&quot;widgets&quot;&amp;gt;
+                &amp;lt;ul class=&quot;widgets&quot; ui-sortable=&quot;{handle:&apos;.heading&apos;}&quot;&amp;gt;
                 &amp;lt;li id=&quot;summary-bar&quot;&amp;gt;
                     &amp;lt;div class=&quot;heading&quot;&amp;gt;Summary &amp;lt;a href=&apos;#&apos; class=&apos;configure&apos;&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;
-                        &amp;lt;ul class=&quot;tiles&quot;&amp;gt;
+                        &amp;lt;ul class=&quot;tiles&quot; ui-sortable&amp;gt;
                         &amp;lt;li class=&quot;span3&quot; ng-repeat=&quot;widget in widgets | filter:{type: &apos;summary&apos;} | orderBy: &apos;order&apos;&quot;&amp;gt;
                             &amp;lt;h3&amp;gt;{{widget.value}}&amp;lt;/h3&amp;gt;
@@ -36,7 +36,7 @@
                 &amp;lt;/li&amp;gt;
                 &amp;lt;li id=&quot;task-bar&quot;&amp;gt;
                     &amp;lt;div class=&quot;heading&quot;&amp;gt;My Tasks &amp;lt;a href=&apos;#&apos; class=&apos;configure&apos;&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;
-                        &amp;lt;ul class=&quot;tasks&quot;&amp;gt;
+                        &amp;lt;ul class=&quot;tasks&quot; ui-sortable&amp;gt;
@@ -151,55 +131,44 @@
-    $(&apos;.widgets&apos;).sortable({
-        cursor: &quot;move&quot;,
-        handle: &quot;.heading&quot;
-    }).disableSelection();
-    $(&apos;.tiles,.tasks&apos;).sortable();
&lt;/pre&gt;
&lt;p&gt;This directive uses jQuery UI under the covers, so it accepts all the same arguments you&apos;d normally use.&lt;/p&gt;
&lt;h3 id=&quot;dialogs-with-jquery&quot;&gt;Dialogs with jQuery&lt;/h3&gt;
&lt;p&gt;The next feature I implemented was a dialog that allowed end users to add/remove widgets from their dashboard. Since I knew how to do this with jQuery, that&apos;s the path I started down. I figured it&apos;d be easier to get something working quickly and then refactor to Angular later. I put the HTML for the dialog at the bottom of the page:
&lt;/p&gt;
&lt;pre class=&quot;brush: html&quot;&gt;
&amp;lt;script type=&quot;text/javascript&quot; src=&quot;js/move.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script type=&quot;text/javascript&quot; src=&quot;js/upDown.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;div id=&quot;configure-widgets&quot; class=&quot;hidden&quot; data-title=&quot;Bar Configuration&quot; data-close=&quot;Close&quot;&amp;gt;
    &amp;lt;div class=&quot;row-fluid center&quot;&amp;gt;
        &lt;p&gt;Decide which items you would like to display and set the display order.&lt;/p&gt;
        &amp;lt;div class=&quot;span5&quot; style=&quot;margin-left: 25px&quot;&amp;gt;
            &amp;lt;label for=&quot;available-widgets&quot; class=&quot;bold&quot;&amp;gt;Available Widgets&amp;lt;/label&amp;gt;
            &amp;lt;select size=10 id=&quot;available-widgets&quot; multiple class=&quot;width100pr&quot;&amp;gt;&amp;lt;/select&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div class=&quot;span1 arrows&quot; style=&quot;padding-top: 50px&quot;&amp;gt;
            &amp;lt;img src=&quot;images/arrow_up.png&quot; class=&quot;arrow arrow-up&quot; alt=&quot;Move Up&quot; onclick=&quot;return moveUp($(&apos;#assigned-widgets&apos;)&amp;#91;0&amp;#93;);&quot;/&amp;gt;
            &amp;lt;img src=&quot;images/arrow_right.png&quot; class=&quot;arrow arrow-right&quot; alt=&quot;Move Right&quot; onclick=&quot;return move($(&apos;#available-widgets&apos;)&amp;#91;0&amp;#93;, $(&apos;#assigned-widgets&apos;)&amp;#91;0&amp;#93;);&quot;/&amp;gt;
            &amp;lt;img src=&quot;images/arrow_left.png&quot; class=&quot;arrow arrow-left&quot; alt=&quot;Move Left&quot; onclick=&quot;return move($(&apos;#assigned-widgets&apos;)&amp;#91;0&amp;#93;, $(&apos;#available-widgets&apos;)&amp;#91;0&amp;#93;);&quot;/&amp;gt;
            &amp;lt;img src=&quot;images/arrow_down.png&quot; class=&quot;arrow arrow-down&quot; alt=&quot;Move Down&quot; onclick=&quot;return moveDown($(&apos;#assigned-widgets&apos;)&amp;#91;0&amp;#93;);&quot;/&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div class=&quot;span5&quot;&amp;gt;
            &amp;lt;label for=&quot;assigned-widgets&quot; class=&quot;bold&quot;&amp;gt;Assigned Widgets&amp;lt;/label&amp;gt;
            &amp;lt;select size=10 id=&quot;assigned-widgets&quot; multiple class=&quot;width100pr&quot;&amp;gt;&amp;lt;/select&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Then I changed the &lt;code&gt;WidgetController&lt;/code&gt; to split the &lt;code&gt;widgets&lt;/code&gt; variable into variables for each widget type.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
+    var filter = $filter(&apos;filter&apos;);
+    var orderBy = $filter(&apos;orderBy&apos;);
+
+    $scope.summaryWidgets = filterData($scope.widgets, {type: &apos;summary&apos;}, filter, orderBy);
+    $scope.taskWidgets = filterData($scope.widgets, {type: &apos;task&apos;}, filter, orderBy);
+    $scope.chartWidgets = filterData($scope.widgets, {type: &apos;chart&apos;}, filter, orderBy);
+}
+
+function filterData(array, query, filter, orderBy) {
+    var data = filter(array, query);
+    return orderBy(data, &apos;order&apos;);
+}
&lt;/pre&gt;
&lt;p&gt;Finally, I added a new &lt;em&gt;config&lt;/em&gt; directive and hooked it into the page by adding &lt;em&gt;config=&quot;type&quot; &lt;/em&gt;to each heading (e.g. &lt;code&gt;&amp;lt;div class=&quot;heading&quot; config=&quot;task&quot;&amp;gt;My Tasks&amp;lt;/div&amp;gt;&lt;/code&gt;).&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;

.directive(&apos;config&apos;, function ($sanitize, $filter) {
    return {
        restrict: &apos;A&apos;,
        link: function (scope, element, attrs) {
            var configBtn = $(&apos;&amp;lt;a href=&quot;#&quot;/&amp;gt;&apos;).addClass(&apos;configure&apos;);
            configBtn.appendTo(element);
            var widgets = scope&amp;#91;attrs.config + &apos;Widgets&apos;&amp;#93;;
             
            // availableWidgets is defined as a global variable embedded in the page
            var allWidgets = availableWidgets; 
            $(configBtn).on(&apos;click&apos;, function (e) {
                e.preventDefault();
                var configDialog = $(&apos;#configure-widgets&apos;);
                var availableWidgets = $(&apos;#available-widgets&apos;);
                availableWidgets.empty();
                var filter = $filter(&apos;filter&apos;);
                var orderBy = $filter(&apos;orderBy&apos;);
                allWidgets = filter(allWidgets, {type: attrs.config});
                allWidgets = orderBy(allWidgets, &apos;order&apos;);
                var unselectedWidgets = jQuery.grep(allWidgets, function(item) {
                    return jQuery.inArray(item, widgets) &amp;lt; 0;
                });
                $.each(unselectedWidgets, function(index, item) {
                    var title = (item.title) ? item.title.replace(/&amp;amp;quot;/g, &apos;&quot;&apos;) : &apos;No Title&apos;;
                    availableWidgets.append(new Option(title, item.id));
                });
                var assignedWidgets = $(&apos;#assigned-widgets&apos;);
                assignedWidgets.empty();
                $.each(widgets, function(index, item) {
                    var title = (item.title) ? item.title.replace(/&amp;amp;quot;/g, &apos;&quot;&apos;) : &apos;No Title&apos;;
                    assignedWidgets.append(new Option(title, item.id));
                });
                configDialog.dialog({
                    title: $(element).text() + &apos; &apos; + configDialog.attr(&apos;data-title&apos;),
                    width: 600,
                    modal: true,
                    buttons: &amp;#91;{
                            text: configDialog.attr(&apos;data-close&apos;),
                            &apos;class&apos;: &apos;btn&apos;,
                            click: function() {
                                $(this).dialog(&quot;close&quot;);
                            }
                        }&amp;#93;
                });
            });
        }
    }
})
&lt;/pre&gt;
&lt;p&gt;As you can tell, this is quite a bit of code, and it doesn&apos;t even show you the JavaScript in move.js and upDown.js (included at the top of the dialog HTML). While writing this code, I could tell that I was not doing things the &lt;em&gt;Angular Way&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;To refactor, I did some research, found the &lt;a href=&quot;https://github.com/angular-ui/bootstrap/tree/master/src/dialog&quot;&gt;$dialogProvider&lt;/a&gt; service and went to work.&lt;/p&gt;
&lt;h3 id=&quot;dialogs-with-angular&quot;&gt;Dialogs with Angular&lt;/h3&gt;
&lt;p&gt;I started by refactoring the &lt;em&gt;config&lt;/em&gt; directive to be much shorter.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
.directive(&apos;config&apos;, function() {
    return {
        restrict: &apos;A&apos;,
        link: function (scope, element, attrs) {
            var configBtn = $(&apos;&amp;lt;a href=&quot;&quot;/&amp;gt;&apos;).addClass(&apos;configure&apos;);
            configBtn.appendTo(element);
            configBtn.bind(&apos;click&apos;, function(e) {
                e.preventDefault();
                e.stopPropagation();
                scope.$apply(scope.configureDialog(attrs.config, $(element).text()))
            });
        }
    }
})
&lt;/pre&gt;
&lt;p&gt;Line #10 that starts with &lt;code&gt;scope.$apply&lt;/code&gt; is what makes the magic happens.This calls the &lt;code&gt;configureDialog()&lt;/code&gt; function in &lt;code&gt;WidgetController&lt;/code&gt;. The &lt;code&gt;$dialog&lt;/code&gt; service you see below is injected by adding it as a parameter to the &lt;code&gt;WidgetController&lt;/code&gt; function.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;

$scope.configureDialog = function (type, title) {
    var dialog = $dialog.dialog({
        dialogClass: &apos;modal draggable&apos;,
        modalFade: false,
        backdropClick: true,
        controller: &apos;ConfigController&apos;,
        template: $(&apos;#configure-widgets&apos;).html(),
        resolve: {
            title: function() {
                return title;
            },
            hiddenItems: function () {
                return Widget.getHiddenWidgets(type);
            },
            items: function () {
                return angular.copy($scope&amp;#91;type + &apos;Widgets&apos;&amp;#93;);
            }
        }
    });
    dialog.open().then(function(results) {
        if (!angular.isUndefined(results)) {
            $scope&amp;#91;type + &apos;Widgets&apos;&amp;#93; = results.items;
            Preferences.saveWidgetPreferences(type, results);
        }
    });
};
&lt;/pre&gt;
&lt;div class=&quot;alert alert-info&quot;&gt;
&lt;p style=&quot;margin-top: 0&quot;&gt;&lt;strong&gt;draggable directive&lt;/strong&gt;&lt;br/&gt;
If you look closely, you&apos;ll see this dialog is initialized with a &apos;draggable&apos; class. I created a &lt;em&gt;draggable &lt;/em&gt;directive that gives draggability using jQuery UI to any elements with this class.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
.directive(&apos;draggable&apos;, function() {
    return {
        restrict: &apos;C&apos;,
        link: function(scope, elem, attr, ctrl) {
            elem.draggable({handle: &apos;.modal-header&apos;});
        }
    };
});
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The interesting parts of this file are the &lt;strong&gt;controller&lt;/strong&gt;, &lt;strong&gt;template&lt;/strong&gt;, and &lt;strong&gt;resolve&lt;/strong&gt; parameters. The &lt;strong&gt;controller&lt;/strong&gt; is just another function in &lt;em&gt;controllers.js&lt;/em&gt;, the &lt;strong&gt;template&lt;/strong&gt; is HTML in the page (so text could be i18n-ized) and &lt;strong&gt;resolve&lt;/strong&gt; has the variables to pass to the controller. &lt;code&gt;Widget&lt;/code&gt; and &lt;code&gt;Preferences&lt;/code&gt; are services I&apos;ll talk about in the Part 3. The reason angular.copy() is used is so the widget arrays aren&apos;t modified while the dialog is displayed (we want to wait until the user clicks &quot;Save&quot;).&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;ConfigController&lt;/code&gt; function started quite simply:&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
function ConfigController($scope, $sanitize, dialog, title, hiddenItems, items) {
    $scope.title = title;
    $scope.hiddenItems = hiddenItems;
    $scope.items = items;
}
&lt;/pre&gt;
&lt;p&gt;The HTML (defined by the aforementioned &lt;strong&gt;template&lt;/strong&gt; parameter) is as follows. You can see that &lt;em&gt;title&lt;/em&gt;, &lt;em&gt;hiddenItems&lt;/em&gt; and &lt;em&gt;items&lt;/em&gt; are the variables defined in $scope and used to render the data.&lt;/p&gt;
&lt;pre class=&quot;brush: html&quot;&gt;
&amp;lt;div id=&quot;configure-widgets&quot; class=&quot;modal hidden&quot;&amp;gt;
    &amp;lt;div class=&quot;modal-header&quot; style=&quot;border: 0&quot;&amp;gt;
        &amp;lt;button type=&quot;button&quot; class=&quot;close&quot; ng-click=&quot;close(false)&quot; aria-hidden=&quot;true&quot; style=&quot;margin-top: -3px&quot;&amp;gt;&amp;amp;times;&amp;lt;/button&amp;gt;
        &amp;lt;h4&amp;gt;{{title}} Bar Configuration&amp;lt;/h4&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;modal-body&quot;&amp;gt;
        &lt;p&gt;Decide which items you would like to display and set the display order.&lt;/p&gt;
        &amp;lt;div class=&quot;row-fluid center&quot;&amp;gt;
            &amp;lt;div class=&quot;span4&quot; style=&quot;width: 320px; margin-left: 25px&quot;&amp;gt;
                &amp;lt;label for=&quot;available-widgets&quot; class=&quot;bold&quot;&amp;gt;Available Widgets&amp;lt;/label&amp;gt;
                &amp;lt;select size=10 id=&quot;available-widgets&quot; ng-model=&quot;items.available&quot; ng-options=&quot;i.title for i in hiddenItems&quot;
                        class=&quot;width100pr&quot; multiple=&quot;multiple&quot;&amp;gt;&amp;lt;/select&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class=&quot;span1 arrows&quot; style=&quot;padding-top: 50px&quot;&amp;gt;
                &amp;lt;img src=&quot;images/arrow_up.png&quot; class=&quot;arrow arrow-up&quot; alt=&quot;Move Up&quot; ng-click=&quot;moveUp(items.selected, items)&quot;/&amp;gt;
                &amp;lt;img src=&quot;images/arrow_right.png&quot; class=&quot;arrow arrow-right&quot; alt=&quot;Move Right&quot; ng-click=&quot;moveItem(items.available, hiddenItems, items)&quot;/&amp;gt;
                &amp;lt;img src=&quot;images/arrow_left.png&quot; class=&quot;arrow arrow-left&quot; alt=&quot;Move Left&quot; ng-click=&quot;moveItem(items.selected, items, hiddenItems)&quot;/&amp;gt;
                &amp;lt;img src=&quot;images/arrow_down.png&quot; class=&quot;arrow arrow-down&quot; alt=&quot;Move Down&quot; ng-click=&quot;moveDown(items.selected, items)&quot;/&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class=&quot;span4&quot; style=&quot;width: 320px&quot;&amp;gt;
                &amp;lt;label for=&quot;assigned-widgets&quot; class=&quot;bold&quot;&amp;gt;Assigned Widgets&amp;lt;/label&amp;gt;
                &amp;lt;select size=10 id=&quot;assigned-widgets&quot; ng-model=&quot;items.selected&quot; ng-options=&quot;i.title for i in items&quot;
                        class=&quot;width100pr&quot; multiple=&quot;multiple&quot;&amp;gt;&amp;lt;/select&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;modal-footer&quot;&amp;gt;
        &amp;lt;button ng-click=&quot;close(true)&quot; class=&quot;btn btn-primary&quot;&amp;gt;Save and Close&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/pre&gt;
&lt;p&gt;There&apos;s a couple of new directives introduced by this code: &lt;em&gt;ngOptions&lt;/em&gt;&amp;nbsp;and &lt;em&gt;ngClick&lt;/em&gt;. The former is used to display options in a &amp;lt;select&amp;gt;, the latter to call functions in the controller. These functions are defined in &lt;code&gt;ConfigController&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
$scope.moveUp = function(items, list) {
    angular.forEach(items, function(item) {
        var idx = list.indexOf(item);
        if (idx != -1) {
            list.splice(idx - 1, 0, list.splice(idx, 1)[0]);
        }
    });
};

$scope.moveDown = function(items, list) {
    angular.forEach(items, function(item) {
        var idx = list.indexOf(item);
        if (idx != -1) {
            list.splice(idx + 1, 0, list.splice(idx, 1)[0]);
        }
    });
};

$scope.moveItem = function(items, from, to) {
    angular.forEach(items, function(item) {
        var idx = from.indexOf(item);
        if (idx != -1) {
            from.splice(idx, 1);
            to.push(item);
        }
    });
};

$scope.close = function(save) {
    if (save) {
        dialog.close({
            hidden: $scope.hiddenItems,
            items: $scope.items
        });
    } else {
        dialog.close();
    }
};
&lt;/pre&gt;
&lt;p&gt;As you can see, Angular allows you to easily access and manipulate the data. Its two-way binding feature is great because when you modify the object in JavaScript, it auto-updates the displayed HTML.The only thing you need to do to the HTML is to add the &lt;a href=&quot;http://docs.angularjs.org/api/ng.directive:ngModel&quot;&gt;&lt;em&gt;ngModel&lt;/em&gt;&lt;/a&gt; directive.&lt;/p&gt;
&lt;pre class=&quot;brush: html&quot;&gt;
&amp;lt;ul class=&quot;tasks&quot; ng-model=&quot;taskWidgets&quot; ui-sortable&amp;gt;
&lt;/pre&gt;
&lt;p&gt;The dialog&apos;s &lt;code&gt;close()&lt;/code&gt; method is called in the header (where it passes false) and in the footer (where it passes true). The &lt;code&gt;configureDialog()&lt;/code&gt; function handles saving if the user indicates they wanted to do so. The 3rd line (starts with $scope) is all that&apos;s needed to update the UI. The Preferences service is covered in the next article.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
dialog.open().then(function(results) {
    if (!angular.isUndefined(results)) {
        $scope[type + &apos;Widgets&apos;] = results.items;
        Preferences.saveWidgetPreferences(type, results);
    }
});
&lt;/pre&gt;
&lt;h3 id=&quot;modals-with-data&quot;&gt;Modals with Data&lt;/h3&gt;
&lt;p&gt;Displaying the widgets in a consolidated dashboard is a nice product feature, but we wanted to take it a step further and allow users to &quot;click through&quot; to see the data. To do this, I added an &quot;event&quot; directive that could read from our JSON data and act upon it accordingly. We decided on 2 types of events: &lt;strong&gt;function&lt;/strong&gt; and &lt;strong&gt;href&lt;/strong&gt;. The &lt;strong&gt;href&lt;/strong&gt; event type is for report widgets, because we want to allow users to click on the widget and it takes them directly to the report. For &lt;strong&gt;function&lt;/strong&gt;, we simply &lt;em&gt;eval()&lt;/em&gt; what&apos;s passed in. The function is expected to have a &lt;em&gt;container&lt;/em&gt; argument that it can use to render data in a modal window.&lt;/p&gt;
&lt;p&gt;Using the &lt;em&gt;event&lt;/em&gt; directive, you can attach this behavior to a widget simply by adding a class.&lt;/p&gt;
&lt;pre class=&quot;brush: html&quot;&gt;
&amp;lt;h3 class=&quot;events&quot;&amp;gt;{{widget.value}}&amp;lt;/h3&amp;gt;
&lt;/pre&gt;
&lt;p&gt;The &lt;em&gt;event&lt;/em&gt; directive that attaches click behavior is below:&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
directive(&apos;events&apos;, function () {
    // This is necessary because widgets may be chunked when charts are displayed 2-up
    function getWidget(element, widget, scope) {
        if (element.hasClass(&apos;chart-title&apos;)) {
            if (element.parent().hasClass(&apos;first&apos;)) {
                widget = scope.widget[0];
            } else if (element.parent().hasClass(&apos;second&apos;)) {
                widget = scope.widget[1];
            } else {
                widget = scope.widget;
            }
        } else {
            widget = scope.widget;
        }
        return widget;
    }
    return {
        restrict: &apos;C&apos;,
        link: function (scope, element, attrs) {
            var widget = getWidget(element, widget, scope);
            if (angular.isUndefined(widget)) {
                return;
            }
            var events = widget.events;
            if (!angular.isUndefined(events)) {
                for (var e in events) {
                    if (e === &apos;function&apos;) {
                        if ($(&apos;#dialog-frame&apos;).length === 0) {
                            $(&apos;&amp;lt;div id=&quot;dialog-frame&quot; class=&quot;modal hide&quot;/&amp;gt;&apos;).appendTo(&apos;body&apos;);
                            var header = $(&apos;&amp;lt;div class=&quot;modal-header&quot;&amp;gt;&apos;);
                            header.append($(&apos;&amp;lt;button type=&quot;button&quot; class=&quot;close&quot; data-dismiss=&quot;modal&quot;&amp;gt;&amp;times;&amp;lt;/button&amp;gt;&apos;));
                            header.append($(&apos;&amp;lt;h4/&amp;gt;&apos;).append(scope.widget.title));
                            header.appendTo(&apos;#dialog-frame&apos;);
                            $(&apos;&amp;lt;div class=&quot;modal-body&quot;/&amp;gt;&apos;).appendTo(&apos;#dialog-frame&apos;);
                        }
                        element.bind(&apos;click&apos;, function(event) {
                            event.preventDefault();
                            event.stopPropagation();
                            var dialog = $(&apos;#dialog-frame&apos;);
                            var title = widget.title;
                            dialog.find(&apos;h4&apos;).html(title);
                            var dialogBody = dialog.find(&apos;.modal-body&apos;);
                            dialogBody.empty();
 
                            // display a checking for new data message when widget&apos;s value is 0
                            var checkingMessage = $(&apos;#wait-checking&apos;).html();
                            if (scope.widget.value === &quot;0&quot;) {
                                dialogBody.html(checkingMessage);
                            // otherwise, display a loading message
                            } else {
                                dialogBody.html($(&apos;#wait-loading&apos;).html());
                            }
 
                            // center the dialog on the page
                            dialog.css({
                                width: &apos;560px&apos;,
                                &apos;margin-left&apos;: function() {
                                    return -($(this).width() / 2);
                                }
                            });
 
                            dialog.modal(&apos;show&apos;);
                            var container = dialogBody;
                            eval(events[e]);
                        });
                    } else if (e === &quot;href&quot;) {
                        element.bind(&apos;click&apos;, function() {
                            location.href = events[e];
                        })
                    } else {
                        console.log(&apos;Event type &quot;&apos; + e + &apos;&quot; not supported.&apos;);
                    }
                }
            }
        }
    }
})
&lt;/pre&gt;
&lt;h3 id=&quot;empty-widgets-message&quot;&gt;Empty Widgets Message&lt;/h3&gt;
&lt;p&gt;When there are no widgets for a particular type, we wanted to display a message telling the end user. To do this, I used the &lt;a href=&quot;http://docs.angularjs.org/api/ng.directive:ngHide&quot;&gt;&lt;em&gt;ngHide&lt;/em&gt;&lt;/a&gt; directive and passed in the array&apos;s length as an expression. I originally had this on a &amp;lt;li&amp;gt; in the respective widget list, but noticed it causes issues when dragging and dropping.&lt;/p&gt;
&lt;pre class=&quot;brush: html&quot;&gt;
&amp;lt;div ng-hide=&quot;summaryWidgets.length&quot; class=&quot;widgets-empty&quot;&amp;gt;
    No Summary Bar Widgets currently visible
&amp;lt;/div&amp;gt;
&lt;/pre&gt;
&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;This article has provided an overview of how I changed some of My Dashboard&apos;s features to use Angular instead of jQuery. I hope it&apos;s helped to show how powerful directives can be and how MVC works in Angular. I particularly enjoyed learning how to use the $dialog service. As a word of warning, its usage might change in future releases since it is currently being &lt;a href=&quot;https://github.com/angular-ui/bootstrap/issues/441&quot;&gt;rewritten to be more maintainable&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In the &lt;a href=&quot;http://raibledesigns.com/rd/entry/developing_with_angularjs_part_iii&quot;&gt;next article&lt;/a&gt;, I&apos;ll talk about how I developed Services and integrated DWR. If you see any code that can be improved upon, or issues with the code/architecture in this article, please leave a comment.&lt;/p&gt;</description>          </item>
    <item>
    <guid isPermaLink="true">https://raibledesigns.com/rd/entry/developing_with_angularjs_part_i</guid>
    <title>Developing with AngularJS - Part I: The Basics</title>
    <dc:creator>Matt Raible</dc:creator>
    <link>https://raibledesigns.com/rd/entry/developing_with_angularjs_part_i</link>
        <pubDate>Tue, 18 Jun 2013 09:06:52 -0600</pubDate>
    <category>The Web</category>
    <category>jquery</category>
    <category>highcharts</category>
    <category>javascript</category>
    <category>angularjs</category>
    <category>bootstrap</category>
    <category>taleo</category>
    <atom:summary type="html">&lt;p&gt;There&apos;s &lt;a href=&quot;http://hop.ie/blog/angularjs-introduction/&quot;&gt;many&lt;/a&gt;, &lt;a href=&quot;http://www.raweng.com/blog/2013/01/30/introduction-to-angularjs-part-1/&quot;&gt;many&lt;/a&gt; different &lt;a href=&quot;http://www.webdesignerdepot.com/2013/04/an-introduction-to-angularjs/&quot;&gt;introductions&lt;/a&gt; to &lt;a href=&quot;http://angularjs.org/&quot;&gt;AngularJS&lt;/a&gt; available on the internet. This article is not another introduction, but rather a story about my learning experience. It all started way back in January of this year. I was working as a UI Architecture Consultant at Taleo/Oracle, my client for the last 21 months. My gig there ended last month, but they agreed to let me publish a series of articles about the knowledge I gained.&lt;/p&gt;

&lt;h3 id=&quot;background&quot;&gt;Project Background&lt;/h3&gt;
&lt;p&gt;The Director of Product Management had been working on the concepts for a new project - codenamed &quot;Visual MyView&quot;. Below is a mockup he created for our kickoff meeting on January 4th.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
&lt;a data-href=&quot;http://www.flickr.com/photos/mraible/8904352595/&quot; href=&quot;http://farm3.staticflickr.com/2872/8904352595_1678cfd1ab_c.jpg&quot; title=&quot;My Dashboard - Original Mockup&quot; rel=&quot;lightbox[angular-dashboard1]&quot;&gt;&lt;img src=&quot;http://farm3.staticflickr.com/2872/8904352595_1678cfd1ab.jpg&quot; width=&quot;500&quot; height=&quot;296&quot; alt=&quot;My Dashboard - Original Mockup&quot; style=&quot;border: 1px solid silver; box-shadow: 5px 5px 10px #888&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;
From his original email about the above mockup:
&lt;/p&gt;
&lt;div class=&quot;quote&quot;&gt;
    &lt;p&gt;The intent here is that one of the columns has rows that have a similar width. The rows could be dragged and dropped into a different order &#8211; or potentially the two columns could also be reordered. The rows will basically be comprised of similar widgets. You can see in the mockup how the first two rows might look &#8211; and sample widgets. The widgets shown can be configured by the end user, as well as the order in which they are displayed. Other requirements given to us were the following.
    &lt;/p&gt;
    &lt;ul&gt;&lt;li&gt;Row 1 is comprised of &apos;summary&apos; widgets that are &apos;todo&apos; items. Reviews needing done &#8211; approvals required &#8211; etc.&lt;/li&gt;
        &lt;li&gt;Row 2 will be a graph row &#8211; having graphs and charts to display information &#8211; larger squares will build this row.&lt;/li&gt;
        &lt;li&gt;Row 3&apos;s content was not determined yet.&lt;/li&gt;&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;I started the initial layout with static HTML and CSS and had a wireframe to show by mid January.&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
&lt;a data-href=&quot;http://www.flickr.com/photos/mraible/8904969226/&quot; href=&quot;http://farm3.staticflickr.com/2885/8904969226_c33d020e07_c.jpg&quot; title=&quot;Wireframe&quot; rel=&quot;lightbox[angular-dashboard1]&quot;&gt;&lt;img src=&quot;http://farm3.staticflickr.com/2885/8904969226_c33d020e07_n.jpg&quot; width=&quot;320&quot; height=&quot;268&quot; alt=&quot;Wireframe&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;By the end of January, we&apos;d renamed the project to My Dashboard and had a working prototype using &lt;a href=&quot;http://randomibis.com/coolclock/&quot;&gt;CoolClock&lt;/a&gt; and &lt;a href=&quot;http://momentjs.com/&quot;&gt;moment.js&lt;/a&gt; for the clock in the top right, &lt;a href=&quot;http://angularjs.org/&quot;&gt;AngularJS&lt;/a&gt; to display widget data, &lt;a href=&quot;http://jqueryui.com/&quot;&gt;jQuery UI&lt;/a&gt; for drag-n-drop of rows and widgets, Bootstrap&apos;s &lt;a href=&quot;http://twitter.github.com/bootstrap/javascript.html#carousel&quot;&gt;Carousel&lt;/a&gt; for holding charts and &lt;a href=&quot;http://www.highcharts.com/&quot;&gt;Highcharts&lt;/a&gt; for rendering charts.&lt;/p&gt;
</atom:summary>        <description>&lt;p&gt;There&apos;s &lt;a href=&quot;http://hop.ie/blog/angularjs-introduction/&quot;&gt;many&lt;/a&gt;, &lt;a href=&quot;http://www.raweng.com/blog/2013/01/30/introduction-to-angularjs-part-1/&quot;&gt;many&lt;/a&gt; &lt;a href=&quot;http://www.codeproject.com/Articles/607873/Extending-HTML-with-AngularJS-Directives&quot;&gt;different&lt;/a&gt; &lt;a href=&quot;http://www.webdesignerdepot.com/2013/04/an-introduction-to-angularjs/&quot;&gt;introductions&lt;/a&gt; to &lt;a href=&quot;http://angularjs.org/&quot;&gt;AngularJS&lt;/a&gt; available on the internet. This article is not another introduction, but rather a story about my learning experience. It all started way back in January of this year. I was working as a UI Architecture Consultant at Taleo/Oracle, my client for the last 21 months. My gig there ended last month, but they agreed to let me publish a series of articles about the knowledge I gained.&lt;/p&gt;

&lt;h3 id=&quot;background&quot;&gt;Project Background&lt;/h3&gt;
&lt;p&gt;The Director of Product Management had been working on the concepts for a new project - codenamed &quot;Visual MyView&quot;. Below is a mockup he created for our kickoff meeting on January 4th.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
&lt;a data-href=&quot;http://www.flickr.com/photos/mraible/8904352595/&quot; href=&quot;http://farm3.staticflickr.com/2872/8904352595_1678cfd1ab_c.jpg&quot; title=&quot;My Dashboard - Original Mockup&quot; rel=&quot;lightbox[angular-dashboard1]&quot;&gt;&lt;img src=&quot;//farm3.staticflickr.com/2872/8904352595_1678cfd1ab.jpg&quot; width=&quot;500&quot; height=&quot;296&quot; alt=&quot;My Dashboard - Original Mockup&quot; style=&quot;border: 1px solid silver; box-shadow: 5px 5px 10px #888&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;
From his original email about the above mockup:
&lt;/p&gt;
&lt;div class=&quot;quote&quot;&gt;
    &lt;p&gt;The intent here is that one of the columns has rows that have a similar width. The rows could be dragged and dropped into a different order &#8211; or potentially the two columns could also be reordered. The rows will basically be comprised of similar widgets. You can see in the mockup how the first two rows might look &#8211; and sample widgets. The widgets shown can be configured by the end user, as well as the order in which they are displayed. Other requirements given to us were the following.
    &lt;/p&gt;
    &lt;ul&gt;&lt;li&gt;Row 1 is comprised of &apos;summary&apos; widgets that are &apos;todo&apos; items. Reviews needing done &#8211; approvals required &#8211; etc.&lt;/li&gt;
        &lt;li&gt;Row 2 will be a graph row &#8211; having graphs and charts to display information &#8211; larger squares will build this row.&lt;/li&gt;
        &lt;li&gt;Row 3&apos;s content was not determined yet.&lt;/li&gt;&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;I started the initial layout with static HTML and CSS and had a wireframe to show by mid January.&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
&lt;a data-href=&quot;http://www.flickr.com/photos/mraible/8904969226/&quot; href=&quot;http://farm3.staticflickr.com/2885/8904969226_c33d020e07_c.jpg&quot; title=&quot;Wireframe&quot; rel=&quot;lightbox[angular-dashboard1]&quot;&gt;&lt;img src=&quot;//farm3.staticflickr.com/2885/8904969226_c33d020e07_n.jpg&quot; width=&quot;320&quot; height=&quot;268&quot; alt=&quot;Wireframe&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;By the end of January, we&apos;d renamed the project to My Dashboard and had a working prototype using &lt;a href=&quot;http://randomibis.com/coolclock/&quot;&gt;CoolClock&lt;/a&gt; and &lt;a href=&quot;http://momentjs.com/&quot;&gt;moment.js&lt;/a&gt; for the clock in the top right, &lt;a href=&quot;http://angularjs.org/&quot;&gt;AngularJS&lt;/a&gt; to display widget data, &lt;a href=&quot;http://jqueryui.com/&quot;&gt;jQuery UI&lt;/a&gt; for drag-n-drop of rows and widgets, Bootstrap&apos;s &lt;a href=&quot;http://twitter.github.com/bootstrap/javascript.html#carousel&quot;&gt;Carousel&lt;/a&gt; for holding charts and &lt;a href=&quot;http://www.highcharts.com/&quot;&gt;Highcharts&lt;/a&gt; for rendering charts. For this prototype, we included 4 types of widgets:&lt;/p&gt;
&lt;ol&gt;&lt;li&gt;Summary&lt;/li&gt;&lt;li&gt;Tasks&lt;/li&gt;&lt;li&gt;Charts&lt;/li&gt;&lt;li&gt;Reports&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;To create widgets, we had to decide on a common schema for them.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
&quot;id&quot;: 1, // not necessary for display, but likely needed if we modify and save preferences
&quot;title&quot;: &quot;Appointments Today&quot;,
&quot;type&quot;: &quot;summary&quot;, // others include: task, chart, report
&quot;value&quot;: 3, 
&quot;description&quot;: &quot;10:30 Jim Smith&quot;,
&quot;events&quot;: &quot;url&quot;, // this can have click events
&quot;order&quot;: 1 // used to determine order
&lt;/pre&gt;
&lt;p&gt;Below is a screenshot of our wireframe with some sample widgets.&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
&lt;a data-href=&quot;http://www.flickr.com/photos/mraible/8904969540/&quot; href=&quot;http://farm9.staticflickr.com/8556/8904969540_1cfe0e56c7_c.jpg&quot; title=&quot;Wireframe with Data&quot; rel=&quot;lightbox[angular-dashboard1]&quot;&gt;&lt;img src=&quot;//farm9.staticflickr.com/8556/8904969540_1cfe0e56c7.jpg&quot; width=&quot;500&quot; height=&quot;342&quot; alt=&quot;Wireframe with Data&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;h3 id=&quot;basics&quot;&gt;Angular Basics&lt;/h3&gt;
&lt;p&gt;The decision to use AngularJS came early on in the project, after I read Tyler Renelle&apos;s &lt;a href=&quot;https://gist.github.com/lefnire/4454814&quot;&gt;Rant: Backbone, Angular, Meteor, Derby&lt;/a&gt;. To learn AngularJS, I briefly looked at its homepage documentation and played with some examples. Then I stumbled upon Misko Hevery and Igor Minar&apos;s &lt;a href=&quot;http://parleys.com/play/5148922b0364bc17fc56c91b/about&quot;&gt;AngularJS Presentation from Devoxx 2012&lt;/a&gt;. At that time, the video wasn&apos;t publicly available (it&apos;s free now), so I had to buy a Parley&apos;s subscription ($79). It was well worth the money because that one hour video greatly contributed to my understanding of how AngularJS works. Another resource I used frequently to figure out how to do things was John Lindquist&apos;s &lt;a href=&quot;http://www.egghead.io/&quot;&gt;egghead.io&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To begin with, we wrote the JSON for a bunch of sample widgets and embedded them into the page as a &lt;code&gt;widgetData&lt;/code&gt; JavaScript variable.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
var widgetData = [
    {&quot;id&quot;: 1, &quot;title&quot;: &quot;Appointments Today&quot;, &quot;type&quot;: &quot;summary&quot;, &quot;value&quot;: 3, &quot;description&quot;: &quot;10:30 Jim Smith&quot;, &quot;events&quot;: {&quot;click&quot;: &quot;alert(&apos;foo&apos;);&quot;}, &quot;order&quot;: 1},
    {&quot;id&quot;: 12, &quot;order&quot;: 2, &quot;title&quot;: &quot;Offer Approvals&quot;, &quot;type&quot;: &quot;task&quot;, &quot;class&quot;: &quot;sticky-note&quot;, &quot;value&quot;: 1},
    {&quot;id&quot;: 103, &quot;title&quot;: &quot;Browser market shares at a specific website, 2010&quot;, &quot;order&quot;: 1, &quot;type&quot;: &quot;chart&quot;, &quot;chartType&quot;: &quot;pie&quot;, 
         &quot;tooltip&quot;: {&quot;pointFormat&quot;: &quot;{series.name}: &amp;lt;b&gt;{point.percentage}%&amp;lt;/b&gt;&quot;, &quot;percentageDecimals&quot;: 1}, &quot;series&quot;: [
         {&quot;type&quot;: &quot;pie&quot;, &quot;name&quot;: &quot;Browser share&quot;, &quot;data&quot;: [
             [&quot;Firefox&quot;, 45.0],
             [&quot;IE&quot;, 26.8],
             {&quot;name&quot;: &quot;Chrome&quot;, &quot;y&quot;: 12.8, &quot;sliced&quot;: true, &quot;selected&quot;: true},
             [&quot;Safari&quot;, 8.5],
             [&quot;Opera&quot;, 6.2],
             [&quot;Others&quot;, 0.7]
         ]}
    ]},
    ...
];
&lt;/pre&gt;
&lt;p&gt;I used &lt;a href=&quot;https://github.com/angular/angular-seed&quot;&gt;angular-seed&lt;/a&gt; to create the initial structure of the prototype, and continued using the same JavaScript file names when we moved it into the product I worked on. Since the application takes a while to login and render the My Dashboard page (when working remotely), I decided not to use the &lt;a href=&quot;http://karma-runner.github.io/0.8/index.html&quot;&gt;Karma&lt;/a&gt; testing framework that ships with Angular. Below is what our directory structure looked like for our prototype.&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
&lt;a data-href=&quot;http://www.flickr.com/photos/mraible/8904352305/&quot; href=&quot;http://farm3.staticflickr.com/2860/8904352305_e3b3d40f80_c.png&quot; title=&quot;Angular Seed Directory Structure&quot; rel=&quot;lightbox[angular-dashboard1]&quot;&gt;&lt;img src=&quot;//farm3.staticflickr.com/2860/8904352305_e3b3d40f80_o.png&quot; width=&quot;327&quot; height=&quot;459&quot; alt=&quot;Angular Seed Directory Structure&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;The JavaScript files in the &quot;js&quot; folder are the most important for Angular. The first file, &lt;code&gt;app.js&lt;/code&gt;, loads the other files:&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
angular.module(&apos;dashboard&apos;, [&apos;dashboard.filters&apos;, &apos;dashboard.services&apos;, &apos;dashboard.directives&apos;]);
&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;controllers.js&lt;/code&gt; file contains the Controllers (functions) that get the data and make it available to the page. Here&apos;s the code for our first controller:&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
&apos;use strict&apos;;

/* Controllers */
function WidgetController($scope) {
    $scope.widgets = widgetData;
}
&lt;/pre&gt;
&lt;p&gt;This puts the widgets in scope and then we were able to render them using Angular&apos;s &lt;a href=&quot;http://docs.angularjs.org/api/ng.directive:ngRepeat&quot;&gt;ngRepeat&lt;/a&gt; directive and the following HTML:&lt;/p&gt;
&lt;pre class=&quot;brush: html&quot;&gt;
&amp;lt;div ng-app=&quot;dashboard&quot; class=&quot;dashboard&quot;&amp;gt;
    &amp;lt;div class=&quot;container-widgets&quot; ng-controller=&quot;WidgetController&quot; ng-cloak&amp;gt;
        &amp;lt;div class=&quot;row-fluid&quot;&amp;gt;
            &amp;lt;div class=&quot;span9&quot;&amp;gt;
                &amp;lt;ul class=&quot;widgets&quot;&amp;gt;
                    &amp;lt;li id=&quot;summary-bar&quot;&amp;gt;
                        &amp;lt;div class=&quot;heading&quot;&amp;gt;Summary&amp;lt;/div&amp;gt;
                        &amp;lt;ul class=&quot;tiles&quot;&amp;gt;
                            &amp;lt;li class=&quot;span3&quot; ng-repeat=&quot;widget in widgets | filter:{type: &apos;summary&apos;} | orderBy: &apos;order&apos;&quot;&amp;gt;
                                &amp;lt;h3 class=&quot;events&quot;&amp;gt;{{widget.value}}&amp;lt;/h3&amp;gt;
                                &amp;lt;div class=&quot;title&quot;&amp;gt;{{widget.title}}&amp;lt;/div&amp;gt;
                                &amp;lt;div class=&quot;desc&quot;&amp;gt;{{widget.description}}&amp;lt;/div&amp;gt;
                            &amp;lt;/li&amp;gt;
                        &amp;lt;/ul&amp;gt;
                    &amp;lt;/li&amp;gt;
                    &amp;lt;li id=&quot;task-bar&quot;&amp;gt;
                        &amp;lt;div class=&quot;heading&quot;&amp;gt;My Tasks&amp;lt;/div&amp;gt;
                        &amp;lt;ul class=&quot;tasks&quot;&amp;gt;
                            &amp;lt;li class=&quot;task {{widget.class}}&quot; ng-repeat=&quot;widget in widgets | filter: {type: &apos;task&apos;} | orderBy: &apos;order&apos;&quot;&amp;gt;
                                &amp;lt;div class=&quot;title events&quot;&amp;gt;{{widget.title}}&amp;lt;/div&amp;gt;
                                &amp;lt;div class=&quot;value&quot;&amp;gt;{{widget.value}}&amp;lt;/div&amp;gt;
                            &amp;lt;/li&amp;gt;
                        &amp;lt;/ul&amp;gt;
                    &amp;lt;/li&amp;gt;
                    &amp;lt;li id=&quot;chart-bar&quot;&amp;gt;
                        &amp;lt;div class=&quot;heading&quot;&amp;gt;Charts&amp;lt;/div&amp;gt;
                        &amp;lt;div id=&quot;chartCarousel&quot; class=&quot;carousel slide&quot;&amp;gt;
                            &amp;lt;ol class=&quot;carousel-indicators&quot;&amp;gt;
                                &amp;lt;li data-target=&quot;#chartCarousel&quot;
                                    ng-repeat=&quot;widget in widgets | filter: {type: &apos;chart&apos;} | orderBy: &apos;order&apos;&quot;
                                    data-slide-to=&quot;{{$index}}&quot; ng-class=&quot;{active: $index == 0}&quot;&amp;gt;&amp;lt;/li&amp;gt;
                            &amp;lt;/ol&amp;gt;
                            &amp;lt;div class=&quot;carousel-inner&quot;&amp;gt;
                                &amp;lt;div class=&quot;item chart&quot;
                                     ng-repeat=&quot;widget in widgets | filter: {type: &apos;chart&apos;} | orderBy: &apos;order&apos;&quot;
                                     ng-class=&quot;{active: $index == 0}&quot;&amp;gt;
                                    &amp;lt;chart class=&quot;widget&quot; value=&quot;{{widget}}&quot; type=&quot;{{widget.chartType}}&quot;&amp;gt;&amp;lt;/chart&amp;gt;
                                &amp;lt;/div&amp;gt;
                            &amp;lt;/div&amp;gt;
                            &amp;lt;a class=&quot;left carousel-control&quot; href=&quot;#chartCarousel&quot; data-slide=&quot;prev&quot;&amp;gt;&#8249;&amp;lt;/a&amp;gt;
                            &amp;lt;a class=&quot;right carousel-control&quot; href=&quot;#chartCarousel&quot; data-slide=&quot;next&quot;&amp;gt;&#8250;&amp;lt;/a&amp;gt;
                        &amp;lt;/div&amp;gt;
                    &amp;lt;/li&amp;gt;
                &amp;lt;/ul&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class=&quot;span3&quot;&amp;gt;
                &amp;lt;!-- clock and reports --&amp;gt;
            &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/pre&gt;
&lt;p&gt;The beginning of this HTML shows how Angular is instantiated: &lt;strong&gt;ng-app&lt;/strong&gt; matches the name defined in &lt;code&gt;app.js&lt;/code&gt;, &lt;strong&gt;ng-controller&lt;/strong&gt; instantiates the &lt;code&gt;WidgetController&lt;/code&gt; and &lt;strong&gt;ng-cloak&lt;/strong&gt; is used to hide everything until its processed.&lt;/p&gt;
&lt;pre class=&quot;brush: html&quot;&gt;
&amp;lt;div ng-app=&quot;dashboard&quot; class=&quot;dashboard&quot;&amp;gt;
    &amp;lt;div class=&quot;container-widgets&quot; ng-controller=&quot;WidgetController&quot; ng-cloak&amp;gt;
&lt;/pre&gt;
&lt;p&gt;If you take a closer look at the way ng-repeat attributes, you&apos;ll see how &lt;strong&gt;filters&lt;/strong&gt; are used to filter data. There&apos;s &lt;a href=&quot;http://docs.angularjs.org/api/ng.filter:filter&quot;&gt;&lt;em&gt;filter&lt;/em&gt;&lt;/a&gt;&lt;em&gt; &lt;/em&gt;and &lt;a href=&quot;http://docs.angularjs.org/api/ng.filter:orderBy&quot;&gt;&lt;em&gt;orderBy&lt;/em&gt;&lt;/a&gt; filters that are built in and allow you to filter data. The &lt;em&gt;filter &lt;/em&gt;filter allows you to query arrays by strings, objects and even functions. In the following code block, &quot;task&quot; widgets are filtered, ordered and displayed.&lt;/p&gt;
&lt;pre class=&quot;brush: html&quot;&gt;
&amp;lt;li class=&quot;task {{widget.class}}&quot; ng-repeat=&quot;widget in widgets | filter: {type: &apos;task&apos;} | orderBy: &apos;order&apos;&quot;&amp;gt;
    &amp;lt;div class=&quot;title events&quot;&amp;gt;{{widget.title}}&amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;value&quot;&amp;gt;{{widget.value}}&amp;lt;/div&amp;gt;
&amp;lt;/li&amp;gt;
&lt;/pre&gt;
&lt;p&gt;This was pretty straightforward, but we quickly noticed that if a widget had HTML in its title, it didn&apos;t display correctly (rendering the raw HTML). To process the HTML, we had to use the &lt;a href=&quot;http://docs.angularjs.org/api/ngSanitize.directive:ngBindHtml&quot;&gt;ngBindHtml&lt;/a&gt; directive (tip: directives are camelCase, but written with dashes in HTML).&lt;/p&gt;
&lt;pre class=&quot;brush: html&quot;&gt;
&amp;lt;div class=&quot;title events&quot; ng-bind-html=&quot;widget.title&quot;&amp;gt;&amp;lt;/div&amp;gt;
&lt;/pre&gt;
&lt;p&gt;After getting this to work, we noticed that some titles weren&apos;t fully rendered because they were hidden with overflow: hidden. We tried adding a tooltip with &lt;code&gt;title=&quot;{{widget.title}}&quot;&lt;/code&gt;, but ran into the same issue. I &lt;a href=&quot;https://groups.google.com/d/topic/angular/hG-T1bsmlnk/discussion&quot;&gt;sent an email &lt;/a&gt;to the AngularJS Google Group and received a solution: create an htmlTitle directive:&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
.directive(&apos;htmlTitle&apos;, function ($sanitize) {
    return {
        restrict: &apos;A&apos;,
        link: function (scope, element, attrs) {
            attrs.$observe(&apos;htmlTitle&apos;, function (title) {
                // convert &amp;amp;value; to HTML
                var html = angular.element(&apos;&amp;lt;div&amp;gt;&amp;lt;/div&amp;gt;&apos;).html($sanitize(title)).text();
                element.attr(&apos;title&apos;, html);
                element.html(html);
            });
        }
    }
})
&lt;/pre&gt;
&lt;p&gt;Usage:&lt;/p&gt;
&lt;pre class=&quot;brush: html&quot;&gt;
&amp;lt;div class=&quot;title events&quot; ng-bind-html=&quot;widget.title&quot; html-title=&quot;{{widget.title}}&quot;&amp;gt;&amp;lt;/div&amp;gt;
&lt;/pre&gt;
&lt;h3 id=&quot;dragndrop&quot;&gt;Drag-and-Drop&lt;/h3&gt;
&lt;p&gt;To implement drag-and-drop functionality, I originally used jQuery UI&apos;s &lt;a href=&quot;http://jqueryui.com/sortable/&quot;&gt;sortable&lt;/a&gt;. At the bottom of the page, the following code initialized sorting for the various lists:&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
$(document).ready(function() {
    $(&apos;.widgets&apos;).sortable({
        cursor: &quot;move&quot;,
        handle: &quot;.heading&quot;
    }).disableSelection();
    $(&apos;.tiles,.tasks&apos;).sortable();
    var carousel = $(&apos;.carousel&apos;);
    $(carousel).carousel({
        interval: 0
    });
};
&lt;/pre&gt;
&lt;p&gt;As you can see, it also initializes the carousel and stops it from cycling automatically.&lt;/p&gt;
&lt;h3 id=&quot;carousel-issues&quot;&gt;Carousel Issues&lt;/h3&gt;
&lt;p&gt;The first problem I ran into with Bootstrap&apos;s Carousel was a strange error from Highcharts. If you look in the above HTML, you&apos;ll see there&apos;s a &lt;code&gt;&amp;lt;chart&amp;gt;&lt;/code&gt; element. This is processed by a &lt;a href=&quot;https://github.com/rootux/angular-highcharts-directive/blob/master/src/directives/highchart.js&quot;&gt;highcharts directive&lt;/a&gt;. When I tried to use this directive for Highcharts in a carousel, it results in the following error:&lt;/p&gt;
&lt;p class=&quot;alert alert-error&quot;&gt;
TypeError: Cannot read property &apos;length&apos; of undefined at Object.ob.setMaxTicks
&lt;/p&gt;
&lt;p&gt;This seemed to be caused by the following css in Bootstrap:&lt;/p&gt;
&lt;pre class=&quot;brush: css&quot;&gt;
.carousel-inner &gt; .item { display: none }
&lt;/pre&gt;
&lt;p&gt;When I added an override with &quot;display: block&quot; to my stylesheet, everything worked, but the charts were stacked instead of in a carousel. To fix this, I modified the directive to show/hide the &quot;item&quot; element so Highcharts was able to write to it. I also &lt;a href=&quot;https://github.com/rootux/angular-highcharts-directive/issues/1&quot;&gt;logged an issue&lt;/a&gt; for this.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
if (element.parent().not(&apos;:visible&apos;)) {
    element.parent().show();
}
var chart = new Highcharts.Chart(newSettings);
element.parent().attr(&apos;style&apos;, &apos;&apos;);
&lt;/pre&gt;
&lt;h3 id=&quot;grouping&quot;&gt;ngRepeat and Grouping&lt;/h3&gt;
&lt;p&gt;The last thing I accomplished in our end-of-January prototype was rendering 2 charts side-by-side. I got it working with plain HTML, created a &quot;groupBy&quot; filter for Angular and tried to get it to work with the following:&lt;/p&gt;
&lt;pre class=&quot;brush: html&quot;&gt;
&amp;lt;div id=&quot;chartCarousel&quot; class=&quot;carousel slide&quot;&amp;gt;
    &amp;lt;ol class=&quot;carousel-indicators&quot;&amp;gt;
        &amp;lt;li data-target=&quot;#chartCarousel&quot; ng-repeat=&quot;widget in widgets | filter: {type: &apos;chart&apos;} | groupBy&quot;
            data-slide-to=&quot;{{$index}}&quot; ng-class=&quot;{active: $index == 0}&quot;&amp;gt;&amp;lt;/li&amp;gt;
    &amp;lt;/ol&amp;gt;
    &amp;lt;div class=&quot;carousel-inner&quot;&amp;gt;
        &amp;lt;div class=&quot;item&quot; ng-repeat=&quot;widget in widgets | filter: {type: &apos;chart&apos;} | groupBy&quot; ng-class=&quot;{active: $index == 0}&quot;&amp;gt;
            &amp;lt;div class=&quot;widget&quot;&amp;gt;{{widget&amp;#91;0&amp;#93;.title}}&amp;lt;/div&amp;gt;
            &amp;lt;div class=&quot;widget&quot;&amp;gt;{{widget&amp;#91;1&amp;#93;.title}}&amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;a class=&quot;left carousel-control&quot; href=&quot;#chartCarousel&quot; data-slide=&quot;prev&quot;&amp;gt;&#8249;&amp;lt;/a&amp;gt;
    &amp;lt;a class=&quot;right carousel-control&quot; href=&quot;#chartCarousel&quot; data-slide=&quot;next&quot;&amp;gt;&#8250;&amp;lt;/a&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/pre&gt;
&lt;p&gt;This all worked like I expected it to in Chrome, but I the following errors showed in my console.&lt;/p&gt;
&lt;p class=&quot;alert alert-error&quot;&gt;
Error: 10 $digest() iterations reached. Aborting!
Watchers fired in the last 5 iterations:
&lt;/p&gt;
&lt;p&gt;I &lt;a href=&quot;https://groups.google.com/d/topic/angular/gEv1-YV-Ojg/discussion&quot;&gt;sent an email&lt;/a&gt; to the Angular Google Group and received a link to &lt;a href=&quot;https://groups.google.com/d/msg/angular/IEIQok-YkpU/oKuLvzCnAcoJ&quot;&gt;a discussion&lt;/a&gt; where I found a &quot;chunk&quot; filter that solved the problem. This worked great, but I wanted to make it more responsive.&lt;/p&gt;
&lt;ol&gt;&lt;li&gt;If the user has a screen size big enough to fit 2 charts, show 2 charts and paginate by 2.&lt;/li&gt;&lt;li&gt;If the user has a small screen size that only fits 1 chart, show 1 and paginate by 1.&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;To solve #1 and #2, I ended up rendering two different sections (with classes .oneup and .twoup) and displayed them based on screen size.&lt;/p&gt;
&lt;pre class=&quot;brush: html&quot;&gt;
&amp;lt;li id=&quot;chart-bar&quot;&amp;gt;
    &amp;lt;div class=&quot;heading&quot;&amp;gt;Charts&amp;lt;/div&amp;gt;
    &amp;lt;div id=&quot;chartCarousel1&quot; class=&quot;carousel slide oneup&quot; style=&quot;display: none&quot;&amp;gt;
        &amp;lt;ol class=&quot;carousel-indicators&quot;&amp;gt;
            &amp;lt;li data-target=&quot;#chartCarousel1&quot;
                ng-repeat=&quot;widget in widgets | filter: {type: &apos;chart&apos;} | orderBy: &apos;order&apos;&quot;
                data-slide-to=&quot;{{$index}}&quot; ng-class=&quot;{active: $index == 0}&quot;&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;/ol&amp;gt;
        &amp;lt;div class=&quot;carousel-inner&quot;&amp;gt;
            &amp;lt;div class=&quot;item chart&quot;
                 ng-repeat=&quot;widget in widgets | filter: {type: &apos;chart&apos;} | orderBy: &apos;order&apos;&quot;
                 ng-class=&quot;{active: $index == 0}&quot;&amp;gt;
                &amp;lt;chart class=&quot;widget&quot; value=&quot;{{widget}}&quot; type=&quot;{{widget.chartType}}&quot;&amp;gt;&amp;lt;/chart&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;a class=&quot;left carousel-control&quot; href=&quot;#chartCarousel1&quot; data-slide=&quot;prev&quot;&amp;gt;&#8249;&amp;lt;/a&amp;gt;
        &amp;lt;a class=&quot;right carousel-control&quot; href=&quot;#chartCarousel1&quot; data-slide=&quot;next&quot;&amp;gt;&#8250;&amp;lt;/a&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div id=&quot;chartCarousel2&quot; class=&quot;carousel slide twoup&quot; style=&quot;display: none&quot;&amp;gt;
        &amp;lt;ol class=&quot;carousel-indicators&quot;&amp;gt;
            &amp;lt;li data-target=&quot;#chartCarousel2&quot;
                ng-repeat=&quot;widget in widgets | filter: {type: &apos;chart&apos;} | chunk: 2 | orderBy: &apos;order&apos;&quot;
                data-slide-to=&quot;{{$index}}&quot; ng-class=&quot;{active: $index == 0}&quot;&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;/ol&amp;gt;
        &amp;lt;div class=&quot;carousel-inner&quot;&amp;gt;
            &amp;lt;div class=&quot;item chart&quot;
                 ng-repeat=&quot;widget in widgets | filter: {type: &apos;chart&apos;} | chunk: 2 | orderBy: &apos;order&apos;&quot;
                 ng-class=&quot;{active: $index == 0}&quot;&amp;gt;
                &amp;lt;chart class=&quot;widget&quot; value=&quot;{{widget&amp;#91;0&amp;#93;}}&quot; type=&quot;{{widget&amp;#91;0&amp;#93;.chartType}}&quot;&amp;gt;&amp;lt;/chart&amp;gt;
                &amp;lt;chart class=&quot;widget&quot; value=&quot;{{widget&amp;#91;1&amp;#93;}}&quot; type=&quot;{{widget&amp;#91;1&amp;#93;.chartType}}&quot;&amp;gt;&amp;lt;/chart&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;a class=&quot;left carousel-control&quot; href=&quot;#chartCarousel2&quot; data-slide=&quot;prev&quot;&amp;gt;&#8249;&amp;lt;/a&amp;gt;
        &amp;lt;a class=&quot;right carousel-control&quot; href=&quot;#chartCarousel2&quot; data-slide=&quot;next&quot;&amp;gt;&#8250;&amp;lt;/a&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/li&amp;gt;
&lt;/pre&gt;
&lt;p&gt;The JavaScript to show the correct number of charts is below:&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
var chartBar = $(&apos;#chart-bar&apos;);
function showCharts() {
    if (chartBar.width() &amp;lt; 960) {
        chartBar.find(&apos;.oneup&apos;).show();
        chartBar.find(&apos;.twoup&apos;).hide();
    } else {
        chartBar.find(&apos;.twoup&apos;).show();
        chartBar.find(&apos;.oneup&apos;).hide();
    }
}

$(document).ready(function () {
    showCharts();
});

$(window).resize(showCharts);
&lt;/pre&gt;
&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;Even though I got everything to work for our initial prototype using Angular and jQuery, it didn&apos;t quite feel like I was taking full advantage of Angular&apos;s power. In particular, I learned that Angular UI Bootstrap had their own carousel and Angular UI had a &lt;em&gt;sortable&lt;/em&gt; directive. My suspicions were confirmed when I read &lt;a href=&quot;http://stackoverflow.com/questions/14994391/how-do-i-think-in-angularjs-if-i-have-a-jquery-background&quot;&gt;How do I &quot;think in AngularJS&quot; if I have a jQuery background?&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In the &lt;a href=&quot;http://raibledesigns.com/rd/entry/developing_with_angularjs_part_ii&quot;&gt;next article&lt;/a&gt;, I&apos;ll talk about how I migrated to use Angular UI&apos;s &lt;a href=&quot;http://angular-ui.github.io/bootstrap/#/carousel&quot;&gt;carousel&lt;/a&gt; and &lt;a href=&quot;https://github.com/angular-ui/ui-sortable&quot;&gt;sortable&lt;/a&gt; directives, as well as integrating dialogs.&lt;/p&gt;</description>          </item>
    <item>
    <guid isPermaLink="true">https://raibledesigns.com/rd/entry/the_html5_roadshow_rocks</guid>
    <title>The HTML5 Roadshow Rocks!</title>
    <dc:creator>Matt Raible</dc:creator>
    <link>https://raibledesigns.com/rd/entry/the_html5_roadshow_rocks</link>
        <pubDate>Thu, 14 Mar 2013 15:16:58 -0600</pubDate>
    <category>The Web</category>
    <category>html5</category>
    <category>scottdavis</category>
    <category>html5roadshow</category>
    <category>kylesimpson</category>
    <category>training</category>
    <category>javascript</category>
            <description>&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
&lt;a href=&quot;http://html5roadshow.com/&quot; title=&quot;Canyonland&apos;s Sunset Road by Trish of McGinity Photo&quot;&gt;&lt;img src=&quot;//farm9.staticflickr.com/8248/8557138083_8b32d4a06a_o.png&quot; width=&quot;500&quot; height=&quot;183&quot; alt=&quot;Canyonland&apos;s Sunset Road by Trish of McGinity Photo&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;
In early February, as I was creating my &lt;a href=&quot;http://raibledesigns.com/rd/entry/the_modern_java_web_developer&quot;&gt;Modern Java Web Developer&lt;/a&gt; presentation, I came across a &lt;a href=&quot;http://www.eventbrite.com/event/1470493285/eorg&quot;&gt;training event&lt;/a&gt; that was similarly named. Since I happened to know the instructor, I shot him off an email asking more about it. He promptly replied a week later with an intriguing description. The following sentence really stood out for me:
&lt;/p&gt;
&lt;p class=&quot;quote&quot;&gt;
They are NOT beginning workshops -- newbie information is easily found online, in books, at UG meetings, etc. They are targeted at working professionals who want to take their game to the next level. 
&lt;/p&gt;
&lt;p&gt;The other thing that really struck me about this training was it was &#224; la carte; meaning you could pick and pay for just the days you wanted to attend. Scott gave me a two-for-one deal and I signed up for the following workshops:&lt;/p&gt;
&lt;ul&gt;

&lt;li&gt;&lt;a href=&quot;http://html5roadshow.com/workshops#advanced_javascript&quot;&gt;Advanced JavaScript: The &quot;What You Need To Know&quot; Parts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://html5roadshow.com/workshops#airplane_mode_html5&quot;&gt;Airplane-Mode HTML5: Mobile Web Development for Tablets and Smartphones&lt;/a&gt;&lt;/li&gt;

&lt;/ul&gt;
&lt;p&gt;Normally, attending a training course would have an opportunity cost for me. However, since I work remotely and it doesn&apos;t matter &lt;em&gt;where&lt;/em&gt; I work, I decided to work from the class. It turned out to be a great idea because it had the same feel of a productive day at the coffeeshop, but it was a coffeeshop full of knowledge. Instead of looking up and doing some people watching or getting distracted by social media, I would look up and learn something new. I actually ended up getting more done in those two training classes than I normally do at my &lt;a href=&quot;http://www.businessatthrive.com/v3/&quot;&gt;LoDo Office&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Advanced JavaScript&lt;/strong&gt;&lt;br/&gt;The Advanced JavaScript workshop was taught by &lt;a href=&quot;http://getify.me/&quot;&gt;Kyle Simpson&lt;/a&gt;. This was my first time meeting Kyle and I was immediately impressed by his resume, especially his &lt;em&gt;worked on Firefox Developer Tools for 9 months&lt;/em&gt; credentials. He started out talking about good JavaScript documentation on sites such as &lt;a href=&quot;https://developer.mozilla.org/&quot;&gt;MDN&lt;/a&gt; and &lt;a href=&quot;https://github.com/rwldrn/idiomatic.js&quot;&gt;Principles of Writing Consistent, Idiomatic JavaScript&lt;/a&gt;. Tip: append &quot;mdn&quot; to any searches for JavaScript topics and you&apos;re likely to get better results. Not only that, but MDN is a wiki so you can improve it too. From there, we moved onto DOM Events, event propagation (e.g. bubbling vs. capturing), event management, scopes, &lt;code&gt;this&lt;/code&gt;, closures and design patterns. It was a very deep dive into JavaScript and I enjoyed every minute of it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Airplane-Mode HTML5&lt;/strong&gt;&lt;br/&gt;The Airplane-Mode HTML5 workshop was taught by &lt;a href=&quot;http://thirstyhead.com&quot;&gt;Scott Davis&lt;/a&gt; (his site is currently down, should be fixed next week). Scott talked about many things I was already familiar with, but I still managed to learn a bunch of new tips and tricks. He started by having us install &lt;a href=&quot;http://nodejs.org/&quot;&gt;Node&lt;/a&gt;, &lt;a href=&quot;http://volojs.org/&quot;&gt;volo&lt;/a&gt; and creating projects. He showed how &lt;code&gt;package.json&lt;/code&gt; is the JS project&apos;s equivalent of &lt;code&gt;pom.xml&lt;/code&gt;. 
&lt;/p&gt;
&lt;p&gt;Next, he plunged into viewports, orientation and how to use CSS3 media queries. I&apos;ve always used min-device-width and max-device-width in my media queries, so &lt;code&gt;orientation: portrait&lt;/code&gt; and &lt;code&gt;orientation: landscape&lt;/code&gt; was a useful tip. He taught us about CSS3&apos;s pseudo-classes (esp. for validation) and pointed us to the &lt;a href=&quot;http://alistapart.com/article/forward-thinking-form-validation&quot;&gt;Forward Thinking Form Validation&lt;/a&gt; article. We learned about using &lt;a href=&quot;http://fmbip.com&quot;&gt;findmebyIP.com&lt;/a&gt; and how it can be a useful tool for finding your browser&apos;s capabilities, particularly when you&apos;re in a black box environment (e.g. a browser on a TV). I knew about &lt;a href=&quot;http://modernizr.com/&quot;&gt;Modernizr&lt;/a&gt;, but wasn&apos;t aware of &lt;a href=&quot;https://github.com/phiggins42/has.js/&quot;&gt;has.js&lt;/a&gt; for JavaScript feature detection. 
&lt;/p&gt;
&lt;p&gt;Scott talked about many more topics, from HTML5 forms and mobile links to geolocation and maps. He ended the day talking about local storage and application cache. 
&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Good&lt;/strong&gt;&lt;br/&gt;
First of all, the price was perfect at around $250 per day. The best part for me was that I learned more than I expected to. Not only that, but I didn&apos;t have to endure any opportunity cost to attend these workshops. There were times I wish I didn&apos;t have to work, but it was cool to have &quot;learning stuff&quot; as a distraction rather than social media.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Bad&lt;/strong&gt;&lt;br/&gt;
The Location. I ride my bike to work year-round and only sit in traffic a few times per year. So I know I&apos;m biased. The location was in Louisville and while it should be only a 30-minute commute, I spent 2 hours in the car each day. Once I got there, the facility was great, the internet was fast and the lunch choices were splendid.
&lt;/p&gt;
&lt;p&gt;To see if the &lt;a href=&quot;http://html5roadshow.com/&quot;&gt;HTML5 Roadshow&lt;/a&gt; is coming to your town, checkout &lt;a href=&quot;http://thirstyhead.eventbrite.com/&quot;&gt;ThirstyHead on Eventbrite&lt;/a&gt; or &lt;a href=&quot;http://twitter.com/html5roadshow&quot;&gt;follow them on Twitter&lt;/a&gt;.</description>          </item>
    <item>
    <guid isPermaLink="true">https://raibledesigns.com/rd/entry/what_have_i_been_working</guid>
    <title>What have I been working on at Taleo?</title>
    <dc:creator>Matt Raible</dc:creator>
    <link>https://raibledesigns.com/rd/entry/what_have_i_been_working</link>
        <pubDate>Fri, 9 Dec 2011 12:57:36 -0700</pubDate>
    <category>Java</category>
    <category>javascript</category>
    <category>jquery</category>
    <category>happiness</category>
    <category>overstock</category>
    <category>taleo</category>
            <description>2011 has been a year of great clients for me. I started working with &lt;a href=&quot;http://overstock.com&quot;&gt;O.co&lt;/a&gt; and very much enjoyed my time there, especially on &lt;a href=&quot;http://raibledesigns.com/rd/entry/the_greatest_snow_on_earth&quot;&gt;powder days in Utah&lt;/a&gt;. The people were great, the contract was great (no end date), but the work was not my forte. I was on a project to &lt;em&gt;modularize&lt;/em&gt; the main shopping site&apos;s codebase, which involved mostly refactoring. By refactoring, I mean creating new Maven projects, modifying lots of pom.xml files and literally moving files from one directory to another. IntelliJ made this easy, the hard part was refactoring tests, moving from EasyMock to Mockito and splitting classes into interfaces and implementations where appropriate. As a developer who likes developing UIs and visually seeing my accomplishments, the project wasn&apos;t that exciting. However, I knew that it was strategically important to O.co, so I didn&apos;t complain much.
&lt;/p&gt;
&lt;p&gt;In mid-May, I received a &lt;a href=&quot;http://www.linkedin.com&quot;&gt;LinkedIn&lt;/a&gt; message from the Director of Software Engineering at &lt;a href=&quot;http://www.taleo.com/&quot;&gt;Taleo&lt;/a&gt;. 
&lt;/p&gt;
&lt;p class=&quot;quote&quot;&gt;
This is OB, I am the Director of Software Engineering at Taleo. We are the 2nd largest Software as a Service company. I am building a new specialist UI team that will take the product to the next level. I am looking for someone to lead this initiative. If you are interested to have a chat about it, please let me know.
&lt;/p&gt;
&lt;p&gt;At that time, I&apos;d never heard of Taleo and quickly recommended they not hire me.&lt;/p&gt;
&lt;p class=&quot;quote&quot;&gt;
This probably isn&apos;t the best position for me. While I am a good leader, I&apos;m not willing to relocate from Denver. I&apos;ve found that leaders usually do best when face-to-face with their developers.
&lt;/p&gt;
&lt;p&gt;This conversation continued back-and-forth where I explained how I wasn&apos;t willing to go full-time and I didn&apos;t want to leave Overstock. In the end, OB was persistent and explained how the position would entail lots of UI work and wouldn&apos;t require me to travel much. Our negotiations trailed off in June and resumed in July after I returned &lt;a href=&quot;http://raibledesigns.com/rd/entry/4th_of_july_adventures_in&quot;&gt;from vacation in Montana&lt;/a&gt;. Shortly after, we met each other&apos;s expectations, agreed on a start date and I started working at Taleo in early September. 
&lt;/p&gt;
&lt;p&gt;When I started, there were three features they wanted to add to to &lt;a href=&quot;http://www.taleo.com/solutions/taleo-business-edition&quot;&gt;Taleo Business Edition&lt;/a&gt;: Profile Pictures, Talent Cards and Org Charts. They knew the schedule was tight (8 weeks), but I was confident I could make it happen. At first, I groaned at the fact that they were using Ant to build the project. Then I smiled when I learned they&apos;d standardized on IntelliJ and set things up so you could do everything from the IDE. After using Maven for many years, this setup has actually become refreshing and I rarely have to restart or long for something like JRebel. Of course, a &lt;a href=&quot;http://raibledesigns.com/rd/entry/new_macbook_pro_and_imac&quot;&gt;new kick-ass laptop&lt;/a&gt; and &lt;a href=&quot;http://blogs.jetbrains.com/idea/2011/12/intellij-idea-11-is-out-get-ready-for-a-productivity-takeoff/&quot;&gt;awesome IDE&lt;/a&gt; make it so I rarely wait for anything to happen.
&lt;/p&gt;
&lt;p&gt;
To give you a taste of how I implemented each of these new features in 8 weeks, I&apos;ve broken them into sections below.
&lt;/p&gt;
&lt;p id=&quot;profile-pictures&quot;&gt;&lt;strong&gt;Profile Pictures&lt;/strong&gt;&lt;br/&gt;
Adding profile pictures was a pretty simple concept, one you see on my social networking sites today. I needed to give users the ability to upload a JPEG or PNG and crop it so it looked good. The uploading was a pretty straightforward process and I used a lot of internal APIs to grab the file from the request and save it to disk. The more difficult part was scaling the image to certain dimensions on upload (to save space) and allowing users to crop it after. 
&lt;/p&gt;
&lt;p&gt;Most of Taleo Business Edition (TBE) is written in good ol&apos; servlets and JSPs, with lots of scriptlets in their JSPs. When I saw the amount of HTML produced from Java, I laughed out loud and cringed. Soon after, I breathed a sigh of relief when I learned that any new features could be written using &lt;a href=&quot;http://freemarker.sourceforge.net/&quot;&gt;FreeMarker&lt;/a&gt; templates, which IntelliJ has excellent support for.
&lt;/p&gt;
&lt;p&gt;For image resizing on upload, I used Chris Campbell&apos;s &lt;a href=&quot;http://today.java.net/pub/a/today/2007/04/03/perils-of-image-getscaledinstance.html&quot;&gt;Image.getScaledInstance() tutorial&lt;/a&gt;. For creating thumbnails, I used a combination of scaling, &lt;a href=&quot;http://docs.oracle.com/javase/6/docs/api/java/awt/image/BufferedImage.html#getSubimage(int, int, int, int)&quot;&gt;getSubimage()&lt;/a&gt; and the &lt;a href=&quot;http://docs.oracle.com/javase/6/docs/api/javax/imageio/package-summary.html&quot;&gt;Java Image I/O API&lt;/a&gt;. I made sure to &lt;a href=&quot;http://stackoverflow.com/questions/3204280/is-imageio-write-buffered&quot;&gt;write to BufferedOutputStream&lt;/a&gt; for scalability. For cropping images client-side, I used &lt;a href=&quot;http://jqueryui.com/demos/dialog/&quot;&gt;jQuery UI&apos;s Dialog&lt;/a&gt; and &lt;a href=&quot;http://deepliquid.com/content/Jcrop.html&quot;&gt;Jcrop&lt;/a&gt;, the jQuery image cropping plugin. Below is a screenshot of what the cropping UI looks like:
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
&lt;a href=&quot;http://farm8.staticflickr.com/7142/6475456591_6831fd119e_b.jpg&quot; title=&quot;Taleo&apos;s TBE: Profile Picture&quot; rel=&quot;lightbox[taleo]&quot;&gt;&lt;img src=&quot;//farm8.staticflickr.com/7142/6475456591_6831fd119e.jpg&quot; width=&quot;500&quot; height=&quot;176&quot; alt=&quot;Taleo&apos;s TBE: Profile Picture&quot;&gt;&lt;/a&gt;
&lt;/a&gt;
&lt;/p&gt;
&lt;p id=&quot;talent-cards&quot;&gt;&lt;strong&gt;Talent Cards&lt;/strong&gt;&lt;br/&gt;
Talent Cards were a whole different beast. Not only did they need to display profile pictures, they also needed to contain contact information, work history and a number of other data points. Not just for employees, but for candidates as well. 
They also needed to be rendered with tabs at the bottom that allowed you to navigate between different data sections. 
&lt;/p&gt;
&lt;p&gt;

&lt;a href=&quot;http://farm8.staticflickr.com/7032/6475456677_ae342be56e_o.png&quot; title=&quot;Taleo&apos;s TBE: Talent Card&quot; rel=&quot;lightbox[taleo]&quot;&gt;&lt;img src=&quot;//farm8.staticflickr.com/7032/6475456677_a06514020d_m.jpg&quot; width=&quot;187&quot; height=&quot;240&quot; alt=&quot;Taleo&apos;s TBE: Talent Card&quot; class=&quot;picture&quot; style=&quot;border: 0&quot;&gt;&lt;/a&gt;

I&apos;ll admit, most of the hard work for this feature was done by the server-side developers (Harish and &lt;a href=&quot;http://www.linkedin.com/in/vladimirbazarsky&quot;&gt;Vlad&lt;/a&gt;) on my team. Vlad built the tabbed interface and Harish built the administrative section that allows you to add/remove/sort fields, as well as show and hide certain tabs. I performed most of my magic with jQuery, its &lt;a href=&quot;http://plugins.learningjquery.com/cluetip/&quot;&gt;clueTip plugin&lt;/a&gt; and good ol&apos; CSS. I was particularly thankful for CSS3 and its border-radius, box-shadow and Justin Maxwell&apos;s tutorial on &lt;a href=&quot;http://mattsnider.com/css/css-string-truncation-with-ellipsis/&quot;&gt;CSS String Truncation with Ellipsis&lt;/a&gt;. I used &lt;a href=&quot;http://directwebremoting.org/dwr/index.html&quot;&gt;DWR&lt;/a&gt; to fetch all the data from the server using Ajax. 
&lt;/p&gt;
&lt;p&gt;Talent Cards are a slick feature in TBE 11.5 and I think they&apos;re a great way to see a lot of information about someone very quickly. If you enable them for your company, you&apos;ll be able to mouse over any employee or candidate&apos;s names and see their information.
&lt;/p&gt;
&lt;p id=&quot;org-chart&quot;&gt;&lt;strong&gt;Org Chart&lt;/strong&gt;&lt;br/&gt;
The last feature I completed in this 8-week sprint was creating an organization chart. For this, I was given a rough prototype based on &lt;a href=&quot;http://www.capricasoftware.co.uk/corp/index.php&quot;&gt;Caprica Software&apos;s&lt;/a&gt; &lt;a href=&quot;http://www.capricasoftware.co.uk/jquery/orgchart/orgchart.html&quot;&gt;JQuery/CSS Organisation Chart&lt;/a&gt;. When I received it, it had all kinds of cool CSS 3 transformations (like &lt;a href=&quot;http://css3.bradshawenterprises.com/flip/&quot;&gt;this one&lt;/a&gt;), but they only worked in Safari and Chrome. I ended up removing the transformations and adding the ability to navigate up and down the org tree with Ajax (we currently only show three levels at a time). 
&lt;/p&gt;
&lt;p&gt;
The Org Chart feature also allows you to see how many direct/indirect reports an employee has, as well as access their Talent Card by hovering over their name. It&apos;s one of my favorite features because it&apos;s so visual and because it builds upon all the other features we&apos;ve built.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;&lt;a href=&quot;http://farm8.staticflickr.com/7148/6483181659_619285347d_o.png&quot; rel=&quot;lightbox[taleo]&quot; title=&quot;Taleo&apos;s TBE: Org Chart&quot;&gt;&lt;img src=&quot;//farm8.staticflickr.com/7148/6483181659_7434afaeee.jpg&quot; width=&quot;487&quot; height=&quot;500&quot; alt=&quot;Taleo&apos;s TBE: Org Chart&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Summary&lt;/strong&gt;&lt;br/&gt;
As you might&apos;ve guessed by now, I&apos;ve been having a lot of fun doing UI development over the last few months. While I seem to have a knack for backend Java development, I enjoy developing UIs a lot more. The smile you see on people&apos;s faces during demos is priceless. I can&apos;t help but think this kind of thing contributes greatly to my developer happiness. All these features will be in next week&apos;s release of TBE and I couldn&apos;t be happier.
&lt;/p&gt;
&lt;p&gt;If you&apos;d like to work on my team at Taleo (or even take over my current role as UI Architect), please &lt;a href=&quot;http://raibledesigns.com/contact.jsp&quot;&gt;drop me a line&lt;/a&gt;. If you live near their headquarters (Dublin, CA), it&apos;d also be great to see you at the next Silicon Valley Spring User Group meetup. I&apos;ll be speaking about &lt;a href=&quot;http://www.meetup.com/SV-SUG/events/43376082/&quot;&gt;What&apos;s New in Spring 3.1 on February 1st&lt;/a&gt;.
</description>          </item>
    <item>
    <guid isPermaLink="true">https://raibledesigns.com/rd/entry/phonegap_for_hybrid_app_development</guid>
    <title>PhoneGap for Hybrid App Development</title>
    <dc:creator>Matt Raible</dc:creator>
    <link>https://raibledesigns.com/rd/entry/phonegap_for_hybrid_app_development</link>
        <pubDate>Wed, 16 Nov 2011 10:22:16 -0700</pubDate>
    <category>The Web</category>
    <category>devoxx</category>
    <category>ios</category>
    <category>javascript</category>
    <category>phonegap</category>
    <category>android</category>
    <category>adobe</category>
    <category>devoxx2011</category>
            <description>This afternoon, I attended &lt;a href=&quot;http://brian.io&quot; title=&quot;Brian LeRoux&quot;&gt;Brian LeRoux&lt;/a&gt;&apos;s talk on &lt;a href=&quot;http://www.devoxx.com/display/DV11/PhoneGap+for+Hybrid+App+Development&quot;&gt;PhoneGap for Hybrid App Development&lt;/a&gt; at Devoxx. You might remember that I &lt;a href=&quot;http://raibledesigns.com/rd/entry/phonegap_to_the_rescue&quot;&gt;tried PhoneGap last week&lt;/a&gt; and really enjoyed my experience. Below are my notes from Brian&apos;s talk.
&lt;/p&gt;
&lt;p&gt;
PhoneGap is a project for creating native applications using HTML, CSS and JavaScript. PhoneGap started out as a hack. In 2007, Apple shipped the iPhone and Steve Jobs told everyone they should develop webapps. PhoneGap started in 2008 as a lofty summertime hack and gained traction as a concept at Nitobi with Android and Blackberry implementations in the fall. In 2009, people started to pay attention when PhoneGap got rejected by Apple. They added Symbian and webOS support and Sony Ericsson started contributing to the project. They got rejected because all PhoneGap-developed apps were named &quot;PhoneGap&quot;. This turned out to be good press for the project and Apple let them in shortly after.
&lt;/p&gt;
&lt;p&gt;
In 2010, IBM began tag-teaming with Nitobi and added 5 developers to the project after meeting them at OSCON. In 2011, RIM started contributing as well as Microsoft. Then Adobe bought the company, so they&apos;re obviously contributing.
&lt;/p&gt;
&lt;p&gt;PhoneGaps Goals: the web is a first class platform, so let people create installable web apps. Their second goal is to cease to exist and get browsers to adopt their model.
&lt;/p&gt;
&lt;p&gt;
PhoneGap is NOT a runtime or a compiler/transpiler. It&apos;s not an IDE or predefined framework or proprietary lockin. It&apos;s Apache, MIT and BSD licensed to guarantee it&apos;s as free as free software gets. You can do whatever you want to do with it. PhoneGap has &lt;a href=&quot;http://arstechnica.com/open-source/news/2011/10/phonegap-to-become-an-apache-project-as-adobe-acquires-nitobi.ars&quot;&gt;recently been contributed to the Apache Software Foundation&lt;/a&gt;.
&lt;/p&gt;
As far as Adobe vs. PhoneGap is concerned, the Nitobi team remains contributors to PhoneGap. Adobe is a software tools company and has Apache and WebKit contributors. PhoneGap/Build integration will be added to &lt;a href=&quot;http://www.adobe.com/products/creativecloud.html&quot;&gt;Creative Cloud&lt;/a&gt;. 
&lt;/p&gt;
&lt;p&gt;
The biggest issues with contributing PhoneGap to Apache is renaming the project and source control. I&apos;m not sure why it needs to be renamed, but it&apos;s likely that &lt;em&gt;Apache Callback&lt;/em&gt; is out. There seems to be some consensus on Apache Cordova. Apache likes SVN and the PhoneGap community currently uses Git. They&apos;re trying to find a medium road there, but would prefer to stay on Git.
&lt;/p&gt;
&lt;p&gt;
The PhoneGap technique is colloquially called &quot;the bridge&quot;. It&apos;s a 3 step process: they instantiate a WebView, then they call JavaScript from native code, then they call native code from JavaScript. Apparently, all device APIs are available via JavaScript in a WebView.
&lt;/p&gt;
&lt;p&gt;The primary platforms supported are iOS &gt;= 3, Android &gt;= 1.5 and BlackBerry &gt;= 5.x. They also support webOS, Symbian, Samsung Bada and Windows Phone. No mobile dev platform supports as many deploy targets as PhoneGap. Primary contributors are Adobe, IBM, RIM and Microsoft. 
&lt;/p&gt;
&lt;p&gt;Documentation for PhoneGap is available at &lt;a href=&quot;http://docs.phonegap.com&quot;&gt;http://docs.phonegap.com&lt;/a&gt;. Device APIs for PhoneGap 1.0 included sensors, data and outputs, which all devices have. Examples of sensors are geolocation and camera. Data examples are the filesystem, contacts and media. Outputs are screens, speakers and the speaker jack. All PhoneGap APIs are plugins, but any native API is permitted. 
&lt;/p&gt;
&lt;p&gt;
What about security? Brian recommends looking at the &lt;a href=&quot;http://html5sec.org&quot;&gt;HTML5 Security Cheatsheet&lt;/a&gt;. PhoneGap has added a lot of security measures since they&apos;ve found the native API pretty much opens up everything. 
&lt;/p&gt;
&lt;p&gt;
PhoneGap doesn&apos;t bundle a UI framework, but they support any JavaScript framework that works in the browser. PhoneGap is just a fancy browser, so your code run in less fancy web browsers too. This means you can develop and test your app in your desktop browser and only use PhoneGap to package and distribute your app.
&lt;/p&gt;
&lt;p&gt;Competition? PhoneGap has no competition. 
&lt;/p&gt;
&lt;p&gt;
&lt;a href=&quot;https://build.phonegap.com/&quot;&gt;PhoneGap/Build&lt;/a&gt; is for compiling your apps in the cloud and free for open source projects. The biggest reason they did this is because they couldn&apos;t redistribute all the SDKs and it was a pain for developers to download and install SDKs in training classes.
&lt;/p&gt;
&lt;p&gt;
For mobile app development, you should have a singular goal. Do one thing really well if you want to be successful. Great UX happens iteratively. You know that the web works and has been widely successfully cross-platform. It&apos;s likely you&apos;ve already invested in the web. Start by building a mobile web client and use PhoneGap as a progressive enhancement technique. 
&lt;/p&gt;
&lt;p&gt;
Shipping and unit testing should be a daily activity. Automate everything so you can have one-click builds (test/dev/release). For web client design, constraints are your ally in the battle against complexity and &quot;clients who are not &lt;em&gt;chill&lt;/em&gt;&quot;. Phones suck and consume a lot: cpu, ram, bandwidth, battery, network... everything! Start with a benchmark of app performance and monitor that benchmark. If you have tons and tons of features, consider splitting into multiple apps.
&lt;/p&gt;
The mobile web is not WebKit! Opera is huge, Firefox is making strides and IE still happens. For layouts: use flex-box rules (anyone got a link to these?), css media queries and meta tags for viewport. You should try to develop your app without frameworks because they come with a ton of code and can effect the size of your app.
&lt;/p&gt;
&lt;p&gt;
Looks can kill: aesthetics that can hurt performance: border-radius, box-shadow and gradients can slow down your apps. Chances are, you really don&apos;t need these features. Design your app for your brand, not for the device manufacturer. An app that looks like an iPhone app on Android doesn&apos;t give a positive impression.&lt;/p&gt;
&lt;p&gt;
For JavaScript libraries, start with your problem, not a generic solution like Sencha or jQuery Mobile. &lt;a href=&quot;http://zeptojs.com/&quot;&gt;Zepto&lt;/a&gt; and its older brother &lt;a href=&quot;http://xuijs.com/&quot;&gt;XUI&lt;/a&gt; are all you need to start. &lt;a href=&quot;http://joapp.com/&quot;&gt;Jo&lt;/a&gt; is a fantastic option. &lt;a href=&quot;http://backbonejs.org/&quot;&gt;Backbone&lt;/a&gt; and &lt;a href=&quot;http://spine.github.io/&quot;&gt;Spine&lt;/a&gt; are worth watching.
&lt;/p&gt;
&lt;p&gt;
For testing, &lt;a href=&quot;http://docs.jquery.com/QUnit&quot;&gt;QUnit&lt;/a&gt; and &lt;a href=&quot;http://pivotal.github.com/jasmine/&quot;&gt;Jasmine&lt;/a&gt; are pretty popular. For deployment, concat, minify and obfuscate your JavaScript and CSS. Or you can inline everything into the markup to minimize HTTP chatter. Gmail inlines and comments all their JavaScript and then evals it.
&lt;/p&gt;
&lt;p&gt;From there, Brian recommended leveraging HTML5&apos;s &lt;a href=&quot;http://www.html5rocks.com/en/tutorials/appcache/beginner/&quot;&gt;AppCache&lt;/a&gt; and and using RESTful JSON endpoints for legacy systems. Next, he tried to show us a demo of a photo sharing application. Unfortunately, the Demo Gods were grumpy and Brian couldn&apos;t get his computer to recognize his Android phone. He did show us the &lt;a href=&quot;http://gist.github.com/1219277&quot;&gt;client code&lt;/a&gt; and it&apos;s pretty impressive you can use 1 line of code to take a picture on a phone.
&lt;/p&gt;
&lt;p&gt;The last thing we looked at was &lt;a href=&quot;http://debug.phonegap.com&quot;&gt;debug.phonegap.com&lt;/a&gt;. This is an app that&apos;s powered by &lt;a href=&quot;http://phonegap.github.com/weinre/&quot;&gt;weinre&lt;/a&gt;. It lets you enter a line of JavaScript in your client and then remotely debug it in a tool that looks like Chrome&apos;s Web Inspector. Very cool stuff if you ask me.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Summary&lt;/strong&gt;&lt;br/&gt;
I really enjoyed learning more about PhoneGap, particularly because Brain emphasized all my web development skills can be used. I don&apos;t have to learn Objective-C or Android to develop native apps and I don&apos;t even have to install an SDK if I use PhoneGap/Build. Of course, my mobile developer friends might disagree with this approach. In the meantime, I look forward to using PhoneGap to turn my mobile web clients into native apps and finding out if it&apos;s really as good as they say it is.
</description>          </item>
    <item>
    <guid isPermaLink="true">https://raibledesigns.com/rd/entry/scaling_flash_movies_to_match</guid>
    <title>Scaling Flash Movies to match Browser Zoom Levels</title>
    <dc:creator>Matt Raible</dc:creator>
    <link>https://raibledesigns.com/rd/entry/scaling_flash_movies_to_match</link>
        <pubDate>Tue, 13 Jul 2010 12:18:42 -0600</pubDate>
    <category>The Web</category>
    <category>javascript</category>
    <category>flex</category>
    <category>browsers</category>
    <category>flash</category>
    <category>textzoom</category>
    <category>actionscript</category>
            <description>Recently I was tasked with figuring out how to scale the Flash assets in the web application I&apos;m working on. In the app, there&apos;s two different Flash assets: a Spotlight (cycles through images) and a Video Player. Before I started working on the issue, our assets would stay the same fixed size no matter what the browser zoom level. You can see this issue in action by going to &lt;a href=&quot;http://hulu.com&quot;&gt;Hulu&lt;/a&gt; or &lt;a href=&quot;http://fancast.com&quot;&gt;Fancast&lt;/a&gt; and zooming in/out (Command +/- on Mac, Control +/- on Windows). The Flash assets don&apos;t scale with the browser&apos;s text.
&lt;/p&gt;
&lt;p&gt;I found a &lt;a href=&quot;http://delicious.com/mraible/textzoom&quot;&gt;lot of references&lt;/a&gt; for how to trap and handle resizing in JavaScript, so that&apos;s the initial path I took. I ended up having issues trapping the resize event in IE, as well as persisting the appropriate zoom level on page reload. Because of this, I ended up using a pure &lt;a href=&quot;http://raibledesigns.com/rd/entry/scaling_flash_movies_to_match#actionscript&quot;&gt;ActionScript solution&lt;/a&gt; that works much better. This article shows how I implemented both solutions.
&lt;/p&gt;
&lt;p&gt;
Regardless of implementation, the first change I had to make was to move the height and width from the Flash asset (object/embed/JS) to its surrounding tag (&amp;lt;section&gt; in our app). Then I changed the height/width to 100% on the Flash asset.
&lt;/p&gt;
&lt;p&gt;
&lt;strong id=&quot;javascript&quot;&gt;JavaScript Implementation&lt;/strong&gt;&lt;br/&gt;
To allow zooming in ActionScript, I modified our main class to expose a &quot;zoom&quot; method to JavaScript:
&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot;&gt;
ExternalInterface.addCallback(&quot;zoom&quot;, _zoom);

...

private function _zoom(scale:Number):void {
    _view.scaleX = _view.scaleX * scale;
    _view.scaleY = _view.scaleY * scale;
}
&lt;/pre&gt;
In the code above, _view refers to the container that holds all the items in the player. To call this method in JavaScript, I added the following code:
&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot;&gt;
var windowHeight;
var documentHeight;

$(document).ready(function() { 
    ...
    windowHeight = $(window).height();
    documentHeight = $(document).height();

    $(window).resize(resizeWindow);
}

// Resize Flash assets when page is zoomed
function resizeWindow() {
    var newWindowHeight = $(window).height();
    var newDocumentHeight = $(document).height();
    // if document height hasn&apos;t changed, it&apos;s a browser window resize
    // event instead of a text zoom - don&apos;t change anything
    if (newDocumentHeight === documentHeight) {
        return;
    } else {
        documentHeight = newDocumentHeight;
    }
    var scale = (windowHeight / newWindowHeight); 

    var player = getFlashMovie(&apos;playerId&apos;);
    if (player &amp;&amp; player.zoom) {
        player.zoom(scale);
    }
    var spotlight = getFlashMovie(&apos;spotlightId&apos;);
    if (spotlight &amp;&amp; spotlight.zoom) {
        spotlight.zoom(scale);
    }

    windowHeight = newWindowHeight;
}
&lt;/pre&gt;
&lt;p&gt;
This seemed to work well in Firefox, Safari and Opera, but not in IE. I found &lt;a href=&quot;http://old.nabble.com/$%28window%29.bind%28%27resize%27,-fn%29-not-working-in-IE-td21447946s27240.html&quot;&gt;this explanation&lt;/a&gt; about why it might not work, but I was unsuccessful in getting IE to recognize a resize/zoom event. 
&lt;/p&gt;
&lt;p&gt;
To fix scaling in our Spotlight asset, I used a similar solution. However, since the Spotlight didn&apos;t have all its elements in a container (they were being added directly to the stage), I had to refactor the code to add a SpotlightView (extends Sprite) that contains the bulk of the code.
&lt;/p&gt;
&lt;p&gt;Browsers persist the zoom level you&apos;ve set for a site. The problem with the solution used here is it only scales up and down properly if you start from scale = 1 and revert to scale = 1 before leaving the site. If you zoom in and close your browser, when you come back the flash movies will be scale = 1 while the rest of the site is zoomed in. To solve this problem, I attempted to save the scale value in a cookie. This worked, and I was able to read the cookie in the *.as files to scale the movie correctly. However, I experienced some issues with this approach and didn&apos;t like having to delete cookies when I wanted the Flash assets to scale correctly.
&lt;/p&gt; 
&lt;p&gt;&lt;strong id=&quot;actionscript&quot;&gt;ActionScript Implementation&lt;/strong&gt;&lt;br/&gt;
After discovering issues with the JavaScript implementation, I did some research to see if it was possible to listen for the browser resize event in ActionScript. The &lt;a href=&quot;http://www.republicofcode.com/tutorials/flash/as3fluidresize/&quot;&gt;Flash Fluid Layouts and Stage Resize in AS3&lt;/a&gt; tutorial clued me in that the stage could listen for a resize event. 
&lt;/p&gt;
&lt;pre class=&quot;brush: java; toolbar: false&quot;&gt;
stage.addEventListener(Event.RESIZE, resizeListener); 
&lt;/pre&gt;
&lt;p&gt;After adding the above line in the initialization, I added a resizeListener function that scales based on the default dimensions. It also ensures no scaling happens in full screen mode. 
&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot;&gt;
private function resizeListener(e:Event):void {
    // don&apos;t scale if entering full screen mode
    if (stage.displayState == StageDisplayState.FULL_SCREEN)  {
        _view.scaleX = 1;
        _view.scaleY = 1;
    } else {
        _view.scaleX = stage.stageWidth / 964;
        _view.scaleY = stage.stageHeight / 586;
    }
}
&lt;/pre&gt;
&lt;p&gt;
For the Spotlight asset, there are a number of different layouts (home, featured and news). The main class has a resizeListener function that scales accordingly to which layout type is being used.
&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot;&gt;
private function resizeListener(e:Event):void {
    var type:String = _view.getLayoutType();

    if (type == &quot;featured&quot;) { 
        _view.scaleX = stage.stageWidth / 958;
       _view.scaleY = stage.stageHeight / 180;
   } else if (type == &quot;home&quot;) { 
        _view.scaleX = stage.stageWidth / 964;
        _view.scaleY = stage.stageHeight / 428;
    } else if (type == &quot;news&quot;) {
        _view.scaleX = stage.stageWidth / 964;
        _view.scaleY = stage.stageHeight / 189;
    }
}
&lt;/pre&gt;
&lt;p&gt;Because the layout type isn&apos;t set until the XML is loaded, I listen for that event in my URLLoader.
&lt;/p&gt;
&lt;pre class=&quot;brush: java; toolbar: false&quot;&gt;
xmlLoader.addEventListener(Event.COMPLETE, resizeListener);
&lt;/pre&gt;
&lt;p&gt;With the pure ActionScript implementation, the zoom level is automatically persisted. The Event.RESIZE event is fired by the Flash plugin when the page first loads if the dimensions are not the default. 
&lt;/p&gt;
&lt;p&gt;
That&apos;s it! Special thanks to &lt;a href=&quot;http://www.jamesward.com/&quot;&gt;James Ward&lt;/a&gt; for clueing me into &lt;em&gt;scaleX&lt;/em&gt; and &lt;em&gt;scaleY&lt;/em&gt;. Hopefully Hulu and Comcast can use this tutorial to scale their video players too. &lt;img src=&quot;https://raibledesigns.com/images/smileys/wink.gif&quot; class=&quot;smiley&quot; alt=&quot;;-)&quot; title=&quot;;-)&quot; /&gt;
</description>          </item>
    <item>
    <guid isPermaLink="true">https://raibledesigns.com/rd/entry/introduction_to_objective_j_and</guid>
    <title>Introduction to Objective-J and Cappuccino with Tom Robinson</title>
    <dc:creator>Matt Raible</dc:creator>
    <link>https://raibledesigns.com/rd/entry/introduction_to_objective_j_and</link>
        <pubDate>Thu, 3 Dec 2009 11:21:59 -0700</pubDate>
    <category>The Web</category>
    <category>tlrobinson</category>
    <category>javascript</category>
    <category>dom</category>
    <category>objectivej</category>
    <category>richwebexperience</category>
    <category>richweb</category>
    <category>280north</category>
    <category>cappuccino</category>
            <description>This morning, I attended &lt;a href=&quot;http://tlrobinson.net/&quot;&gt;Tom Robinson&apos;s&lt;/a&gt; talk on &lt;a href=&quot;http://www.therichwebexperience.com/conference/orlando/2009/12/session?id=15958&quot;&gt;Objective-J and Cappuccino&lt;/a&gt;. Tom is one of the founders of &lt;a href=&quot;http://280north.com/&quot;&gt;280 North&lt;/a&gt; and creators of the &lt;a href=&quot;http://cappuccino.org/&quot;&gt;Cappuccino framework&lt;/a&gt; and Objective-J language, so I was very interested in hearing about Cappuccino from &lt;em&gt;the source&lt;/em&gt;. The text below are my notes, but they&apos;re also mostly Tom&apos;s words, not mine. I&apos;ve added a &quot;Thoughts&quot; section at the end that are my words.&lt;/p&gt;
&lt;p&gt;Tom&apos;s Team was Cocoa programmers before they started building Cappuccino. They wanted to focus on building Desktop Class Web Applications (for example, Google Maps, Meebo and 280 Slides). Tom showed a  demo of 280 Slides and how it can rotate and scale images very easily, something you don&apos;t often see in web applications. 
&lt;/p&gt;
&lt;p&gt;
To build desktop class web applications, you can use Flash or Silverlight, but they&apos;re controlled by Adobe and Microsoft. Also, they have no iPhone support and poor Mac and Linux performance. The other option is JavaScript + DOM. They&apos;re open standards, available almost everywhere (including mobile devices) and its a very rich ecosystem with lots of competition. The downside to JavaScript is standards bodies, many incompatibilities, technical limitations (e.g. can&apos;t access web cam) and the DOM is very document-centric.
&lt;/p&gt;
&lt;p&gt;The bottom line is we can&apos;t fix Flash, but we can fix JavaScript. 
&lt;/p&gt;
&lt;p&gt;
This is what 280 North is trying to do with Objective-J. It&apos;s a proper superset of JavaScript, has a few syntax additions, has a powerful runtime and is implemented &lt;em&gt;in&lt;/em&gt; JavaScript. Objective-J is analogous to Objective-C. It adds to JavaScript like Objective-C adds to C. &lt;/p&gt;
&lt;p&gt;One of the first things Objective-J adds is Dependency Management. You can import from search paths:&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot;&gt;
@import &amp;lt;Foundation/CPObject.j&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Or from relative paths:&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot;&gt;
@import &quot;ApplicationController.j&quot;
&lt;/pre&gt;
&lt;p&gt;@import prevents duplicate loads has asynchronous downloading and synchronous execution. That means all files are downloaded before evaluation begins, but to the programmer, it seems to happen synchronously.&lt;/p&gt;
&lt;p&gt;The thing that sets Cappuccino apart from other libraries is its inheritance model. It uses  classical OO inheritance (using Objective-C syntax).
&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot;&gt;
@implementation Person : CPObject {
    String firstName @accessors;
    String lastName @accessors;
}

- (String) fullName {
    return firstName + lastName;
}

@end
&lt;/pre&gt;
&lt;p&gt;The type definitions (String) are ignored for now and primarily used for documentation. In the future, they plan to add optional static typing, hence the reason for having them. Tom is unsure if you can leave off the String type or if the compiler requires it.&lt;/p&gt;
&lt;p&gt;@implementation has proper support for &lt;code&gt;super&lt;/code&gt; and language syntax support. One of the reasons they chose Objective-C is because classical inheritance works great for UI Frameworks. &lt;/p&gt;
&lt;p&gt;Objective-J uses &quot;send a message&quot; syntax instead of &quot;call a method&quot; syntax. In the code snippets below, the first line is JavaScript, the second is Objective-J:&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot;&gt;
object.method()
[object method]

object.methodWithFoot(arg1)
[object methodWithFoo:arg1]

object.methodWithFooBar(arg1, arg2)
[object methodWithFoo:arg1 bar:arg2]
&lt;/pre&gt;
&lt;p&gt;Dynamic Dispatch is one of the most interesting parts of Objective-J. &lt;em&gt;forwardInvocation&lt;/em&gt; in Objective-C is like &lt;em&gt;method_missing&lt;/em&gt; in Ruby. Methods can be used as references, for example:
&lt;/p&gt;
&lt;pre&gt;
var action = @selector(someMethod:);
&lt;/pre&gt;
&lt;p&gt;Runtime mutability is important for KeyValueCoding (KVC) and KeyValueObserving (KVO). KVC allows you to swap classes at runtime and KVO allows you to listen for when property values change. At runtime, a $KVO_ClassName is generated. This class notifies any registered observers when values are changed and then calls the original class to change the property.
&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cappuccino&lt;/strong&gt;&lt;br/&gt;
Cappuccino is an application framework, not a library. It uses the &lt;em&gt;Hollywood Principle&lt;/em&gt;: &quot;Don&apos;t call us, we&apos;ll call you&quot;. 
&lt;/p&gt;
&lt;p&gt;
The Framework handles document management (open, save, revert), content editing (undo, redo, copy, paste) and graphics manipulation. The DOM is designed for documents (same is true for HTML and CSS). Tom doesn&apos;t like the DOM as its not a good API for building applications. Proof is all the JavaScript libraries built to make the DOM better. &lt;/p&gt;
&lt;p&gt;Cappuccino has an MVC framework and CPView is its View. It&apos;s analogous to a &amp;lt;div&amp;gt; and represents a rectangle on the screen. Everything visible is a CPView or one of its subclasses. It defines resizing and layout behavior. CoreGraphics is Cappuccino&apos;s canvas-like drawing API. It uses VML on IE, canvas on everything else. &lt;/p&gt;
&lt;p&gt;Very little of the code in Cappuccino talks to the DOM (less than 2%). It&apos;s not just about providing widgets that work in all browsers, it&apos;s a way to write platform independent display code. &lt;/p&gt;
&lt;p&gt;Events are done very differently than most JavaScript libraries. Browser&apos;s dispatching is not used. A single event listener is registered for each type of event on the window. These events are captured and sent to the objects that need to know about them. This allows for consistent events across all browsers, even keyboard events. It also allows for creating custom event flows and easily creating custom events. Cappuccino events allow you to get around a common problem with DOM Events where you can&apos;t click on overlapping rectangles.&lt;/p&gt;
&lt;p&gt;Notifications can be registered and sent very easily. Both &quot;scoped&quot; and private notifications can be created.&lt;/p&gt;
&lt;p&gt;Undo Management is included in Cappuccino. It manages a stack of undos for you. Redos are &quot;free&quot; and undo functionality is part of the document architecture. This makes it easy to integrate with auto-save functionality.&lt;/p&gt;
&lt;p&gt;Run loops (also called event loops) are an advanced feature of Cappuccino. They allow you to perform actions on every run loop. This enables complex optimizations for DOM/Graphic operations and undo grouping. &lt;/p&gt;
&lt;p&gt;The final part of Cappuccino is Keyed Archiving. Keyed Archiving stores a graph of Objective-J objects. It handles reference cycles, conditional inclusions, has an efficient data format and works on the client and server (Objective-J can be run on the server). The data format is similar like binary, but it&apos;s UTF-8. Keyed Archiving is used for archiving views and used heavily in &lt;a href=&quot;http://280slides.com/&quot;&gt;280 Slides&lt;/a&gt; for storing, retrieving, and exporting presentations. 
&lt;/p&gt;
&lt;p&gt;Other applications implemented with Cappuccino include &lt;a href=&quot;http://almost.at&quot;&gt;almost.at&lt;/a&gt; 
and &lt;a href=&quot;http://gomockingbird.com&quot;&gt;Mockingbird&lt;/a&gt;. &lt;a href=&quot;http://enstore.com&quot;&gt;EnStore&lt;/a&gt; uses it too, but only for its admin interface.&lt;/p&gt;
&lt;p&gt;An interesting extension for Rails developers is &lt;a href=&quot;http://github.com/nciagra/Cappuccino-Extensions/tree/master/CPActiveRecord/&quot;&gt;CPActiveRecord&lt;/a&gt;, a reimplementation of Rails&apos; ActiveRecord in Cappuccino.
&lt;/p&gt;
&lt;p&gt;There are several tools included with Cappuccino:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;objj: command line Objective-J&lt;/li&gt;
&lt;li&gt;objcc: &quot;compile&quot; ahead of time&lt;/li&gt;
&lt;li&gt;press: optimize code and resources&lt;/li&gt;
&lt;li&gt;nib2cib: convert Mac OS X nibs&lt;/li&gt;
&lt;li&gt;capp: project creation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All these tools are built on &lt;a href=&quot;http://github.com/tlrobinson/narwhal&quot;&gt;Narwhal&lt;/a&gt; (which conforms to the CommonJS standard).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CommonJS&lt;/strong&gt;&lt;br/&gt;
&lt;a href=&quot;http://wiki.commonjs.org/wiki/CommonJS&quot;&gt;CommonJS&lt;/a&gt; is an effort among server-side JavaScript projects to standardize non-browser JavaScript APIs. There&apos;s numerous API specifications (so far):
&lt;ul&gt;
&lt;li&gt;Binary, File, IO&lt;/li&gt;
&lt;li&gt;stdin, stdout, stderr, args, env&lt;/li&gt;
&lt;li&gt;Web server gateway (&lt;a href=&quot;http://jackjs.org/jsgi-spec.html&quot;&gt;JSGI&lt;/a&gt;) - similar to &lt;a href=&quot;http://wsgi.org/wsgi/&quot;&gt;WSGI&lt;/a&gt; and &lt;a href=&quot;http://rack.rubyforge.org/&quot;&gt;Rack&lt;/a&gt; for Python and Ruby&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To learn more about CommonJS, see &lt;a href=&quot;http://arstechnica.com/web/news/2009/12/commonjs-effort-sets-javascript-on-path-for-world-domination.ars&quot;&gt;CommonJS effort sets JavaScript on path for world domination&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;
Narwhal is 280 North&apos;s implementation of CommonJS APIs. It works with multiple JavaScript engines, including Rhino, JavaScriptCore (SquirrelFish) and XUL Runner. According to Tom, Rhino is an order of magnitude slower than JavaScriptCore and V8. Of course, Narwhal supports Objective-J too.
&lt;/p&gt;
&lt;p&gt;
&lt;strong&gt;Aristo&lt;/strong&gt;&lt;br/&gt;
Aristo is the new default theme in Cappuccino and was created by the &lt;a href=&quot;http://www.madebysofa.com/&quot;&gt;Sofa&lt;/a&gt; design firm. It includes windows, tabs and menus and is &lt;a href=&quot;http://wiki.github.com/280north/aristo&quot;&gt;open source&lt;/a&gt; so you can modify. 
&lt;/p&gt;
&lt;p&gt;
&lt;strong&gt;Atlas&lt;/strong&gt;&lt;br/&gt;
Atlas is an IDE for Cappuccino, focused on building user interfaces graphically. Atlas is a downloadable application for OS X. It&apos;s written almost entirely in Cappuccino. The desktop version bridges Cappuccino windows to native windows. Tom did a demo of Atlas and showed how its layout feature allows you pin, center and align very easily. It&apos;s all done with JavaScript because doing layouts with CSS is often very painful. After that, he showed us how can you Atlas to very easily build a Web Application and then export it as a native OS X application without changing a line of code. Atlas includes Mozilla&apos;s &lt;a href=&quot;https://bespin.mozilla.com/&quot;&gt;Bespin&lt;/a&gt; for code editing. &lt;/p&gt;
&lt;p&gt;
&lt;p&gt;To learn more about Aristo and Atlas, you might want to checkout Ajaxian&apos;s &lt;a href=&quot;http://ajaxian.com/archives/big-news-from-cappuccino-aristo-and-atlas&quot;&gt;Big News from Cappuccino: Aristo and Atlas&lt;/a&gt; from earlier this year.&lt;/p&gt;
&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://280atlas.com&quot;&gt;Atlas&lt;/a&gt; has a $20 &lt;a href=&quot;https://atlas-beta.heroku.com/users/new&quot;&gt;Beta Program&lt;/a&gt; if you&apos;re interested in trying it out.&lt;/p&gt;
&lt;p style=&quot;border-top: 1px dotted silver; padding-top: 10px&quot;&gt;
&lt;strong&gt;My Thoughts&lt;/strong&gt;&lt;br/&gt;
Cappuccino looks like a very cool web framework. It reminds me of GWT in that you have to learn a new language to use it. However, Atlas takes a lot of that pain away. I particularly like how it has document and undo/redo support built-in. On my current GWT project, this would be very useful as we&apos;ve had to build this functionality by hand. </description>          </item>
    <item>
    <guid isPermaLink="true">https://raibledesigns.com/rd/entry/javascript_and_css_concatenation</guid>
    <title>JavaScript and CSS Concatenation with wro4j</title>
    <dc:creator>Matt Raible</dc:creator>
    <link>https://raibledesigns.com/rd/entry/javascript_and_css_concatenation</link>
        <pubDate>Mon, 9 Nov 2009 10:44:44 -0700</pubDate>
    <category>Roller</category>
    <category>roller</category>
    <category>compression</category>
    <category>yuicompressor</category>
    <category>minification</category>
    <category>javascript</category>
    <category>wro4j</category>
    <category>appfuse</category>
    <category>css</category>
    <category>yslow</category>
    <category>jawr</category>
            <description>This past weekend, I decided it was about time to fix my &lt;a href=&quot;http://developer.yahoo.com/yslow/&quot;&gt;YSlow&lt;/a&gt; score on this site. I did the easiest thing first by moving all my JavaScript files to the bottom of each page. Then I turned on GZip compression using Roller&apos;s built-in CompressionFilter. These changes helped, but the most glaring problem continued to be &lt;em&gt;too many requests&lt;/em&gt;. To solve this, I turned to &lt;a href=&quot;http://wro4j.googlecode.com&quot;&gt;wro4j&lt;/a&gt; (as &lt;a href=&quot;http://twitter.com/alexobjelean/status/5481711667&quot;&gt;recommended on Twitter&lt;/a&gt;) to concatenate my JS and CSS files into one.
&lt;/p&gt;
&lt;p&gt;I have to say, I&apos;m very happy with the results. I&apos;m now sitting at a YSlow (V2) score of 75; 90 if I use the &quot;Small Site or Blog&quot; ruleset. I believe I can improve this by adding expires headers to my images, js and css. More than anything, I&apos;m impressed with wro4j, its &lt;a href=&quot;http://groups.google.com/group/wro4j/browse_thread/thread/33b85936c8f1e8b3?hl=en&quot;&gt;great support&lt;/a&gt; and &lt;a href=&quot;http://code.google.com/p/wro4j/wiki/GettingStarted&quot;&gt;easy setup&lt;/a&gt;. I was looking for a runtime solution (b/c I didn&apos;t want to have to rebuild Roller) and it seems to be perfect for the job. Furthermore, wro4j minifies everything on the fly and they&apos;ll have an &lt;a href=&quot;http://groups.google.com/group/wro4j/msg/b85b4aba0f9500ba?hl=en&quot;&gt;expires header filter in the next release&lt;/a&gt;. 
&lt;/p&gt;
&lt;p&gt;
&lt;a href=&quot;http://jawr.dev.java.net&quot;&gt;JAWR&lt;/a&gt; and the &lt;a href=&quot;http://developer.yahoo.com/yui/compressor/&quot;&gt;YUI Compressor&lt;/a&gt; are other alternatives to this filter, but I&apos;m currently sold on wro4j. First of all, it passed the 10-minute test. Secondly, it didn&apos;t require me to modify Roller&apos;s build system.  
&lt;/p&gt;
&lt;p&gt;At this point, if I&apos;m going to implement JS/CSS concatenation and minification in &lt;a href=&quot;http://appfuse.org&quot;&gt;AppFuse&lt;/a&gt; and &lt;a href=&quot;http://roller.apache.org&quot;&gt;Roller&lt;/a&gt;, wro4j seems like the best option. If you disagree, I&apos;d love to hear your reasoning.&lt;/p&gt;
&lt;p style=&quot;font-style: italic; color: #666; border-top: 1px dotted silver&quot;&gt;
&lt;strong&gt;TIP:&lt;/strong&gt; See &lt;a href=&quot;http://www.sonatype.com/people/2009/11/javascript-compression-in-nexus/&quot;&gt;Javascript Compression in Nexus&lt;/a&gt; for information on using YUI Compressor with Maven.
&lt;/p&gt;</description>          </item>
    <item>
    <guid isPermaLink="true">https://raibledesigns.com/rd/entry/json_parsing_with_javascript_overlay</guid>
    <title>JSON Parsing with JavaScript Overlay Types in GWT</title>
    <dc:creator>Matt Raible</dc:creator>
    <link>https://raibledesigns.com/rd/entry/json_parsing_with_javascript_overlay</link>
        <pubDate>Wed, 24 Jun 2009 09:52:49 -0600</pubDate>
    <category>Java</category>
    <category>twitter</category>
    <category>javascript</category>
    <category>json</category>
    <category>gwt</category>
    <category>overlaytypes</category>
    <category>requestbuilder</category>
            <description>A reader recently &lt;a href=&quot;http://raibledesigns.com/rd/entry/enhancing_evite_com_with_gwt#comment-1245137914000&quot;&gt;asked&lt;/a&gt;:&lt;/p&gt;
&lt;p class=&quot;quote&quot;&gt;
I would love to see a snippet of how to eval the JSON coming from RequestBuilder into the OverlayTypes. What is the mapping like? I used OverlayTypes to read in static data that I render into the head section of the hosted page, which is pretty easy and fast, but I don&apos;t know how to do this &quot;reading&quot; dynamically at runtime.&lt;/p&gt;
&lt;p&gt;If you&apos;re not familiar with GWT&apos;s Overlay Types (added in 1.5), see &lt;a href=&quot;http://googlewebtoolkit.blogspot.com/2008/08/getting-to-really-know-gwt-part-2.html&quot;&gt;Getting to really know GWT, Part 2: JavaScript Overlay Types&lt;/a&gt;. In our project, we&apos;re using Overlay Types to simplify JSON parsing and make our application lean-and-mean as possible. 
&lt;/p&gt;
&lt;p&gt;
First of all, we have a JSOModel class that acts as our overlay type:
&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot;&gt;
import java.util.HashSet;
import java.util.Set;

import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.core.client.JsArrayString;

/**
 * Java overlay of a JavaScriptObject.
 */
public abstract class JSOModel extends JavaScriptObject {

    // Overlay types always have protected, zero-arg constructors
    protected JSOModel() {
    }

    /**
     * Create an empty instance.
     * 
     * @return new Object
     */
    public static native JSOModel create() /*-{
        return new Object();
    }-*/;

    /**
     * Convert a JSON encoded string into a JSOModel instance.
     * &amp;lt;p/&amp;gt;
     * Expects a JSON string structured like &apos;{&quot;foo&quot;:&quot;bar&quot;,&quot;number&quot;:123}&apos;
     *
     * @return a populated JSOModel object
     */
    public static native JSOModel fromJson(String jsonString) /*-{
        return eval(&apos;(&apos; + jsonString + &apos;)&apos;);
    }-*/;

    /**
     * Convert a JSON encoded string into an array of JSOModel instance.
     * &amp;lt;p/&amp;gt;
     * Expects a JSON string structured like &apos;&amp;#91;{&quot;foo&quot;:&quot;bar&quot;,&quot;number&quot;:123}, {...}&amp;#93;&apos;
     *
     * @return a populated JsArray
     */
    public static native JsArray&amp;lt;JSOModel&amp;gt; arrayFromJson(String jsonString) /*-{
        return eval(&apos;(&apos; + jsonString + &apos;)&apos;);
    }-*/;

    public final native boolean hasKey(String key) /*-{
        return this&amp;#91;key&amp;#93; != undefined;
    }-*/;

    public final native JsArrayString keys() /*-{
        var a = new Array();
        for (var p in this) { a.push(p); }
        return a;
    }-*/;

    @Deprecated
    public final Set&amp;lt;String&amp;gt; keySet() {
        JsArrayString array = keys();
        Set&amp;lt;String&amp;gt; set = new HashSet&amp;lt;String&amp;gt;();
        for (int i = 0; i &amp;lt; array.length(); i++) {
            set.add(array.get(i));
        }
        return set;
    }

    public final native String get(String key) /*-{
        return &quot;&quot; + this&amp;#91;key&amp;#93;;
    }-*/;

    public final native String get(String key, String defaultValue) /*-{
        return this&amp;#91;key&amp;#93; ? (&quot;&quot; + this&amp;#91;key&amp;#93;) : defaultValue;
    }-*/;

    public final native void set(String key, String value) /*-{
        this&amp;#91;key&amp;#93; = value;
    }-*/;

    public final int getInt(String key) {
        return Integer.parseInt(get(key));
    }

    public final boolean getBoolean(String key) {
        return Boolean.parseBoolean(get(key));
    }

    public final native JSOModel getObject(String key) /*-{
        return this&amp;#91;key&amp;#93;;
    }-*/;

    public final native JsArray&amp;lt;JSOModel&amp;gt; getArray(String key) /*-{
        return this&amp;#91;key&amp;#93; ? this&amp;#91;key&amp;#93; : new Array();
    }-*/;
}
&lt;/pre&gt;
&lt;p&gt;This class alone allows you to easily parse JSON returned in a callback. For example, here&apos;s an example of parsing &lt;a href=&quot;http://twitter.com/statuses/user_timeline.json&quot;&gt;Twitter&apos;s User Timeline&lt;/a&gt; in my &lt;a href=&quot;http://raibledesigns.com/rd/entry/implementing_oauth_with_gwt&quot;&gt;OAuth with GWT&lt;/a&gt; application.
&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot;&gt;
private class TwitterApiCallback implements RequestCallback {
    public void onResponseReceived(Request request, Response response) {
        if (response.getStatusCode() == 200) {
            JsArray&amp;lt;JSOModel&amp;gt; data = JSOModel.arrayFromJson(response.getText());
            List&amp;lt;JSOModel&amp;gt; statuses = new ArrayList&amp;lt;JSOModel&amp;gt;();
            for (int i = 0; i &amp;lt; data.length(); i++) {
                statuses.add(data.get(i));
            }

            // populate textarea with returned statuses
            for (JSOModel status : statuses) {
                payload.setValue(payload.getValue() + status.get(&quot;text&quot;) + &quot;\n\n&quot;);
            }
            
            Label success = new Label(&quot;API call successful!&quot;);
            success.setStyleName(&quot;success&quot;);
            form.add(success);
        } else {
            onError(request, new RequestException(response.getText()));
        }
    }

    public void onError(Request request, Throwable throwable) {
        Window.alert(&quot;Calling API failed. &quot; + OAuthPage.STANDARD_ERROR + &quot;\n\n&quot; + throwable.getMessage());
    }
}
&lt;/pre&gt;
&lt;p&gt;To simply things even more, we created a BaseModel class that can be extended.&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot;&gt;
import java.util.Map;
import java.util.HashMap;

import com.google.gwt.core.client.JsArrayString;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.DOM;

public abstract class BaseModel {

    protected JSOModel data;

    public BaseModel(JSOModel data) {
        this.data = data;
    }

    public String get(String field) {
        String val = this.data.get(field);
        if (val != null &amp;amp;&amp;amp; &quot;null&quot;.equals(val) || &quot;undefined&quot;.equals(val)) {
            return null;
        } else {
            return escapeHtml(val);
        }
    }

    public Map&amp;lt;String, String&amp;gt; getFields() {
        Map&amp;lt;String, String&amp;gt; fieldMap = new HashMap&amp;lt;String, String&amp;gt;();

        if (data != null) {
            JsArrayString array = data.keys();

            for (int i = 0; i &amp;lt; array.length(); i++) {
                fieldMap.put(array.get(i), data.get(array.get(i)));
            }
        }
        return fieldMap;
    }

    private static String escapeHtml(String maybeHtml) {
        final Element div = DOM.createDiv();
        DOM.setInnerText(div, maybeHtml);
        return DOM.getInnerHTML(div);
    }
}
&lt;/pre&gt;
&lt;p&gt;You can extend this class and create model objects that represent a more Java-like view of your data. For example, I could create a Status class with the following code:&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot;&gt;
public class Status extends BaseModel {
    
    public Status(JSOModel data) {
        super(data);
    }

    public String getText() {
        return get(&quot;text&quot;);
    }
}
&lt;/pre&gt;
&lt;p&gt;Then I could change my JSON parsing in TwitterApiCallback to be:&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot;&gt;
    private class TwitterApiCallback implements RequestCallback {
    public void onResponseReceived(Request request, Response response) {
        if (response.getStatusCode() == 200) {
            JsArray&amp;lt;JSOModel&amp;gt; data = JSOModel.arrayFromJson(response.getText());
            List&amp;lt;Status&amp;gt; statuses = new ArrayList&amp;lt;Status&amp;gt;();
            for (int i = 0; i &amp;lt; data.length(); i++) {
                Status s = new Status(data.get(i));
                statuses.add(s);
            }

            // populate textarea with returned statuses
            for (Status status : statuses) {
                payload.setValue(payload.getValue() + status.getText() + &quot;\n\n&quot;);
            }

            Label success = new Label(&quot;API call successful!&quot;);
            success.setStyleName(&quot;success&quot;);
            form.add(success);
        } else {
            onError(request, new RequestException(response.getText()));
        }
    }

    public void onError(Request request, Throwable throwable) {
        Window.alert(&quot;Calling API failed. &quot; + OAuthPage.STANDARD_ERROR + &quot;\n\n&quot; + throwable.getMessage());
    }
}
&lt;/pre&gt;
&lt;p&gt;That&apos;s how we&apos;re doing lightweight JSON parsing with GWT. I&apos;ve updated my &lt;a href=&quot;http://demo.raibledesigns.com/gwt-oauth/&quot;&gt;GWT with OAuth demo&lt;/a&gt; with this code. You can also &lt;a href=&quot;http://static.raibledesigns.com/downloads/gwt-oauth-1.1.zip&quot;&gt;download the source&lt;/a&gt;. Please let me know if you have any questions. &lt;/p&gt;
&lt;p id=&quot;update1&quot;&gt;&lt;strong&gt;Update October 20, 2009:&lt;/strong&gt; I recently had to enhance the JSOModel and BaseModel classes in my project to handle nested objects and arrays. In my project, I have a Conversation object that has a Channel and a List of Task objects. These objects are available in the JSOModel of my BaseModel, I just needed to grab them a bit differently. 
&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot;&gt;
public Channel getChannel() {
    return new Channel(data.getObject(&quot;channel&quot;));
}

public List&amp;lt;Task&amp;gt; getTasks() {
    JsArray&amp;lt;JSOModel&amp;gt; array = data.getArray(&quot;tasks&quot;);
    List&amp;lt;Task&amp;gt; tasks = new ArrayList&amp;lt;Task&amp;gt;(array.length());

    for (int i = 0; i &amp;lt; array.length(); i++) {
        Task task = new Task(array.get(i));
        tasks.add(task);
    }
    
    return tasks;
}
&lt;/pre&gt;
&lt;p&gt;To set a Channel, it&apos;s as simple as:
&lt;pre class=&quot;brush: java&quot;&gt;
data.set(&quot;channel&quot;, channel.toJson().toString());
&lt;/pre&gt;
&lt;p&gt;
To allow setting Lists, I had to enhance JSOModel by adding the following two methods:
&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot;&gt;
public final void set(String key, List&amp;lt;JSOModel&amp;gt; values) {
    JsArray&amp;lt;JSOModel&amp;gt; array = JavaScriptObject.createArray().cast();
    for (int i=0; i &amp;lt; values.size(); i++) {
        array.set(i, values.get(i));
    }
    setArray(key, array);
}

protected final native void setArray(String key, JsArray&amp;lt;JSOModel&amp;gt; values) /*-{
    this&amp;#91;key&amp;#93; = values;
}-*/;
&lt;/pre&gt;
&lt;p&gt;After making this change, I was able to convert my List&lt;Task&gt; to List&lt;JSOModel&gt; and set it on the underlying JSOModel.&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot;&gt;
public void setTasks(List&amp;lt;Task&amp;gt; tasks) {
    List&amp;lt;JSOModel&amp;gt; values = new ArrayList&amp;lt;JSOModel&amp;gt;();
    for (Task task : tasks) {
        values.add(task.getModel());
    }

    data.set(&quot;tasks&quot;, values);
}
&lt;/pre&gt;
&lt;p&gt;To allow the task.getModel() method to work, I added a getter to BaseModel to allow retrieving the underlying JSOModel. Currently, I&apos;m using a homegrown JSON.java class to produce JSON from my BaseModel objects. It all seems to work great and I&apos;m pumped I can receive and send all my JSON using overlay types.
</description>          </item>
    <item>
    <guid isPermaLink="true">https://raibledesigns.com/rd/entry/implementing_oauth_with_gwt</guid>
    <title>Implementing OAuth with GWT</title>
    <dc:creator>Matt Raible</dc:creator>
    <link>https://raibledesigns.com/rd/entry/implementing_oauth_with_gwt</link>
        <pubDate>Thu, 18 Jun 2009 13:59:13 -0600</pubDate>
    <category>Java</category>
    <category>gwt</category>
    <category>oauth</category>
    <category>google</category>
    <category>javascript</category>
            <description>I&apos;ve heard about &lt;a href=&quot;http://oauth.net/&quot;&gt;OAuth&lt;/a&gt; for quite some time, but never had an opportunity to implement it on a project. For a good explanation of what OAuth is, see its &lt;a href=&quot;http://oauth.net/about&quot;&gt;Introduction&lt;/a&gt;. Here&apos;s an excerpt: &lt;/p&gt;
&lt;p class=&quot;quote&quot;&gt;
...it allows you the User to grant access to your private resources on one site (which is called the Service Provider), to another site (called Consumer, not to be confused with you, the User). While OpenID is all about using a single identity to sign into many sites, OAuth is about giving access to your stuff without sharing your identity at all (or its secret parts).
&lt;/p&gt;
&lt;p&gt;The reason I needed OAuth was to interact with the &lt;a href=&quot;http://code.google.com/apis/contacts/&quot;&gt;Google Contacts API&lt;/a&gt;. I&apos;ve always hated how sites make you import all your contacts from Gmail. I wanted to develop a system that&apos;d let you simply read your contacts from Google in real-time.
&lt;/p&gt;
&lt;p&gt;Since the &lt;a href=&quot;http://raibledesigns.com/rd/entry/enhancing_evite_com_with_gwt&quot;&gt;application I&apos;m working on uses GWT&lt;/a&gt;, I chose to implement an OAuth client in GWT. After googling for &quot;&lt;a href=&quot;http://www.google.com/search?q=gwt+oauth&quot;&gt;gwt oauth&lt;/a&gt;&quot;, I found &lt;a href=&quot;http://code.google.com/p/gwt-examples/wiki/oAuth&quot;&gt;two&lt;/a&gt; &lt;a href=&quot;www.sergimansilla.com/blog/?p=75&quot;&gt;examples&lt;/a&gt;. Unfortunately, neither worked out-of-the-box. 
&lt;/p&gt;
&lt;p&gt;
The good news is I did manage to create a working solution. The bad news is it only seems to work at random. That&apos;s right folks, I created a solution that only works 50% of the time. I&apos;m somewhat embarrassed to post it here, but I also realize the power of open source and community. By sharing, I hope we can find the flaws in my logic and come up with a solution for all GWT applications. 
&lt;/p&gt;
&lt;p&gt;The best project for OAuth libraries seems to be &lt;a href=&quot;http://code.google.com/p/oauth/&quot;&gt;oauth on Google Code&lt;/a&gt;. However, you&apos;ll notice that there is no JavaScript implementation listed on the homepage. I did look at the &lt;a href=&quot;http://oauth.googlecode.com/svn/code/java/&quot;&gt;Java&lt;/a&gt; implementation, but quickly realized it wouldn&apos;t be usable in GWT. Therefore, I opted for the &lt;a href=&quot;http://oauth.googlecode.com/svn/code/javascript/&quot;&gt;JavaScript&lt;/a&gt; implementation.
&lt;/p&gt;
&lt;p&gt;OAuth consists of several steps. The following diagram explains the authentication flow nicely.&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
&lt;a href=&quot;http://farm4.static.flickr.com/3384/3638983340_e353831f5c_o.png&quot; title=&quot;OAuth Authentication Flow&quot; rel=&quot;lightbox[gwt-oauth]&quot;&gt;&lt;img src=&quot;//farm4.static.flickr.com/3384/3638983340_4d939fd820.jpg&quot; width=&quot;500&quot; height=&quot;368&quot; alt=&quot;OAuth Authentication Flow&quot; /&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;In a nutshell, you have to complete the following steps:
&lt;/p&gt;
&lt;ol&gt;&lt;li&gt;Get a token from the service provider.&lt;/li&gt;
&lt;li&gt;Redirect user to service provider to grant access and redirect back to application.&lt;/li&gt;
&lt;li&gt;Request access token to access protected resources.&lt;/li&gt;
&lt;li&gt;Access protected resources and pull/push data.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;
To access a service provider&apos;s OAuth service, you&apos;ll likely need to start by registering your application. For Google, &lt;a href=&quot;http://code.google.com/apis/accounts/docs/OAuth.html&quot;&gt;OAuth Authentication for Web Applications&lt;/a&gt; is an excellent resource. Google&apos;s &lt;a href=&quot;http://googlecodesamples.com/oauth_playground/&quot;&gt;OAuth Playground&lt;/a&gt; is a great way to with the Google Data APIs after you&apos;ve registered.
&lt;/p&gt;
&lt;p&gt;
Now that you know how OAuth works, let&apos;s look at how I implemented it with GWT.  I started by adding the necessary JavaScript references to my *.gwt.xml file.&lt;/p&gt;
&lt;pre class=&quot;brush: xml&quot;&gt;
&amp;lt;script src=&quot;//oauth.googlecode.com/svn/code/javascript/oauth.js&quot;/&amp;gt;
&amp;lt;script src=&quot;//oauth.googlecode.com/svn/code/javascript/sha1.js&quot;/&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Next, I needed a way to sign the request. I tried to use Sergi Mansilla&apos;s &lt;a href=&quot;http://sergimansilla.com/public/flyqlr/flyqlr/src/com/sergi/client/OAuth.java&quot;&gt;OAuth.java&lt;/a&gt; for this, but discovered issues with how the parameters were being written with GWT 1.6. I opted for Paul Donnelly&apos;s &lt;a href=&quot;http://paul.donnelly.org/2008/10/31/2-legged-oauth-javascript-function-for-yql/&quot;&gt;makeSignedRequest function&lt;/a&gt; instead. By adding this to my application&apos;s HTML page, I&apos;m able to call it using the following JSNI method:
&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot;&gt;
public native static String signRequest(String key, String secret, String tokenSecret, String url) /*-{
    return $wnd.makeSignedRequest(key, secret, tokenSecret, url);
}-*/;
&lt;/pre&gt;
&lt;p&gt;After the URL is signed, it needs to be sent to the provider to get a &lt;em&gt;request token&lt;/em&gt;. To do this, I used GWT&apos;s RequestBuilder and created a &lt;em&gt;send()&lt;/em&gt; method:&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot;&gt;
protected void send(RequestCallback cb, String URL) {
    RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, URL);
    builder.setTimeoutMillis(10000);
    builder.setCallback(cb);
    
    Request req = null;
    try {
        req = builder.send();
    } catch (RequestException e) {
        cb.onError(req, e);
    }
}
&lt;/pre&gt;
&lt;p&gt;If you try this with Google&apos;s &lt;a href=&quot;https://www.google.com/accounts/OAuthGetRequestToken&quot;&gt;Request Token URL&lt;/a&gt; in GWT&apos;s hosted mode, nothing will happen. Compile/browse to Safari and you&apos;ll still see nothing. Try it in Firefox and you&apos;ll see the following.&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
&lt;img src=&quot;//farm3.static.flickr.com/2437/3639119788_b675d86360_o.png&quot; width=&quot;400&quot; alt=&quot;SOP Error&quot; /&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;To workaround browsers&apos; &lt;a href=&quot;http://code.google.com/p/google-web-toolkit-doc-1-5/wiki/FAQ_SOP&quot;&gt;Same Origin Policy&lt;/a&gt;, I added a proxy servlet to send the requests. I started with Jason Edwards&apos;s &lt;a href=&quot;http://edwardstx.net/wiki/attach/HttpProxyServlet/ProxyServlet.java&quot;&gt;ProxyServlet&lt;/a&gt; and modified it to fit my needs. I then registered it in both *.gwt.xml and web.xml.
&lt;/p&gt;
&lt;pre class=&quot;brush: xml&quot;&gt;
&amp;lt;servlet path=&quot;/google/&quot; class=&quot;org.appfuse.gwt.servlet.AlternateHostProxyServlet&quot;/&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Now, before calling the &lt;em&gt;send()&lt;/em&gt; method, I replace the start of the URL so the request would be routed through the servlet.&lt;/p&gt;
&lt;pre class=&quot;brush:java&quot;&gt;
public void getToken(RequestCallback cb) {
    String url = signRequest(provider.getConsumerKey(), 
                             provider.getConsumerSecret(), 
                             &quot;&quot;, provider.getRequestTokenURL());
    url = url.replace(&quot;https://www.google.com/&quot;, &quot;/google/&quot;);
    send(cb, url);
}
&lt;/pre&gt;
&lt;p&gt;When the request returns, I create two cookies by calling a &lt;em&gt;createOAuthCookies()&lt;/em&gt; method with the payload returned:&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot;&gt;
public static String[] createOAuthCookies(String data) {
    String oauth_token = data.substring(data.indexOf(&quot;oauth_token=&quot;) + 12);
    oauth_token = oauth_token.substring(0, oauth_token.indexOf(&quot;&amp;&quot;));

    String oauth_token_secret = data.substring(data.indexOf(&quot;oauth_token_secret=&quot;) + 19);

    Cookies.setCookie(&quot;oauth_token&quot;, URL.decode(oauth_token));
    Cookies.setCookie(&quot;oauth_token_secret&quot;, URL.decode(oauth_token_secret));
    return new String[]{oauth_token, oauth_token_secret};
}
&lt;/pre&gt;
&lt;p&gt;The next step is to authorize the token. This is where things got tricky with my proxy servlet and I had to add some special logic for GWT. Google was sending back a 302 with a Location header, but it wasn&apos;t hitting the &lt;em&gt;onResponseReceived()&lt;/em&gt; method in my callback. For this reason, I had to change it to a 200 status code and add the redirect location to the body. I also discovered that sometimes they&apos;d return an HTML page with a &lt;code&gt;&amp;lt;meta http-equiv=&quot;refresh&quot; ...&amp;gt;&lt;/code&gt; tag. When using Twitter, I discovered the full HTML for the allow/deny page was returned.
Below is the callback I&apos;m using. &lt;a href=&quot;http://gwt-widget.sourceforge.net/docs/xref/org/gwtwidgets/client/util/WindowUtils.html&quot;&gt;WindowUtils&lt;/a&gt; is a class I got from Robert Hanson and the &lt;a href=&quot;http://gwt-widget.sourceforge.net/&quot;&gt;gwt-widget&lt;/a&gt; project.
&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot;&gt;
public void onResponseReceived(Request request, Response response) {
    String text = response.getText();
    if (response.getStatusCode() == 200 &amp;&amp; response.getText().startsWith(&quot;http&quot;)) {
        WindowUtils.changeLocation(response.getText());
    } else {
        // look for meta-tag that refreshes and grab its URL
        if (text.contains(&quot;&lt;meta http-equiv=\&quot;refresh\&quot;&quot;)) {
            String tokenToStartWith = &quot;url=&amp;#39;&quot;;
            String tokenToEndWith = &quot;&amp;#39;\&quot;&gt;&quot;;
            String url = text.substring(text.indexOf(tokenToStartWith) + tokenToStartWith.length());
            url = url.substring(0, url.indexOf(tokenToEndWith) + tokenToEndWith.length());
            WindowUtils.changeLocation(url);
        } else {
            // Twitter returns a full HTML page, so redirect to the authorize URL manually
            if (provider instanceof Twitter) {
                String url = provider.getAuthorizeTokenURL();
                url = url.replace(&quot;$1&quot;, OAuthRequest.getAuthToken());
                url = url.replace(&quot;$2&quot;, DefaultRequest.getCurrentLocation());
                WindowUtils.changeLocation(url);
            } else {
                onError(request, new RequestException(text));
            }
        }
    }
}

public void onError(Request request, Throwable caught) {
    Window.alert(&quot;Calling authorize token failed. &quot; + OAuthPage.STANDARD_ERROR + &quot;\n\n&quot; + caught.getMessage());
}&lt;/pre&gt;
&lt;p&gt;The 3rd step is to get an access token. The most important thing to remember when you do this is to include the &quot;oauth_token_secret&quot; value when signing the request.&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot;&gt;
signRequest(provider.getConsumerKey(), provider.getConsumerSecret(), 
            getAuthTokenSecret(), url);
&lt;/pre&gt;
&lt;p&gt;After this completes with a 200, I create the cookies again (since oauth_token and oauth_token_secret are returned in the body), then call the API to get a list of contacts. The ContactsRequests class is responsible for making the call. The DefaultRequest class contains the &lt;em&gt;send()&lt;/em&gt; method as well as utility methods to get the cookie values of the oauth tokens.&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot;&gt;
public class ContactsRequest extends DefaultRequest {
    private static final String GOOGLE_CONTACTS_URL = 
        &quot;http://www.google.com/m8/feeds/contacts/default/thin?oauth_token=$1&quot;;
    private OAuthProvider provider;

    public ContactsRequest(OAuthProvider provider) {
        this.provider = provider;
    }

    public void getContacts(RequestCallback cb) {
        String url = GOOGLE_CONTACTS_URL.replace(&quot;$1&quot;, getAuthToken());
        url = signRequest(provider.getConsumerKey(), provider.getConsumerSecret(), 
                          getAuthTokenSecret(), url);

        String proxiedURLPrefix = &quot;/contacts/&quot;;
        // allow for deploying at /gwt-oauth context
        if (WindowUtils.getLocation().getPath().contains(&quot;gwt-oauth&quot;)) {
            proxiedURLPrefix = &quot;/gwt-oauth&quot; + proxiedURLPrefix;
        }

        url = url.replace(&quot;http://www.google.com/&quot;, proxiedURLPrefix);

        send(cb, url);
    }
}&lt;/pre&gt;
&lt;p&gt;If all goes well, the response contains the data you requested and it&apos;s used to populate a textarea (at least in this demo application). Of course, additional processing needs to occur to parse/format this data into something useful.&lt;/p&gt;
&lt;p&gt;This all sounds pretty useful for GWT applications, right? I believe it does - but only if it works consistently. I sent &lt;a href=&quot;http://groups.google.com/group/oauth/browse_thread/thread/e180000f5bd9dc2c&quot;&gt;a message&lt;/a&gt; to the OAuth Google Group explaining the issues I&apos;ve had.
&lt;/p&gt;
&lt;p class=&quot;quote&quot;&gt;
I&apos;m trying to use the JavaScript API to authenticate with OAuth from a
GWT application. I&apos;ve got it working with both Google and Twitter&apos;s
OAuth implementations. However, it seems to fail to sign the URL at
random. In other words, it works 1 out of 3 times.
...
Any idea why this could be happening? 
&lt;/p&gt;
&lt;p&gt;I received a &lt;a href=&quot;http://groups.google.com/group/oauth/msg/ba2cb1bed6eecceb&quot;&gt;response&lt;/a&gt; with a cleaner &lt;em&gt;makeSignedRequest()&lt;/em&gt; function. I tried it and, unfortunately, it seems to be equally unreliable. I suspect the problem is with the OAuth JavaScript implementation, GWT&apos;s interpretation of it, or that OAuth isn&apos;t as mature as it appears to be. I&apos;d like to think one of the first two causes the problem.
&lt;/p&gt;
&lt;p&gt;To make it easier to create a robust example of GWT and OAuth, I created a gwt-oauth project you can &lt;a href=&quot;http://static.raibledesigns.com/downloads/gwt-oauth-1.0.zip&quot;&gt;download&lt;/a&gt; or &lt;a href=&quot;http://demo.raibledesigns.com/gwt-oauth&quot;&gt;view online&lt;/a&gt;. Please keep in mind the demo is likely to be flakey. If you&apos;re persistent and try enough times, it&apos;s likely to work. Firefox seems to succeed moreso than Safari or Chrome. If you have any suggestions for improving this example, please let me know.</description>          </item>
    <item>
    <guid isPermaLink="true">https://raibledesigns.com/rd/entry/appcelerator_with_matt_quinlan</guid>
    <title>Appcelerator with Matt Quinlan</title>
    <dc:creator>Matt Raible</dc:creator>
    <link>https://raibledesigns.com/rd/entry/appcelerator_with_matt_quinlan</link>
        <pubDate>Tue, 7 Oct 2008 19:08:19 -0600</pubDate>
    <category>Open Source</category>
    <category>appcelerator</category>
    <category>soa</category>
    <category>ajax</category>
    <category>ria</category>
    <category>javascript</category>
    <category>opensource</category>
    <category>ruby</category>
    <category>java</category>
            <description>This evening I attended the Denver Open Source User Group meeting where the Basic Concepts talk was on &lt;a href=&quot;http://appcelerator.org&quot;&gt;Appcelerator&lt;/a&gt;. Matt Quinlan (&lt;a href=&quot;http://twitter.com/MattQuinlan&quot;&gt;Twitter&lt;/a&gt;, &lt;a href=&quot;http://MattQuinlan.com&quot;&gt;Blog&lt;/a&gt;, &lt;a href=&quot;http://www.linkedin.com/in/mattquinlan&quot;&gt;LinkedIn&lt;/a&gt;) was the presenter. I arrived 10 minutes late, so I didn&apos;t hear any of the intro stuff. Below are my notes from the event.
&lt;/p&gt;
&lt;p&gt;The Appcelerator developers liked the &quot;onclick&quot; syntax, but found it was too limited to do everything they wanted. Rather than &lt;em&gt;onclick&lt;/em&gt;, they use an &lt;em&gt;on&lt;/em&gt; attribute. For example:
&lt;/p&gt;
&lt;pre&gt;
&amp;lt;div on=&quot;click then l:show.box&quot;&gt;
Click me
&amp;lt;div&gt;
&lt;/pre&gt;
&lt;p&gt;&quot;DOM Manipulation is JavaScript cruft.&quot; 
&lt;/p&gt;
&lt;p&gt;Appcelerator allows you to implement the Observer pattern in the browser. In addition to allowing DOM elements to subscribe to messages, server-side objects (in any language) can subscribe as well. In Appcelerator syntax &quot;l:&quot; means local and &quot;r:&quot; means remote. The messages that are passed to the server are JSON and have payloads. JSON is more popular than XML because you can eval() it and create JavaScript objects from it. Appcelerator allows you to do &lt;em&gt;Declarative Ajax&lt;/em&gt;. On the server-side, you can annotation Java and C# classes with @Service to subscribe to messages. In other languages (i.e. PHP), Javadoc-style comments are used.&lt;/p&gt;
&lt;p&gt;Tagline: The seamless fusion of RIA and SOA.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Web Expression Language&lt;/strong&gt;&lt;br/&gt;
Goal is to eliminate 90% of the JavaScript you write. Example syntax:
&lt;/p&gt;
&lt;pre&gt;
on=&quot;[condition] then [action]&quot;
&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Conditions&lt;/em&gt; include DOM events (click, focus, blur, change, mouse events), key events (up, down, press), other (history, drag/drop, selected, resize, iPhone orient, sortXYZ), subscribe to custom message. &lt;em&gt;Actions&lt;/em&gt; include Scriptaculous effects (show/hide, fade, move, drop, grow), set element value (static, dynamic, bind), set CSS class or attribute, execute custom JavaScript, publish custom message.&lt;/p&gt;
&lt;p&gt;Now Matt is doing a demo on &lt;a href=&quot;http://try.appcelerator.org&quot;&gt;http://try.appcelerator.org&lt;/a&gt;. This site consists of a form that allows you to type in Appcelerator code and run it. 3 attributes can be added to any tag: draggable, droppable and resizeable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Client-Side&lt;/strong&gt;&lt;br/&gt;
Add simple tags to your HTML to inject RIA widgets. Add single property to existing HTML elements for dynamic behavior. Eclipse Plugin built on Aptana, but is generally targeted towards web developers moreso than business analysts (no drag and drop of widgets).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Server-Side&lt;/strong&gt;&lt;br/&gt;
Server-side development done with your IDE of choices. Based on your server-side tehchnology platform. Easily create services using annotations.&lt;/p&gt;
&lt;P&gt;&lt;strong&gt;App Command&lt;/strong&gt;&lt;br/&gt;
The &lt;em&gt;app&lt;/em&gt; command is similar to Rails&apos; GEM command. Allows you to build scaffolding and deploy to cloud (AppEngine, Amazon S3). It also allows you to pulldown components from the main server and auto-updating.&lt;/p&gt;
&lt;p&gt;Examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://apptunes.appspot.com&quot;&gt;appTunes&lt;/a&gt;: widget that wraps Flex &lt;a href=&quot;http://en.wikipedia.org/wiki/Cover_Flow&quot;&gt;Cover Flow&lt;/a&gt; widget&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://snapshot.appcelerator.org&quot;&gt;Snapshot&lt;/a&gt;: photo editing application&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://radiojavan.com&quot;&gt;Radio Javan&lt;/a&gt;: Persian music online&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://skyblox.com&quot;&gt;SkyBlox&lt;/a&gt;: wireless company&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;Appcelerator allows you to create prototypes easily by using a JavaScript file with mocks for the server-side objects. In the next version, you can &quot;annotate&quot; the UI and allow end-users to Ctrl+Click on elements and add feedback. For an example of this, see &lt;a href=&quot;http://dev.appcelerator.com/pearson&quot;&gt;http://dev.appcelerator.com/pearson&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When starting with Appcelerator, you can start by crawling (including appcelerator.js for widgets) then move to walking (decouple server-side and client-side) and finally running (developing working prototypes with mocks for server-side).
&lt;/p&gt;
&lt;p&gt;

&quot;Let&apos;s face it, ASP, JSP, PHP and Ruby are just lipstick on CGI.&quot;
</description>          </item>
    <item>
    <guid isPermaLink="true">https://raibledesigns.com/rd/entry/oscon_2008_even_faster_web</guid>
    <title>[OSCON 2008] Even Faster Web Sites by Steve Souders</title>
    <dc:creator>Matt Raible</dc:creator>
    <link>https://raibledesigns.com/rd/entry/oscon_2008_even_faster_web</link>
        <pubDate>Thu, 24 Jul 2008 16:23:12 -0600</pubDate>
    <category>The Web</category>
    <category>stylesheets</category>
    <category>css</category>
    <category>yslow</category>
    <category>ie</category>
    <category>javascript</category>
    <category>firefox</category>
    <category>performance</category>
    <category>webperformance</category>
    <category>oscon</category>
    <category>oscon08</category>
    <category>browsers</category>
    <category>firebug</category>
            <description>&lt;a href=&quot;http://stevesouders.com/&quot;&gt;Steve&lt;/a&gt; works at Google on web performance and open source initiatives. To begin his talk, Steve is running YSlow in &quot;autorun&quot; mode. This runs YSlow Performance tests on the top 100 sites according to &lt;a href=&quot;http://www.alexa.com/&quot;&gt;Alexa&lt;/a&gt;. Before Google, Steve was at Yahoo for 7 years. You can download Steve&apos;s slides from &lt;a href=&quot;http://stevesouders.com/&quot;&gt;his site&lt;/a&gt; (don&apos;t ask me where).
&lt;/p&gt;
&lt;p&gt;iGoogle with an empty cache: 9% of the time is spent getting the HTML document. The % of time of what a webserver does is a pretty small percentage of the overall picture. If the cache is primed, the time goes up to 17% of the time. &lt;/p&gt;
&lt;p&gt;80-90% of the end-user response time is spent on the frontend. Start there.&lt;/p&gt;
&lt;p&gt;There&apos;s a greater potential of improvement on the frontend. If you improve the backend performance by 50%, chances are the end-user only sees a 5% improvement. 
&lt;/p&gt;
&lt;p&gt;
The 14 Rules are encapsulated in the YSlow plugin. At OSCON last year, Yahoo released YSlow. 500,000 downloads since it was released. Following the release of YSlow, Steve wrote &lt;a href=&quot;http://oreilly.com/catalog/9780596529307/&quot;&gt;High Performance Web Sites&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;High Performance Web Sites, Vol 2. The 3 most important rules are:
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Split the initial payload&lt;/li&gt;
&lt;li&gt;Load scripts without blocking&lt;/li&gt;
&lt;li&gt;Don&apos;t scatter inline scripts&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
Why focus on JavaScript? A lot of the top sites use JavaScript. For example, up until a few weeks ago, Facebook served up 1MB of JavaScript, uncompressed. Scripts block parallel downloads and page rendering. To see it in action, go to &lt;a href=&quot;http://stevesouders.com/cuzillion/?ex=10008&quot;&gt;http://stevesouders.com/cuzillion/?ex=10008&lt;/a&gt;. 
&lt;/p&gt;
&lt;p&gt;
Any content below a &amp;lt;script&gt; is blocked from rendering - even if it&apos;s already cached in the browser. &lt;a href=&quot;http://stevesouders.com/cuzillion/&quot;&gt;Cuzillion&lt;/a&gt; is an open source project that Steve is releasing that allows you to add components to a page and test their performance.&lt;/p&gt;
&lt;p&gt;Split your JavaScript between what&apos;s needed to render the page and everything else. Load &quot;everything else&quot; &lt;em&gt;after&lt;/em&gt; the page is rendered. To do this, you can use Firebug to do it manually, or you can use &lt;a href=&quot;http://research.microsoft.com/projects/doloto/&quot;&gt;Doloto&lt;/a&gt; from Microsoft to automate the splitting of your files.&lt;/p&gt;
&lt;p&gt;MSN.com solves the script blocking problem by using &lt;a href=&quot;http://ajaxpatterns.org/On-Demand_Javascript#DOM-Based_On-Demand_Javascript&quot;&gt;JS DOM&lt;/a&gt; to allow for parallel downloading. There&apos;s 6 techniques for doing this:
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;XHR Eval (must have same domain as page)&lt;/li&gt;
&lt;li&gt;XHR Injection (same domain)&lt;/li&gt;
&lt;li&gt;Script in iFrame (same domain)&lt;/li&gt;
&lt;li&gt;Script DOM Element (domains can differ)&lt;/li&gt;
&lt;li&gt;Script Defer (only supported in IE, domains can differ)&lt;/li&gt;
&lt;li&gt;document.write (not recommended, parallelization only works in IE)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;How do these techniques cause &quot;browser busy&quot; indicators? XHR Eval and Injection don&apos;t trigger any indicators. You need to choose when you want to show busy indicators. It&apos;s good to show them when you want to show your users that something is processing (but not for lazy-loading JavaScript that&apos;s not required for load). For the different techniques, most don&apos;t ensure order of parsing. 
&lt;/p&gt;
&lt;p&gt;
Based on 3 factors, Steve can tell you which technique is best to use. These three factors are 1) the URL of the page and script 2) if you want busy indicators and 3) if you care about order. Steve thinks it would be awesome if web frameworks could support this to write out JavaScript appropriately for the developer&apos;s input.
&lt;/p&gt;
&lt;p&gt;
Long executing inline scripts block rendering and downloads. If you know you&apos;re going to have scripts like this, you can solve it with a couple workarounds:
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Initiate execution with setTimeout(&gt;250 for Firefox)&lt;/li&gt;
&lt;li&gt;Move JavaScript to enternal script with advanced downloading techniques&lt;/li&gt;
&lt;li&gt;Use defer attribute for IE&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
In Firefox 2, stylesheets block parallel downloads just like scripts. IE doesn&apos;t. However, IE will block when you have a stylesheet followed by an inline script. To solve, it&apos;s best to move line scripts above stylesheets or below other resources. use &amp;lt;link&gt;, not @import. 
&lt;/p&gt;
&lt;p&gt;
&lt;strong&gt;Takeaways:&lt;/strong&gt; focus on the frontend, run &lt;a href=&quot;http://developer.yahoo.com/yslow/&quot;&gt;YSlow&lt;/a&gt;, focus on JavaScript (split initial payload, load scripts w/o blocking, don&apos;t scatter inline scripts).
&lt;/p&gt;
&lt;p&gt;
Three Announcements:
&lt;ul&gt;
&lt;li&gt;
&lt;a href=&quot;http://httpwatch.com&quot;&gt;HTTPWatch&lt;/a&gt; for Firefox (not public yet, not free). 
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;http://getfirebug.com/lite.html&quot;&gt;Firebug Light 1.2&lt;/a&gt; (can be used with IE or Opera).
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;http://ejohn.org/blog/firebuggin/&quot;&gt;Mozilla is Firebuggin&apos;&lt;/a&gt; (Mozilla is dedicating 3 people to work on Firebug).
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
</description>          </item>
    <item>
    <guid isPermaLink="true">https://raibledesigns.com/rd/entry/oscon_2008_css_for_high</guid>
    <title>[OSCON 2008] CSS for High Performance JavaScript UI by Gavin Doughtie</title>
    <dc:creator>Matt Raible</dc:creator>
    <link>https://raibledesigns.com/rd/entry/oscon_2008_css_for_high</link>
        <pubDate>Thu, 24 Jul 2008 15:30:32 -0600</pubDate>
    <category>The Web</category>
    <category>css</category>
    <category>javascript</category>
    <category>oscon</category>
    <category>oscon08</category>
    <category>ie6</category>
            <description>&lt;a href=&quot;http://blog.xdraw.org/&quot;&gt;Gavin&lt;/a&gt; is a front end developer on Google&amp;rsquo;s Picasa Web Albums and a contributor to the dojo toolkit. The original designer tools for Web 1.0 were the FONT tag, TABLE hacks and spacer gifs. Now we live in the future (shows a picture of the iPhone). Maybe it&apos;s time to accept that we&apos;re stuck with CSS. &quot;I&apos;m a coder, not a designer&quot; is what happens at OSCON.
&lt;/p&gt;
&lt;p&gt;There are costs of JavaScript: development, download, parse time and runtime performance. It&apos;s extremely powerful and high-level, but it&apos;s slow because it&apos;s interpreted. Drawing a box with JavaScript in Firefox 3 isn&apos;t too difficult. Another way to do the same thing is with CSS. Doing the same thing in CSS is much faster and requires less code. Surrender To Win. You don&apos;t have to code it all. You can hand off part of that to your runtime environment (browser).
&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CSS Fundamentals&lt;/strong&gt;&lt;br/&gt;
You want to how things &lt;em&gt;flow&lt;/em&gt; in a page. Browsers were originally designed to render text. They&apos;re built to render flowing text. Other important fundamentals include &lt;em&gt;float&lt;/em&gt;, &lt;em&gt;positioning&lt;/em&gt;, &lt;em&gt;negative margins&lt;/em&gt;, &lt;em&gt;relative units&lt;/em&gt;, &lt;em&gt;pseudo-selectors&lt;/em&gt;. Lastly, you need to know when to use tables. Gavin is now showing an example of using CSS to modify an image so it scales with the browser window. It&apos;s pretty simple:
&lt;/p&gt;
&lt;pre&gt;
body {
  margin: 0;
  padding: 0;
  height: 100%;
  width: 100%;
  overflow: hidden;
}

img { 
  position: relative;
  width: 100%;
  height: 100%;
}
&lt;/pre&gt;
&lt;p&gt;Relatively Absolutely: &quot;Absolute&quot; in relative to &lt;code&gt;position:relative&lt;/code&gt;. If an element has absolute positioning, it will only be positioned relative to it&apos;s parent element. To draw a box on an image, it&apos;s usually best to calculate the percentage position and sizes in script or on the server. The world&apos;s greatest psuedo-selector is &lt;code&gt;:hover&lt;/code&gt;. Unfortunately, &lt;code&gt;:hover&lt;/code&gt; doesn&apos;t work on anything but &amp;lt;a&gt; in IE 6. &lt;/p&gt;
&lt;p&gt;With &lt;em&gt;floats&lt;/em&gt;, you can &quot;float&quot; elements to the top, right, bottom and left. In Firefox and Safari, you can set a border-radius to draw lines that are curved. Before Firefox 3, these lines where terrible looking.&lt;/p&gt;
&lt;p&gt;Tables make a lot of sense when you have a grid layout. Use &lt;code&gt;table-layout:fixed&lt;/code&gt; if you don&apos;t want table cells to be the width of their contents.
&lt;/p&gt;
&lt;p&gt;For sizing images and other elements, it&apos;s usually best to use &lt;code&gt;em&lt;/code&gt; for the height and width and relative measurements. You can then use &lt;code&gt;style.fontSize&lt;/code&gt; to resize the images. Using this technique is must faster than using JavaScript to set the height and width on an image.&lt;/p&gt;
&lt;p&gt;WebKit, the CSS Wonderland. Gavin loves WebKit. It&apos;s on Safari (Windows, OS X and iPhone), Android, GNOME and KDE. With WebKit, you can build expanding lists w/o any coding. To do gradients in WebKit, you use:
&lt;/p&gt;
&lt;pre&gt;
background: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#00abeb));
-webkit-border-radius: 0.24em;
&lt;/pre&gt;
&lt;p&gt;
To do animation, you can use:&lt;/p&gt;
&lt;pre&gt;
-webkit-transition-property: width, height;
-webkit-transition-duration: 250ms;
-webkit-transition-timing-function: default;
&lt;/pre&gt;
&lt;p&gt;
Another thing you can do with WebKit is rotate images (using &lt;code&gt;-webkit-transform: rotate()&lt;/code&gt;) and just about anything (divs, forms, etc.). WebKit also supports SVN masks, which means you can open up a hole and view an image through it. 
&lt;/p&gt;
&lt;p&gt;
Gecko&apos;s pretty cool too. Firefox 3 introduced a new rendering engine based on Cairo. You can do an SVG transform in Firefox 3 to create reflections of elements (divs) in your page.
&lt;/p&gt;
&lt;p&gt;
&quot;With browsers, you cut people off at the knees, but everyone&apos;s the same height.&quot; -- Alex Russell
&lt;/p&gt;
&lt;p&gt;
IE 6 is not the problem, we&apos;re the problem. If you drop support for IE 6, you won&apos;t have to worry about coding towards it. It might not be possible if you work for a large company, but if you&apos;re small, you should definitely think about it hard. 
&lt;/p&gt;
&lt;p&gt;
It&apos;s funny to think that IE 6 is the new Netscape 4.
&lt;/p&gt;
&lt;p&gt;Gavin&apos;s slides from this talk will be available at &lt;a href=&quot;http://xdraw.org/oscon2008.html&quot;&gt;http://xdraw.org/oscon2008.html&lt;/a&gt; in the next few days.</description>          </item>
    <item>
    <guid isPermaLink="true">https://raibledesigns.com/rd/entry/shadowbox_a_slick_lightbox_that</guid>
    <title>Shadowbox - a slick Lightbox that supports Flash</title>
    <dc:creator>Matt Raible</dc:creator>
    <link>https://raibledesigns.com/rd/entry/shadowbox_a_slick_lightbox_that</link>
        <pubDate>Fri, 25 Jan 2008 21:05:16 -0700</pubDate>
    <category>The Web</category>
    <category>javascript</category>
    <category>lightbox</category>
            <description>&lt;p&gt;Via &lt;a href=&quot;http://ajaxian.com/archives/library-agnostic-lightbox&quot;&gt;Ajaxian&lt;/a&gt;, I learned about &lt;a href=&quot;http://mjijackson.com/shadowbox/&quot;&gt;Shadowbox.js&lt;/a&gt;. From its creator, &lt;a href=&quot;http://mjijackson.com/2008/01/22/shadowbox-js-media-viewer-1-0-beta/&quot;&gt;Michael Jackson&lt;/a&gt;:&lt;/p&gt;
&lt;p class=&quot;quote&quot;&gt;
A few weeks ago, I was looking for a &lt;a href=&quot;http://www.huddletogether.com/projects/lightbox2/&quot;&gt;Lightbox&lt;/a&gt;-like script that would allow me to display more than just pictures. In addition to static images, my client required the ability to display various types of movies including &lt;a href=&quot;http://www.apple.com/quicktime/&quot;&gt;QuickTime&lt;/a&gt; and &lt;a href=&quot;http://en.wikipedia.org/wiki/SWF&quot;&gt;SWF&lt;/a&gt;. The only script that fit the bill was &lt;a href=&quot;http://www.stickmanlabs.com/lightwindow/&quot;&gt;Lightwindow&lt;/a&gt;?a nice piece of work to be sure?but it required the &lt;a href=&quot;http://www.prototypejs.org&quot;&gt;Prototype&lt;/a&gt; + &lt;a href=&quot;http://script.aculo.us&quot;&gt;Scriptaculous&lt;/a&gt; combo and I was already using &lt;a href=&quot;http://developer.yahoo.com/yui/&quot;&gt;YUI&lt;/a&gt;.
&lt;br/&gt;&lt;br/&gt;
Besides, I thought, it would be really great to have a full-featured media viewing application that was &lt;a href=&quot;http://snook.ca/archives/javascript/be_library_agnostic/&quot;&gt;library agnostic&lt;/a&gt;. Then, if I need to use a different framework for some particular reason, I can easily switch.&lt;br/&gt;&lt;br/&gt;
Thus was born &lt;a href=&quot;/shadowbox/&quot;&gt;Shadowbox&lt;/a&gt;?a cross-browser, cross-platform, cleanly-coded and fully-documented media viewer application built entirely in JavaScript. 
&lt;/p&gt;
&lt;p&gt;I&apos;ve been using Lightbox JS on this site for almost 2 years. The next time I need lightbox functionality for an application, I&apos;ll definitely try out Shadowbox. I dig the look and feel. I agree with Ajaxian commentors that rel=&quot;lightbox[name]&quot; would be awesome. If it&apos;s added, I could theoretically replace lightbox.js with shadowbox.js and I wouldn&apos;t have to make any other changes.
&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://mjijackson.com/shadowbox/#demos&quot;&gt;View Shadowbox Demos &amp;raquo;&lt;/a&gt;</description>          </item>
    <item>
    <guid isPermaLink="true">https://raibledesigns.com/rd/entry/ext_js_tag_library</guid>
    <title>Ext JS Tag Library</title>
    <dc:creator>Matt Raible</dc:creator>
    <link>https://raibledesigns.com/rd/entry/ext_js_tag_library</link>
        <pubDate>Tue, 22 Jan 2008 10:01:27 -0700</pubDate>
    <category>Java</category>
    <category>extjs</category>
    <category>javascript</category>
    <category>ajax</category>
            <description>James Carr in &lt;a href=&quot;http://blog.james-carr.org/2008/01/22/making-extjs-more-accessible-to-java-developers/&quot;&gt;Making extJS More Accessible to Java Developers&lt;/a&gt;:&lt;/p&gt;
&lt;p class=&quot;quote&quot;&gt;
With my recent interest in extjs, I was playing with the idea of making a Struts2/WebWork component library or a tag library to handle a lot of the boilerplate extjs code (i.e. creating layouts and such) but, luckily, discovered someone beat me to the punch!
&lt;br/&gt;&lt;br/&gt;
I randomly came across &lt;a href=&quot;http://www.exttld.com/&quot;&gt;ExtTLD&lt;/a&gt; this morning while sifting through my rss feeds, and I must say I am rather impressed. Although I consider myself a pretty good javascript developer, there seems to be a lot of java developers who aren?t that hot at javascript... which is why whenever I attend any java related conference there is always several sessions touting &quot;javascript free ajax!&quot; frameworks like GWT, Ajax4JSF, or IceFaces. Although I&apos;ve always been skeptical of such frameworks, I do see their benefits... especially for the java developer who excels at serverside JEE development but generally sucks when it comes to adding javascript behavior to the presentation layer.
&lt;/p&gt;
&lt;p&gt;I can definitely see how Ajax-with-IDE-code-completion would appeal to many developers. However, I do have to agree with James:&lt;/p&gt;
&lt;p class=&quot;quote&quot;&gt;
So far it looks good, but I haven&apos;t had a chance to play with it yet. Basically, I&apos;ll have to see if it passes my &quot;good javascript generator framework&quot; test. I?m a pretty staunch advocate of unobtrusive javascript, and generally hate any presentation layer framework that seeks to dump several hundred (or thousand) lines of javascript inline in the html document.
&lt;/p&gt;
&lt;p&gt;For development shops that have UI-only developers for the front-end and Java developers for the controller/validation part of an application, frameworks that generate JavaScript usually don&apos;t make sense.
&lt;/p&gt;
&lt;p&gt;ExtTLD&apos;s &lt;a href=&quot;http://code.google.com/p/exttld/&quot;&gt;license&lt;/a&gt;:&lt;/p&gt;
&lt;p class=&quot;quote&quot;&gt;
ExtTLD is published under GPL 3.0 license however restricts use by companies participating in animal abuse, such as animal testing laboratories etc.&lt;/p&gt;
&lt;p&gt;</description>          </item>
  </channel>
</rss>