ALEX: How is it going, AngularConnect! How are you guys doing? It has been an awesome day. Alright, well as Ed said, I'm Alex. This talk is called Advanced Angular Concepts. So, I'm a software engineer at Google, and,
for the last three years, I've been a member of the Angular core team. On the team most recently, I worked on our
HTTP client implementation, the Angular API which is stable in Angular 5. It supports a number of advanced features
line interceptors which we are going to see today. With 5, I launched our service worker implementation
which is something I've been working on for a long while making Angular more reliable
across flaky network connections. Service workers are part of our progressive
web app story which I've been passionate about and talked at other conferences, including
last year at Connect. But today, we're going to do something that
is completely and totally different. Occasionally, I go to these events and I talk
to people, and I'm active on our community chat on Gitter which is a good place to meet
other people in the Angular community. People pose questions there like, "How do
I design this thing with Angular?" . Some of the more elegant answers use the
lesser known elements of the APIs. Not necessarily more complex but things that
your average developer doesn't encounter on a day-to-day basis. That's what we will do today: dive into a
few cases and learn how to use Angular APIs to build better services and components that
we share and create with others and for ourselves. The first is to learn how to configure services
and libraries and learn interesting ways of communicating between components that go beyond
simple inputs and outputs. Rather than explore all of these topics abstractly,
we are going to consider them in the context of two design challenges that I've seen come
up in the past. The first is to build a retry interceptor
for the HTTP client which takes its configuration from the consumer. It's going to allow a configurable number
of retries as well as specific retry limits for individual domains because not every service
that we depend on is as reliable. The second thing we're going to build is a
wizard component. That's going to be our example of intercomponent
communications. A wizard is an orchestration component that
displays the sequence of steps to the user and moves them through them, and those stems
we will try to decouple of as much as we can. We have two topics: dependency injection and
queries. We will talk about them individually and show
an awesome way to combine the two. Let's go ahead and jump straight in: so dependency
injection: it is not that new of a concept in computer science. It is very popular in a lot of backend frameworks
- Spring for Java, and Google has that juice implementation. Angular.js was one of the first to implement
the I in the browser and Angular improved it by coupling it with TypeScript. The main goal of any DI framework is to separate
the concept of depending on something from the knowledge of how to construct it. So, with DI, we don't have to know how our
services or other depend ease are instantiated. We simple ask the framework for an instance
that someone else has considered for us. That decoupling of dependency is one of the
i's many benefits. We can depend on the aim of something without
necessarily knowing what implementation we will get at runtime. DI is an excellent mechanism for packing up
a service or set of services and publishing them in in a usable fashion in how many have
used Firebase? Awesome. Keep your hand up if you used Angular Fire. It is a fairly complicated library, internally,
but for the user, it is relatively simple to use because it comes with a couple of modules
that you drop into your application and configure, and that's thanks to DI. DI is very important in testing. It's what allows us to test components in
isolation by marking out their dependencies so we don't have to bring up entire applications
to test one small part. The first thing we are going to do is implement
an HTTP interceptor and retry request. That in itself is easy to do. We're not concerned with how this interceptor
works internally, we care about how the user sees it, can configure it and make it behave
they want it to. It should be reusable, that means having a
module that you can you can install to use it and allows the customer to customise the
retry limits. Finally, it should be safe to use. If you're a user and you drop this thing in
your application and you don't set it up correctly, it should tell you at compile time when your
application starts. It should not silently pale to work. This is going to be our interceptor. We're not too concerned with the implementation. Our RxJS makes this a one-liner so it is not
that interesting. We care more about how it's configured. This is our module. Right now, this is a template. We will answer the questions of what should
it provide and what should its implementation be? We're going to put some code inside the module
class which is not something you often see in Angular. We're going to fill these in as we explore
the capabilities of DI. I'm going to do that through four topics:
we're going to talk about the tokens, which are the building blocks; then the providers
that configure the tokens; the modules that contain and organise the providers; and finally,
the injectors that we are providing from those modules. Tokens are building blocks. If you think of injecting dependencies is
looking up values on a map, then tokens are the keys. Because they actually exist in a map at run
time, they have to have runtime values. You can't use any type-like string as a token. Many tokens we're used to dealing with are
class types - classes we can declare and they can even be abstract. But there are a lot of things that you didn't
use directly with DI. You can't provide an interface, or a primitive
type like a string because those don't have runtime values and there are a lot of uses
where you might want to do that. If you're taking configuration, your configuration
is to set primitive values or interface to expect some objects you're going to get. You need to mesh them with the DI. In Angular, that comes in the form of injection
token. Injection token is the key that allows us
to reference and inject an arbitrary type. So, in this case, we have an example where
we are injecting a token, that represents an - url value - and then inject it into the
service. When we are injecting this thing, we can't
just say "inject a string" because Angular has no idea what string we're talking about. The TypeScript type isn't enough. We add this inject metadata and that tells
Angular which token we want to use to satisfy this injection. We can now get started working on this interceptor. We need to know the number of times we want
a retry request so we will try that with the DI token we creatively call retries. You get a hold of this value and use it when
it is handling requests. But to see how to configure this, we need
to talk a little bit about providers. So, we are going to go through the different
kind of providers and apply what we learn to start filling out our module a bit. So a provider in Angular is a declaration
of where the value of a token should come from. In most cases, we ask Angular to provide us
the token by instantiating a class. You can also provide a literal value or ask
Angular to alias the token to another type. Finally, you can take over completely and
give Angular a factory function that it will use to take over the token. Let's go through a quick example of each of
these. The most common way to provide something is
with use Class. You're asking Angular to instantiate it for
you by looking at the instructor. A lot of the times the class that needs to
be instantiated is the class that you're using for the token. You're just providing a service. So Angular has some syntactic sugar for expressing
that. The two that are listed here are actually
equivalent. Both specify that retry logging service should
be provided by instantiating itself. The second way you can provide a value for
a token is to list it literally. So, in this example, we are providing the
retries token we created earlier using the constant value of three. Right, that's relatively straightforward. Another way to configure a provider that I
actually don't see used that often is to specify that it should have the same value as another
existing token, right. So, to do that, you say provide something,
use existing, and there's something you should actually be careful about with this used class,
because the two can look very similar, but they have a major difference in behaviour. So take a look at this example. Right, this is using use Class. The first provider provides it with itself. Angular's going to create an instance of it. The second provider says to provide a different
token, retry service, using a class of time-out retry service. So we get a second instance of the service. That can be a problem because, depending on
which token you use to inject it, you get different instances of the same service. If the service happens to be stateful, you
can have different versions in different places and they don't work together, right? You can have really subtle bugs in applications
happen this way. I've seen it. UseExisting doesn't have this problem because
you're creating an alias, creating the same value for the new one. We have one service here that you can inject
in each token which is a lot safer to do. The last way we have of declaring a provider
is to tell Angular how it should be constructed directly by providing a factory function. If you use factories, it's not where you have
the export function and the - we are actually using a lambda here. In Angular 5, this works. It took forever! If you want your factory to have arguments,
and those arguments to be injected, it's unfortunately not enough just to specify types in TypeScript,
you have to literally declare out what types of arguments are in this deps array, and that
can be annoying, and you can trip up on this. We tried services using an off service to
get a token from it before it creates it in the factory. So, ordinarily, if you provide the same token
twice, the one that you choose will override the one that happened before. Sometimes, that's not the behaviour that you
want. You really want to get all the values that
people have provided for one token. For an example of this, think of the router. Right? With the router, we are able to, in different
parts of our application, import router module for child, specify a where did you go of routes,
and the router doesn't pick one of them to go with, it actually collects all of them
and builds the full routing configuration from all the individual pieces. That's called a multi-provider. They're relatively easy to use. You have to specify multi- to true when you're
providing values to tokens and Angular are take care of injecting them all. Here's a pretty example of that. We have a token cookie name. We are providing two different values for
it, both with use values, so this year literals. When we inject cookie name, we don't get the
last value but an array of all the different values. So now we can begin building out our retry
module. The first thing we probably want to do is
configure our interceptor. As it turns out, HTP client uses a multi-provider
to get the list of interceptors. All we have to do is provide this token, HTP
interceptor, we name our class that we want Angular to instantiate for us and we say that
multi is true because there can be more than one interceptor in this. Angular are build an array and inject it into
the APC client. That's pretty cool. Next, we want to specify a default value for
the retry token. If a user installs the module, they will get
sensible behaviour. Three is a pretty good choice. Now, anyone who wants to use our interceptor
can just import retry module. They're probably importing the HTP client
- because that's what they are using for the make request - and the interceptor will just
start working. That's a fairly simple API. They can also use it to override the behaviour,
if you like. If we expose the retries token to them, they
can specify their own provider for it. Since they don't use a multi-provider, it
will override the retry module because the current module providers overwrite those from
the imports. The next goal we had was to set specific retry
limits for specific domains. We can do that by requiring the user to provide
a token that represents a map, and the map will associate a domain with a specific limit
for it, and the user would declare all of that in their application module. But it would be nicer if users could move
the configuration for a specific domain right beside where they can configure their service
that accesses that domain. They have a module for each API that they
access. Like Angular does HPC interceptors or routes,
we can use our own multi-provider to do this. So the first thing we need is a type-two inject. In this case, it is going to be an interface
called domain retry limit that specifies the domain and the limit. We need an injection token for that interface
because we can't unions the interface directly in the DI. Nothing about this token suggests that we
are using multi-injection with it. It is completely up to the consumer to decide
how to provide it. So with this token, a consumer can specify
a value for a particular API domain directly from the module where they configure their
API for that domain. Here they have an API module, providing some
service for it, and they're also saying for this domain, we want to do five retries instead
of three. That keeps all of the configuration in the
user's application for the API in a single module. That's a really nice property to have. So, on the interceptor side, all we have to
do is inject this token and tell TypeScript that we are expecting an array, and inside
it will probably build up the mapping we talked about earlier and use that whenever a request
comes through. But what happens if nobody specifies this
token, right? What happens if the user doesn't want to do
any customisation? Angular doesn't know about the token, so if
we just ask to inject it, we will get an error, right? Nothing actually provided it. But, fortunately, we have a way of telling
Angular this dependency is actually optional. We can use this optional metadata marker and
Angular won't care, it will simply inject null. So we've already talked about modules a little
bit as we build out the retry module but we are going to learn a few more things about
them. So we've seen how modules package code for
reuse, and also a little bit about how they affect the ordering of providers. We will take a look at their role in providing
a nice API for user configuration better than what we've come up with so far. So, firstly, wrapping our retry interceptor
into a module does make reusing it very nice. All someone has to do is import it into the
at module. We've seen - the appmodule. The provider is inside the app module, overriding
the ones that are importing. Provider importing is not only important in
the application itself but it is important in testing. It's what allows us to have testing modules
- for example, a hypothetical mock retry module - that overrides the providers from the application. So, inside of a test, for example, we could
disable retry because we know the server isn't going to succeed if the test is designed to
fail, so the test will run a little faster. However, I'm sure you will agree with me that
this API is not really even the nicest that you might imagine. For example, the user has to know that this
retries token exists, right? They have to find that in the documentation
somewhere. The token is configured in a different section
of the app module and there is nothing here that indicates that the retries token and
module have anything to do with each other. As it turns out, Angular gives us some tools
to do a little bit better here. This is what it possible. We can eliminate the need for the user to
specify providers directly. And hide it behind a static function they
call on the module from their import section. In this case, we've named it "with retries". This is a much cleaner API. It is clear we are configuring the module,
discoverable through code completion and we get much better type safety. So how do we get this? What can we do in our module to implement
this? The answer is a feature in Angular called
a "module With Providers function". They can take parameters. But they're not functions in the sense that
they will be evaluated at run time, they're instructions to the compiler so they've special
rules associated with them. They are the same rules that you see inside
of metadata. We can't have logic, we can't do operations
and code, we can only return an object. This object specifies a module, and a bunch
of providers to layer on top. So, here's what this looks like. Implemented in retry module. We declare a static function called With Retries. It has a parameter, and that is used to provide
the retries token with use value and the object that we return. So the interceptor hasn't noticed anything
has changed. It is still getting its configuration through
DI but now the complexity of setting up this provider, and this example is simple but you
could imagine something like Angular Fire has five or six parameters that you have to
define been the complexity of providing all of those is abstracted away inside the module
itself. The consumer only has to call the function,
which has really nice type signatures, to import the right configuration. That's a much better experience if you ask
me. We can do the exact same thing with our per-domain
configuration. Providing the user to code out this environment
is riskier - there's a lot more they can get wrong, especially if they forget to say multi-true
and forget everything that came before it. This can also be wrapped in one of those modules
with providers' functions. Let's call it retry module.customise domain. It will take two arguments and it request
be imported from the API module like before. There's no harm in importing a module in different
places. They will be deduped at runtime. The installation is similar. Instead of one parameter, we take two. We bind them in DI as before, except the user
doesn't have to know about the custom interface we are dealing with, either. We can construct the object that gets bound
instead requiring the user to do that, so even cleaner. That brings us to our last DI topic of the
day which is injectors. There are a lot of those running around. They are the runtime interface to DI. You can think of them as a map of tokens to
the values for those tokens that were determined by the providers. A really thing about injectors is they exist
in a hierarchy in Angular. They have a parent. If an injector doesn't have a parent when
you inject it, it will delegate up the tree. It's this hierarchy I want to talk to you
about. It starts in Angular with the platform injector,
the top-level injector on any page. It has in it single instances of all of the
services that interact with DOM APIs, location bar, et cetera. Everything that there is only one of. No matter how many applications you have running
on the page, they're all sharing the same location bar, right? You can't use the router for three applications
on the same page without them trying to trip over each other, updating the current location. It represents the single ton state. The next down on the application injector. That's a child of the platform. This is the one that has all the providers
from your application module. This is the one that gets created when you
call bootstrap module or bootstrap module factory. There can be many such applications on a single
page, each created from an application module, all of them children of the same platform. So, in the hierarchy, it looks like this:
simple so far with platform at the top, application down below. As it turns out, it gets more complicated. When you start lazy-loading things, which
everyone should be doing in Angular, it gets its own injector, right? When you lazy-load a module, that module has
its own injector - that makes sense. A separate piece of code injected in. It would be strange if we stuck them in the
application that was already running. Maybe we have to recreate a few services because
their provider changed, and that can get complicated really quickly. It makes sense when we load new code in, we
get a new level on the injector hierarchy. If the use the router, for example, to display
a lazy-loader component, that component class actually gets injected from the lazy injector,
not the application. It will delegate up the application, and so
on. You can do really cool things with that but
it creates a real problem known as shadowing. So shadowing is what happens when you have
multiple providers for the same token in different injectors. We saw a taste of use Class and useExisting. Especially if it is broadcasting events that
your application wants to listen to, you can get weird behaviour if you have different
components injecting different components of the service. This is what it looks like in the hierarchy. An application component coming from the app
injector. That gets instance A. We also have a lazy
route which is injecting the same service, same token, but it gets its one from the lazy
module injector, and the lazy module injector has instance B this. That can create really subtle problems. This is really hard to debug, actually. Fortunately, the community has a solution
for this, in the form of something called a four-route function. You may have seen these running around and
wondering how they work internally: it is just a convention been what it says this is
a module with providers that should only include providers that go in the application module. Nobody should ever export one of these things
in a lazy-loaded context. Here is what our forRoot would look like. We have things at the top which don't matter
- things that are stateless - but the critical providers would go in the section of the forRoot. This would work great if everyone followed
the right rooms and only never wrote code like on the right which is a safe assumption
because developers use APIs 100 per cent of the time the way they are meant to be used. Everyone reads the documentation. What could possibly happen here! So, instead of relying on convention, I suggest
you direct your modules to actually check for the conditions that cause problems. For example, you can actually ask Angular
if the parent injector and the current injector have two different versions of the same service. Right, if they do, you can complain about
it loudly instead of getting your user into a situation where they end up with subtle
bugs. So the problem with our retry interceptor
is that HTP client has to be provided in the same configure with - our users get a nice
error instead of wondering why their interceptor isn't working. So it's actually relatively he's a to do this. Modules are allowed to have constructors. Those constructors do get called at runtime. As long as we inject HTP client, Angular will
make sure that it is provided. If a user drops in retry module and isn't
using HTP client, they will get an error. We can tag this injection with metadata @self. It says get it from the current injector,
don't go up the hierarchy. That is much better than failing silently
if the user puts the module accidentally somewhere that the HTP client isn't configured. So, with this, we've accomplished all the
goals we set out to design retry interceptor. It is easy to configure and it is safe to
use. We still have a little bit more about injectors
to cover but we will move on to the next design challenge which is building a wizard component. Like I said before, a wizard is an orchestrator. It takes a sequence of steps and walks the
user through them in order and prompting them how to complete each step. Once again, we're not going to care about
the implementation of the wizard, we care more about the architecture, right? How do we model a wizard and its steps and
how do they communicate with each other. In principle been we want this implementation
to be as decoupled as possible. Ideally, the wizard should know nothing about
the step components except the API that they use, and the steps should have no knowledge
of the wizard that they're going to be used in. Here's the API we would like our wizard to
have. It's a component that you can use on a template,
and you content-project a bunch of steps on to it. That's it. It is clean and efficient, but sadly impossible. We can almost get there, though. So our wizard implementation is going to start
off rather simply. It is a component with a template which just
projects everything passed into it. So let's take a look how we can architect
this wizard and its steps and learn more about the injectors in the process. As it turns out, beyond the platform, the
injection, any application and lazy-loaded modules that you have, there are injectors
in the view. So many times, elements in the view, especially
when they have a component attached, will have their own injector, and the hierarchy
of those view injectors roughly follows the DOM hierarchy up to the component that loaded
that in the first place. Understand that a little more and how it affects
component design we can take a look at how template-driven forms work. This is a simple DOM hierarchy with a application
for a form. We have a root component, the app component,
the form inside of that, and one input with an ngModel directive opinion when you're using
template-driven forms, the form element has a directive applied to it called ngForm. That directive is available for injection
by anything inside the form such as ngModel. So here's what this looks like in the chart,
right? Geneva the application module injector, and
form has an injector. The injector for form has ngForm provided. Because input is the child of the form injector,
the ngModel and input it- inject the ngForm. That's how they communicate, right? NgModel doesn't get the current form pass
in, it injects did. This is simplifying it a little bit. But this kind of implicit component communication
that uses DI in the view hierarchy, powerful. Let's apply that to design our wizard. So since the step components are project he
could into the wizard into the -- projected into the wizard in the view, we can inject
the wizard into the step. Because we want the wizard to talk to the
steps, the steps have to register themselves with the wizard when they have created. They parse themselves. All the wizard has to do is implement the
API. That's easy. Well, not quite, because this doesn't solve
the design challenge that we set out to do, right? This has strong coupling between the wizard
and its steps. Not only does each step know what wizard is
going to be used in - the exact type of the component - but they can't actually work without
one. They are required to inject it. Instead, let's move on from talking about
DI to talking about queries. Use them to decouple the wizard in its steps. Queries are a mechanism in Angular for reading
state out of the view. They work using these four annotations. You place these annotations on component properties. View child, view children, content child,
and content children. They don't use DOM APIs to go query and find
elements. They result in dominance in compile time generation,
set up ahead of time, and that means they are very efficient. To study queries, we will simplify our wizard
a little bit. Instead of content-projecting, we will assume
it's the one step hard-coded into the view. It's in the wizard template, not projected
in. Since we know the wizard is going to have
this child step component, we can create a property step of the right type and add this
view child annotation to it. The annotation tells Angular we would like
to keep this property updated with a reference to the step component that gets created in
the view. Unfortunately, that reference isn't going
to exist right away. It will be undefined. There's a because when the object is being
constructed, which is what this wizard is, the - that's a product that happens asynchronously. We need a way for Angular to tell us the view
is ready, and we can do with AfterViewinIt. It's inside this method that Angular is letting
us know the view is ready and any view queries you have are good to go. So now we can console-log out our reference. It's important to be careful with this view
event because it is change at the time ex-which starts creating the view in the first place,
so the view is initialised after the first round of change detection has completed. As you know, Angular has unidirectional data
flow, so we can't change component state after change detection has occurred in the middle
of this micro task. We have to actually trigger a set time out
and a whole new round of change detection. So just something to be careful about. As I mentioned before, this view child reference
is actually live. We're not only getting the instance when it
is first created, it updates if the component changes or goes away. So, for example, if this step is behind an
ngIf and when the "if" condition becomes false, the step reference can go to null. It is also possible to ask Angular to query
for an element that doesn't have a component or directive on it. This is done using the number tag symbol to
apply a hashtag like name to the element, in this case called "step", and that's called
a reference. We pass that string to view child instead
of component type. What we are getting back is not - but actually
an element reference which is Angular's way of talking about an element that doesn't have
any other type associated with it. We can use the element raft to interact natively
with the element on the page. But what happens if you have more than one
step? For example, if our step component is repeated
with an ngFor, what comes out of this query? Does it come back as an array? Unfortunately, no, it only gives us back the
first step we created. If we want all of them, we have to use an
annotation called View Children, and that corresponds to all of the matches instead
of a single one. It returns a thing called a query list which
is like an array but has a few extra options on it. So it has all of the array operators, right? You can go four each, reduce, access something
like index. If you need a true array, you can call to
array on it. It has changes to property. Changes gives you an observable that admits
whenever the view changes. If someone changes the array you're its demonstrating
over and you get new steps created, changes will emit and we can react to it. The wizard component can watch the contents
of this query in real time and react when new steps are created. Maybe it needs to reduce some internal state. The only probably is that these step we have
are configured inside the wizard's view. Can we query for them if they're not in the
view but are instead projected in as we designed earlier? It turns out you can do this with content
child and content children which work similarly to the view queries but work on projected
content and not templates. They have their own event which specifies
whenever they're ready to use. We can convert our wizard to the conventional
design, injecting content in for all of its steps. The users will use it like this, right? The only thing we have to change is every
step component has to be the same one because that's what we are using the query for them. So, with that, the wizard can get a list of
all the projected steps and interact with the API on each step. It can tell every step that the user is currently
on wants to hide itself, so can now the title of each step so know the right bread crumb
trail at the top. This design is getting closer and closer to
what we want it to be. It is still annoying in one way. Every step component has to be the same, and
the wizard those what they are. But we wanted a design where users could bring
their own component and use it as a wizard step as long as it uses it as the API. Let's fix that. One way to do is querying for a directive
instead of a component. So, for every step, we can apply a directive
to it. It's called "wizard step". This is more verbose than listing the steps
without doing anything, but I would argue that it is actually useful. We are making it clear we intend these to
be treated as steps and opening the door for the wizard to project other content besides
just the steps that it wants to process. This directive doesn't have to do anything
at all. Its own job is to exist so that we can query
for it. That's what we will do. We can query for the directive instead of
querying for the step component, and here we are using content children to get instances
of it. Who thinks this is going to work? Wow. Nobody! We will actually get out a list of all the
directive instances. But the directive itself is going to be useless. Those are empty objects. We have no way of interacting with the step
components. We can't even talk to the DOM directly because
we can't get that from the directive. What we really want is a way to query based
on the directive but get something else back in our list. We can take advantage of a feature of queries
to do just that, called Reading. We pass in a read instruction to view children
or content children, and that allows us to specify that the query should select elements
with the wizard step directive but we really are interested in the element references. We can't read out the component type because
the component is different, we don't know what it is. This is a little more useful. We can manipulate the style of the native
element to show our hide steps, but it is still not as good as being able to talk to
the API of those components directly. To do that, we have to do something rather
unusual. As it turns out, we can use read with queries
to get access to dependency injection from the injector of the element that you're querying
for. We are going to use this fact to finish our
wizard. So our plan is to have each wizard step implement
an interface, right? This is a common API that our wizard is going
to depend on. Using a DI query, the wizard it then read
the service from every step, without necessarily knowing what type of step it is in the first
place. Let's start by defining our service. It is going to be an abstract class, because
we're going to use it as a DI token, and it has to have the methods that we are going
to require each step to implement. In this case, we are using show and hide but
in each case the wizard may have interests about dependencies between steps or titles,
or stuff like that. Any time we write a step, we are going to
have to implement this wizard step interface. That's relatively straightforward. Here's what an our wizard is going to do:
we're going to query for the step as before but this time, ask for the injected value
of wizard step service instead of the element and that is convenient. That will give us a query list to interact
with the API for each step. Injecting is only half the API story, we have
to provide it. We need to set up a provider in the step that
binds the wizard step-service token, and an easy way to do that is, you say, to use existing
and point it to the component instances already in the injector. We are going from a component that the wizard
knows nothing about to an interface that it does, and every component, every step component
provides that interface with its own type. That almost works. Except this is actually a TypeScript error,
right? We are using step 1 as a value inside the
metadata. This is one of the few cases where the order
of declaration actually matters. So Angular has a way around this, and it's
called forwardref which captures the early access inside of a enclosure, and evaluates
that closure after the class has been defined, and that solves the problem. With that, we have our finished wizard and
step API. So we've explored dependency injection, built
a retry module that is safe to use and convenient to configure, and we use queries to develop
a wizard that would use injection to communicate between the wizard and the steps, and finally,
we combined queries in dependency injection to decouple the two entirely and run all communication
through them on a communication service we declared right in the view. So, thank you very much. That's all I have for you today. If you follow me on Twitter, I will tweet
out ... [Applause]. Thank you. I have a link to my slides, and also a stack
for each example that has the whole thing implemented. I will tweet those out later today. Thank you very much. [Applause]. >> Just a couple of quick things. The lightning talk, you can sign up for those
now. There's a board outside the Saturn Room. We are also looking for a couple of people
who will be joining Shai's gameshow, so looking for Alex Peshev and Darrel Brown. If you were in the room, come to the front,
please.
Very useful talk if you are a library dev/maintainter