header source
my icon
esplo.net
ぷるぷるした直方体
Cover Image for Using AngularDart as a Small Web App Development Environment

Using AngularDart as a Small Web App Development Environment

about23mins to read

What framework do you use when you want to create a small web app?

Of course, the requirements for "small" vary widely, so it's hard to give a definitive answer. Personally, I've been using create-react-app and Next.js. But for small apps, I want to develop quickly and efficiently.

Personally, I think development is much more enjoyable with TypeScript and Material Design components. While TypeScript can be resolved with react-scripts-ts, Material Design libraries require manual selection and setup. As I was doing this, I felt like creating my own template and uploading it to GitHub... a mistake from my youthful days. A few months later, security warnings appeared on GitHub, and when I upgraded the package, it conflicted and broke... a common phenomenon, like a summer breeze.

I was getting tired of this, so I decided to try a different framework. There are several alternatives, but when I think of "types and Material Design," AngularDart comes to mind naturally. Thanks to Flutter, Dart is also expected to gain popularity in the future.

So, below, I'll talk about creating a web app that generates cards using AngularDart, and the advantages and disadvantages I experienced during development. I've published the source code, so please refer to it while reading.

Advantages of AngularDart

As I mentioned earlier, AngularDart's strength lies in its many built-in features that support development. Specifically, the fact that types are naturally usable is a huge merit for me personally.

Personally, I felt like I was working on a JavaScript project with the following features already set up:

  • TypeScript
  • prettier
  • Material Design
  • SCSS

The code formatting is standard, so I didn't need to set up prettier. Material Design and SCSS are external libraries, but I only needed to add one line. I didn't get tired during the initial development phase.

I haven't delved deep into Angular itself, so I won't compare it. Since I have experience with React and Vue.js, I didn't feel much discomfort. I didn't create a large-scale component or manage states this time, but I'll investigate if I get the chance.

Disadvantages of AngularDart

Everything has its pros and cons, and AngularDart is no exception.

The biggest reason to be cautious about AngularDart is that it's not JavaScript. While it has many built-in features, it also means leaving the existing massive ecosystem, which can lead to a significant decrease in followers and a lot of trouble. Furthermore, the community is currently small, which is a drawback that can't be ignored.

I'll summarize the issues I encountered during development below.

  • Some popular libraries can't be used
  • Deployment services don't support it
  • It's hard to find solutions through search
  • (Currently) hot-replace requires using webpack's power

Although these issues didn't affect me much this time since it was a small project, they can be fatal for personal projects that don't scale.

Development Diary, or RTA

From here, I'll write about creating a web app that extracts Open Graph data and generates static HTML cards. Through this, I'll explore how easily I can create a small app. I didn't keep track of time, but it's more like RTA (Real-Time Attack) than development.

Preparation

First, I decided on the rough specifications. This is crucial.

"I want to create an app that takes a URL, presses a button, and displays the extracted meta tags as HTML. I also want a preview feature. I'll use Material Design components."

I followed the guide for setup.

Next, I opened WebStorm and checked if I had the latest version. This is crucial. If I had the latest version, I warmed up by running the official sample.

The main file had only nine lines and looked impressive.

main file
main file

The process was simple: git clone, pub get, and webdev serve. Then, "Hello Angular!" appeared.

Reading the Official Guide

I read the learning guide kindly provided... but since this was an RTA, I only read the basic parts. I skipped the Architecture section, which I'll read later. I also skipped Displaying Data.

Input Configuration

I read the user input guide and created a simple input feature using HTML's input tag. I used keyup.enter to make it work without a button.

HTTP Communication and CORS

I implemented the main feature, which is to retrieve data from an external site. This was the most troublesome part of the app.

Since the app runs only on the client-side browser, it's bound to encounter CORS issues. This time, I used the publicly available CORS avoidance server, Rob--W/cors-anywhere. If this API server goes down, it means the end of this app. I'll refrain from making old-man comments about YQL...

The GET process was straightforward. I used Future types, which gave me a sense of security.

import 'package:http/http.dart' as http;

Future<String> requestCrossDomain(String url) async {
  final corsGateway = 'https://cors-anywhere.herokuapp.com/' + url;
  final jsonString = await http.get(corsGateway);
  final body = jsonString.body;
  ...

Component Splitting

Next, I worked on the preview feature, which is independent. I'll challenge myself to split the components.

It's not that difficult. I read about file splitting and wrote it like the main component. It seems that files are usually placed under src/. I also used @Input to declare props.

Moreover, I found it interesting that the target of @Input can be a setter.

()
set setHtml(String html) {
  final _htmlValidator = PermissiveNodeValidator();
  querySelector('#html-preview')
      ?.setInnerHtml(html, validator: _htmlValidator);
}

Calling component

<preview [setHtml]="result" [width]="previewWidth" [height]="previewHeight"></preview>

This way, whenever the result variable in the calling component changes, setHtml is called. Convenient.

setInnerHTML and Sanitizer

This part was quite tricky. If I didn't know about it, I would have gotten stuck.

In Angular, setInnerHTML and other security-critical functions are well-managed by policies. By default, they are rejected, and the contents are deleted. In this app, I created a PermissiveValidator to allow everything, just like in the dart-pad repository.

class PermissiveNodeValidator implements NodeValidator {
  bool allowsElement(Element element) => true;

  bool allowsAttribute(Element element, String attributeName, String value) {
    return true;
  }
}

Usage example

final _htmlValidator = PermissiveNodeValidator();
querySelector('#html-preview')
    ?.setInnerHtml(html, validator: _htmlValidator);

CSS Sanitizer

I also had to implement a process that changes the preview component's CSS based on user input. This was also subject to security policies, so I wrote a sanitizer to avoid it.

I used a Pipe, which is an example of how to display dynamic HTML without sanitizing or filtering. I made it work like a Pipe.

('safe')
class Safe extends PipeTransform {
  DomSanitizationService sanitizer;
  Safe(this.sanitizer);
  transform(style) {
    return this.sanitizer.bypassSecurityTrustStyle(style);
  }
}

(
  pipes: [Safe],
  template: '''
  
   [style]="'width: ' + width + 'px; height: ' + height + 'px; border: 1px dashed black;' | safe"
  >
  ''',

Material Design

Finally, I worked on the appearance. I used the official package and followed the official guide. For example, I used a directive to replace the button tag.

directives: [PreviewComponent, MaterialButtonComponent]

<material-button raised (trigger)="onEnter(box.value)">submit</material-button>

I also referred to the package documentation to learn how to use material-input. There's also an example that I found helpful during development.

The HTML input tag became a material-input tag.

<material-input
        floatingLabel
        type="number"
        checkPositive
        [(ngModel)]="previewWidth"
        leadingText="width: "
        trailingText="px"
        [rightAlign]="true"
></material-input>

I almost forgot to add materialInputDirectives to the directives. If I forgot, ngModel wouldn't work, and I'd get stuck.

Also, the right-hand value of ngModelChange must be a class member.

Bad

(ngModelChange)="previewWidth=int.parse(\$event)">

Good

(ngModelChange)="previewWidth=intParser(\$event)">
...
int intParser(String str) => int.parse(str);

I needed to create a form to include the submit button.
The process of disabling the submit button until validation passes is easily done by specifying ngControl within the form.

HTML Extraction

The template got too big, so I extracted it into an HTML file. Since I wasn't using any special symbols like $, I just extracted it into a separate file and specified it.

templateUrl: 'app_component.html',

In WebStorm, the formatting is applied when I extract it, so I don't need to worry about formatting.

SCSS

I wanted to use SCSS even for a small app, so I followed the sass_builder package's Readme. Note that I load .css files, not .scss files.

It seems that in the past, I needed to write transformers, but now it's easy to use the package.

Finally

I updated the pubspec.yaml explanation, added a favicon, and tweaked the head tag.

Deployment

Where to deploy depends on the situation, but for a static app like this, Netlify is convenient. I wrote about it in a separate article, so I'll just prepare a script file according to that.

https://qiita.com/esplo/items/d045a140bee4a2762dd7

Conclusion

I was able to develop very smoothly. The learning cost wasn't as high as I thought, and I didn't feel much discomfort since I had experience with other frameworks. This time, I didn't use many libraries, so I didn't encounter many drawbacks of not using JavaScript. I was able to enjoy many benefits.

Summary

When the requirements are "small enough," "don't use many external libraries," and "care about appearance," I felt that I could develop more efficiently using AngularDart than create-react-app or Next.js. Dart is expected to grow in the future, and I want to use it actively.

On the other hand, when creating a large-scale app, a different perspective is necessary, and the drawbacks can't be ignored. This is something I'll experience in the future.

Share