Continued from Part 1
Developing Angular2 Dart Asset Services, Part 2
For the fictive scenario described in the previous article, we built a simple content service that would provide randomized lorem ipsum text to our article application. Now our manager tells us the latest news: the content will now be developed in-house by our company’s ad department, who are non-techy. The copywriters have agreed to author articles in Markdown and commit using GitHub’s web interface, but that’s the extent of their willingness to meet us halfway.
Since we planned ahead so nicely in Part 1 by creating an injectable ContentService
, this isn’t a problem. In fact, we need only write one new class and alter one line of our original app! As for the content itself, we’ll utilize Dart’s powerful transformers to build a content repository that is authored in markdown but served in HTML; this with only a handful of markup code.
Client-based Content Service
Recall from Part 1 that we designed a very simple interface to define our content services behavior:
abstract class ContentService {
Future<String> getContent(String id);
}
We begin the new network-based implementation by constructing a injectable skeleton service that implements the above interface:
@Injectable()
class ClientContentService implements ContentService {
ClientContentService();
@override
Future<String> getContent(String id) async {
//TODO: implement
}
}
Our goal is that this implementation fetches HTML from an outside network resource. We know there will be some base URI common to all articles. We’ll complete the URL by simply tppending the article identifier and “.html” extension. As first attempt at implementing this, we declare a field to store the base URL to the content, and then update getContent
to build the full URL using the article identifier and extension.
@Injectable()
class ClientContentService implements ContentService {
final String _contentUrl = "http://localhost:8090/content/section";
ClientContentService();
@override
Future<String> getContent(String id) async {
final String url = path.join(_contentUrl, "$id.html");
//TODO: fetch an article
}
}
While this works, the hard-coded URI base makes our implementation brittle. Our service will be much more flexible if we make this field variable. One such way is to use Angular’s dependency injection. We rewrite ClientContentService
constructor to inject the value of _contentUrl
:
final String _contentUrl;
ClientContentService(@Inject(contentUrl) this._contentUrl);
Every injection needs a provider, so somewhere we must provision the contentUrl
token. We’ll handle that later in the article. For now, we finish our ClientContentService
by implementing the details of network retrieval of HTML:
@Injectable()
class ClientContentService implements ContentService {
final Client _http;
final String _contentUrl;
ClientContentService(@Inject(contentUrl) this._contentUrl, this._http);
@override
Future<String> getContent(String id) async {
final String url = path.join(_contentUrl, "$id.html");
try {
final Response response = await _http.get(url);
return response.body;
} catch (e) {
_handleError(url, e.runtimeType);
return "<h3>Error</h3><p>Failed to locate content at $url</p>";
}
}
void _handleError(String url, dynamic e) {
//TODO: something useful with an error
}
}
The implementation utilizes Client.get()
to fetch the network HTML resource, simply returning the response body. Note we modified the constructor to inject a Client
implementation.
Providing the New Content Service
We’ve finished coding our new ContentService
, but the app is still utilizing the original lorem ipsum implementation. Recall from Part 1 our bootstrap:
bootstrap(AppComponent, <Provider>[
provide(ContentService, useClass: PlaceholderContentService)
]);
Modifying the bootstrap to use our new implementation is simple. We need only swap the PlaceholderContentService
with our new ClientContentService
and add the two new providers it requires: Client
and contentUrl
:
bootstrap(AppComponent, <Provider>[
provide(ContentService, useClass: ClientContentService),
provide(Client, useFactory: () => new BrowserClient(), deps: <Object>[]),
provide(contentUrl, useValue: "http://localhost:8090/content/article")
]);
That’s it. Without touching any code other than the bootstrap, our application is now ready to utilize the new network-client content service. When the app is run, the loreum ipsum content is gone and instead is shown a 404 Not Found message. This makes sense, as we’re trying to fetch article content that hasn’t yet been written. Let’s do something about that. But first:
A Note About Same Origin Policy and CORS
Our app, being run in a browser, is subject to browser same-origin policy which places constraints on retrieval of HTML content. If our app and content are served from the same host and port, then we do not run afoul of the same-origin policy. However, if our content is served from another host or port, some allowance for Cross-Origin Resource Sharing (CORS) must be made.
In a production environment, possible solutions include a server configuration that proxies the third party content or inclusion of CORS headers to authorize our app origin.
During development, we have more options as we can afford to be more liberal. If our content is served via pub
, then the Access-Control-Allow-Origin header
is already set to the wildcard and thus can be used from any origin. If our content is served from another source, and we do not have the ability to adjust the CORS headers, we can temporarily disable security on our browser. This can be done on Dartium with the flag --disable-web-security
. NB! Disabling browser web security opens significant security risks and should be used only for development. Use caution.
An External Content Repository
We’ve implemented everything we need to fetch article content remotely. However, our ad-team needs a dedicated repository to author articles in Markdown. We’ll accomplish this simply by building a new Dart web project that is little more than a collection of markdown pages with a single transformer to convert these to HTML. In fact, just such a transformer already exists in Pub.
Starting from a stagehanded simple Dart web project, we update to pubspec.yaml
to include the md_to_html
transformer:
dependencies:
md_to_html: ">=0.1.0 <0.2.0"
dev_dependencies:
browser: '>=0.10.0 <0.11.0'
dart_to_js_script_rewriter: '^1.0.1'
transformers:
- dart_to_js_script_rewriter
- md_to_html:
template: "web/content/article/template.html"
Per the md_to_html
documentation, we’ve added a Mustache template template.html
which informs the transformer how to construct the HTML. Ours is trivial , consisting only of the single line:
{{content}}
That’s it. Really. Our ad team can now author markdown files in the web/content/article
directory. We provide an example with fuzzy.md
:
Kittens are **fuzzy** *wuzzy*.
When we use pub to serve this project (e.g. pub serve --port=8090
), the md_to_html
transformer consumes the markdown files and outputs HTML files in their stead. When we reload the original app in our browser, we now see the Fuzzy article has our new content, retrieved from our article content repository.
When all the articles are finalized, pub build
will likewise produce HTML and it would only remain for our system administrator to host these pages somewhere our app can reach.
Conclusion
Angular Dart’s dependency injection makes building and using alternative service implementations fast and fun. With reasonable planning and just a few lines of code, we’ve easily adapted to non-trivial requirements changes.
In Part 3, we’ll build a more elaborate asset provider and content repository for photographic images, allowing us to externalize not only the assets themselves but also license and attribution concerns.
Source
The full source code of this example is available on GitHub.