This codelab walks you through creating a web app that uses AngularDart and features material design components.

If you know how to program, you can complete this codelab. You don't need previous experience with Dart or with web programming.

Do you have experience writing web apps?

No. Some, but not a lot. Yes, I'm comfortable writing web apps.

What are Dart and AngularDart?

Dart is an application programming language from Google that's easy to learn, easy to use, and deployable everywhere.

AngularDart—the Dart version of the Angular web app framework—focuses on productivity, performance, and stability. Hundreds of Google engineers use AngularDart to build the sophisticated, mission-critical apps that bring in much of Google's revenue. Google provides a growing set of material design components—called AngularDart Components—for use with AngularDart.

What you'll build

You'll use AngularDart to implement a simple web app that proposes startup names. The user can select and unselect the proposed names, saving the best ones before generating another batch of names.

TRY IT

What you'll learn

This codelab uses—but doesn't try to teach you about—HTML, CSS, Angular syntax, the Dart language, and common Dart APIs. You can learn more about these glossed-over topics by following the links in each page's More information section.

What you'll use

This codelab requires version 1.23 (or higher) of the Dart SDK. The instructions feature Dartium and the WebStorm IDE, but if you have some experience with Dart, you can use whichever tools you prefer.

In this step, you download the software that you need.

Get Dart and Dartium

If you haven't already done so, get the Dart SDK (1.23 or a compatible version) and Dartium.

Note where you installed the SDK and Dartium. You'll need these paths when you configure WebStorm.

Get WebStorm, IntelliJ IDEA, or a plugin

If you don't have a favorite editor already, install and configure WebStorm. Alternatively, download one of the Dart plugins for other IDEs and editors.

More information

In this step, you create an AngularDart app, no coding required. WebStorm uses a template to provide all the files your app needs.

Launch WebStorm.

In the Welcome to WebStorm window, click Create New Project. A New Project form appears.

Select Dart in the list on the left.

If the Dart SDK path and Dartium path fields don't have values, enter them. Make sure the Dart version is at least 1.23.

Make sure that Generate sample content is checked. (It might take a few seconds to show up.)

Select AngularDart Web App from the list of sample content.

In the Location input field, change the last path segment from untitled to startup_namer.

Check your settings against the following screenshot, then click Create.

What did you get?

You got a directory named startup_namer that contains all the files you need for a simple web app that uses AngularDart Components. Here are the most important files:

More information

Problems?

If you don't see all the files listed in "What did you get?" then you probably chose a different template. Try again, and make sure you choose AngularDart Web App.

If you had a pub error and didn't use startup_namer as your directory name, it's possible you used a directory name that wasn't legal as a package name. Try recreating the app, using a directory name like startup_namer2.

If you prefer not to use WebStorm, you can use the stagehand command-line interface (which you can get using pub global activate) to get the web-angular template.

You can also find the starting source code in the 1_basic branch of the io_2017_components_codelab repo.

In this step, you run the app in Dartium, a special build of Chromium that includes the Dart VM.

In the upper-right corner of WebStorm, click the green Run button:

WebStorm launches the app in a Dartium window. After a few seconds, you should see something like the following:

More information

Problems?

If the browser displays 502 Bad Gateway, then WebStorm probably didn't download the packages that the app needs. To fix this issue, right-click pubspec.yaml and choose Pub: Get Dependencies. Then try running the app again.

If the browser doesn't seem to be running the right HTML file or the Run button isn't enabled, try running the app another way: in the Project view (list of files) under the web directory, find index.html, right-click it, and choose Run ‘index.html'.

In this step, you edit HTML files to change both the <my-app> template and the page that hosts <my-app>.

In WebStorm, open web/index.html. Insert the title Name your next startup, using an <h1> element just below the <body> tag:

<body>
  <h1>Name your next startup</h1>
  <my-app>Loading...</my-app>
</body>

In WebStorm, open lib/app_component.html:

  1. Make sure you have the right file. The contents should start with <h1>My First AngularDart App</h1>.
  1. Replace everything in lib/app_component.html with the following code:
<material-button>
    Get new ideas
</material-button>

Save your work, and reload the app in Dartium. You should see this in the browser window:

Click the text GET NEW IDEAS.

Because GET NEW IDEAS is in a material button, it reacts with a nice ripple animation. However, the button doesn't really do anything yet.

More information

Problems?

The files you edited in this step should now look like this:

web/index.html

<!DOCTYPE html>
<html>
  <head>
    <title>startup_namer</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <script defer src="main.dart" type="application/dart"></script>
    <script defer src="packages/browser/dart.js"></script>
    <link rel="stylesheet" href="styles.css">
  </head>
  <body>
    <h1>Name your next startup</h1>
    <my-app>Loading...</my-app>
  </body>
</html>

lib/app_component.html

<material-button>
    Get new ideas
</material-button>

You've removed much of the UI, so you don't need the Dart code that refers to it. Time to clean up.

Remove the test directory and all of its contents.

Remove the lib/todo_list directory and all of its contents.

Edit lib/app_component.dart:

  1. Remove the line that imports todo_list/todo_list_component.dart.
    (To see this line, expand the import section.)
  2. Remove TodoListComponent from the directives list.
  3. Delete the comment in the AppComponent class body that refers to TodoListComponent.

Save your changes, and reload the app.

The app should look and act exactly the same as in the previous step, since all you did was remove unneeded code.

Problems?

The file you edited in this step should now look like this:

lib/app_component.dart

import 'package:angular2/angular2.dart';
import 'package:angular_components/angular_components.dart';

// AngularDart info: https://webdev.dartlang.org/angular
// Components info: https://webdev.dartlang.org/components

@Component(
  selector: 'my-app',
  styleUrls: const ['app_component.css'],
  templateUrl: 'app_component.html',
  directives: const [materialDirectives],
  providers: const [materialProviders],
)
class AppComponent {
}

Google has developed many material design icons that you can use for free. This app uses the lightbulb icon, which you can add using the <glyph> component from AngularDart Components.

Edit lib/app_component.html, adding the following line just above Get new ideas:

<glyph icon="lightbulb_outline"></glyph>

Save your work, and reload.

You should see a lightbulb icon:

Problems?

If your app looks wrong or won't run, a typo is probably to blame—perhaps an extra quotation mark.

The file you edited in this step should now look like this:

lib/app_component.html

<material-button>
    <glyph icon="lightbulb_outline"></glyph>
    Get new ideas
</material-button>

More information

In this step, you'll start using an open-source package named english_words, which contains a few thousand of the most used English words plus some utility functions.

In pubspec.yaml, add english_words (2.0.0 or higher) to the dependencies list:

english_words: ^2.0.0

Click Get dependencies, at the upper right of the WebStorm editor's view of pubspec.yaml.

In lib/app_component.dart, just after the imports, add the following line:

import 'package:english_words/english_words.dart';

Still in lib/app_component.dart, add a name variable to the AppComponent class. Initialize it using the generateWordPairs() function from the english_words library:

class AppComponent {
  WordPair name = generateWordPairs().first;
}

To prove that you're getting a real word pair, use Dart's print() function, which prints to the console. Because the value of name doesn't depend on any app state, you can put the print statement in an AppComponent constructor:

class AppComponent {
  ...
  AppComponent() { print('name: $name'); }
}

Save your work, and reload the app.

Open the console in Developer Tools.

You should see the value of the name property in the console:

Problems?

If your app isn't running correctly, look for typos.

The files you edited in this step should now look like this:

pubspec.yaml

name: startup_namer
description: A web app that uses AngularDart Components
version: 0.0.1

environment:
  sdk: '>=1.23.0 <2.0.0'

dependencies:
  angular2: ^3.0.0
  angular_components: ^0.5.1
  english_words: ^2.0.0

dev_dependencies:
  angular_test: ^1.0.0-beta+2
  browser: ^0.10.0
  dart_to_js_script_rewriter: ^1.0.1
  test: ^0.12.0

transformers:
- angular2:
    entry_points: web/main.dart
- angular2/transform/reflection_remover:
    $include: test/**_test.dart
- test/pub_serve:
    $include: test/**_test.dart
- dart_to_js_script_rewriter

lib/app_component.dart

import 'package:angular2/angular2.dart';
import 'package:angular_components/angular_components.dart';
import 'package:english_words/english_words.dart';

// AngularDart info: https://webdev.dartlang.org/angular
// Components info: https://webdev.dartlang.org/components

@Component(
  selector: 'my-app',
  styleUrls: const ['app_component.css'],
  templateUrl: 'app_component.html',
  directives: const [materialDirectives],
  providers: const [materialProviders],
)
class AppComponent {
  WordPair name = generateWordPairs().first;

  AppComponent() { print('name: $name'); }
}

If you can't find your mistake, start fresh with the source code in the 2_english_words branch of the io_2017_components_codelab repo.

More information

The easiest way for an Angular app to display the value of a property (a public variable in a component) is to put the property name in double curly braces: {{propertyName}}.

At the bottom of lib/app_component.html, add a reference to the name variable:

...
<p>{{name}}.com</p>

Save your work, and reload the app.

You should see the value of the name property in the UI:

More information

Problems?

The file you edited in this step should now look like this:

lib/app_component.html

<material-button>
    <glyph icon="lightbulb_outline"></glyph>
    Get new ideas
</material-button>

<p>{{name}}.com</p>

Displaying multiple items is almost as easy as displaying a single item. First you need to make the data available as a property that has an Iterable type such as List. Then use the ngFor directive to display the data.

In lib/app_component.dart:

  1. Add CORE_DIRECTIVES to the list of directives, so that you can use the ngFor directive:
directives: const [CORE_DIRECTIVES, materialDirectives],
  1. Change all occurrences of name to names.
  1. Change the type of names from WordPair to List<WordPair>, and initialize the property like this:
List<WordPair> names = generateWordPairs().take(5).toList();

In app_component.html, change the name-display code to this:

<ul>
    <li *ngFor="let item of names">
        {{item}}.com
    </li>
</ul>

Save both files, then reload the app.

The code *ngFor="let item of names" creates one copy of the enclosing element (<li>) for each value (item) in the Iterable property (names). The resulting UI looks something like this:

More information

Problems?

Make sure you added CORE_DIRECTIVES to the directives list, and have a leading asterisk in *ngFor.

The files you edited in this step should now look like this:

lib/app_component.dart

import 'package:angular2/angular2.dart';
import 'package:angular_components/angular_components.dart';
import 'package:english_words/english_words.dart';

// AngularDart info: https://webdev.dartlang.org/angular
// Components info: https://webdev.dartlang.org/components

@Component(
  selector: 'my-app',
  styleUrls: const ['app_component.css'],
  templateUrl: 'app_component.html',
  directives: const [CORE_DIRECTIVES, materialDirectives],
  providers: const [materialProviders],
)
class AppComponent {
  List<WordPair> names = generateWordPairs().take(5).toList();

  AppComponent() { print('name: $names'); }
}

lib/app_component.html

<material-button>
    <glyph icon="lightbulb_outline"></glyph>
    Get new ideas
</material-button>

<ul>
    <li *ngFor="let item of names">
        {{item}}.com
    </li>
</ul>

In this step, you'll change your code to use a list from the AngularDart Components package (<material-list>), instead of a plain HTML list.

In lib/app_component.html, change the code so that it uses <material-list> instead of <ul>, and <material-list-item> instead of <li>:

<material-list>
    <material-list-item *ngFor="let item of names">
        {{item}}.com
    </material-list-item>
</material-list>

Save and reload.

You should see something like this:

That's not an obvious improvement, but later you'll use <material-list> and <material-list-item> features such as grouping, labels, and interactivity.

More information

Problems?

If you use earlier angular* versions, the formatting will be off (all on one line). Update dependencies as described in Create a basic web app.

The file you edited in this step should now look like this:

lib/app_component.html

<material-button>
    <glyph icon="lightbulb_outline"></glyph>
    Get new ideas
</material-button>

<material-list>
    <material-list-item *ngFor="let item of names">
        {{item}}.com
    </material-list-item>
</material-list>

In this step, you update the app to react to clicks or taps of the button.

To create an event binding in Angular, you put the event name in parentheses, followed by the action to take when the event occurs: (event)="onEvent()". With AngularDart Components, instead of handling click or tap events, you generally handle trigger events, which improve accessibility by responding to enter/space keypresses as well as clicks or taps.

In lib/app_component.html, add a trigger event binding that calls a method named generateNames():

<material-button (trigger)="generateNames()">

In lib/app_component.dart, add the generateNames() method to the AppComponent class:

class AppComponent {
  ...
  void generateNames() {
    names = generateWordPairs().take(5).toList();
  }
}

Save and reload.

Each time you click the button, the list of names should change.

More information

Problems?

The files you edited in this step should now look like this:

lib/app_component.html

<material-button (trigger)="generateNames()">
    <glyph icon="lightbulb_outline"></glyph>
    Get new ideas
</material-button>

<material-list>
    <material-list-item *ngFor="let item of names">
        {{item}}.com
    </material-list-item>
</material-list>

lib/app_component.dart

import 'package:angular2/angular2.dart';
import 'package:angular_components/angular_components.dart';
import 'package:english_words/english_words.dart';

// AngularDart info: https://webdev.dartlang.org/angular
// Components info: https://webdev.dartlang.org/components

@Component(
  selector: 'my-app',
  styleUrls: const ['app_component.css'],
  templateUrl: 'app_component.html',
  directives: const [CORE_DIRECTIVES, materialDirectives],
  providers: const [materialProviders],
)
class AppComponent {
  List<WordPair> names = generateWordPairs().take(5).toList();

  AppComponent() { print('name: $names'); }

  void generateNames() {
    names = generateWordPairs().take(5).toList();
  }
}

You might notice that you now have some duplicated code. To remove the duplication, the initial value of names should be set using generateNames().

A more interesting issue is where names is initialized. In general, it's good to do component initialization in the ngOnInit() method. To make ngOnInit() be called, the component class must implement the OnInit interface.

To fix the component's initialization code, edit lib/app_component.dart:

  1. Declare that AppComponent implements OnInit:
class AppComponent implements OnInit {
  1. Click AppComponent.
    A red lightbulb appears.
  2. Click the red lightbulb, and choose Create 1 missing override(s).
    An implementation of the ngOnInit() method appears:
class AppComponent implements OnInit {
  ...
  @override
  ngOnInit() {
    // TODO: implement ngOnInit
  }
}
  1. Implement the method, making it call generateNames(). Declare that ngOnInit returns void.
class AppComponent implements OnInit {
  ...
  @override
  void ngOnInit() {
    generateNames();
  }
}
  1. Remove the AppComponent() constructor.
  2. Initialize names to be an empty list of WordPair items. You don't need to declare the type, since it's implied by the initial value.
var names = <WordPair>[];

Save and reload.

The app should look and act exactly as it did before (except for console messages, since you removed the code that generated them).

More information

Problems?

The file you edited in this step should now look like this:

lib/app_component.dart

import 'package:angular2/angular2.dart';
import 'package:angular_components/angular_components.dart';
import 'package:english_words/english_words.dart';

// AngularDart info: https://webdev.dartlang.org/angular
// Components info: https://webdev.dartlang.org/components

@Component(
  selector: 'my-app',
  styleUrls: const ['app_component.css'],
  templateUrl: 'app_component.html',
  directives: const [CORE_DIRECTIVES, materialDirectives],
  providers: const [materialProviders],
)
class AppComponent implements OnInit {
  var names = <WordPair>[];

  void generateNames() {
    names = generateWordPairs().take(5).toList();
  }

  @override
  void ngOnInit() {
    generateNames();
  }
}

If you can't find your mistake, start fresh with the source code in the 3_ngoninit branch of the io_2017_components_codelab repo.

Now it's time to add a little color to the app. Having the two words in each name be a different color will add a little visual interest, plus it will help users distinguish between the words.

Angular uses CSS for these kinds of UI tweaks. Before you implement the CSS, you'll need to update the template so that each word that you want to style separately is in its own span. Fortunately, the WordPair class supports getting each word separately, using properties named first and second.

Edit lib/app_component.html, changing the content of the <material-list-item> element from {{item}}.com to this:

<span class="first">{{item.first}}</span>{{item.second}}.com

Edit lib/app_component.css, adding this style:

.first {
    color: #2196F3;
}

Save and reload.

The UI should now look like this:

More information

Problems?

The files you edited in this step should now look like this:

lib/app_component.html

<material-button (trigger)="generateNames()">
    <glyph icon="lightbulb_outline"></glyph>
    Get new ideas
</material-button>

<material-list>
    <material-list-item *ngFor="let item of names">
        <span class="first">{{item.first}}</span>{{item.second}}.com
    </material-list-item>
</material-list>

lib/app_component.css

:host {
    /* This is equivalent of the 'body' selector of a page. */
}

.first {
    color: #2196F3;
}

The next feature this app needs to support is making a list of good names. You'll implement this by adding a savedNames property that holds the names the user has selected. AppComponent also needs to supply methods that event handlers can use to add and remove names from savedNames.

Edit lib/app_component.dart:

  1. Add a savedNames property to AppComponent.
final savedNames = new Set<WordPair>();
  1. Add methods for adding and removing names from savedNames:
class AppComponent implements OnInit {
...
  void addToSaved(WordPair name) {
    savedNames.add(name);
    print('added $name: $savedNames');
  }

  void removeFromSaved(WordPair name) {
    savedNames.remove(name);
    print('removed $name: $savedNames');
  }
}
  1. Add a method for toggling the presence of a name in savedNames:
void toggleSavedState(WordPair name) {
  if (savedNames.contains(name)) {
    removeFromSaved(name);
    return;
  }
  addToSaved(name);
}

In lib/app_component.html, add an event binding so that clicking a name toggles its presence in savedNames:

<material-list-item *ngFor="let item of names"
                    (trigger)="toggleSavedState(item)">

Save your work, and reload the app.

Open the console, and then click a few names.

The console should have messages like the following:

added flatround: {flatround}
removed flatround: {}
added flatround: {flatround}
added juststrike: {flatround, juststrike}
added oilrim: {flatround, juststrike, oilrim}

More information

Problems?

Check for typos. Do your HTML and Dart files use the same names for methods?

The files you edited in this step should now look like this:

lib/app_component.html

<material-button (trigger)="generateNames()">
    <glyph icon="lightbulb_outline"></glyph>
    Get new ideas
</material-button>

<material-list>
    <material-list-item *ngFor="let item of names"
                        (trigger)="toggleSavedState(item)">
        <span class="first">{{item.first}}</span>{{item.second}}.com
    </material-list-item>
</material-list>

lib/app_component.dart

import 'package:angular2/angular2.dart';
import 'package:angular_components/angular_components.dart';
import 'package:english_words/english_words.dart';

// AngularDart info: https://webdev.dartlang.org/angular
// Components info: https://webdev.dartlang.org/components

@Component(
  selector: 'my-app',
  styleUrls: const ['app_component.css'],
  templateUrl: 'app_component.html',
  directives: const [CORE_DIRECTIVES, materialDirectives],
  providers: const [materialProviders],
)
class AppComponent implements OnInit {
  var names = <WordPair>[];
  final savedNames = new Set<WordPair>();

  void generateNames() {
    names = generateWordPairs().take(5).toList();
  }

  @override
  void ngOnInit() {
    generateNames();
  }

  void addToSaved(WordPair name) {
    savedNames.add(name);
    print('added $name: $savedNames');
  }

  void removeFromSaved(WordPair name) {
    savedNames.remove(name);
    print('removed $name: $savedNames');
  }

  void toggleSavedState(WordPair name) {
    if (savedNames.contains(name)) {
      removeFromSaved(name);
      return;
    }
    addToSaved(name);
  }
}

If you can't find your mistake, start fresh with the source code in the 4_property branch of the io_2017_components_codelab repo.

Just like before, you can use *ngFor and <material-list-item> to display each name in the savedNames list. Thanks to MaterialListComponent's support for grouping and labeling items, you can reuse the existing <material-list> to hold these items.

In lib/app_component.html:

  1. Enclose the <material-list-item> element in a <div group> element:
<material-list>
    <div group>
        <material-list-item *ngFor="let item of names"
                            (trigger)="toggleSavedState(item)">
            <span class="first">{{item.first}}</span>{{item.second}}.com
        </material-list-item>
    </div>
</material-list>
  1. Copy the whole <div group> element, so that it appears twice in the <material-list>, and change the second group's names to savedNames:
<material-list>
    <div group>
        <material-list-item *ngFor="let item of names"
                            (trigger)="toggleSavedState(item)">
            <span class="first">{{item.first}}</span>{{item.second}}.com
        </material-list-item>
    </div>
    <div group>
        <material-list-item *ngFor="let item of savedNames"
                            (trigger)="toggleSavedState(item)">
            <span class="first">{{item.first}}</span>{{item.second}}.com
        </material-list-item>
    </div>
</material-list>
  1. In the second div, change the trigger event handler from toggleSavedState to removeFromSaved:
<material-list-item *ngFor="let item of savedNames"
                    (trigger)="removeFromSaved(item)">
</material-list-item>
  1. Add a "Saved names" label to the second div:
<div group>
    <div label>Saved names</div>
    <material-list-item *ngFor="let item of savedNames"
    ...

In lib/app_component.dart, remove the 2 lines that call print().

Save and reload.

Generate some ideas, clicking the ones you like.

The app should look something like this:

More information

Problems?

The files you edited in this step should now look like this:

lib/app_component.html

<material-button (trigger)="generateNames()">
    <glyph icon="lightbulb_outline"></glyph>
    Get new ideas
</material-button>

<material-list>
    <div group>
        <material-list-item *ngFor="let item of names"
                            (trigger)="toggleSavedState(item)">
            <span class="first">{{item.first}}</span>{{item.second}}.com
        </material-list-item>
    </div>
    <div group>
        <div label>Saved names</div>
        <material-list-item *ngFor="let item of savedNames"
                            (trigger)="removeFromSaved(item)">
            <span class="first">{{item.first}}</span>{{item.second}}.com
        </material-list-item>
    </div>
</material-list>

lib/app_component.dart

import 'package:angular2/angular2.dart';
import 'package:angular_components/angular_components.dart';
import 'package:english_words/english_words.dart';

// AngularDart info: https://webdev.dartlang.org/angular
// Components info: https://webdev.dartlang.org/components

@Component(
  selector: 'my-app',
  styleUrls: const ['app_component.css'],
  templateUrl: 'app_component.html',
  directives: const [CORE_DIRECTIVES, materialDirectives],
  providers: const [materialProviders],
)
class AppComponent implements OnInit {
  var names = <WordPair>[];
  final savedNames = new Set<WordPair>();

  void generateNames() {
    names = generateWordPairs().take(5).toList();
  }

  @override
  void ngOnInit() {
    generateNames();
  }

  void addToSaved(WordPair name) {
    savedNames.add(name);
  }

  void removeFromSaved(WordPair name) {
    savedNames.remove(name);
  }

  void toggleSavedState(WordPair name) {
    if (savedNames.contains(name)) {
      removeFromSaved(name);
      return;
    }
    addToSaved(name);
  }
}

You're almost done, but the UI needs a little more work. In this step, you'll use an Angular feature called class binding, so that you can apply special styles when a condition is true.

In lib/app_component.html, add a class binding to the first list of names, so that names that are in savedNames have the CSS class is-saved:

<material-list-item *ngFor="let item of names"
                    (trigger)="toggleSavedState(item)"
                    [class.is-saved]="savedNames.contains(item)">
    <span class="first">{{item.first}}</span>{{item.second}}.com
</material-list-item>

In lib/app_component.css, add styles for elements that belong to the is-saved class, and a special style for spans that also have the first class:

.is-saved {
    color: #ccc;
}

.is-saved .first {
    color: #ddd;
}

Save and reload.

Your app should now style names differently if they're saved:

More information

Problems?

Compare your code to the source code in the master branch of the io_2017_components_codelab repo.

The files you edited in this step should now look like this:

lib/app_component.html

<material-button (trigger)="generateNames()">
  <glyph icon="lightbulb_outline"></glyph>
  Get new ideas
</material-button>

<material-list>
  <div group>
    <material-list-item *ngFor="let item of names"
                        (trigger)="toggleSavedState(item)"
                        [class.is-saved]="savedNames.contains(item)">
      <span class="first">{{item.first}}</span>{{item.second}}.com
    </material-list-item>
  </div>
  <div group>
    <div label>Saved names</div>
    <material-list-item *ngFor="let item of savedNames"
                        (trigger)="removeFromSaved(item)">
      <span class="first">{{item.first}}</span>{{item.second}}.com
    </material-list-item>
  </div>
</material-list>

lib/app_component.css

:host {
    /* This is equivalent of the 'body' selector of a page. */
}

.first {
    color: #2196F3;
}

.is-saved {
    color: #ccc;
}

.is-saved .first {
    color: #ddd;
}

You've written an AngularDart app that uses material design components. Along the way, you edited Dart code to provide app-specific functionality, HTML to specify the app's user interface, and CSS to tweak the user interface styles.

If you'd like to use WebStorm to improve your code, try these features:

If you'd like to continue working on this app, here are some suggestions:

Or maybe you'd like to follow another Dart codelab:

You can learn more about Dart at www.dartlang.org, and AngularDart at webdev.dartlang.org/angular.