Angular学习笔记(一)

以下内容基于Angular 文档中文版的学习

目录

使用Angular CLI 工具创建项目

HTML标签中{{}}插入值,[]绑定属性,()绑定事件,[(ngModel)]双向绑定

绑定属性

类和样式绑定

事件绑定

双向绑定

循环

IF

定义输入属性

定义输出事件

特殊符号

模板引用变量

页面跳转(路由)

  路由表定义

  路由模块占位

  跳转

  参数接收

  自定义标题策略

  路由守卫

  LocationStrategy 和浏览器的网址样式

  自定义路由匹配器

  用命名出口(outlet)显示多重路由

管道处理

HttpClient

  使用例

  从服务器请求数据

  处理请求错误

  拦截请求和响应

  将元数据传递给拦截器

  跟踪和显示请求进度

  安全:XSRF 防护

异步取值


使用Angular CLI 工具创建项目

  npm install -g @angular/cli@11.2.14
  ng new my-app
  cd my-app
  ng serve --open
  
  ng build 将Angular应用程序编译到输出目录。
  ng serve 构建并提供应用程序,并在修改文件时重新构建。
  ng generate 根据方案生成或修改文件。
     ng generate module app-routing --flat --module=app
       在当前目录生成app-routing模块并注册到AppModule中
     ng generate module heroes/heroes --module app --flat --routing
       在 heroes 目录下创建一个带路由的 HeroesModule,并把它注册到根模块 AppModule 中
     ng generate component product-alerts
     ng generate component customer-dashboard/CustomerDashboard
     ng generate service cart
  ng test 在特定项目上运行单体测试。
  ng e2e 构建并提供Angular应用程序,并运行端到端测试。

HTML标签中{{}}插入值,[]绑定属性,()绑定事件,[(ngModel)]双向绑定

绑定属性

  [property] = "variable
  property = "{{variable}}"
  [attr.property] = "xxx"
  attribute 是由 HTML 定义的。property 是由 DOM (Document Object Model) 定义的。
    少量 HTML attribute 和 property 之间有着 1:1 的映射,如id。
    有些 HTML attribute 没有对应的 property,如rowspan。
    有些 DOM property 没有对应的 attribute,如textContent。
    property: [colSpan]  ,  attribute: [attr.colspan]
  angular不推荐在初始化后更改 attribute 的值,会报警告 。

类和样式绑定

  绑定到单个 CSS class
    [class.sale]="onSale"
      当绑定表达式 onSale 为真值时,Angular 会添加类,当表达式为假值时,它会删除类 —— undefined 除外。
  绑定到多个 CSS 类
    [class]="classExpression"
      表达式可以是以下之一:
        用空格分隔的类名字符串 "my-class-1 my-class-2 my-class-3"
        以类名作为键名并将真或假表达式作为值的对象 {foo: true, bar: false}
        类名的数组 ['foo', 'bar']
      对于任何类似对象的表达式(比如 object、Array、Map 或 Set,必须更改对象的引用,Angular 才能更新类列表。在不更改对象引用的情况下只更新其 Attribute 是不会生效的。
  绑定到单一样式
    <nav [style.background-color]="expression"></nav>
    <nav [style.backgroundColor]="expression"></nav>
  绑定到多个样式
    [style]="styleExpression"
      styleExpression 可以是如下格式之一:
        样式的字符串列表,比如 "width: 100px; height: 100px; background-color: cornflowerblue;"
        一个对象,其键名是样式名,其值是样式值,比如 {width: '100px', height: '100px', backgroundColor: 'cornflowerblue'}
      注意,不支持把数组绑定给 [style]
      当把 [style] 绑定到对象表达式时,该对象的引用必须改变,这样 Angular 才能更新这个类列表。在不改变对象引用的情况下更新其属性值是不会生效的。

事件绑定

  <button (click)="onSave($event)">Save</button>
  <button type="button" (myClick)="clickMessage=$event" clickable>click with myClick</button>
  绑定到键盘事件
    可以指定要绑定到键盘事件的键值或代码。它们的 key 和 code 字段是浏览器键盘事件对象的原生部分。默认情况下,事件绑定假定你要使用键盘事件上的 key 字段。你还可以用 code 字段。
    键的组合可以用点(.)分隔。例如, keydown.enter 将允许你将事件绑定到 enter 键。你还可以用修饰键,例如 shift 、 alt 、 control 和 Mac 中的 command 键。
    <input (keydown.shift.t)="onKeydown($event)" />
    根据操作系统的不同,某些组合键可能会创建特殊字符,而不是你期望的组合键。
    例如,当你同时使用 option 和 shift 键时,MacOS 会创建特殊字符。如果你绑定到 keydown.shift.alt.t ,在 macOS 上,该组合会生成 ˇ 而不是 t ,它与绑定不匹配,也不会触发你的事件处理程序。
    要绑定到 macOS 上的 keydown.shift.alt.t ,请使用 code 键盘事件字段来获取正确的行为。
    <input (keydown.code.shiftleft.altleft.keyt)="onKeydown($event)" />

双向绑定

  Angular 的双向绑定语法是方括号和圆括号的组合 [()]。[] 进行属性绑定,() 进行事件绑定
  为了使双向数据绑定有效,@Output() 属性的名字必须遵循 inputChange 模式,其中 input 是相应 @Input() 属性的名字。比如,如果 @Input() 属性为 size,则 @Output() 属性必须为 sizeChange。
  子:

      @Input()  size!: number | string;
      @Output() sizeChange = new EventEmitter<number>();
      resize(delta: number) {
        this.size = Math.min(40, Math.max(8, +this.size + delta));
        this.sizeChange.emit(this.size);
      }


  父:

<app-sizer [(size)]="fontSizePx"></app-sizer>

      等同于

<app-sizer [size]="fontSizePx" (sizeChange)="fontSizePx=$event"></app-sizer>


  表单中的双向绑定
    因为没有任何原生 HTML 元素遵循了 x 值和 xChange 事件的命名模式,所以与表单元素进行双向绑定需要使用 NgModel

循环

  <div *ngFor="let product of products"></div>

IF

  <p *ngIf="product.description"></p>

定义输入属性

  子:

@Input() product!: Product;

  父:

<app-product-alerts [product]="product"></app-product-alerts>

定义输出事件

  子:

@Output() notify = new EventEmitter();
<button type="button" (click)="notify.emit()">Notify Me</button>

  父:

<app-product-alerts [product]="product" (notify)="onNotify()"></app-product-alerts>

特殊符号

  ?. 用来检查问号前面的变量是否为null或者undefined时,程序不会出错;
     {{currentHero?.name}}
  !. 用来检查感叹号后面的变量为null或者undefined时,程序不会出错
     {{hero!.name}}
  ?: 可以把某个属性声明为可选的。选参数与默认参数一定要放在必选参数之后声明。
     interface Person {
       name: string;
       age?: number;
     }
  !: 断言属性为non-null,non-undefined,开启了strictNullChecks时不会报相应的警告
     hero!: Hero
  $: 声明属性为Observable流对象
     heroes$!: Observable<Hero[]>
  ?? 空值合并运算符,当左侧操作数为 null 或 undefined 时,其返回右侧的操作数,否则返回左侧的操作数。
     const v1 = expr1 ?? expr2
  !! 强制转化为bool值
     let flag1 = !!message

模板引用变量

  Angular 根据你所声明的变量的位置给模板变量赋值:
    如果在组件上声明变量,该变量就会引用该组件实例。
    如果在标准的 HTML 标记上声明变量,该变量就会引用该元素。
    如果你在 <ng-template> 元素上声明变量,该变量就会引用一个 TemplateRef 实例来代表此模板。

    <input #phone placeholder="phone number" />
    <button type="button" (click)="callPhone(phone.value)">Call</button>

  指定名称的变量
    如果该变量在右侧指定了一个名字,比如 #var="ngModel",那么该变量就会引用所在元素上具有这个 exportAs 名字的指令或组件。

    <form #itemForm="ngForm" (ngSubmit)="onSubmit(itemForm)">
      <label for="name">Name</label>
      <input type="text" id="name" class="form-control" name="name" ngModel required />
      <button type="submit">Submit</button>
    </form>
    <div [hidden]="!itemForm.form.valid">
      <p>{{ submitMessage }}</p>
    </div>


  模板输入变量
 

    <ul>
      <ng-template ngFor let-hero let-i="index" [ngForOf]="heroes">
        <li>Hero number {{i}}: {{hero.name}}
      </ng-template>
    </ul>

页面跳转(路由)

  路由表定义

    路由的顺序很重要,因为 Router 在匹配路由时使用“先到先得”策略,所以应该在不那么具体的路由前面放置更具体的路由。
    首先列出静态路径的路由,然后是一个与默认路由匹配的空路径路由。通配符路由是最后一个,因为它匹配每一个 URL,只有当其它路由都没有匹配时,Router 才会选择它。

    RouterModule.forRoot([
      { path: 'productList', component: ProductListComponent, title: 'Product List' },
      { path: 'products/:productId', component: ProductDetailsComponent },
      { path: 'cart', component: CartComponent },
	  { path: 'first-component', component: FirstComponent,
        children: [{ path: 'child-a', component: ChildAComponent },
                   { path: 'child-b', component: ChildBComponent } ]},
	  { path: '',   redirectTo: '/productList', pathMatch: 'full' },
	  { path: '**', component: PageNotFoundComponent },
    ])

  路由模块占位

    <router-outlet></router-outlet>

  跳转

    链接跳转

    <a [routerLink]="['/products', product.id]">{{ product.name }}</a>
    <a [routerLink]="['/heroes', { id: heroId, foo: 'foo' }]">Crisis Center</a>
	  参数对象作为可选参数拼接到URL中,变成/heroes;id=15;foo=foo
    <a routerLink="/cart" routerLinkActive="activebutton" ariaCurrentWhenActive="page">Cart</a>
	  通过添加 routerLinkActive 指令,可以通知你的应用把一个特定的 CSS 类应用到当前的活动路由中。

    代码跳转

	this.router.navigate(['/heroes', { id: heroId, foo: 'foo' }]);
	// 跳转添加信息
	   // ../list?page=1&name=ab#top
	   this.router.navigate(['../list'], {
         relativeTo: this.route,           // 指定要用于相对导航的根 URI
		 queryParams: { page: 1 },         // 将查询参数设置为 URL
		 fragment: 'top',                  // 设置 URL 的哈希片段
		 queryParamsHandling: "merge",     // 如果处理查询参数:preserve :保留当前参数 / merge :将新参数与当前参数合并
		 preserveFragment: false,          // 当为 true 时,为下一个导航保留 URL 片段
       });

  参数接收

    constructor(private route: ActivatedRoute){ }
    ngOnInit() {
	  // 第一种方式:如果不会发生自页面跳转,使用第一次的参数
      const routeParams = this.route.snapshot.paramMap;
      const productIdFromRoute = Number(routeParams.get('productId'));
	  // 第二种方式:会发生自页面跳转,组件会被复用,参数会发生变化,需要使用Observable监视参数变化
	  this.hero$ = this.route.paramMap.pipe(
        switchMap((params: ParamMap) =>
          this.service.getHero(params.get('id')!))
      );
	  // 接收查询参数
      this.sessionId = this.route.queryParamMap.pipe(map(params => params.get('session_id') || 'None'));
      // 接收书签名
      this.token = this.route.fragment.pipe(map(fragment => fragment || 'None'));
    }

  自定义标题策略

    @Injectable({providedIn: 'root'})
    export class TemplatePageTitleStrategy extends TitleStrategy {
      constructor(private readonly title: Title) {
        super();
      }
    
      override updateTitle(routerState: RouterStateSnapshot) {
        const title = this.buildTitle(routerState);
        if (title !== undefined) {
          this.title.setTitle(`My Application | ${title}`);
        }
      }
    }
    
    @NgModule(...
      providers: [
        {provide: TitleStrategy, useClass: TemplatePageTitleStrategy},
      ]
    })

  路由守卫

    使用路由守卫来防止用户未经授权就导航到应用的某些部分。守卫返回一个值,以控制路由器的行为:
      true        导航过程会继续
      false      导航过程就会终止,且用户留在原地。
      UrlTree  取消当前导航,并开始导航到所返回的 UrlTree

    Angular 中提供了以下路由守卫:
      canActivate            需要身份验证
      canActivateChild    保护子路由
      canDeactivate        处理未保存的更改
      canMatch               根据应用程序中的条件控制 Route 匹配
      resolve                   预先获取组件数据
      canLoad                 保护对特性模块的未授权加载

    在分层路由的每个级别上,你都可以设置多个守卫。
    路由器会先按照从最深的子路由由下往上检查的顺序来检查 canDeactivate() 守卫。
    然后它会按照从上到下的顺序检查 canActivate() 守卫。
    如果特性模块是异步加载的,在加载它之前还会检查 canLoad() 守卫。


    除 canMatch 之外,如果任何一个守卫返回 false,其它尚未完成的守卫会被取消,这样整个导航就被取消了。如果 canMatch 守卫返回 false,那么 Router 就会继续处理这些 Routes 的其余部分,以查看是否有别的 Route 配置能匹配此 URL。
 

    canActivate示例

	  export const yourGuard: CanActivateFn = (
        next: ActivatedRouteSnapshot,
        state: RouterStateSnapshot) => {
          // your  logic goes here
      }
	  { path: '/your-path', component: YourComponent, canActivate: [yourGuard]}

       这里是使用的函数,也可以是实现了CanActivate接口的类

  LocationStrategy 和浏览器的网址样式

    路由器通过两种 LocationStrategy 提供者来支持所有这些风格:
      PathLocationStrategy   默认的 “HTML 5 pushState” 风格
      HashLocationStrategy   “hash URL”风格
    RouterModule.forRoot() 函数把 LocationStrategy 设置成了 PathLocationStrategy,使其成为了默认策略。
    你还可以在启动过程中改写(override)它,来切换到 HashLocationStrategy 风格。

      RouterModule.forRoot(routes, { useHash: true })  // .../#/crisis-center/

  自定义路由匹配器

    // 添加匹配器
    RouterModule.forRoot({
      matcher: (url) => {
        if (url.length === 1 && url[0].path.match(/^@[\w]+$/gm)) {
          return {
            consumed: url,
            posParams: {
              username: new UrlSegment(url[0].path.slice(1), {})
            }
          };
        }
        return null;
      },
      component: ProfileComponent
    })
	// ProfileComponent读取参数
    username$ = this.route.paramMap
      .pipe(
        map((params: ParamMap) => params.get('username'))
      );
    // 路由链接
	<a routerLink="/@Angular">my profile</a>

  用命名出口(outlet)显示多重路由

    路由表信息填写outlet名称

      { path: 'compose', component: ComposeMessageComponent, outlet: 'popup' },

    跳转链接

      <a [routerLink]="[{ outlets: { popup: ['compose'] } }]">Contact</a>

    路由占位

      <router-outlet name="popup"></router-outlet>

    跳转后浏览器URL,括号内为第二路由
      http://…/crisis-center(popup:compose)
    清除第二路由的显示内容

      this.router.navigate([{ outlets: { popup: null }}]);

管道处理

  管道是在模板表达式中使用的简单函数,用于接受输入值并返回转换后的值。
  管道用于数据转换,用法:{{xxxx | 管道1:管道参数1 | 管道2:管道参数2}}
  例如:

    {{ product.price | currency }}
    {{ product.price | currency:'CAD':'symbol':'4.2-2':'fr' }}
    {{ dateTime | date:'yyyy-MM-dd HH:mm:ss'}}

  管道操作符要比三目运算符(?:)的优先级高,这意味着 a ? b : c | x 会被解析成 a ? b : (c | x)
  官方管道:
    String -> String
      UpperCasePipe
      LowerCasePipe
      TitleCasePipe
    Number -> String
      DecimalPipe
      PercentPipe
      CurrencyPipe
    Object -> String
      JsonPipe
      DatePipe
    Tools
      SlicePipe
      AsyncPipe
        Async pipe 负责订阅和变化检测,以及在组件被销毁时取消订阅。
        定义:
          shippingCosts!: Observable<{ type: string, price: number }[]>;
        输出:
          <div class="shipping-item" *ngFor="let shipping of shippingCosts | async">

      I18nPluralPipe
      I18nSelectPipe

HttpClient

  使用例

    app.module

      import { HttpClientModule } from '@angular/common/http';
	  @NgModule({
        imports: [
         BrowserModule,
         HttpClientModule,

    服务类

      import { HttpClient } from '@angular/common/http';
	  constructor(private http: HttpClient) {}
	  // 取得列表
      this.http.get<{type: string, price: number}[]>('/assets/shipping.json');
	  httpOptions = {
        headers: new HttpHeaders({ 'Content-Type': 'application/json' })
      };
	  // 更新
	  return this.httpClient.put(this.heroesUrl, hero, this.httpOptions).pipe(
        tap(_ => this.log(`updated hero id=${hero.id}`)),
        catchError(this.handleError<any>('updateHero'))
      );
	  // 添加
      return this.httpClient.post<Hero>(this.heroesUrl, hero, this.httpOptions).pipe(
        tap((newHero: Hero) => this.log(`added hero w/ id=${newHero.id}`)),
        catchError(this.handleError<Hero>('addHero'))
      );
	  // 删除
      return this.httpClient.delete<Hero>(url, this.httpOptions).pipe(
        tap(_ => this.log(`deleted hero id=${id}`)),
        catchError(this.handleError<Hero>('deleteHero'))
      );
	  // 搜索
      return this.httpClient.get<Hero[]>(`${this.heroesUrl}/?name=${term}`).pipe(
        tap(_ => this.log(`found heroes matching "${term}"`)),
        catchError(this.handleError<Hero[]>('searchHeroes', []))
      );
	  // 错误处理
      private handleError<T>(operation = 'operation', result?: T) {
        return (error: any): Observable<T> => {
    
          // TODO: リモート上のロギング基盤にエラーを送信する
          console.error(error); // かわりにconsoleに出力
    
          // TODO: ユーザーへの開示のためにエラーの変換処理を改善する
          this.log(`${operation} failed: ${error.message}`);
    
          // 空の結果を返して、アプリを持続可能にする
          return of(result as T);
        };
      }

  从服务器请求数据

    使用 HttpClient.get() 方法从服务器获取数据。
    该异步方法会发送一个 HTTP 请求,并返回一个 Observable,它会在收到响应时发出所请求到的数据。

    返回的类型取决于你调用时传入的 observe 和 responseType 参数。
    get() 方法有两个参数。要获取的端点 URL,以及一个可以用来配置请求的选项对象。

      options: {
        headers?: HttpHeaders | {[header: string]: string | string[]},
        observe?: 'body' | 'events' | 'response',             // 默认body
        params?: HttpParams|{[param: string]: string | number | boolean | ReadonlyArray<string | number | boolean>},
        reportProgress?: boolean,
        responseType?: 'arraybuffer'|'blob'|'json'|'text',    // 默认json
        withCredentials?: boolean,
      }

    observe 和 response 选项的类型是字符串的联合类型,而不是普通的字符串。
      正确:client.get('/foo', {responseType: 'text'})
      错误:TypeScript 会把 options 的类型推断为 {responseType: string}。
            const options = {
              responseType: 'text',
            };
            client.get('/foo', options)
            
      正确:使用 as const,可以让 TypeScript 知道确实想使用常量字符串类型
            const options = {
              responseType: 'text' as const,
            };
            client.get('/foo', options);
 

    读取完整的响应体

      getConfigResponse(): Observable<HttpResponse<Config>> {
        return this.http.get<Config>(
          this.configUrl, { observe: 'response' });
      }
      this.configService.getConfigResponse().subscribe(resp => {
        // display its headers
        const keys = resp.headers.keys();
        this.headers = keys.map(key =>
          `${key}: ${resp.headers.get(key)}`);
	  
        // access the body directly, which is typed as `Config`.
        this.config = { ...resp.body! };
      });

    发起 JSONP 请求
      在 Angular 中,通过在 NgModule 的 imports 中包含 HttpClientJsonpModule 来使用 JSONP。

        searchHeroes(term: string): Observable {
          term = term.trim();
          const heroesURL = `${this.heroesURL}?${term}`;
          // 'callback'是服务端接收的要包含回调函数名的参数名称
          return this.http.jsonp(heroesUrl, 'callback').pipe(
              catchError(this.handleError('searchHeroes', [])) // then handle the error
            );
        }
        this.searchHeroes('王').subscribe(data => {
		  this.data = data;
		})

  处理请求错误

    private handleError(error: HttpErrorResponse) {
      if (error.status === 0) {
        // 出现客户端或网络错误。相应地处理。
        console.error('An error occurred:', error.error);
      } else {
        // 后端返回了一个不成功的响应代码。
        // 响应主体可能包含出现问题的线索。
        console.error(
          `Backend returned code ${error.status}, body was: `, error.error);
      }
      // Return an observable with a user-facing error message.
      return throwError(() => new Error('Something bad happened; please try again later.'));
    }
    getConfig() {
      return this.http.get<Config>(this.configUrl)
        .pipe(
          retry(3), // 最多重试3次失败的请求
          catchError(this.handleError) // 然后处理错误
        );
    }

  拦截请求和响应

    借助拦截机制,你可以声明一些拦截器,它们可以检查并转换从应用中发给服务器的 HTTP 请求。
    这些拦截器还可以在返回应用的途中检查和转换来自服务器的响应。多个拦截器构成了请求/响应处理器的双向链表。
    拦截器可以用一种常规的、标准的方式对每一次 HTTP 的请求/响应任务执行从认证到记日志等很多种隐式任务。

    Angular 会按你提供拦截器的顺序应用它们。
    请求时:HttpClient->拦截器1->拦截器2...->拦截器N->HttpBackend->服务器
    响应时:HttpClient<-拦截器1<-拦截器2...<-拦截器N<-HttpBackend<-服务器
    
    如果必须修改请求体,请执行以下步骤。
      复制请求体并在副本中进行修改。
      使用 clone() 方法克隆这个请求对象。
      用修改过的副本替换被克隆的请求体。

    示例:

// 设置默认请求头
@Injectable()
export class AuthInterceptor implements HttpInterceptor {

  constructor(private auth: AuthService) {}

  intercept(req: HttpRequest<any>, next: HttpHandler) {
    const authToken = this.auth.getAuthorizationToken();

    // Clone the request and replace the original headers with
    // cloned headers, updated with the authorization.
    const authReq = req.clone({
      headers: req.headers.set('Authorization', authToken)
    });

    return next.handle(authReq);
  }
}
// 请求响应日志
@Injectable()
export class LoggingInterceptor implements HttpInterceptor {
  constructor(private messenger: MessageService) {}

  intercept(req: HttpRequest<any>, next: HttpHandler) {
    const started = Date.now();
    let ok: string;

    // extend server response observable with logging
    return next.handle(req)
      .pipe(
        tap({
          // Succeeds when there is a response; ignore other events
          next: (event) => (ok = event instanceof HttpResponse ? 'succeeded' : ''),
          // Operation failed; error is an HttpErrorResponse
          error: (error) => (ok = 'failed')
        }),
        // Log when response observable either completes or errors
        finalize(() => {
          const elapsed = Date.now() - started;
          const msg = `${req.method} "${req.urlWithParams}" ${ok} in ${elapsed} ms.`;
          this.messenger.add(msg);
        })
      );
  }
}
// 可以在单独文件中引入所有拦截器
export const httpInterceptorProviders = [
  { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
  { provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptor, multi: true },
];
// 加到 AppModule 的 providers array 中
providers: [ httpInterceptorProviders ],

  将元数据传递给拦截器

    HttpClient 请求包含一个上下文,该上下文可以携带有关请求的元数据。
    该上下文可供拦截器读取或修改,尽管发送请求时它并不会传输到后端服务器。
    这允许应用程序或其他拦截器使用配置参数来标记这些请求,比如重试请求的次数。

    创建上下文令牌

      export const RETRY_COUNT = new HttpContextToken(() => 3);

        HttpContextToken 创建期间传递的 lambda 函数 () => 3 有两个用途:
          它允许 TypeScript 推断此令牌的类型:HttpContextToken<number>。这个请求上下文是类型安全的 —— 从请求上下文中读取令牌将返回适当类型的值。
          它会设置令牌的默认值。如果尚未为此令牌设置其他值,那么这就是请求上下文返回的值。使用默认值可以避免检查是否已设置了特定值。


    在发起请求时设置上下文值

      this.httpClient.get('/data/feed', {
        context: new HttpContext().set(RETRY_COUNT, 5),
      }).subscribe(results => {/* ... */});

    在拦截器中读取上下文值

      export class RetryInterceptor implements HttpInterceptor {
        intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
          const retryCount = req.context.get(RETRY_COUNT);
      
          return next.handle(req).pipe(
              // Retry the request a configurable number of times.
              retry(retryCount),
          );
        }
      }

    上下文是可变的(Mutable)

      并且在请求的其他不可变转换过程中仍然存在。这允许拦截器通过此上下文协调来操作。

      export const RETRY_COUNT = new HttpContextToken(() => 3);
      export const ERROR_COUNT = new HttpContextToken(() => 0);
      
      export class RetryInterceptor implements HttpInterceptor {
        intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
          const retryCount = req.context.get(RETRY_COUNT);
      
          return next.handle(req).pipe(
              tap({
                    // An error has occurred, so increment this request's ERROR_COUNT.
                   error: () => req.context.set(ERROR_COUNT, req.context.get(ERROR_COUNT) + 1)
                  }),
              // Retry the request a configurable number of times.
              retry(retryCount),
          );
        }
      }

  跟踪和显示请求进度

    要想发出一个带有进度事件的请求,你可以创建一个 HttpRequest 实例,并把 reportProgress 选项设置为 true 来启用对进度事件的跟踪。

      const req = new HttpRequest('POST', '/upload/file', file, {
        reportProgress: true
      });

      每个进度事件都会触发变更检测,所以只有当需要在 UI 上报告进度时,你才应该开启它们。
      当 HttpClient.request() 和 HTTP 方法一起使用时,可以用 observe: 'events' 来查看所有事件,包括传输的进度。

    接下来,把这个请求对象传给 HttpClient.request() 方法,该方法返回一个 HttpEvents 的 Observable(与 拦截器 部分处理过的事件相同)。

	  return this.http.request(req).pipe(
        map(event => this.getEventMessage(event, file)),
        tap(message => this.showProgress(message)),
        last(), // return last (completed) message to caller
        catchError(this.handleError(file))
      );
	  private getEventMessage(event: HttpEvent<any>, file: File) {
        switch (event.type) {
          case HttpEventType.Sent:
            return `Uploading file "${file.name}" of size ${file.size}.`;
      
          case HttpEventType.UploadProgress:
            // Compute and show the % done:
            const percentDone = event.total ? Math.round(100 * event.loaded / event.total) : 0;
            return `File "${file.name}" is ${percentDone}% uploaded.`;
      
          case HttpEventType.Response:
            return `File "${file.name}" was completely uploaded!`;
      
          default:
            return `File "${file.name}" surprising upload event: ${event.type}.`;
        }
      }

  安全:XSRF 防护

    跨站请求伪造 (XSRF 或 CSRF)是一个攻击技术,它能让攻击者假冒一个已认证的用户在你的网站上执行未知的操作。
    HttpClient 支持一种通用的机制来防范 XSRF 攻击。
    当执行 HTTP 请求时,一个拦截器会从 cookie 中读取 XSRF 标记(默认名字为 XSRF-TOKEN),并且把它设置为一个 HTTP 头 X-XSRF-TOKEN,由于只有运行在你自己的域名下的代码才能读取这个 cookie,因此后端可以确认这个 HTTP 请求真的来自你的客户端应用,而不是攻击者。
    默认情况下,拦截器会在所有的修改型请求中(比如 POST 等)把这个请求头发送给使用相对 URL 的请求。但不会在 GET/HEAD 请求中发送,也不会发送给使用绝对 URL 的请求。

    你的服务器需要在页面加载或首个 GET 请求中把一个名叫 XSRF-TOKEN 的标记写入可被 JavaScript 读到的会话 cookie 中。
    这个标记必须对每个用户都是唯一的,并且必须能被服务器验证,因此不能由客户端自己生成标记。把这个标记设置为你的站点认证信息并且加了盐(salt)的摘要,以提升安全性。
    为了防止多个 Angular 应用共享同一个域名或子域时出现冲突,要给每个应用分配一个唯一的 cookie 名称。

    配置自定义 cookie/header 名称
      如果你的后端服务中对 XSRF 标记的 cookie 或头使用了不一样的名字,就要使用 HttpClientXsrfModule.withOptions() 来覆盖掉默认值。

      imports: [
        HttpClientModule,
        HttpClientXsrfModule.withOptions({
          cookieName: 'My-Xsrf-Cookie',
          headerName: 'My-Xsrf-Header',
        }),
      ],

异步取值

  方法返回Observable,属性定义为正常:

    heroes: Hero[] = [];
    this.heroService.getHeroes().subscribe(heroes => this.heroes = heroes);

  方法返回Observable,属性定义为Observable,输出时使用async管道:

    shippingCosts!: Observable<{ type: string, price: number }[]>;
	this.shippingCosts = this.cartService.getShippingCosts();
	<div class="shipping-item" *ngFor="let shipping of shippingCosts | async">

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值