4

延迟视图

@defer 是 Angular v16 引入的一项功能,用于优化组件的加载和渲染。它提供了一种延迟加载组件的方法,可以减少初始渲染时的开销,延迟加载(Lazy Loading)主要是通过路由实现的。通过路由配置中的 loadChildren 属性,可以在需要时加载特定的模块。这种方式适合处理大型应用程序中的模块划分问题,但不能直接用于组件级别的延迟加载。随着angular的发展,在angular v16开始引入了@defer,延迟加载不仅可以用于路由模块。也可以用户组件和模版内容。

传统的懒加载(Routing 模块懒加载)

懒加载通过 AppRoutingModule 配置,使用 loadChildren 延迟加载模块。

配置路由懒加载

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

const routes: Routes = [
  {
    path: 'dashboard',
    loadChildren: () =>
      import('./dashboard/dashboard.module').then(m => m.DashboardModule),
  },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
})
export class AppRoutingModule {}

Dashboard模块

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import {DashboardComponent} from './dashboard.component';

const routes: Routes = [
  {
    path: '',
    component: DashboardComponent
  },
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class DashboardModule { }

当前用户访问/dashboard路径的时候,DashboardModule 才会被加载,而不是在应用初始化时全部加载。

存在的问题

  1. 组件延迟加载:比如页面上有一个复杂的组件,想要当用户交户的时候再进行加载
  2. 部分模块加载:如果模块中有些片段需要根据条件或事件动态加载

@defer 的出现

Angular v16引入@defer增加延迟加载的场景,使得组件、管道、指令都可以进行按需加载

简单示例

ChoiceQuestionComponent 组件

import {Component, OnInit} from '@angular/core';
import {CommonModule} from "@angular/common";

@Component({
  selector: 'app-choice-question',
  template: '<div class="card-shadow">
              <p>choice-question.component被加载成功</p>
            </div>',
  standalone: true,
  styleUrls: ['./choice-question.component.css']
})
export class ChoiceQuestionComponent {
}

AppComponent组件

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterModule,ChoiceQuestionComponent],
  templateUrl: './app.component.html',
  styleUrl: './app.component.css'
})

export class AppComponent {
  title = 'h5';
  name = 'yunzhi';
}
@defer (when name === "yunzhi") {
  <app-choice-question></app-choice-question>
} @placeholder {
  <div>displayed until name is not equal to yunzhi</div>
} @loading(after 1ms; minimum 3s) {
  <div> Loading content...</div>
}

当name如何等于yunzhi,就进行加载当前组件

image.png

不满足条件走@placeholder

name = 'angular';

image.png

@loading触发条件,当前组件需要进行加载一段时间的时候可以使用@loading实现加载效果

@loading(after 1ms; minimum 3s) {
  <div> Loading content...</div>
}

after 1ms:
指定加载状态(占位符)在延迟多久后开始显示。
1ms 表示如果加载内容未完成,占位符会在 1 毫秒后显示。
如果在 1ms 内内容加载完成,则不会显示占位符(因为加载速度足够快)。

minimum 3s:
指定占位符至少显示的时间长度。
即使内容加载在 1 秒内完成,占位符也会继续显示 至少 3 秒,以避免加载完成后界面快速跳转而影响用户体验。

ChoiceQuestionComponent 组件

设置setTimeout,10秒过后把dataLoaded设置为true,渲染视图

<div @ngif="dataLoaded" class="card-shadow">
   <p>choice-question.component被加载成功</p>
</div>

export class ChoiceQuestionComponent implements OnInit {
  dataLoaded = false;

  ngOnInit() {
    setTimeout(() => {
      this.dataLoaded = true;
    }, 10000);
  }

image.png

到这里简单的用法已经介绍完成

On指定触发@defer块的条件。

触发器描述
idle在浏览器空闲时触发。
viewport当指定的内容进入视口时触发。
interaction当用户与指定元素交互时触发。
hover当鼠标悬停在指定区域时触发。
immediate在非延迟内容渲染完成后立即触发。
timer在指定的时间间隔后触发。

1.idle

on idle:表示在浏览器空闲时才加载

@defer (on idle) {
  <app-choice-question></app-choice-question>
} @placeholder {
  <div>on 用法案例</div>
}

实际使用场景

适合延迟加载大型组件,是一个占用较多资源的组件,可以使用 @defer 在空闲时加载。

2.interaction

点击占位符进行触发

@defer (on interaction) {
  <app-choice-question></app-choice-question>
} @placeholder {
  <div>on 用法案例</div>
}

image.png

image.png

或者,您可以在与@defer块相同的模板中指定一个模板引用变量,作为被监视以进入视口的元素,点击后进行触发

<div #greeting>on 用法案例</div>
@defer (on interaction(greeting)) {
  <app-choice-question></app-choice-question>
}

image.png

3.hover

当鼠标悬停在指定区域时触发

// 用法一
@defer (on hover) {
  <app-choice-question></app-choice-question>
} @placeholder {
  <div>on 用法案例</div>
}

或者采用模板引用变量的用法

// 用法二

<div #greeting>on 用法案例</div>
@defer (on hover(greeting)) {
  <app-choice-question></app-choice-question>
} 

image.png

在此就接介绍这几种使用方法, 其他用法可以参考官网的写法

https://v18.angular.dev/guide/templates/defer#

弃用HttpClientModule

自从 Angular 14 版本引入独立组件以来,模块在 Angular 中已经变得可选。现在我们看到了第一个被弃用的模块:HttpClientModule。

以前用法

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    HttpClientModule,
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

现在的用法

HttpClient是使用provideHttpClient helper函数提供的,目前app.config.ts中包含了这个函数。

bootstrapApplication(AppComponent, {
  providers: [
    provideHttpClient()
  ]
});

如果使用ngModule的方式定义

@NgModule({
  providers: [
    provideHttpClient(),
  ],
})
export class AppModule {}

支持配置HttpClient请求功能

provideHttpClient可以用于配置和提供 HTTP 客户端服务

withFetch 功能

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(
      withFetch(),
    ),
  ]
};

在默认情况下,Angular 的 HttpClient 使用的是 XMLHttpRequest API 来发起 HTTP 请求,现在可以通过设置withFetch配置,切换尾fetch API的方式

配置 HTTP 客户端拦截器的两种方法

1. withInterceptors(...)

withInterceptors(...) 配置函数式拦截器的方式,这些拦截器将在通过 HttpClient 发出的请求中依次执行,函数接受请求对象 (HttpRequest) 和下一个处理函数 (HttpHandler),并返回请求或响应的修改,不需要引入额外的类和复杂的依赖注入

定义函数式拦截器

export function loggingInterceptor(req: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>> {
  console.log(req.url);
  return next(req);
}

配置withInterceptors拦截器

bootstrapApplication(AppComponent, {providers: [
  provideHttpClient(
    withInterceptors([loggingInterceptor,cachingInterceptor]),
  )
]});

拦截器按照您在提供程序中列出的顺序链接在一起。在上面的示例中,loggingInterceptor将处理请求,然后将其转发到cachingInterceptor,也就是按照当前loggingInterceptor拦截器执行完,就执行cachingInterceptor拦截器

2.withInterceptorsFromDi() 基于DI注入的拦截器

withInterceptorsFromDi 使用 基于类的拦截器,这些拦截器是通过 Angular 的依赖注入(DI)机制进行管理的,每个拦截器类需要实现 HttpInterceptor 接口,并定义 intercept() 方法来处理请求和响应。

@Injectable()
export class LoggingInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<any>, handler: HttpHandler): Observable<HttpEvent<any>> {
    console.log('Request URL: ' + req.url);
    return handler.handle(req);
  }
}

基于withInterceptorsFromDi是通过依赖注入多提供商配置的:

bootstrapApplication(AppComponent, {providers: [
  provideHttpClient(
    withInterceptorsFromDi(),
  ),
  {provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptor, multi: true},
]});

两者的区别

withInterceptorswithInterceptorsFromDi
函数式拦截器类式拦截器(通过依赖注入管理)
传递一个或多个拦截器函数从依赖注入(DI)容器中加载拦截器类
适合简单、快速实现的需求适合复杂的业务逻辑或依赖注入的情况,如需要使用 Angular 服务时

总结

总体来说变好还是挺大的,看的出angular已经想要逐步移除模块,很多写法都已经进行了变更,不像17之前的版本,总体变更不是很大,但是到了17之后语法大部分已经变更,这需要进行重新学习,也不能使用历史的开发规范来使用当前版本的用法,其他新语法还需要多看官网进行学习。

https://v18.angular.dev/


kexb
519 声望18 粉丝