BRAD GREEN: Good morning, everyone.
I'm Brad Green.
I'm the manager of the Angular project here at Google.
And I'm joined by John and Darrel from TiKL.
And they're going to talk to us about building
Chrome Apps with AngularJS.
And I'm sorry, you guys.
For this holiday addition, John's
actually wearing some spectacular Christmas pants,
but you can't see it on camera.
Maybe at the end, we can get a shot of that.
So hey guys, tell us a little bit
about what TiKL does before we get
into the presentation about building apps.
DARREL SUMI: Sure.
JOHN FEIG: Sure, yeah.
We're mainly a mobile app company.
We have several communications apps.
We have three on Android.
And wait-- no, we have four on Android.
DARREL SUMI: Four on Android, two on iOS.
JOHN FEIG: Two on iOS.
So we've got TiKL, which is like a walkie-talkie.
It works just like a walkie-talkie.
It's pretty fun.
We have Talkray, which is our main focus these days,
which is a unified communications app.
You do texting, calling-- it does groups up to 25 people.
KeeChat, which is what we're going
to be talking about today, which is
group texting with usernames.
And then we have a new app out that we launched a couple weeks
ago, which is a PTT recorded app,
so you can send voice mails back and forth.
It's called VoMessenger.
BRAD GREEN: So you guys have been primarily doing Java and--
DARREL SUMI: Yes.
BRAD GREEN: --maybe Objective-C development.
And you've just recently come into the web
way of developing things.
And so how long have you been doing this?
DARREL SUMI: Since we started as a company,
it's probably-- I don't know, five months or so where
we've been kind of jumped into this.
JOHN FEIG: Yeah, we started over the summer doing the web stuff.
DARREL SUMI: Yeah.
I mean, both of us have obviously
dabbled in a lot of web stuff before.
But this is our first real, larger project.
JOHN FEIG: Yeah, right.
Definitely the first big project.
And the company, as a company, doesn't really
have much experience with web things.
TiKL was started in 2010.
They had the TiKL app for a while.
And then we jumped into Talkray.
And that was iOS and Android.
And we've basically been focused on mobile exclusively
until this product.
BRAD GREEN: OK, very cool.
All right.
Well, then, let's jump into the presentation.
And tell us about what you guys have
learned about the environment.
JOHN FEIG: Great.
So we can start with the slides.
So this is KeeChat.
DARREL SUMI: Yeah.
So these are just a couple screenshots
of what the mobile app looks like.
So basically, like what we said, it's a unified messaging app.
So you can send regular texting images to anyone
within our system.
JOHN FEIG: And we recently built a Chrome App out of it.
We started with a web version of this.
And then we sort of transitioned to a Chrome App.
And we'll talk a little bit about why
we picked Angular and Chrome.
DARREL SUMI: Yeah.
Angular really fit with, like we said, our background in Java.
We wanted the look and feel of the entire thing
to be very much like an app, less a traditional web
style of doing things.
And everything, like the MVC architecture of Angular,
really fit into the pre-existing model
that we had built everything around on the mobile side.
JOHN FEIG: Right.
Yeah.
It was very comfortable for us to look at
because it just felt a lot like being able to build an app.
And you can see here the structure
that we chose for KeeChat, which is basically
Angular's suggested structure for especially large projects.
So we broke it out into controllers and services
and models are the main components here,
trying to pick apart with the MVC architecture
and do this properly.
And it was really nice to have services.
For us, coming from Java, where they're sort of like
singletons.
They work in a similar way.
There's a lot of nice stuff.
And organizing your code like this
really helps in larger projects.
DARREL SUMI: Yes.
So I guess one of the reasons, or one of the things,
is why did we chose to do a Chrome App off the Angular app.
Why not just keep it a website kind of thing?
So obviously, the biggest one is the Chrome Web Store.
That's a great way to get distribution for our app.
The other one is that, like I said,
we really wanted to make it feel like an app.
We wanted users who came from our mobile
to be able to experience the same kind of experience
that we provided on their computers.
So one of the big things is notifications.
That makes it feel like a much more rich experience.
And obviously, raw sockets were pretty important for us
because we're a communication app.
JOHN FEIG: Yeah.
We had played around with a couple different socket clients
and decided that for us to get out the door,
raw sockets was going to be the easiest way for us to do it.
So that was something that was available in Chrome Apps
that really you can't do in HTML5.
There's WebSockets, but it's not the same thing.
Java can use WebSockets in Java 7,
but we're not running Java 7 on the server.
DARREL SUMI: So some of the tools
we use are-- we'll just briefly go over this-- Yeoman, Grunt,
Bower, and Karma.
Karma's our testing suite.
The other three really help us organize our code a lot,
bring in all the dependencies, keep everything clean.
JOHN FEIG: Yeah.
It's pretty standard stuff.
And it was really nice with Grunt, actually.
You can run Grunt server.
And it has listeners for things.
And so it was really funny.
Because when we were really running through this stuff,
it was faster developing on the web than it was for Android.
Because you'd change something, and then your browser
would get updated right away.
I don't know.
It was kind of funny.
DARREL SUMI: Yeah, we're just used to waiting, compiling,
and doing all this stuff for Android development.
JOHN FEIG: Yeah.
And we didn't even have to hit Refresh on the browser window
here.
So it's kind of nice.
DARREL SUMI: So one of the main concerns, when you're
thinking about building a Chrome App,
is what are all the dependencies in your project
that you're going to need to re-work around the Chrome Apps.
And one of them, the big one, was RequireJS.
So we used Require a lot.
It's a great modularization tool that you can use-- or library.
A lot of people use it.
It's a very common thing.
But they kind of conflict a little bit
with the module system in Angular.
They don't necessarily directly-- they're
not against each other.
In fact, that they're orthogonal ideas of the way
they treat modularization.
But they are going to step over each other a little bit,
so you've got to understand if you really need it
and how to deal with it.
JOHN FEIG: For us, we had all these third-party libraries
that we just had to use.
And some of those required Require.
So it's like, OK, we're stuck using Require.
It's kind of painful to get implemented properly
with Angular.
If you can avoid it, it's probably better to avoid it,
but--
DARREL SUMI: But it's still a great tool.
And if you need it, there are several resources
online that show examples of how exactly to do it.
So it's not-- there's ways around it.
JOHN FEIG: And I have read that Angular
is supposed to be getting better about trying
to integrate with Require.
BRAD GREEN: We do want to work on that more.
So like you say, these are two orthogonal things.
One is about what does my JavaScript class need in order
to run.
The other is, what order should I load vals.
DARREL SUMI: Right.
Dependency order and then lazy loading, and things like that.
BRAD GREEN: It would be nice if these worked well together.
This is some future thing we'd like to get right.
JOHN FEIG: So, yeah.
I guess now we're just going to dive
into what the process of converting a web Angular
app to a Chrome App was.
DARREL SUMI: So just kind of the quick few basics
that every kind of Chrome App needs
is that it's going to need a manifest.json file that pretty
much describes all the permissions and the app name
and things that go into the store.
There's going to be a background.js file that
describes several entry points into the app,
like what happens when you install
the app, what happens on launch.
And you're going to need here, obviously, your index files,
your main index file.
But there's a trick to this one is that it sometimes
differs from what you normally would have in your Angular
project.
JOHN FEIG: OK.
So we can look at the manifest here for a little demo app
that I did.
And it's really pretty basic.
You've got your name, description.
You have the entry point of the JavaScript for your app
to find, which is background.js.
That's a pretty standard thing within Chrome Apps.
And then permissions-- so for example,
I need to be able to talk to the ajax.googleapis.com site.
And I also need to have storage permissions.
And then you also specify your icons.
DARREL SUMI: Yeah, pretty straightforward.
And like I said, for the background,
you can pretty much describe generic things
about your Chrome App.
So in this case, all we said is the width is going to be 1,200,
the height's going to be 700.
JOHN FEIG: Yeah.
And then your index is chomeindex.html.
And you can name it whatever you want.
But I think it's easy just to call it Chrome Index.
And your regular one's Index.
OK.
DARREL SUMI: So once you add all these things in,
and you think, OK, this is going to-- things
are going to look good.
And you try running it.
Of course, nothing works, right?
JOHN FEIG: Yeah.
You get a lot of red.
DARREL SUMI: Right, right.
And so one of the main, first ways to approach this
is there's a thing called a Content Security
Policy that web browsers implement.
It basically restricts what you can
do so that third-party external code doesn't maliciously
run some hacks on your website and cause
all kinds of problems.
So this is something that's already
out in most web browsers.
But then Chrome Apps have a tighter restriction
on this Content Security Policy.
So the three main things that differ from traditional web are
is that it's going to restrict any inline scripting.
It's going to restrict any external resources.
So if you link to some extra library somewhere else,
you can't necessarily just link to that
to bring that script in.
And it's going to restrict anything
that pretty much converts strings into JavaScript.
So things that use the eval or new Function methods
are not going to work in Chrome Apps.
So the second thing that I talked about,
the external resources, so the first thing
you're going to look at is what third-party libraries
you bring in that you might actually
reference from a CDN or something.
These you can't bring into your app directly,
so you're going to have to download
these libraries or any styles, or fonts, images,
any other kind of static content that you might
be able to just bring into your app and have it
hosted straight from there.
And so you're going to have to modify your index
file, obviously, because that's where all these things get
referenced.
So here's a quick example of how the index that you might have
for your regular Angular projects
will differ from the Chrome one.
So first one, when we reference the app,
is going to be a ngCsp.
JOHN FEIG: I don't think it highlights.
DARREL SUMI: Yeah, it doesn't highlight.
There's going to be a ngCsp Angular
tag that you're going to put there.
What this describes is that it tells
Angular to be in a CSP mode, essentially.
Angular has a lot of optimizations
that use things like the eval and new Function,
I believe, basically just to optimize Angular.
But for it to run in the Chrome App,
you have to restrict those kinds of things.
So it has a CSP mode that'll get around that.
BRAD GREEN: It's actually only a few little things.
You don't lose very much at all.
DARREL SUMI: OK.
Well, yeah.
Great.
BRAD GREEN: It's one small optimization.
But yes, you do lose it.
DARREL SUMI: Right, right.
And so you do need to specify that.
And obviously, now that you downloaded-- say,
in this example it's a Socket.IO.
We were referencing it from the CDN.
And so now we just have to bring it into your own project
and reference it that way.
Pretty straightforward.
JOHN FEIG: Yep.
And then you try running it again, and it's still broken.
So--
DARREL SUMI: OK, so here's a little demo-- let me switch.
Here's a little demo of a socket app that I made.
So basically, all this is-- it's just a small little test
demo that connects to a Node.js server.
The Node.js server starts counting
every one, two, three-- every second.
And it would reply back on Socket.IO
to the client and display what the current count is,
essentially.
Really simple-- that's all that's happening.
And so here, you'll see that the counter's incrementing.
But at this point, the Angular project
doesn't know anything about what the count was previously.
It doesn't know anything about who its friends are.
These are all initialized and given to us by the Node server.
JOHN FEIG: Yeah.
You don't want to dive into-- OK.
DARREL SUMI: Whoa.
Whoops.
There we go.
OK.
So one of the key components to Angular as well as Chrome Apps
is that the way you think about web servers
traditionally is switched to be using an API server.
So the web server you need to treat as an API server.
And what that means is that the web server is not
going to hand down HTML or things like that, where it's
traditionally-- they might have injected information, say.
You grab information from your database,
and you put it in your friends list or something, for example.
And it's not going to inject [? a string ?] into the HTML
and hand you back the HTML.
Rather, all that's going to exist in your Angular app
already.
And really, what your server should be doing
is sending back just the data, none of the UI components.
So in this example, this is how the HTML
looks like for the example I just showed you.
Pretty much all we know on the Angular side
is that there's going to be a count.
There's going to be a count listed here.
Whoops.
That doesn't do that.
JOHN FEIG: [LAUGHTER] No.
DARREL SUMI: OK.
So there's going to be a count referenced there.
And we're going to be repeating over a list of friends.
But we don't know who the friends are at this point.
There might be one friend.
There might be hundreds.
And we don't know.
But really, you need to treat your web server
to give you those things as an API server.
BRAD GREEN: And this is the great thing about Angular
is you're going to build your app this way anyway.
So there's not much in the transition.
JOHN FEIG: Right.
DARREL SUMI: Exactly.
JOHN FEIG: So we're going to look at Socket.IO here.
DARREL SUMI: OK.
JOHN FEIG: Whoops.
Uh-oh.
That's not good.
DARREL SUMI: OK.
So here is some of the code for this example.
And this is all, by the way, {public} GitHub {repress},
so that you can look it later on your time.
JOHN FEIG: Yeah.
We have links at the end.
DARREL SUMI: Right, right.
So this is the main controller for the example I was just
[? saying ?].
So basically, there is going to be a count that's
initialized at zero and an empty friends list.
Like I said, you don't really know anything
about what's happening.
And the way we have it structured
is that initially, you just connect to the server.
The server is, like I said, a Node.js server
that's running locally.
And here is a service that controls the communication
to that Node server.
So in server connect, where you start off,
essentially we just connect to-- I have a hard-coded thing
to look a host right here.
And at this point, we don't know any kind
of data that's been passed through.
We just know that we started with nothing.
So what we do is we tell it to connect.
And when we connect, we event an emit back to our controller,
saying, hey, we've connected.
And at this point--
BRAD GREEN: Actually, Darrel, maybe just bump the font
up a point or two.
DARREL SUMI: Sure.
What was it?
Command-Plus?
JOHN FEIG: Yeah.
DARREL SUMI: There we go.
BRAD GREEN: That'll be good.
JOHN FEIG: Sorry about that.
DARREL SUMI: So in our controller,
when you do connect, we're going to tell it to initialize,
initialize our state.
So at this point, your API server
doesn't really know anything about where you've left off
or what it is.
It's just going to start counting from some random--
from zero, if the server doesn't know anything.
So it helps if you pass in some kind of initialization data
to tell it, hey, this is where we left off.
And this leads into a key point of how
you want to treat offline and online mode.
But we'll get to that in a second.
And so we initialize it with our current count,
which at this point is zero.
And when we get a response back from the server here,
and it responds here, we'll get back some data.
And in the data-- like I just know
it's going to contain the current count and just
a list of friends, which doesn't really change.
And then from that point onward, the socket's
going to connect-- it's going to listen for a counter update
event that's going to get pushed down once
every second from the Node server.
And it's just going emit the count right here.
So the count is actually incremented on the server side.
So all that kind of logic-- obviously,
this is a contrived example of what
you might want to pass down, what information you
want to pass down from your server.
But essentially, the server's going
to be handling all that kind of data management,
a lot of that stuff that goes on.
But you'll notice here that this is all just
standard Socket.IO stuff if you have worked with it before.
[INAUDIBLE] All right.
JOHN FEIG: There we go.
DARREL SUMI: OK.
Right.
And so one of the things that you
have to consider when you build your Chrome app,
and for Angular as well, is how this
is going to get treated in an offline mode
or in a state where you're not sure about the connectivity
of things.
So you do need to handle things like disconnections or crappy
connections.
And a lot of that needs to go into any kind of socket code
that you write.
So luckily, Socket.IO handles a lot
of the disconnect and auto-reconnect logic.
But if you're working with raw sockets,
you're going to need to handle these kinds of things, as well.
But more than just the actual connectivity,
you need to think about how your app is going
to work from the user's standpoint
when you don't have connectivity.
If you start up the app, will you be showing a blank page?
If you lose connection in the middle, what's
going to happen to, let's say, in our example,
the counter that you have going on?
JOHN FEIG: Right.
You don't want to just throw up a Refresh button
and make the user think about that stuff.
You want to handle it in a smart way
so that it's as automatic as it can be.
DARREL SUMI: Right, right.
So in our example-- can we go-- what's it called?
Terminate the server.
JOHN FEIG: Oh, the-- OK.
Um, yeah.
There we go.
OK.
Server's up there.
DARREL SUMI: OK.
So we just--
JOHN FEIG: Server's dead.
DARREL SUMI: Yeah, killed our server, essentially.
And when you go back to this, you'll
notice that the counter has stopped.
You'll probably want to do something
to signify that you're in an offline mode, maybe.
That might help, so the user doesn't
get confused, like, why isn't anything working?
But you notice that the count is just stopped at here.
But if you were to redo the server,
or reconnect it, after some retrial logic,
it's going to essentially start back
from where we previously were counting.
So you don't want to necessarily start from scratch
every time you reconnect.
You want to leave the user off where they were before
and [? smoothen ?] the transition
between these things.
JOHN FEIG: You can see it worked.
We were stuck at 419, and now we're counting again.
DARREL SUMI: Right, right.
So you don't necessarily want to start at zero every time.
JOHN FEIG: OK.
So go here.
DARREL SUMI: So this whole example
was, [? obviously, ?] just a regular Angular app.
And the same thing works for--
JOHN FEIG: Kill this one.
DARREL SUMI: Yeah.
Same thing works-- the transition
from bringing Socket.IO into Chrome Apps
is really, really simple, pretty much like what
we showed earlier where you reference the Socket.IO, not
from CDN but from your own project.
After you do that, that's pretty much it.
There isn't a whole lot of things
that you need to do to get Socket.IO working.
It actually just works out of the box.
JOHN FEIG: Yeah, which was really surprising to me
because I thought that this might be restricted
or it might be really difficult.
But Socket.IO just works.
Where do you want to go?
DARREL SUMI: Yeah, there we go.
And so on top of Socket.IO, Chrome Apps
provides a really great Sockets API.
It gives you access to raw TCP and UDP sockets.
It's through the chrome.socket API.
I have an example on the GitHub that is the same exact demo
that we just showed but just rewritten for TCP.
So if you want to look at that, here's a link for that.
But we necessarily won't go into that code.
JOHN FEIG: It's a little bit more verbose,
but it's also a lot more powerful.
Like I mentioned at the top, you get access
to real raw sockets with this, which, like I said,
we just needed for our app.
BRAD GREEN: Just so folks can think about,
which one would I need?
What did you guys need the raw sockets for?
JOHN FEIG: So we're sending binary data back and forth
to the server.
Socket.IO sends back and forth strings.
So if you want to send that sort of data over Socket.IO,
you have to encode it as a string first.
And then you have to decode it on the server side.
You also have to have server-side components that
can handle Socket.IO.
Depending on the server that you're running the sockets,
you might have a good Socket.IO library available to you.
Like if you're running Node, it's really easy
to just pull in Socket.IO.
But if you're running Java, there's
not really a great solution for Socket.IO.
So it's a lot easier to use raw sockets there.
DARREL SUMI: It just depends on what your current architecture
is and structures.
For us, since we came from a mobile app
with a pre-existing system, it was easier to plug into that
rather than recreate everything on a different system.
JOHN FEIG: Right.
And there's also WebSockets, too, like we mentioned.
And those are maybe a little bit more available on servers.
So it just depends on what your needs are.
But it's really nice to have Chrome sockets available.
So the next thing that you want to think about,
in terms of how your app behaves when it's offline,
is your local storage stuff.
And this is actually something that I
started with in my demo, which is a little podcast app,
before I even thought about converting this into a Chrome
App.
The main reason is because I wanted
it to behave like an app.
I wanted people to be able to subscribe to podcasts
and then listen to episodes.
I wanted it to keep track of how much they
had listened to so far.
One thing that really drives me nuts is I
have a couple tools that I use, aside from my app,
for listening to podcasts, and they always
forget where I left off.
And some of the podcasts I listen to
are multiple hours long.
And if the thing goes down or you accidentally click Refresh,
and you start back at zero when you're an hour and a half
in, that's not too fun .
So this stuff is really important.
It's really useful.
And storing stuff on the client side
in certain cases-- like for KeeChat for example,
all of your chats are stored locally-- your contacts list.
You want all that stuff local.
Especially if it's just going to be for you,
there's no reason to have it on the server.
So there are some slight differences
between how Chrome handles local storage versus how
HTML5 handles local storage.
And I will show you right here.
So the big difference is that HTML5 is synchronous.
It has a synchronous API for local storage, which
means that you can just say, myVal equals localStorage key.
So that's just getting the thing right off a disk.
It blocks in-line.
And so you're going to be blocking on whatever thread
you're calling that on.
And Chrome is asynchronous.
So it's going to go out to disk and then
return in a callback the thing.
And you can just follow along here.
I highlighted in blue the value that I'm trying to set.
And then orange here is the value
that's coming back from disk.
So in the HTML5 example at the top,
that's just one line that we have to call.
But again, it blocks there.
And then the example below is a little bit more verbose,
but you can see we start out with null set to that.
And then we define a callback function
that's going to take a single parameter
and assign it to the value that we want set.
Oh, boy.
Ha.
There we go.
And then we do this chrome.storage.local.get.
And then the first parameter is the key,
which is the same key that we have up above.
And then whatever gets returned is
going to get passed to a function.
And it's going to be callback and then data, key.
So you get an object back.
And you have to pull the value out of the object.
It's really not too tough.
It's just slightly different because it's asynchronous.
And the big deal here is that there
may be some third-party libraries, especially if you're
in a larger project, that make assumptions
about having the synchronous API for local storage.
So there was one library that we ran
into that was a pretty integral part of KeeChat that just made
assumptions all over the place about local storage
being synchronous.
And we made assumptions-- we kind of used their API
in a synchronous way because those were synchronous calls.
So it made sense to use it in a synchronous way.
So what happened was when we did this change to Chrome,
we had to go through and rewrite quite a bit
of that third-party library to make
it function asynchronously.
That was one of the more painful things.
It wasn't a huge deal.
And it wasn't technically difficult.
It was just annoying because you have this dependency.
And it's not working for you.
So that stuff you might run into.
But--
DARREL SUMI: So pretty much, you always want
to check and see what kind of assumptions
any third-party banks on the access to the APIs,
as well as the thing like we talked
about earlier-- the Content Security Policy.
A lot of these libraries are going to make assumptions,
a lot of them more towards the way web
has been done up till now and not the Angular
way or the Chrome App way.
JOHN FEIG: And another great way to persist data locally
is with IndexedDB, which is an HTML5 API.
And it can actually be a much better fit
for certain types of data.
So if you have larger data sets, anything that's not a string,
really, you probably want to use IndexedDB.
It's a NoSQL database on a client.
The API's a little-- it takes a little getting used to.
It's a little verbose.
I actually wrote a wrapper to help
with doing things with Angular.
So it's an AngularJS IndexedDB wrapper
so you can just inject it.
And then you just do much simpler calls.
But here's an example of IndexedDB Put.
So you have a datastore.
You have a database.
The storeName is kind of like a table.
So it's like-- I mean, it's a datastore for NoSQL.
And we're just going to put some data, which is an object,
so it's going to be key value pairs in that object.
And so you have a reference to the database already.
You have to have opened the database.
And there's a whole process for that.
And then you get a transaction.
With the storeName, it has to be in read-write mode
if you're going to be persisting things.
I'm going to say objectStore, dot put, and then your object.
And then, hopefully, you'll go through this on,
success callback that has your event.
And now what I'm doing here is I'm
emitting an event through Angular, through the Angular
scope, that this data was put, and then on what database,
and what storeName.
And then I have a similar event in the error case.
DARREL SUMI: And this whole wrapper
is put as a Angular service, right?
JOHN FEIG: Mm-hm.
Yep.
So it's a singleton.
You can call it when you set up your app.
I tried to make it as simple as possible.
And we actually used the same library in KeeChat.
It cut way down on the amount of code
that I had to write over trying to do things
with local storage.
BRAD GREEN: And you'll be able to share that link with us?
DARREL SUMI: Yes.
JOHN FEIG: Yep, yep.
It's at the end.
BRAD GREEN: Great.
JOHN FEIG: So I actually also have a wrapper
for the local storage to choose between Chrome versus the HTML5
thing.
It tests to see if chrome.storage exists.
And if it does, it uses that.
And you can also set it up to use
the sync, chrome.storage.sync versus local,
which will sync any of those strings
across multiple instances of Chrome.
So if you're logged into Chrome on your laptop at home
and your laptop at work, you can actually
have that stuff synced up, which is nice.
So back to the slide.
Angular has-- it's just an aside,
but Angular has these really nice event emitters.
In KeeChat, we used a third-party event emitter
library because we had-- like I mentioned at the top,
there's some third-party libraries
that make assumptions about what's available.
One of those assumptions was that event emitters
would be available.
In my podcast demo, I didn't need that.
I didn't really use much third-party stuff.
So I could use the Angular event emitters,
which are really nice because this is done on the scope,
on Angular's scope.
So Angular knows about these things.
You don't have to run scope.apply
to get the digest cycle going.
So when you send a controller an event, it's going to show up.
So it's a really simple API.
You listen for-- you register for events with scope.on.
You say, like, i_am_event is the string that's
going to identify this.
And then the second parameter is a function
with two parameters, event and data.
The event I never use at all.
But data is important.
And so then you can emit it with rootScope.emit.
And it's rootScope because you're usually
going to be doing this from a service.
Services can't get scope injected.
And they probably wouldn't be on the same scope anyway.
So rootScope is what you use.
DARREL SUMI: The syntax-- the whole notion of this
is fairly common, standard event emitter type of paradigm
that people are probably used to for any other system.
It's just that these events get passed through the scope,
so you do have to understand how it goes up and down the scope.
JOHN FEIG: Right.
Yeah.
There are a couple options here.
So it's depending on where you're firing events from
and where you want to get them to, you might need emit
or you might need broadcast.
And they take the same parameters.
But broadcast basically sends things down to children.
Emit goes up the stack.
So you have to know which one you need.
But yeah, event emitters are really useful.
And they really help clean up code.
And you don't have to have as many things explicitly
calling one another.
DARREL SUMI: It just really helps
keep your code structured in a really nice, clean way
and modularized that way.
JOHN FEIG: Right Yeah.
It really cuts down on interdependency stuff.
And then I have just one other quick example of IndexedDB Get.
So very similar to Put, this is just
getting some item out of the database.
This time, we're going to just use
a key, which can be a string, whatever the type for your key
is.
And so we have a similar thing where we get the transaction
out of the database with the storeName.
You don't need to be in read-write mode.
You don't need to pass the mode in.
The default is just read.
And then the onsuccess case again,
we're emitting something with Angular's rootScope emitter.
We say, we've gotten this item.
And then we're going to pass an array of results.
So this is nice.
With IndexedDB, I like to always pass back
the database name, the storeName, and then the data so
that in my controller-- whatever is getting this data, if it's
a service controller or whatever, I can first
check to see-- OK, getitem is pretty generic, right?
And since this is a service library that I wrote,
I'm not going to have dozens of different names
for these things.
If I have common names, and I pass back
identifiers in the data, it just helps
so that I can register the listener with that common name
and then just inspect things that come back on those event
listeners when I get them.
So after you do your local storage, convert things
to IndexedDB, it's still not going to work.
You probably have some dynamic content
that you need to handle.
DARREL SUMI: And this goes back to the notion of the CSP
that you can't bring in external content
and just think it's going to run all fine and dandy.
JOHN FEIG: Right.
So actually, this would probably be a good time
to explain one thing here.
So this is my little podcast app that I did.
And this is actually the Chrome App version.
So you can see, I launched it there.
And so this middle section here you can see
is all pulled in with a web request.
So this side-- the Add Podcast and List
a Podcast side-- that's local.
And then I have this section up top.
This-- you've got a playlist here on the left.
That's local.
But then the content with text that
pulls in information about either the show or all
these episodes, that's all being pulled in with a web request.
And you need XHR to do that.
And it's a slightly different call
that I had to make in the Chrome environment
versus with just the Angular web environment.
And so here's what you can use to get, say, an image.
And this is an example of getting an image out of imgur.
So I am going to set it up with just a regular HTTP.
This is Angular's HTTP thing that
can be injected into your app.
As I said, the method's Get-- here's the URL.
And it's going to be a blob response so that I can actually
set it as the source of an image.
In a success case, you get all this stuff back.
Again, I'm going to ignore everything
except for the data that's coming back.
I keep scrolling.
And there.
So I'm going to ignore everything except the data.
I have in my HTML this image tag with an ID
that I specified, onepod_img.
So I'm going to grab a reference to it.
And then I can set the source with
this window.webkitURL.createObjectURL
and then the data.
And that's going to allow me to just take that data that's
the result of this web request, which is-- actually,
it works the same way as the XHR stuff.
It's just a slightly different API.
And then set that into the image.
And there's an error case where it failed.
And hopefully, it won't fail for you.
And you also have to make sure that imgur
is in your permissions.
So this is not something that-- it's not my server,
so I have to put it in the permissions thing.
Once I do that, it should be OK.
DARREL SUMI: Right.
And you'll notice that there's other things that
might need to go into your permissions.
So any of the Chrome Apps require
you to specify either, like what he said,
which domains you might be getting external content
from that you put in your approved list.
But also things like your storage here.
You have to tell it that you're going to use storage.
And even for your sockets, if you're
going to use the Chrome Socket API,
you need to specify that, hey, I'm
going to allow TCP connections.
JOHN FEIG: And then we're going to keep iterating on this.
So one thing that was interesting was-- here's
my controller to get individual episodes of a podcast.
And this is the method here, makeRequest,
that's going to actually get the data for each episode--
or for each podcast, actually, and all the episodes within it.
So I do the test up top to see.
If it's chrome.storage, then I'm in the Chrome environment.
There's probably a better way to do that.
But that's the thing that I used.
And now we get this request URL, which is a really long thing.
What's interesting here is I'm actually
using a Google service.
So with the Content Security Policy, again,
and since this is a podcast app, so I
needed to go through a single place that's
going to return the proper headers.
So very basically, with an RSS feed app like mine,
you want to be able to subscribe to whoever.
And whoever may not have a server that's
going to return you the headers that allow you to make
requests, like cross-site requests to it.
So what Google's done is to set up
a service that allows you to get these RSS feeds
from a single location.
So I can say, ajax.googleapis.com.
I'm giving that one permission.
It returns me back the proper header
so that the requests are valid.
And everybody's happy.
The one big difference between the HTML5 version
and the Chrome version is in the HTML5 version,
I can actually use this .jsonp thing, which, basically,
this returns as a callback.
I don't really need this.
There's probably a way that I could get rid of this
completely in and do them both the same way.
But it's a little nicer.
This is how I was doing things before,
where it was-- you see a callback equals JSON_CALLBACK
and the URL here.
And that works with this API.
And then for Chrome, if you do that, it'll throw red at you
and say, you're not allowed to specify
callbacks and responses.
So instead, you just do a regular HTTP get.
And then in the success, you can still
parse the data the same way.
So that was the only difference here, really.
And then, obviously, the other thing
is you've got to be able to load images.
And as I showed you before, it's the same deal.
So yeah.
And then you just iterate until you don't have any more red.
There probably are going to be other issues that you'll
run into outside of this stuff.
DARREL SUMI: So just to recap.
You have to really understand where the Content Security
Policy's coming from.
That's going to be the major source for most of the errors.
But you have to look into each of the third-party libraries
that you use.
A lot of them will make assumptions of what environment
they're working in, whether that be how they
access sockets, how they access local storage, what they're
used to being brought in.
So if they use a AMD or Require style of being packaged up,
you got to understand how that's all
wrapped around those libraries.
JOHN FEIG: Yeah.
So I can show-- so here's the app in Chrome.
Oh, I closed it out.
Never mind.
Yeah.
So here's the app in Chrome.
This stuff works.
It remembers where you left off because I
saved that information in local storage.
So I have this method, refreshInterval.
So every-- I think it's three seconds, until it's
done playing, I send a message to say, update the progress
with the current time.
DARREL SUMI: And this gets saved into the local storage.
JOHN FEIG: It gets saved into the local storage,
whereas this data is saved in IndexedDB.
I originally wrote it with local storage.
And I switched it over to IndexedDB and cut down like-- I
just deleted a third of my code.
It was really nice.
DARREL SUMI: And just to reiterate,
the whole point of that is to make sure
that the transition from offline mode to online is smooth.
So if you do lose connection, it does
remember where you left off.
And that's really important for the whole user experience
point of view.
JOHN FEIG: Yeah.
And there's definitely some other stuff
that I'd like to get to in this app,
like basically downloading the audio ahead of time.
So that if you have no internet connection,
you can still play your stuff.
That I did not get to.
But it's still a pretty functional tool.
It's nicer than some of the other web tools
that I've used, which is why I built it.
And then we've got-- just throw up some links.
We'll make the presentation available to everybody
so that they can look at the stuff on their own.
We have our app that we're modeling this talk on.
This was actually-- I think, basically,
all we really needed to do to get KeeChat up and running.
The content security stuff was more of a minor issue for us.
And then we didn't really have anything outside
of this that turned out to be much a problem.
It was a pretty straightforward conversion.
It took me very little time to get up and running-- really,
just a couple days.
And this is a pretty decent-size project.
So I was very surprised at how quickly
we were able to do the conversion.
DARREL SUMI: Yeah, definitely.
If you come from the Angular setup, it's really easy.
JOHN FEIG: And now, we've got Darrel's socket demo linked,
which has both the server side and the client side
in the same project.
DARREL SUMI: Yes.
Yep, yep.
JOHN FEIG: And then I have two versions
of the source for my app.
So I created a couple branches so
that you can look at before I started the Chrome App
conversion and after it was finished.
And these specific branches will not have any more commits,
so they'll be exactly what I presented to you today.
And then I wrote a couple little libraries
that work nice with Angular and some of these APIs,
like the IndexedDB and localStorage wrapper.
And I have to get documentation up for those, but they work.
BRAD GREEN: Good, good.
Guys, thanks so much.
This was a really nice introduction for Chrome Apps
and the differences between an HTML5 app and a Chrome App.
I think it'll get people started really quickly.
And for everyone else, thanks for joining us today.
We will see you on the web.
We'll share all of these links to the presentation.
We love to take questions on Google+ and Twitter.
And we'll see you at the next GDL.
JOHN FEIG: Thanks for having us.
DARREL SUMI: Yeah, thanks for having us.
BRAD GREEN: Thanks, guys.