NexusCS

AngularDart

Dart
AngularDart is the Dart port of Angular, sharing the same architecture but using Dart instead of TypeScript. Used at Google for products like Google Ads.
archived

Getting started

Setup (Google packages)

# pubspec.yaml - Original (archived)
dependencies:
  angular: ^7.0.2
  angular_components: ^1.0.4
  angular_forms: ^4.0.1
  angular_router: ^3.0.1
dev_dependencies:
  angular_test: ^4.0.1
  build_runner: ^2.0.0
  build_web_compilers: ^3.0.0

Original Google packages (archived).

Setup (Community fork)

# pubspec.yaml - Community (active)
dependencies:
  ngdart: ^7.1.1
  ngforms: ^4.1.2
  ngrouter: ^3.1.1
dev_dependencies:
  ngtest: ^4.1.0
  build_runner: ^2.0.0
  build_web_compilers: ^4.0.0

Active community-maintained packages.

Build commands

# Development
webdev serve
webdev serve --auto restart

# Production build
webdev build
webdev build --output web:build

# Alternative (build_runner)
dart run build_runner serve
dart run build_runner build

Components

Basic component

import 'package:angular/angular.dart';

(
  selector: 'my-app',
  template: '''
    <h1>{{title}}</h1>
    <p>Welcome to AngularDart!</p>
  ''',
  styles: ['h1 { color: blue; }'],
  directives: [coreDirectives],
)
class AppComponent {
  String title = 'My App';
}

Must import coreDirectives explicitly.

Input/Output

class HeroComponent {
  ()
  Hero hero;

  final _deleted = StreamController<Hero>();
  ()
  Stream<Hero> get deleted => _deleted.stream;

  void onDelete() => _deleted.add(hero);
}

Uses Dart Streams, not EventEmitter.

Lifecycle hooks

class HeroComponent implements OnInit, OnDestroy {
  
  void ngOnInit() {
    // Initialize component
  }

  
  void ngOnDestroy() {
    // Cleanup
  }
}

Template syntax

Bindings

Syntax Purpose Example
{{expr}} Interpolation {{hero.name}}
[property]="expr" Property binding [disabled]="isDisabled"
(event)="stmt" Event binding (click)="onSave()"
[(ngModel)]="prop" Two-way binding [(ngModel)]="hero.name"

Identical to Angular TypeScript.

Structural directives

<!-- Conditional rendering -->
<div *ngIf="hero != null">{{hero.name}}</div>

<!-- Loop -->
<li *ngFor="let hero of heroes">
  {{hero.name}}
</li>

<!-- Switch -->
<div [ngSwitch]="hero.emotion">
  <div *ngSwitchCase="'happy'">:)</div>
  <div *ngSwitchCase="'sad'">:(</div>
  <div *ngSwitchDefault>:|</div>
</div>

Template references

<input #heroInput type="text">
<button (click)="onSave(heroInput.value)">
  Save
</button>

Use #var for template refs.

Dependency Injection

Injectable service

import 'package:angular/angular.dart';

()
class HeroService {
  final Client _http;
  HeroService(this._http);

  Future<List<Hero>> getHeroes() async {
    final response = await _http.get('/api/heroes');
    return _parseHeroes(response.body);
  }
}

Provider types

(
  providers: [
    ClassProvider(HeroService),
    ValueProvider(apiUrl, 'https://api.example.com'),
    FactoryProvider(HeroService, heroServiceFactory),
    ExistingProvider(HeroService, SuperHeroService),
  ],
)
Provider Usage
ClassProvider Class instance
ValueProvider Static value
FactoryProvider Factory function
ExistingProvider Alias existing

Routing

Route definitions

import 'package:angular_router/angular_router.dart';

class Routes {
  static final heroes = RouteDefinition(
    path: 'heroes',
    component: ng.HeroListComponentNgFactory,
  );

  static final hero = RouteDefinition(
    path: 'hero/:id',
    component: ng.HeroDetailComponentNgFactory,
  );

  static final all = [heroes, hero];
}

Navigation

class AppComponent {
  final Router _router;
  AppComponent(this._router);

  void goToHero(int id) {
    _router.navigate(
      Routes.hero.toUrl(parameters: {'id': '$id'})
    );
  }
}

Router directives

(
  directives: [routerDirectives],
  template: '''
    <nav>
      <a [routerLink]="Routes.heroes.toUrl()">Heroes</a>
    </nav>
    <router-outlet></router-outlet>
  ''',
)

Forms

Template-driven form

import 'package:angular_forms/angular_forms.dart';

(
  directives: [formDirectives],
  template: '''
    <form (ngSubmit)="onSubmit()">
      <input [(ngModel)]="model.name"
             ngControl="name"
             required>
      <button type="submit">Submit</button>
    </form>
  ''',
)
class HeroFormComponent {
  Hero model = Hero();

  void onSubmit() {
    // Handle submission
  }
}

Must import formDirectives.

HTTP

HTTP service

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

class HeroService {
  final Client _http;
  HeroService(this._http);

  Future<List<Hero>> getAll() async {
    final response = await _http.get(
      Uri.parse('/api/heroes')
    );
    final data = jsonDecode(response.body) as List;
    return data.map((json) => Hero.fromJson(json)).toList();
  }

  Future<Hero> create(Hero hero) async {
    final response = await _http.post(
      Uri.parse('/api/heroes'),
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode(hero.toJson()),
    );
    return Hero.fromJson(jsonDecode(response.body));
  }
}

Uses Dart's http package.

Pipes

Custom pipe

('truncate')
class TruncatePipe implements PipeTransform {
  String transform(String value, [int length = 10]) {
    return value.length > length
        ? '${value.substring(0, length)}...'
        : value;
  }
}

Usage: {{ longText | truncate:20 }}

Built-in pipes

Pipe Usage
DatePipe Date formatting
UpperCasePipe Uppercase text
LowerCasePipe Lowercase text
CurrencyPipe Currency formatting
PercentPipe Percentage formatting
SlicePipe Array/string slice
AsyncPipe Unwrap async values
JsonPipe JSON stringify

Lifecycle Hooks

Available hooks

Hook Interface When Called
ngOnInit OnInit After first change detection
ngOnDestroy OnDestroy Before destroy
ngOnChanges OnChanges Input property changes
ngDoCheck DoCheck Custom change detection
ngAfterContentInit AfterContentInit After content projection
ngAfterViewInit AfterViewInit After view init

Migration Guide

Package migration

Google (archived) Community (active)
angular ngdart
angular_forms ngforms
angular_router ngrouter
angular_test ngtest

vs Angular (TypeScript)

Angular (TS) AngularDart
TypeScript Dart
npm/yarn pub
ng serve webdev serve
RxJS Observables Dart Streams
EventEmitter StreamController
@angular/cli Manual setup
Node.js runtime Dart VM/dart2js
webpack build_runner

Gotchas

  • Archived by Google (Nov 2025) — community continues at angulardart-community
  • NOT Flutter — different framework, same language (Dart)
  • Uses Dart Streams, not RxJS Observables
  • Build system: webdev/build_runner, not Angular CLI
  • Must explicitly import coreDirectives in every component
  • Limited ecosystem compared to Angular TypeScript
  • Package names changed: angularngdart (community fork)

Also see