第一个 Angular 项目 - 静态页面

第一个 Angular 项目 - 静态页面

之前的笔记:

这是在学完了上面这三个内容后能够完成的项目,目前因为还没有学到数据在不同组件之前的传输,因此只会有一个 UI 的渲染,而不会涉及到事件的实现

业务需求

wireframe 如下:

简单的说起来就是当前页面需要包含一个 Header,一个部分列举出所有的食谱(recipe),另外一个部分则列举出对应食谱的购物清单(shopping list)

虚线边框框起来的则是我认为的组件构成,将其转化成 Venn Diagram 则如下:

ingridentrecipe 为 Model,也就是 MVVM 中一直没讨论的部分(之前一直讨论的都是 VM 层和 V 层之间的互动)

根据需求也就可以开始下一步的实现了

创建项目

主要通过 bash 实现:

bash 复制代码
❯ ng new recipe-book --no-strict --standalone false --routing false
❯ cd recipe-book
# 在 angular.json 中配置对应的 bootstrap CSS 文件,第一篇笔记中有提
❯ npm i bootstrap@3
❯ ng g c header --skip-tests
❯ ng g c recipes --skip-tests
❯ ng g c recipes/recipe-list --skip-tests
❯ ng g c recipes/recipe-list/recipe-item --skip-tests
❯ ng g c shopping-list --skip-tests
❯ ng g c shopping-list/shopping-edit --skip-tests

# 这是当前目录的结构
❯ tree src/app/
src/app/
├── app.component.css
├── app.component.html
├── app.component.spec.ts
├── app.component.ts
├── app.module.ts
├── header
│   ├── header.component.css
│   ├── header.component.html
│   └── header.component.ts
├── recipes
│   ├── recipe-detail
│   │   ├── recipe-detail.component.css
│   │   ├── recipe-detail.component.html
│   │   └── recipe-detail.component.ts
│   ├── recipe-list
│   │   ├── recipe-item
│   │   │   ├── recipe-item.component.css
│   │   │   ├── recipe-item.component.html
│   │   │   └── recipe-item.component.ts
│   │   ├── recipe-list.component.css
│   │   ├── recipe-list.component.html
│   │   └── recipe-list.component.ts
│   ├── recipes.component.css
│   ├── recipes.component.html
│   └── recipes.component.ts
└── shopping-list
    ├── shopping-edit
    │   ├── shopping-edit.component.css
    │   ├── shopping-edit.component.html
    │   └── shopping-edit.component.ts
    ├── shopping-list.component.css
    ├── shopping-list.component.html
    └── shopping-list.component.ts

8 directories, 26 files

component 的创建主要则是根据上面提到的业务需求进行实现,目前还没有创建对应 Model 对应的文件

实现功能

这里会用 bootstrap 内置的 class 实施不少功能,而 bootstrap 的部分不会细谈,主要还是针对 Angular 的学习

添加骨架

修改的部分为这里的 V 层:

bash 复制代码
src/app/
├── app.component.html

修改内容如下:

html 复制代码
<app-header></app-header>
<div class="container">
  <div class="row">
    <div class="col-md-12">
      <app-recipes></app-recipes>
      <app-shopping-list></app-shopping-list>
    </div>
  </div>
</div>

这里主要提供的是一个结构,并且展示三大组件:header, recipe 和 shopping list

修改的部分为这里的 V 层:

bash 复制代码
src/app/
├── header
│   ├── header.component.html

实现如下:

html 复制代码
<nav class="navbar navbar-default">
  <div class="container-fluid">
    <div class="navbar-header">
      <a href="#" class="navbar-brand">Recipe Book</a>
    </div>

    <div class="collapse navbar-collapse">
      <ul class="nav navbar-nav">
        <li><a href="#">Recipes</a></li>
        <li><a href="#">Shopping List</a></li>
      </ul>
      <ul class="nav navbar-nav navbar-right">
        <li class="dropdown">
          <a
            href="#"
            class="dropdown-toggle"
            data-toggle="dropdown"
            role="button"
            aria-haspopup="true"
            aria-expanded="false"
          >
            Manage <span class="caret"></span>
          </a>
          <ul class="dropdown-menu">
            <li><a href="#">Save Data</a></li>
            <li><a href="#">Fetch Data</a></li>
          </ul>
        </li>
      </ul>
    </div>
  </div>
</nav>

这里全都是 bootstrap 就不多赘述了,实现后效果如下:

recipe

recipe 部分的结构通过上面的文件结构也能看出来了,简化一下如下:

bash 复制代码
├── recipes
│   ├── recipe-detail
│   ├── recipe-list
│   │   ├── recipe-item
│   ├── recipe.model.ts # 即将创建的 model
recipes V 层

这个文件就是 recipes.component.html 这个文件,实现比较简单,只是导入 recipe-list 和当前选中的 recipe-detail:

html 复制代码
<div class="row">
  <div class="col-md-5">
    <app-recipe-list></app-recipe-list>
  </div>
  <div class="col-md-7">
    <app-recipe-detail></app-recipe-detail>
  </div>
</div>

这个会让 recipe-list 和 recipe-detail 出现在同一行

接下来就可以处理细节了

recipe model

这里主要就是定义了 recipe 应该有的数据,实现如下:

ts 复制代码
export class Recipe {
  constructor(
    public name: string,
    public description: string,
    public imagePath: string
  ) {}
}

这代表着 Recipe 对象会有名字、描述和图片三个属性

recipe list VM 层

VM 层目前的逻辑也比较简单,它只需要存储一个 recipes 的数组,让 V 层可以渲染即可,代码如下:

ts 复制代码
import { Component } from '@angular/core';
import { Recipe } from '../recipe.model';

@Component({
  selector: 'app-recipe-list',
  templateUrl: './recipe-list.component.html',
  styleUrl: './recipe-list.component.css',
})
export class RecipeListComponent {
  recipes: Recipe[] = [
    {
      name: 'Recipe 1',
      description: 'Description 1',
      imagePath: 'http://picsum.photos/200/200',
    },
    {
      name: 'Recipe 2',
      description: 'Description 2',
      imagePath: 'http://picsum.photos/200/200',
    },
  ];
}

这里没用 new Recipe() 创建也不会报错,本质上来说 TS 的类型检查是检查数据是否对的上,而不是真的会检查 a instanceof A,而是做 a 有 name, a 有 description, a 有 imagePath -> a 是 A ✅ 这样一个检查

⚠️:picsum.photos 是我在 placeimg.com 上找到的代替网站。placeimg.com 于去年年中正式关站了(🕯️)

recipe list V 层

这一层要做的也比较简单,主要就是跑一个 ngFor 去渲染当前 list 中包含的数据,并且正确的渲染 recipe.name, recipe.descriptionrecipe.imagePath 即可,这里主要用到的还是 string interpolation 和 property binding

实现代码如下:

html 复制代码
<div class="row">
  <div class="col-xs-12">
    <button class="btn btn-success">New Recipe</button>
  </div>
</div>
<div class="row">
  <div class="col-xs-12">
    <a href="#" class="list-group-item clearfix" *ngFor="let recipe of recipes">
      <div class="pull-left">
        <h4 class="list-group-item-heading">{{ recipe.name }}</h4>
        <p class="list-group-item-text">{{ recipe.description }}</p>
      </div>
      <span class="pull-right">
        <img
          [src]="recipe.imagePath"
          [alt]="recipe.name"
          class="image-responsive"
          style="max-height: 50px"
        />
      </span>
    </a>
  </div>

  <app-recipe-item></app-recipe-item>
</div>

完成后的效果:

⚠️:这里的 ngFor + 输出所有的数据在 ngFor 是因为还没有实现跨组件交流,否则直接在 ngFor 中渲染 app-recipe-item,并传递对应对象即可

recipe detail V 层

同样因为跨组件交流还没实现,目前只会渲染一个静态且不会动的 V 层:

html 复制代码
<div class="row">
  <div class="col-sx-12">
    <img [src]="" [alt]="" class="img-responsive" style="max-height: 300px" />
  </div>
</div>
<div class="row">
  <div class="col-xs-12">
    <h1>Recipe Name</h1>
  </div>
</div>
<div class="row">
  <div class="col-xs-12">
    <div class="btn-group">
      <button type="button" class="btn btn-primary dropdown-toggle">
        Manage Recipe <span class="caret"></span>
      </button>
      <ul class="dropdown-menu">
        <li><a href="#">To Shopping List</a></li>
        <li><a href="#">Edit Recipe</a></li>
        <li><a href="#">Delete Recipe</a></li>
      </ul>
    </div>
  </div>
</div>

<div class="row">
  <div class="col-sx-12">Description</div>
</div>

<div class="row">
  <div class="col-sx-12">Ingredients</div>
</div>

完成后效果:

至此 recipe 部分结束

shopping-list

shopping-list 的实现和 recipe 差不多,也是创建 model,随后填充 VM 层

ingredient model

这个 model 在的目录不太一样:

bash 复制代码
src/app/
├── shared
│   └── ingredient.model.ts

个人的话,大概便好创建一个新的 src/app/model 用来存放所有的 model 吧,不过这种有点看个人便好/项目规定了。实现如下:

ts 复制代码
export class Ingredient {
  constructor(public name: string, public amount: number) {}
}
shopping-list VM 层

这个和 recipe VM 层差不多,添加 Ingredient[] 即可

ts 复制代码
import { Component } from '@angular/core';
import { Ingredient } from '../shared/ingredient.model';

@Component({
  selector: 'app-shopping-list',
  templateUrl: './shopping-list.component.html',
  styleUrl: './shopping-list.component.css',
})
export class ShoppingListComponent {
  ingredients: Ingredient[] = [
    new Ingredient('Apples', 5),
    new Ingredient('Tomatoes', 10),
  ];
}
shopping-list V 层

这里的实现也和上面 recipe list 的实现对应:

html 复制代码
<div class="row">
  <div class="col-xs-10">
    <app-shopping-edit></app-shopping-edit>
    <hr />
    <ul class="list-group">
      <a
        class="list-group-item"
        style="cursor: pointer"
        *ngFor="let ingredient of ingredients"
      >
        {{ ingredient.name }} ({{ ingredient.amount }})
      </a>
    </ul>
  </div>
</div>

实现后效果如下:

shopping-list edit V 层

出于同样的原因,这里只有 V 层,实现如下:

html 复制代码
<div class="row">
  <div class="col-xs-12">
    <form>
      <div class="row">
        <div class="col-sm-5 form-group">
          <label for="name">Name</label>
          <input type="text" id="name" class="form-control" />
        </div>
        <div class="col-sm-2 form-group">
          <label for="amount">Amount</label>
          <input type="number" id="amount" class="form-control" />
        </div>
      </div>
      <div class="row">
        <div class="col-xs-12">
          <div class="btn-toolbar">
            <button class="btn btn-success mr-2" type="submit">Add</button>
            <button class="btn btn-danger mr-2" type="button">Delete</button>
            <button class="btn btn-primary" type="button">Edit</button>
          </div>
        </div>
      </div>
    </form>
  </div>
</div>

最终结果:

下个章节开始数据传输之类的,也就是让页面动起来的部分

相关推荐
熊的猫1 小时前
JS 中的类型 & 类型判断 & 类型转换
前端·javascript·vue.js·chrome·react.js·前端框架·node.js
瑶琴AI前端1 小时前
uniapp组件实现省市区三级联动选择
java·前端·uni-app
会发光的猪。1 小时前
如何在vscode中安装git详细新手教程
前端·ide·git·vscode
我要洋人死2 小时前
导航栏及下拉菜单的实现
前端·css·css3
科技探秘人3 小时前
Chrome与火狐哪个浏览器的隐私追踪功能更好
前端·chrome
科技探秘人3 小时前
Chrome与傲游浏览器性能与功能的深度对比
前端·chrome
JerryXZR3 小时前
前端开发中ES6的技术细节二
前端·javascript·es6
七星静香3 小时前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
q2498596933 小时前
前端预览word、excel、ppt
前端·word·excel
小华同学ai3 小时前
wflow-web:开源啦 ,高仿钉钉、飞书、企业微信的审批流程设计器,轻松打造属于你的工作流设计器
前端·钉钉·飞书