AngularDart4.0 指南- 表单

表单是商业应用程序的主流。您可以使用表单登录,提交帮助请求,下订单,预订航班,安排会议,并执行无数其他数据录入任务。

在开发表单时,创建一个数据录入体验非常重要,该体验可以通过工作流高效地引导用户。

开发表单需要设计技巧(超出本页面的范围),以及双向数据绑定,更改跟踪,验证和错误处理的框架支持,您将在本页面上了解这些信息。

本页面向您展示了如何从头构建一个简单的表单。一路上你将学习如何:

  • 用组件和模板构建一个Angular表单。
  • 使用ngModel创建读取和写入输入控制值的双向数据绑定。
  • 跟踪状态变化和表单控件的有效性。
  • 使用跟踪控件状态的特殊CSS类提供视觉反馈。
  • 向用户显示验证错误并启用/禁用表单控件。
  • 使用模板引用变量在HTML元素之间共享信息。

您可以在Plunker中运行实例(查看源代码)并从那里下载代码。

模板驱动的形式

您可以通过使用本页中描述的特定于表单的指令和技术在Angular模板语法中编写模板来构建表单。

您也可以使用响应式(或模型驱动)方法来构建表单。 但是,此页面重点介绍模板驱动的表单。

您可以使用Angular模板 构建几乎任何表单- 登录表单,联系表单和几乎任何业务表单。 您可以创造性地设计控件,将它们绑定到数据,指定验证规则和显示验证错误,有条件地启用或禁用特定控件,触发内置的视觉反馈等等。

Angular通过许多重复的,模板化的任务使处理过程变得简单。

您将学习如何构建一个模板驱动的表单,如下所示:

英雄就业机构使用这种形式来维护关于英雄的个人信息。 每个英雄都需要一份工作。 让正确的英雄与正确的危机相匹配是公司的使命。

这个表格中的三个字段中的两个是必需的。 遵循材料设计准则,必填字段带有星号(*)。

如果您删除了英雄名称,表单将以吸引人注意的风格显示验证错误:

请注意提交按钮被禁用,并且输入控件从绿色变为红色。

您将以小步骤构建此表单:

  1. 创建英雄模型类。
  2. 创建控制表单的组件。
  3. 用初始表单布局创建一个模板。
  4. 使用ngModel双向数据绑定语法将数据属性绑定到每个表单控件。
  5. 为每个表单输入控件添加一个ngControl指令。
  6. 添加自定义CSS来提供视觉反馈。
  7. 显示和隐藏验证错误消息。
  8. 使用ngSubmit处理表单提交。
  9. 禁用窗体的提交按钮,直到窗体有效。

建立

按照设置说明创建一个名为表单的新项目。

添加angular_forms

Angular表单功能位于angular_forms库中,该库位于其自己的包中。 将该包添加到pubspec依赖项:

025844_9xC4_3647851.png

创建一个模型

当用户输入表单数据时,您将捕获其更改并更新模型的实例。 直到你知道模型是什么样子,你才能布置表格。

一个模型可以像“钱包”一样简单,掌握关于应用程序重要事实的事实。 这很好地描述了英雄类与三个必填字段(id, name, power)和一个可选字段(alterEgo)。

在lib目录中,使用给定的内容创建以下文件:lib/src/hero.dart

class Hero {
  int id;
  String name, power, alterEgo;
  Hero(this.id, this.name, this.power, [this.alterEgo]);
  String toString() => '$id: $name ($alterEgo). Super power: $power';
}

这是一个缺乏要求,没有行为的鸡肋模型,对于演示来说足够了。

alterEgo是可选的,所以构造函数可以让你忽略它。 请注意[this.alterEgo]中的括号。

你可以像这样创建一个新的英雄:

var myHero = new Hero(
    42, 'SkyDog', 'Fetch any object at any distance', 'Leslie Rollover');
print('My hero is ${myHero.name}.'); // "My hero is SkyDog."

创建一个基本的表单

一个Angular表单有两个部分:一个基于HTML的模板和一个组件类,以编程方式处理数据和用户交互。 从课程开始,因为它简要地说明了英雄编辑可以做什么。

创建一个表单组件

使用给定的内容创建以下文件:lib/src/hero_form_component.dart (v1)

import 'package:angular/angular.dart';
import 'package:angular_forms/angular_forms.dart';

import 'hero.dart';

const List<String> _powers = const [
  'Really Smart',
  'Super Flexible',
  'Super Hot',
  'Weather Changer'
];

@Component(
  selector: 'hero-form',
  templateUrl: 'hero_form_component.html',
  directives: const [CORE_DIRECTIVES, formDirectives],
)
class HeroFormComponent {
  Hero model = new Hero(18, 'Dr IQ', _powers[0], 'Chuck Overstreet');
  bool submitted = false;

  List<String> get powers => _powers;

  void onSubmit() => submitted = true;
}

这个组件没有什么特别之处,没有任何特定的形式,没有什么区别它与你之前写的任何组件。

理解这个组件只需要前面几页中介绍的Angular概念。

  • 代码导入您刚创建的主Angular库和Hero模型。
  • hero-form@Component选择器值意味着您可以使用<hero-form>元素将此表单放在父模板中。
  • templateUrl属性指向模板HTML的单独文件(您将很快创建)。
  • 您为modelpower定义了模拟数据。

顺便说一句,您可以注入数据服务来获取和保存真实数据,或者将这些属性作为输入和输出(请参阅“模板语法”页面中的输入和输出属性)来绑定到父组件。 这不是现在的问题,这些未来的变化不会影响表单。

修改应用程序组件

AppComponent是应用程序的根组件。 它将承载HeroFormComponent

将初学者应用版本的内容替换为以下内容:lib/app_component.dart

import 'package:angular/angular.dart';

import 'src/hero_form_component.dart';

@Component(
  selector: 'my-app',
  template: '<hero-form></hero-form>',
  directives: const [HeroFormComponent],
)
class AppComponent {}

创建一个初始表单模板

使用以下内容创建模板文件:lib/src/hero_form_component.html (start)

<div class="container">
  <h1>Hero Form</h1>
  <form>
    <div class="form-group">
      <label for="name">Name&nbsp;*</label>
      <input type="text" class="form-control" id="name" required>
    </div>
    <div class="form-group">
      <label for="alterEgo">Alter Ego</label>
      <input type="text" class="form-control" id="alterEgo">
    </div>
    <div class="row">
      <div class="col-auto">
        <button type="submit" class="btn btn-primary">Submit</button>
      </div>
      <small class="col text-right">*&nbsp;Required</small>
    </div>
  </form>
</div>

该语言只是HTML5。 您将展示两个Hero字段,namealterEgo,并在输入框中将其打开以供用户输入。

Name <input>控件具有HTML5必需属性; Alter Ego <input>控件什么也不做,因为alterEgo是可选的。

您在底部添加了一个提交按钮,其中有一些类用于样式。

你还没有使用Angular。 没有绑定或额外的指令,只是布局。

在模板驱动的表单中,如果已经导入了angular_forms库,则不必为了使用库功能而对<form>标记执行任何操作。 继续看看这是如何工作的。

刷新浏览器。 你会看到一个简单的,没有样式的表单。

表单的样式

一般的CSS类containerbtn来自Bootstrap。 Bootstrap还具有form-specific的类,包括form-controlform-group。 一起,这些给表单了一些样式。

Angular可不使用Bootstrap类或任何外部库的样式。 Angular的应用程序可以使用任何CSS库或不使用。

通过将以下链接插入到index.html的<head>中来添加Bootstrap样式:web/index.html (bootstrap)

<link rel="stylesheet"
      href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css"
      integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M"
      crossorigin="anonymous">

刷新浏览器。 你会看到一个样式化的表单!

使用* ngFor添加powers

英雄必须从一个固定的机构批准的权力列表中选择一个超级大国。 您在内部维护该列表(在HeroFormComponent中)。

您将在表单中添加一个select,并使用ngFor(先前在“显示数据”页面中看到的一种技术)将选项绑定到powers列表。

在Alter Ego group下方添加以下HTML:lib/src/hero_form_component.html (powers)

<div class="form-group">
  <label for="power">Hero Power&nbsp;*</label>
  <select class="form-control" id="power" required>
    <option *ngFor="let p of powers" [value]="p">{{p}}</option>
  </select>
</div>

这段代码重复列表中每个power 的<option>标签。 p模板输入变量在每次迭代中是不同的power; 您使用插值语法显示其名称。

与ngModel的双向数据绑定

现在运行应用程序有点令人失望。

你没有看到英雄数据,因为你还没有绑定到英雄。 你知道如何从早期的页面做到这一点。 显示数据教导属性绑定。 用户输入显示如何使用事件绑定监听DOM事件以及如何使用显示的值更新组件属性。

现在您需要同时显示,聆听和提取。

你可以使用你已经知道的技术,但是你会使用新的[(ngModel)]语法,这使得绑定到模型的表单变得容易。

找到Name的<input>标签,并像下面这样更新它:lib/src/hero_form_component.html (name)

<!-- TODO: remove the next diagnostic line -->
<mark>{{model.name}}</mark><hr>
<div class="form-group">
  <label for="name">Name&nbsp;*</label>
  <input type="text" class="form-control" id="name" required
         [(ngModel)]="model.name"
         ngControl="name">
</div>

你在form-group之前添加了一个诊断插值,所以你可以看到你在做什么。 当你完成的时候,你留下一张纸条扔掉它。

关注绑定语法:[(ngModel)] =“...”。

现在运行应用程序并输入名称输入,添加和删除字符。 您会看到这些字符出现在诊断文本中并消失。 在某个时候,它可能看起来像这样:

诊断结果表明数值确实是从输入流向模型,再返回。

这是双向的数据绑定。 有关更多信息,请参见模板语法页面上的与NgModel的双向绑定。

请注意,您还为<input>标记添加了一个ngControl指令,并将其设置为“name”,这对于英雄的名字是有意义的。 任何唯一值将会这样做,但使用描述性名称是有帮助的。 将[(ngModel)]与表单结合使用时,定义ngControl指令是一项要求。

在内部,Angular创建NgFormControl实例,并使用Angular附加到<form>标签的NgForm指令注册它们。 每个NgFormControl都是在您分配给ngControl指令的名称下注册的。 本指南稍后将详细介绍NgForm

Alter EgoHero Power添加类似的[(ngModel)]绑定和ngControl指令。

model替换诊断绑定表达式。 通过这种方式,您可以确认双向数据绑定适用于整个英雄模型。

修改后,表单的核心应该是这样的:lib/src/hero_form_component.html (controls)

<!-- TODO: remove the next diagnostic line -->
<mark>{{model}}</mark><hr>
<div class="form-group">
  <label for="name">Name&nbsp;*</label>
  <input type="text" class="form-control" id="name" required
         [(ngModel)]="model.name"
         ngControl="name">
</div>
<div class="form-group">
  <label for="alterEgo">Alter Ego</label>
  <input type="text" class="form-control" id="alterEgo"
         [(ngModel)]="model.alterEgo"
         ngControl="alterEgo">
</div>
<div class="form-group">
  <label for="power">Hero Power&nbsp;*</label>
  <select class="form-control" id="power" required
          [(ngModel)]="model.power"
          ngControl="power">
    <option *ngFor="let p of powers" [value]="p">{{p}}</option>
  </select>
</div>
  • 每个input元素都有一个id属性,label元素的for属性使用它来匹配labelinput控件。
  • 每个input元素都有一个ngControl指令,Angular表单需要用这个指令在表单上注册控件。

如果您现在运行应用程序并更改每个英雄model属性,表单可能会显示如下:

靠近表单顶部的诊断确认所有的更改都反映在model中。

从模板中删除诊断绑定,因为它已经达到了目的。

根据控制状态给出视觉反馈

使用CSS和类绑定,您可以更改表单控件的外观以反映其状态。

跟踪控制状态

Angular表单控件可以告诉您用户是否触摸了该控件,值是否改变,或者该值是否失效。

每个Angular控制(NgControl)都跟踪自己的状态,并通过以下字段成员使状态可供检查:

  • dirtypristine表明控制的值是否已经改变。
  • toucheduntouched指示控件是否被访问过。
  • valid反映了控制值的有效性。

样式控件

有效的控制属性是最有趣的,因为当一个控制值无效时,你想发送一个强烈的视觉信号。 要创建这样的视觉反馈,您将使用Bootstrap自定义表单类 is-validis-invalid

将名为name模板引用变量添加到Name <input>标记中。 使用name类绑定来有条件地分配适当的表单有效性类。

临时将另一个名为spy的模板引用变量添加到Name <input>标记,并使用它显示输入的CSS类。

lib/src/hero_form_component.html (name)

<input type="text" class="form-control" id="name" required
       [(ngModel)]="model.name"
       #name="ngForm"
       #spy
       [class.is-valid]="name.valid"
       [class.is-invalid]="!name.valid"
       ngControl="name">
<!-- TODO: remove the next diagnostic line -->
{{spy.className}}

模板引用变量

spy模板引用变量绑定到<input> DOM元素,而name变量(通过#name =“ngForm”语法)绑定到与input元素关联的NgModel

为什么“ngForm”? 指令的exportAs属性告诉Angular如何将引用变量链接到指令。 您将name设置为“ngForm”,因为ngModel指令的exportAs属性是“ngForm”。

刷新浏览器,然后按照下列步骤操作:

1.看看名字输入。

  • 它有一个绿色的边框。
  • 它具有类形式控制和有效性。

2.通过添加一些字符来更改name。 类保持不变。
3.删除名称。

  • 输入框边框变为红色。
  • is-invalid类替换为is-valid。

删除#spy模板引用变量和使用它的诊断。

作为类绑定的替代方法,可以使用NgClass指令来设置控件的样式。 首先,添加以下方法来设置控件的依赖于状态的CSS类名称:

lib/src/hero_form_component.dart (setCssValidityClass)

Map<String, bool> setCssValidityClass(NgControl control) {
  final validityClass = control.valid == true ? 'is-valid' : 'is-invalid';
  return {validityClass: true};
}

使用此方法返回的映射值绑定到NgClass指令 - 在模板语法页面中详细了解此指令及其替代方法。

lib/src/hero_form_component.html (power)

<select class="form-control" id="power" required
        [(ngModel)]="model.power"
        #power="ngForm"
        [ngClass]="setCssValidityClass(power)"
        ngControl="power">
  <option *ngFor="let p of powers" [value]="p">{{p}}</option>
</select>

显示并隐藏验证错误消息


你可以改善表格。 名称输入是必需的,清除它将框的轮廓变为红色。 这说明有些事情是错的,但用户不知道什么是错的,或者该怎么做。 利用控件的状态来显示有用的消息。

使用有效的和原始的状态

当用户删除名称时,表单应该如下所示:

为了达到这个效果,在Name <input>之后立即添加下面的<div>

lib/src/hero_form_component.html (hidden error message)

<div [hidden]="name.valid || name.pristine" class="invalid-feedback">
  Name is required
</div>

刷新浏览器并删除Name 输入。 显示错误消息。

您可以通过根据名称控制的状态设置<div>隐藏属性来控制错误消息的可见性。

在这个例子中,当控件是有效的或者原始的时候隐藏消息 - “pristine”意味着用户没有改变这个值,因为它是以这种形式显示的。

用户体验是开发者的选择

有些开发人员希望消息始终显示。 如果您忽略原始状态,则只有在该值有效时才会隐藏该消息。 如果您使用新(空白)英雄或无效英雄到达此组件,则在您执行任何操作之前,您将立即看到错误消息。

有些开发人员希望仅在用户进行无效更改时显示消息。 当控件是“原始的”时隐藏消息实现了这个目标。 当您向表单添加一个“清除”按钮时,您会看到此选项的重要性。

英雄Alter Ego是可选的,所以你可以不用关那个。

英雄power选择是必需的。 如果需要,可以将相同类型的错误消息添加到<select>中,但这不是必须的,因为选择框已经将权限限制为有效值。

添加一个清除按钮

clear()方法添加到组件类中:lib/src/hero_form_component.dart (clear)

void clear() {
  model.name = '';
  model.power = _powers[0];
  model.alterEgo = '';
}

在提交按钮后面添加一个带有点击事件绑定的清除按钮:lib/src/hero_form_component.html (Clear button)

<button (click)="clear()" type="button" class="btn">
  Clear
</button>

刷新浏览器。 点击清除按钮。 文本字段变为空白,如果您更改了power,它将恢复为默认值。

用ngSubmit提交表单

用户应该能够在填写表单后提交这个表单。表单底部的Submit按钮本身不做任何事情,但是由于它的类型(type =“submit”),它会触发一个表单提交。

表单提交目前是无用的。 为了使它有用,将表单组件的onSubmit()方法分配给表单的ngSubmit事件绑定:

<form (ngSubmit)="onSubmit()" #heroForm="ngForm">

请注意模板引用变量#heroForm。 正如前面所解释的,变量heroForm被绑定到整体管理表单的NgForm指令。

NgForm指令

Angular自动创建并附加一个NgForm指令给<form>标签。

NgForm指令补充表单元素的附加功能。 它包含用ngModelngControl指令为元素创建的控件,并监视它们的属性,包括它们的有效性。

您将通过heroForm变量将表单的整体有效性绑定到按钮的disabled属性:

<button [disabled]="!heroForm.form.valid" type="submit" class="btn btn-primary">
  Submit
</button>

刷新浏览器。 你会发现这个按钮是启用的,尽管它没有做任何有用的事情。

现在,如果您删除Name,则违反了“必需的”规则,这在错误消息中正确记录。 提交按钮也被禁用。

没有留下深刻印象? 想一想。 如果没有Angular的帮助,你需要做什么才能将按钮的启用/禁用状态连接到表单的有效性?

对你来说,这很简单:

  • 在(增强的)表单元素上定义一个模板引用变量。
  • 在多处的按钮中引用该变量。

显示Model(可选)

提交表单目前没有视觉效果。

如预期的演示。 增加代码过后的demo不会教你任何关于表单的新东西。 但是这是一个锻炼一些新获得的绑定技巧的机会。 如果您不感兴趣,请跳至本页的摘要

作为一种视觉效果,您可以隐藏数据输入区域并显示其他内容。

将表单封装在<div>中,并将其hidden属性绑定到HeroFormComponent.submitted属性。

lib/src/hero_form_component.html (excerpt)

<div [hidden]="submitted">
  <h1>Hero Form</h1>
  <form (ngSubmit)="onSubmit()" #heroForm="ngForm">
  </form>
</div>

该表单从一开始就是可见的,因为在提交表单之前,提交的属性为false,因为HeroFormComponent中的片段显示为:lib/src/hero_form_component.dart (submitted)

bool submitted = false;

void onSubmit() => submitted = true;

现在在刚刚写的<div>包装器下面添加下面的HTML:lib/src/hero_form_component.html (submitted)

<div [hidden]="!submitted">
  <h1>Hero data</h1>

  <table class="table">
    <tr>
      <th>Name</th>
      <td>{{model.name}}</td>
    </tr>
    <tr>
      <th>Alter Ego</th>
      <td>{{model.alterEgo}}</td>
    </tr>
    <tr>
      <th>Power</th>
      <td>{{model.power}}</td>
    </tr>
  </table>

  <button (click)="submitted=false" class="btn btn-primary">Edit</button>
</div>

刷新浏览器并提交表单。 提交的标志变为真,表格消失。 您将看到表格中显示的英雄模型值(只读)。

该视图包含一个编辑按钮,其单击事件绑定将清除提交的标志。 当您单击编辑按钮时,该表消失,并且可编辑的表单重新出现。

概要

Angular表单为数据修改,验证等提供支持。 在此页面中,您学习了如何使用以下功能:

  • 一个HTML表单模板和一个带有@Component注解的表单组件类。
  • 表单提交,通过ngSubmit事件绑定处理。
  • 模板引用变量,如heroFormname
  • 双向数据绑定([(ngModel)])。
  • 用于验证和表单元素更改跟踪的NgControl 指令。
  • 输入控件(通过模板引用变量访问)的valid 属性,用于检查控件有效性以及显示/隐藏错误消息。
  • NgForm.form的有效性来设置提交按钮的启用状态。
  • 自定义CSS类为用户提供有关控制状态的可视反馈。

最终的项目文件夹结构应该如下所示:

010738_WTLg_3647851.png

以下是应用程序最终版本的代码:

lib/app_component.dart

import 'package:angular/angular.dart';
import 'src/hero_form_component.dart';
@Component(
  selector: 'my-app',
  template: '<hero-form></hero-form>',
  directives: const [HeroFormComponent],
)
class AppComponent {}

lib/src/hero.dart 

class Hero {
  int id;
  String name, power, alterEgo;
  Hero(this.id, this.name, this.power, [this.alterEgo]);
  String toString() => '$id: $name ($alterEgo). Super power: $power';
}

lib/src/hero_form_component.dart

import 'package:angular/angular.dart';
import 'package:angular_forms/angular_forms.dart';
import 'hero.dart';
const List<String> _powers = const [
  'Really Smart',
  'Super Flexible',
  'Super Hot',
  'Weather Changer'
];
@Component(
  selector: 'hero-form',
  templateUrl: 'hero_form_component.html',
  directives: const [CORE_DIRECTIVES, formDirectives],
)
class HeroFormComponent {
  Hero model = new Hero(18, 'Dr IQ', _powers[0], 'Chuck Overstreet');
  bool submitted = false;
  List<String> get powers => _powers;
  void onSubmit() => submitted = true;
  /// Returns a map of CSS class names representing the state of [control].
  Map<String, bool> setCssValidityClass(NgControl control) {
    final validityClass = control.valid == true ? 'is-valid' : 'is-invalid';
    return {validityClass: true};
  }
  void clear() {
    model.name = '';
    model.power = _powers[0];
    model.alterEgo = '';
  }
}

lib/src/hero_form_component.html

<div class="container">
  <div [hidden]="submitted">
    <h1>Hero Form</h1>
    <form (ngSubmit)="onSubmit()" #heroForm="ngForm">
      <div class="form-group">
        <label for="name">Name&nbsp;*</label>
        <input type="text" class="form-control" id="name" required
               [(ngModel)]="model.name"
               #name="ngForm"
               [class.is-valid]="name.valid"
               [class.is-invalid]="!name.valid"
               ngControl="name">
        <div [hidden]="name.valid || name.pristine" class="invalid-feedback">
          Name is required
        </div>
      </div>
      <div class="form-group">
        <label for="alterEgo">Alter Ego</label>
        <input type="text" class="form-control" id="alterEgo"
               [(ngModel)]="model.alterEgo"
               ngControl="alterEgo">
      </div>
      <div class="form-group">
        <label for="power">Hero Power&nbsp;*</label>
        <select class="form-control" id="power" required
                [(ngModel)]="model.power"
                #power="ngForm"
                [ngClass]="setCssValidityClass(power)"
                ngControl="power">
          <option *ngFor="let p of powers" [value]="p">{{p}}</option>
        </select>
      </div>
      <div class="row">
        <div class="col-auto">
          <button [disabled]="!heroForm.form.valid" type="submit" class="btn btn-primary">
            Submit
          </button>
          <button (click)="clear()" type="button" class="btn">
            Clear
          </button>
        </div>
        <small class="col text-right">*&nbsp;Required</small>
      </div>
    </form>
  </div>
  <div [hidden]="!submitted">
    <h1>Hero data</h1>
    <table class="table">
      <tr>
        <th>Name</th>
        <td>{{model.name}}</td>
      </tr>
      <tr>
        <th>Alter Ego</th>
        <td>{{model.alterEgo}}</td>
      </tr>
      <tr>
        <th>Power</th>
        <td>{{model.power}}</td>
      </tr>
    </table>
    <button (click)="submitted=false" class="btn btn-primary">Edit</button>
  </div>
</div>

web/index.html

<!DOCTYPE html>
<html>
  <head>
    <script>
      // WARNING: DO NOT set the <base href> like this in production!
      // Details: https://webdev.dartlang.org/angular/guide/router
      (function () {
        var m = document.location.pathname.match(/^(\/[-\w]+)+\/web($|\/)/);
        document.write('<base href="' + (m ? m[0] : '/') + '" />');
      }());
    </script>
    <title>Hero Form</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet"
          href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css"
          integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M"
          crossorigin="anonymous">
    <link rel="stylesheet" href="styles.css">
    <link rel="icon" type="image/png" href="favicon.png">
    <script defer src="main.dart" type="application/dart"></script>
    <script defer src="packages/browser/dart.js"></script>
  </head>
  <body>
    <my-app>Loading ...</my-app>
  </body>
</html>

web/main.dart

import 'package:angular/angular.dart';
import 'package:forms/app_component.dart';
void main() {
  bootstrap(AppComponent);
}

 

转载于:https://my.oschina.net/u/3647851/blog/1585929

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值