[Angular 基础] - service 服务

Angular 基础 - service 服务

之前的笔记就列举三个好了......没想到 Angular 东西这么多(ー ー;)......全加感觉越来越凑字数了


Angular 的 service 如果后端出身的应该很熟悉,它是 Angular 自行管理,并使用 Dependency Injection 去实现的一个类。因此它比较合适使用的场景是,多个嵌套组件需要互相沟通,并需要传递值。

举例说明:

bash 复制代码
|- a
|  |- b
|  |  |- d
|  |- c
|  |  |- e

这个情况下,a 如果需要和 de 进行沟通的话,那么

  • bc 也需要通过 @Input 去获取从 a 传来的值,并将其传到 de 中去;
  • bc 也需要通过 @Output 去获取从 de 传来的事件,并将其传到 a 中去

这就是一个不可避免的沟通环节。

使用 service 就可以比较有效的解决这个问题

创建一个新的案例

这个案例相对比较简单,就是按照上面的结构创建一个项目。在这个简单的案例里,bc 没有任何作用,只是作为 a <--> da <--> e 之间的承接桥梁。在真实的项目中,bc 的作用可能会包括一些数据处理、选择渲染之类的。

项目结构如下:

bash 复制代码
❯ tree src/app/
src/app/
├── app.component.css
├── app.component.html
├── app.component.spec.ts
├── app.component.ts
├── app.module.ts
├── b
│   ├── b.component.css
│   ├── b.component.html
│   ├── b.component.ts
│   └── d
│       ├── d.component.css
│       ├── d.component.html
│       └── d.component.ts
└── c
    ├── c.component.css
    ├── c.component.html
    ├── c.component.ts
    └── e
        ├── e.component.css
        ├── e.component.html
        └── e.component.ts

5 directories, 17 files

a 的实现

这里主要还是传值+绑定事件,具体内容在 Angular 基础 - 自定义事件 & 自定义属性 里,这里就不多做赘述,直接放代码了:

  • V 层

    html 复制代码
    <div class="container">
      <div class="row">
        <div class="col-xs-12 col-md-8 col-md-offset-2">
          <app-b [message]="aToD" (messageFromB)="onRecieveMessageFromB"></app-b>
          <app-c [message]="aToE"></app-c>
        </div>
      </div>
    </div>
  • VM 层:

    typescript 复制代码
    import { Component, EventEmitter, OnInit, Output } from '@angular/core';
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css'],
    })
    export class AppComponent {
      aToD = 'message from a to d';
      aToE = 'message from a to e';
    
      @Output() messageFromB = new EventEmitter<string>();
    
      onRecieveMessageFromB($event: string): void {
        this.aToD = $event;
        console.log('message from b to a: ', $event);
      }
    }

b 的实现

实现基本和 a 一致,这里也就放代码了:

  • V 层

    html 复制代码
    <div class="">
      <app-d [message]="message" (messageToB)="onRecieveMessage($event)"></app-d>
    </div>
  • VM 层

    typescript 复制代码
    import {
      Component,
      EventEmitter,
      Input,
      OnInit,
      Output,
    } from '@angular/core';
    
    @Component({
      selector: 'app-b',
      templateUrl: './b.component.html',
      styleUrl: './b.component.css',
    })
    export class BComponent implements OnInit {
      @Input() message: string;
      @Output() messageToA = new EventEmitter<string>();
    
      ngOnInit(): void {}
    
      onRecieveMessage($event: string): void {
        this.message = $event;
        this.messageToA.emit(this.message);
        console.log('message from b to a: ', this.message);
      }
    }

d 的实现

  • V 层

    html 复制代码
    <input type="text" [value]="message" (input)="onChangeText($event)" />
  • VM 层

    typescript 复制代码
    import {
      Component,
      EventEmitter,
      Input,
      OnInit,
      Output,
    } from '@angular/core';
    
    @Component({
      selector: 'app-d',
      templateUrl: './d.component.html',
      styleUrl: './d.component.css',
    })
    export class DComponent implements OnInit {
      @Input() message: string;
      @Output() messageToB = new EventEmitter<string>();
    
      ngOnInit(): void {}
    
      onChangeText($event: Event): void {
        this.message = ($event.target as HTMLInputElement).value;
        this.messageToB.emit(this.message);
        console.log('message from d to b: ', this.message);
      }
    }

最后实现效果如下:

如果说 React 只是将 onChangeHandler 一个个向子组件里传递,做 props drilling,那么 Angular 除了要在 HTML Template 中传值之外,还需要在组件中实现 @Input@Output 去接受从父组件中传下来的值,并且将事件送到父组件中,对比起来操作更加的麻烦

使用 service 代替

这里使用 service 代替上下传递 @Input@Outpu 进行实现

创建 service

这里依旧使用 cli 去创建 service:

bash 复制代码
❯ ng generate service services/message --skip-tests
CREATE src/app/services/message.service.ts (136 bytes)

此时结构如下:

实现如下:

typescript 复制代码
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root',
})
export class MessageService {
  passedMessage = 'message from a to e';

  constructor() {}

  updateMessage(msg: string) {
    this.passedMessage = msg;
  }
}

具体实现会在下一个 section 说明

调用 service

调用方式是在构造函数中让 Angular 自动使用 dependency injection 实现

a 的修改:
typescript 复制代码
export class AppComponent {
  // 这里的 dependency injection 是由 angular 实现的
  constructor(private messageService: MessageService) {}
}
c 的实现
typescript 复制代码
import { Component, DoCheck, Input } from '@angular/core';
import { MessageService } from '../services/message.service';

@Component({
  selector: 'app-c',
  templateUrl: './c.component.html',
  styleUrl: './c.component.css',
})
export class CComponent implements DoCheck {
  message: string;

  constructor(private messageService: MessageService) {
    this.message = this.messageService.passedMessage;
  }

  ngDoCheck(): void {
    console.log(this.messageService.passedMessage);
  }
}

HTML Template 中只需要渲染一个 e 即可:

html 复制代码
<app-e></app-e>

⚠️:这里主要是 log 一下 service 中变化的值。因为 message 是一个 primitive,所以想要正确的获取 message 的变化是要使用 Observable 的,目前暂时没有涉及到这个部分,因此只是在 ngDoCheck 中输出一下值,表示当前的变化已经被获取了

e 的实现
typescript 复制代码
import { Component, Input } from '@angular/core';
import { MessageService } from '../../services/message.service';

@Component({
  selector: 'app-e',
  templateUrl: './e.component.html',
  styleUrl: './e.component.css',
})
export class EComponent {
  message: string;

  constructor(private messageService: MessageService) {
    this.message = this.messageService.passedMessage;
  }

  onChangeText($event: Event): void {
    this.messageService.updateMessage((<HTMLInputElement>$event.target).value);
  }
}

最终效果:

可以看到,对比 a <--> b <--> d 的沟通, a <--> c <--> e 中使用 service 更加的简洁

深入了解 service

Injectable

这个 decorator 在新版的 Angular 是推荐每个 service 都放上,现在默认使用 cli 就会自动带上 Injectable

providedIn 则是挂载的范围,默认情况下挂载的范围是全局。换言之所有的 component 都共享一个 singleton。如果将 providedIn 删除的话,那么 Angular 就可以创建多个 instance

多个 instance & providers

这里首先需要将 Injectable 中的 providedIn 去掉,只保留 @Injectable 这个 decorator 或者去除都行------新版 Angular 是推荐保留 decorator 的

随后需要修改 @Component decorator,这里是修改 B/C 两个组件中的 decorator:

typescript 复制代码
@Component({
  selector: 'app-b',
  templateUrl: './b.component.html',
  styleUrl: './b.component.css',
  providers: [MessageService],
})

这样当前 component 及其后代 component 都会共享同一个 service:

⚠️:这里页面显示的(d/e 从 MessageService 中接受的信息)与 log 中是一致的

如果修改 d/e decorator 中的 providers 的话,d/e 二者也会有自己的 service instance:

⚠️:这里页面显示的(d/e 从 MessageService 中接受的信息)与 log 中是不一致的

这是因为 providers 是 Angular 接受参数用来配置 Dependency Injection 的地方,提供值就会新建一个新的 instance。因此如果想要组件内共享同一个 service 的话,就需要在最近祖先节点修改对应的 providers

👀:传的信息内容我通过 Faker 的随机 lorem 生成,所以每个 service 会不一样

service 注入 service

我这里的实现是两个 service 都会有 @Injectable 这个装饰器,这样的实现会方便一些。MessageService 的实现基本不变,需要修改的就是在构造函数内,通过依赖注入绑定一个 LoggingService,修改如下:

typescript 复制代码
import { Injectable } from '@angular/core';
import { faker } from '@faker-js/faker';
import { LoggingService } from './logging.service';

@Injectable()
export class MessageService {
  passedMessage = faker.lorem.sentence();

  constructor(private loggingService: LoggingService) {
    this.loggingService.logMessage(
      'MessageService constructor created message to ' + this.passedMessage
    );
  }

  updateMessage(msg: string) {
    this.passedMessage = msg;
    this.loggingService.logMessage('MessageService updated message to ' + msg);
  }
}

LoggingService 则是一个实现了输出信息的 service:

typescript 复制代码
import { Injectable } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class LoggingService {
  constructor() {}

  logMessage(msg: string) {
    console.log(`${msg} received at ${new Date().toLocaleTimeString()}`);
  }
}

这样每次当 MessageService 被实例化和变动的时候,都会调用一次输出日志方法:

services 的应用场景

根据案例可以看出来,它可以实现以下几个功能:

  • 数据共享

    不用使用 @Input 进行不同层级的数据传递

  • 状态管理

    这个作用和 React 的 Context 有点相似,在层级内控制状态,并且通过状态进行数据和组件的对应渲染

  • API 交互

    HTTP 请求的抽象实现,比如说实现一个 API 层级的 CRUD 封装,这样所有的组件都可以较为方便的调用

  • 业务逻辑实现

    也是属于功能的一种抽象,如果某些功能不是特定属于几个组件内,那么就可以将其抽离出来进行共享

  • util

    也是属于功能的一种抽象,如果某些功能不是特定属于几个组件内,那么就可以将其抽离出来进行共享

    其中一个例子就是上面实现的 logging util

相关推荐
大爱一家盟3 分钟前
告别卡点BGM同质化 2026原创卡点音乐素材下载网站 TOP5 推荐
大数据·前端·人工智能
彦为君6 分钟前
算法思维与经典智力题
java·前端·redis·算法
aa小小43 分钟前
localhost 访问异常排查笔记
前端
格子软件43 分钟前
2026年GEO优化系统源码的分布式状态机深度拆解
java·前端·vue.js·vue·geo
陈随易1 小时前
Rust、Golang、MoonBit 编译成 WASM,体积和速度差距有多大?
前端·后端·程序员
IT_陈寒1 小时前
Python多线程的坑,我居然现在才踩到
前端·人工智能·后端
摇滚侠1 小时前
方法 A 等方法 B 执行完再执行 叫同步调用还是异步调用 JS 默认是同步调用还是异步调用
开发语言·javascript·ecmascript
触底反弹2 小时前
🔥 字符串算法面试三连击:反转、回文、回文变种,搞懂这三题稳了!
前端·javascript·算法
触底反弹2 小时前
AI Tool Use 深度解析:大模型是如何"突破物理限制"调用外部工具的?
javascript·人工智能·后端
starrysky8102 小时前
MemAvailable 还有 29GB,系统却报内存压力?——Ubuntu 24.04 CIFS 内核 Page Cache 泄漏排查实录
angular.js