[Angular 基础] - service 服务

[Angular 基础] - service 服务

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

  • [Angular 基础] - 视图封装 & 局部引用 & 父子组件中内容传递

  • [Angular 基础] - 生命周期函数

  • [Angular 基础] - 自定义指令,深入学习 directive


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

举例说明:

|- a
|  |- b
|  |  |- d
|  |- c
|  |  |- e

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

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

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

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

创建一个新的案例

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

项目结构如下:

❯ 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 层

    <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 层:

    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 层

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

    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 层

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

    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:

❯ ng generate service services/message --skip-tests
CREATE src/app/services/message.service.ts (136 bytes)

此时结构如下:

在这里插入图片描述

实现如下:

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 的修改:
export class AppComponent {
  // 这里的 dependency injection 是由 angular 实现的
  constructor(private messageService: MessageService) {}
}
c 的实现
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 即可:

<app-e></app-e>

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

e 的实现
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:

@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,修改如下:

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:

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/400917.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Android T 远程动画显示流程其二——动画的添加流程(更新中)

前言 接着上篇文章分析 Android T 远程动画显示流程其一 切入点——处理应用的显示过渡 下面&#xff0c;我们以从桌面点击一个应用启动的场景来分析远程动画的流程&#xff0c;窗口添加的流程见Android T WMS窗口相关流程 这里我们从AppTransitionController.handleAppTran…

量子加密机的工作原理是什么

量子加密机&#xff0c;作为现代加密技术的一大飞跃&#xff0c;正逐渐成为信息安全领域的研究热点。与传统的加密方法相比&#xff0c;量子加密技术以其独特的优势&#xff0c;为信息安全提供了更为坚实的保障。 量子加密的核心在于利用量子力学的特性&#xff0c;尤其是量子纠…

sizeof()的易错点

你也可以传入一个变量的名字&#xff08;而不只是类型&#xff09;给 sizeof()&#xff0c;但在一些情况下&#xff0c;可能得不到你要的结果&#xff0c;所以要小心使用。例如&#xff0c;看看下面的代码片段&#xff1a; 在第一行&#xff0c;我们为 10 个整数的数组声明了空…

LInux-信号1

文章目录 前言一、信号是什么&#xff1f;二、学习步骤使用kill -l命令查看信号列表可以看到有那么多信号&#xff0c;那么进程是如何识别这么多信号的呢&#xff1f; 使用kill命令终止进程信号的捕捉kill函数raise函数abort函数 Core dump如何查看自己的核心转储功能是否被打开…

C++运算符重载、迭代器、继承、派生类的构造与析构、重载、隐藏和覆盖

运算符重载 普通的C运算符重载成员方法基本都知道&#xff0c;如果没有定义成员方法&#xff0c;那么编译器会优先寻找全局重载运算符看看是否匹配。这里给出了一个使用友元函数和全局重载运算符来实现整数与复数类对象的相加运算。 #include <iostream>class Complex …

Flink双流(join)

一、介绍 Join大体分类只有两种&#xff1a;Window Join和Interval Join Window Join有可以根据Window的类型细分出3种&#xff1a;Tumbling(滚动) Window Join、Sliding(滑动) Window Join、Session(会话) Widnow Join。 &#x1f338;Window 类型的join都是利用window的机制…

蜂鸣器实验

1.有源蜂鸣器简介 蜂鸣器常用于计算机、打印机、报警器、电子玩具等电子产品中&#xff0c;常用的蜂鸣器有两种&#xff1a; 有源蜂鸣器和无源蜂鸣器&#xff0c;这里的有“源”不是电源&#xff0c;而是震荡源&#xff0c;有源蜂鸣器内部带有震荡 源&#xff0c;所以有源蜂…

OD(8)之Mermaid流程图(flowcharts)使用详解

OD(8)之Mermaid流程图(flowcharts)使用详解 Author: Once Day Date: 2024年2月20日 漫漫长路才刚刚开始… 全系列文章可参考专栏: Linux实践记录_Once_day的博客-CSDN博客 参考文章: 关于 Mermaid | Mermaid 中文网 (nodejs.cn)Mermaid | Diagramming and charting tool‍…

Maven 私服 Nexus3

一、Maven和Nexus3 简介 Maven是一个采用纯Java编写的开源项目管理工具&#xff0c;采用一种被称之为Project Object Model(POM)概念来管理项目&#xff0c;所有的项目配置信息都被定义在一个叫做POM.xml的文件中, 通过该文件Maven可以管理项目的整个生命周期&#xff0c;包括…

maven 打包命令

Maven是基于项目对象模型(POM project object model)&#xff0c;可以通过一小段描述信息&#xff08;配置&#xff09;来管理项目的构建&#xff0c;报告和文档的软件项目管理工具。 Maven的核心功能便是合理叙述项目间的依赖关系&#xff0c;通俗点讲&#xff0c;就是通过po…

06 分频器设计

分频器简介 实现分频一般有两种方法&#xff0c;一种方法是直接使用 PLL 进行分频&#xff0c;比如在 FPGA 或者 ASIC 设计中&#xff0c;都可以直接使用 PLL 进行分频。但是这种分频有时候受限于 PLL 本身的特性&#xff0c;无法得到频率很低的时钟信号&#xff0c;比如输入 …

Predis Multi-Zone

A Data Flow Framework with High Throughput and Low Latency for Permissioned Blockchains 联盟链的吞吐瓶颈由共识层和网络层的数据分发过程共同决定。 Predis 协议利用了共识节点的空闲带宽&#xff0c;提前分发区块中的内容即bundle&#xff0c;减少了共识区块中的内容&…

2024龙年特别篇 -- 魔法指针 之 assert断言 传址调用 传值调用

你是否为 assert断言&#xff0c;传址调用&#xff0c;传值调用而进一步加深印象&#xff0c;接下来就让白子寰同学为你详细讲解&#xff01;&#xff01;&#xff01; 目录 assert断言 概念 assert介绍 #define NDEBUG的使用 注意事项 传值调用 VS 传址调用 传值调用…

C语言推荐书籍

本书详细讲解了C语言的基本概念和编程技巧。全书共17章。第1章、第2章介绍了C语言编程的预备知识。第3章&#xff5e;第15章详细讲解了C语言的相关知识&#xff0c;包括数据类型、格式化输入/输出、运算符、表达式、语句、循环、字符输入和输出、函数、数组和指针、字符和字符串…

CS_Smb_Beacon上线不出网机器

当我们想上线不出网的机器的时候&#xff0c;我们可以通过上传工具来实现&#xff0c;但是有没有不用上传工具的方法呢&#xff1f;&#xff1f;&#xff1f; 有&#xff01;&#xff01;&#xff01; 而且cs会自带&#xff01;&#xff01;&#xff01; 1.基础的网络拓扑 以下…

MYSQL数据库详解

一、数据库的基本概念 数据&#xff08;data&#xff09;&#xff1a;指对客观事物进行描述并可以鉴别的符号。这些符号是可识别的&#xff0c;抽象的。 比如数字、图片、音频等。 数据库管理系统&#xff08;DBMS&#xff09;&#xff1a;数据库极其管理它的软件组成。 数据库…

RocketMQ-架构与设计

RocketMQ架构与设计 一、简介二、框架概述1.设计特点 三、架构图1.Producer2.Consumer3.NameServer4.BrokerServer 四、基本特性1.消息顺序性1.1 全局顺序1.2 分区顺序 2.消息回溯3.消息重投4.消息重试5.延迟队列&#xff08;定时消息&#xff09;6.重试队列7.死信队列8.消息语…

2.21C语言学习

Floyd算法原理 Floyd算法是一个经典的动态规划算法&#xff0c;它又被称为插点法。该算法名称以创始人之一、1978年图灵奖获得者、斯坦福大学计算机科学系教授罗伯特弗洛伊德命名。Floyd算法是一种利用动态规划的思想寻找给定的加权图中多源点之间最短路径的算法,算法目标是寻找…

工厂方法模式Factory Method

1.模式定义 定义一个用于创建对象的接口&#xff0c;让子类决定实例化哪一个类。Factory Method 使得一个类的实例化延迟到子类 2.使用场景 1.当你不知道改使用对象的确切类型的时候 2.当你希望为库或框架提供扩展其内部组件的方法时 主要优点&#xff1a; 1.将具体产品和创建…

解决NPE的三种方式

解决NPE的三种方式 NullPointerException&#xff08;空指针异常&#xff0c;NPE&#xff09;是Java编程中常见的错误。解决NPE的方法可以从以下三个方面考虑&#xff1a; 明确处理空引用情况&#xff1a; 在某些情况下&#xff0c;无法避免使用可能为空的引用对象。此时&…