使用angular框架时的开发思维

概述

 

做过传统WEB开发的人,应该能够体会到网页里充斥着各种乱七八遭的脚本,比如一个jquery可能就有多个版本,再加上自己写的各种脚本,有时你都不知道一个功能是在哪个脚本里实现的。你只能Ctrol+Shift+F,来个全项目扫描,有时还能扫出多个实现。然后在JS中还有很多对dom的引用,你都不知道这些dom与在什么地方,实现的什么功能。

Angular对JS及页面进行了封装,那一个功能在一个组件就可以完成闭环,而不是东找西找。

Angular给了一个开发思路,比如使用Typescript减少语法错误,代码分层方便代码维护等等。

脚本、CSS及HTML分离

Angular中最小的可直接呈现的UI单元称之为组件,组件本身分为三个部分:

  • HTML模板--HTML标签、点位符、控制指令,用于声明页面要渲染的内容。
  • TypeScript类--表示组件的行为,存储数据
  • CSS--组件的外观

通常用Angular cli命令生成的组件会包含三个对应的文件。

使用 Angular CLI 创建一个组件:

  1. 在终端窗口中,导航到要放置你应用的目录。

  2. 运行 ng generate component <component-name> 命令,其中 <component-name> 是新组件的名字。

默认情况下,该命令会创建以下内容:

  • 一个以该组件命名的文件夹

  • 一个组件文件 <component-name>.component.ts

  • 一个模板文件 <component-name>.component.html

  • 一个 CSS 文件,<component-name>.component.css

  • 测试文件 <component-name>.component.spec.ts

其中 <component-name> 是组件的名称。

比如为了呈现高德地图,创建了一个amap-container的组件,可以看到有三个文件,运行命令:

ng generate component amap-container

 我们把承载地图的标签放到.html文件里,地图创建的代码放到.ts里,容器的大小可以定义到.css文件里。

值得注意的是,一个组件是相对独立的模型。组件中定义的样式,并不会影响别的组件元素,同样ts中的代码也不会影响别的地方,这就是视图封装,组件和组件之间有着明显的边界,相互不影响。这样做的好处是,一方面方便维护代码维护,一个组件可以相对独立的业务逻辑单元,不需要费力处理整个页面程序的冲突问题;另一方面,可以方便团队协作,一个应用可以分派给多个人同时进行开发,相互之间不会造成冲突。

使用绑定分离示图与逻辑

传统的WEB开发中,我们通常直接用脚本控制dom元素来达到我们需要的动态效果。而Angurlar中将dom操作转移到了属性变化上,Angular不推荐你直接操作dom,而是使用绑定机制及控制指令对属性的变化做出反应。

对于绑定,我们可以参考官方的hero示例:

<h2>{{hero.name | uppercase}} Details</h2>
<div><span>id: </span>{{hero.id}}</div>
<div>
  <label for="name">Hero name: </label>
  <input id="name" [(ngModel)]="hero.name" placeholder="name">
</div>

当input的值在改变的时候,h2中的文本也跟着改变。可以看到,视图中仅仅标注了属性绑定,然后可以通过文本域改变属性的值,并没有做其它事情。那是不是在ts文件中做了什么操作吗?我们再下代码:

import { Component, OnInit } from '@angular/core';
import { Hero } from '../hero';

@Component({
  selector: 'app-heroes',
  templateUrl: './heroes.component.html',
  styleUrls: ['./heroes.component.scss']
})
export class HeroesComponent implements OnInit {
  hero: Hero = {
    id: 1,
    name: 'Windstorm'
  };

  constructor() { }

  ngOnInit(): void {
  }

}

可以看到, HeroesComponent也只定义了一个hero属性,也没做其它事情。这其中的奥秘,就是Angular框架在起作用。Angular会生成一个代理,用于监听属性值的变化,一但有变化就会通知视图中的做了绑定的部分,从而是界面发生变化。

通过绑定,我们还可以进行更复杂的视图控制,比如生成值列表,详细页的显示,我们把视图页改成这样:

<h2>My Heroes</h2>
<ul class="heroes">
  <li *ngFor="let hero of heroes">
    <button [class.selected]="hero === selectedHero" type="button" (click)="onSelect(hero)">
      <span class="badge">{{hero.id}}</span>
      <span class="name">{{hero.name}}</span>
    </button>
  </li>
</ul>

<div *ngIf="selectedHero">
  <h2>{{selectedHero.name | uppercase}} Details</h2>
  <div>id: {{selectedHero.id}}</div>
  <div>
    <label for="hero-name">Hero name: </label>
    <input id="hero-name" [(ngModel)]="selectedHero.name" placeholder="name">
  </div>
</div>

 这里头*ngFor是一个指令,用于迭代输出,可以根据属性数组生成相似的条目。*ngIf用于控制内容是否展示。可以看出,这里边也就是简单的循环操作和简单的条件判断,不过,这里多了一个事件绑定,因为按钮不能输入内容,但按钮可以触发事件,然后我们可以将事件通知告诉给ts文件。我们再看下ts文件:

import { Component, OnInit } from '@angular/core';
import { Hero } from '../hero';
import { HEROES } from '../mock-heroes';

@Component({
  selector: 'app-heroes',
  templateUrl: './heroes.component.html',
  styleUrls: ['./heroes.component.scss']
})

export class HeroesComponent implements OnInit {

  heroes = HEROES;
  selectedHero?: Hero;

  constructor() { }

  ngOnInit(): void {
  }

  onSelect(hero: Hero): void {
    this.selectedHero = hero;
  }
}

 这里头也只定义了两个属性,一个heroes数组,一个selectedHero和一个onSelect事件,但在事件中,也只是返回选中的对象,并没有对dom进行任何控制,但视图上详细内容会跟着不同的按钮进行变换。

 利用组件控制模块的粒度

假如一个页面既有导航,又有列表,还要展示详情页,为了避免程序的复杂度,尽量将代码拆成较小的模块,假如将所有代码写一个类里边,那结果可能变得不可控。我们把前边列表页中,用于显示详细内容的部分单独做成一个组件,然后再删除列表页中详细内容的部分,再引用新的组件,先看一下hello-detail的视图部分:

<div *ngIf="hero">

    <h2>{{hero.name | uppercase}} Details</h2>
    <div><span>id: </span>{{hero.id}}</div>
    <div>
      <label for="hero-name">Hero name: </label>
      <input id="hero-name" [(ngModel)]="hero.name" placeholder="name">
    </div>
  
  </div>

再看下脚本部分:

import { Component, OnInit, Input } from '@angular/core';
import { Hero } from '../hero';

@Component({
  selector: 'app-hero-detail',
  templateUrl: './hero-detail.component.html',
  styleUrls: ['./hero-detail.component.scss']
})
export class HeroDetailComponent implements OnInit {
  @Input() hero?: Hero;

  constructor() { }

  ngOnInit(): void {
  }

}

这里要说明的是,要在父组件的模板中对子控件进行赋值,需要加上@Input装饰器。@Input表明属性在模板中是否可配,以区分其它的属性。最后运行的效果,和前边一样。

利用依赖注入分离UI与业务逻辑

界面无关的内容,我们尽量将代码放到Service层中,比如我们向服务端请求一个数据集合,没必要在每个组件里去。比如定义这么一个Service:

import { Injectable } from '@angular/core';

import { Observable, of } from 'rxjs';

import { Hero } from './hero';
import { HEROES } from './mock-heroes';
import { MessageService } from './message.service';

@Injectable({
  providedIn: 'root',
})
export class HeroService {

  constructor(private messageService: MessageService) { }

  getHeroes(): Observable<Hero[]> {
    const heroes = of(HEROES);
    this.messageService.add('HeroService: fetched heroes');
    return heroes;
  }
}

上边加了这么一个装饰器:


@Injectable({
  providedIn: 'root',
})

 这样,就会生成一个全局的唯一实例。然后通过组件的构造函数注入:

  constructor(private heroService: HeroService, private messageService: MessageService) { }

总结

框架在于提供一种开发规范,有了范式后,就可以很容易地对项目进行分割,规则,任务派发。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值