当前位置: 首页 > news >正文

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#topthis.router.navigate(['../list'], {relativeTo: this.route,           // 指定要用于相对导航的根 URIqueryParams: { page: 1 },         // 将查询参数设置为 URLfragment: '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',             // 默认bodyparams?: HttpParams|{[param: string]: string | number | boolean | ReadonlyArray<string | number | boolean>},reportProgress?: boolean,responseType?: 'arraybuffer'|'blob'|'json'|'text',    // 默认jsonwithCredentials?: 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 headersconst 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 loggingreturn next.handle(req).pipe(tap({// Succeeds when there is a response; ignore other eventsnext: (event) => (ok = event instanceof HttpResponse ? 'succeeded' : ''),// Operation failed; error is an HttpErrorResponseerror: (error) => (ok = 'failed')}),// Log when response observable either completes or errorsfinalize(() => {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 callercatchError(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">

相关文章:

Angular学习笔记(一)

以下内容基于Angular 文档中文版的学习 目录 使用Angular CLI 工具创建项目 HTML标签中{{}}插入值,[]绑定属性,()绑定事件,[(ngModel)]双向绑定 绑定属性 类和样式绑定 事件绑定 双向绑定 循环 IF 定义输入属性 定义输出事件 特殊符号 模板引用变量 页面跳转(路由…...

Linux用户和权限 —— 操作演示

Linux用户和权限——操作演示认知root用户用户、用户组管理查看权限控制修改权限控制- chmod修改权限控制- chownLinux系列&#xff1a; Linux基本命令 —— 操作演示 认知root用户 root用户(超级管理员) 无论是Windows、MacOS、Linux均采用多用户的管理模式进行权限管理。…...

【华为OD机试真题2023 JAVA】单核CPU任务调度

华为OD机试真题,2023年度机试题库全覆盖,刷题指南点这里 单核CPU任务调度 知识点队列优先级队列 时间限制:1s 空间限制:256MB 限定语言:不限 题目描述: 现在有一个CPU和一些任务需要处理,已提前获知每个任务的任务ID、优先级、所需执行时间和到达时间。 CPU同时只…...

News乐鑫科技亮相德国嵌入式展 Embedded World 2023!

3 月 14 日&#xff0c;德国纽伦堡嵌入式展 Embedded World 2023 火热启幕。本届 Embedded World 主题为 “embedded. responsible. sustainable”&#xff0c;乐鑫科技 (688018.SH) 携众多 AIoT 科技成果亮相展会&#xff0c;致力于打造更智能、更互联、更绿色的物联网未来。…...

java如何创建线程

java如何创建线程1. java如何创建线程1.1 通过继承Thread类来创建线程1.2 通过实现Runnable接口来创建线程1.3 通过匿名内部类来创建线程1.4 lambda表达式1.5 通过实现Runnable接口的方式创建线程目标类的优缺点1. java如何创建线程 一个线程在Java中使用一个Thread实例来描述…...

要是早看到这篇文章,你起码少走3年弯路,20年老程序员的忠告

文章目录前言一、程序员的薪资是怎么样的&#xff1f;二、我现在的情况适合做程序员吗&#xff1f;三、大学期间到底应该学些什么&#xff1f;四、工作还是考研&#xff1f;五、总结前言 我是龙叔&#xff0c;一名工作了20多年的退休老程序员。 如果你在工作之前看到这篇文章…...

IP地址的分类

1. 前言 最初设计互联网络时&#xff0c;为了便于寻址以及层次化构造网络&#xff0c;每个IP地址包括两个标识码&#xff08;ID&#xff09;&#xff0c;即网络ID和主机ID。 同一个物理网络上的所有主机都使用同一个网络ID&#xff0c;网络上的一个主机&#xff08;包括网络上工…...

win10下使用docker运行部署nginx,mysql

一、docker的步骤&#xff1a;1.进入docker官网下载安装包2.打开控制面板 - 程序和功能 - 启用或关闭Windows功能&#xff0c;勾选Hyper-V&#xff0c;然后点击确定即可&#xff0c;如图&#xff1a;3.重新启动电脑4.启动Docker在桌面找到Docker for Windows快捷方式&#xff0…...

sprinboot车辆充电桩

sprinboot车辆充电桩演示录像2022开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09; 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;ecli…...

仿京东放大镜效果的实现

仿京东放大镜 &#xff08;1&#xff09; 整个案例可以分为三个功能模块 &#xff08;2&#xff09; 鼠标经过小图片盒子&#xff0c; 黄色的遮挡层 和 大图片盒子显示&#xff0c;离开隐藏2个盒子功能 &#xff08;3&#xff09;黄色的遮挡层跟随鼠标功能。 &#xff08;4&…...

ESP32设备驱动-LM35温度传感器驱动

LM35温度传感器驱动 文章目录 LM35温度传感器驱动1、LM35介绍2、硬件准备3、软件准备4、驱动实现1、LM35介绍 LM35 系列是精密集成电路温度传感器,其输出电压与摄氏(摄氏度)温度成线性比例。 因此,LM35 优于以开尔文校准的线性温度传感器,因为用户无需从其输出中减去较大…...

基于深度学习的犬种识别软件(YOLOv5清新界面版,Python代码)

摘要&#xff1a;基于深度学习的犬种识别软件用于识别常见多个犬品种&#xff0c;基于YOLOv5算法检测犬种&#xff0c;并通过界面显示记录和管理&#xff0c;智能辅助人们辨别犬种。本文详细介绍博主自主开发的犬种检测系统&#xff0c;在介绍算法原理的同时&#xff0c;给出Py…...

【IDEA插件开发】环境搭建

基础信息 GRADLE 7.5.1 IDEA IntelliJ IDEA 2020.1.1 (Ultimate Edition) Build #IU-201.7223.91, built on April 30, 2020 Licensed to https://zhile.io You have a perpetual fallback license for this version Subscription is active until July 8, 2089 Runtime ve…...

【蓝桥杯专题】 DP(C++ | 洛谷 | acwing | 蓝桥)

菜狗现在才开始备战蓝桥杯QAQ 文章目录【蓝桥杯专题】 DP&#xff08;C | 洛谷 | acwing | 蓝桥&#xff09;AcWing 1205. 买不到的数目Acwing 1216. 饮料换购【模拟】01背包271. 杨老师的照相排列最长公共上升子序列PPPPPPPP总结【蓝桥杯专题】 DP&#xff08;C | 洛谷 | acwi…...

咪咕MGV3201_ZG_GK国科6323_UWE5621DS_免拆卡刷固件包

咪咕MGV3201_ZG_GK国科6323_UWE5621DS_免拆卡刷固件包 特点&#xff1a; 1、适用于对应型号的电视盒子刷机&#xff1b; 2、开放原厂固件屏蔽的市场安装和u盘安装apk&#xff1b; 3、修改dns&#xff0c;三网通用&#xff1b; 4、大量精简内置的没用的软件&#xff0c;运行…...

重构数据-Change Value to Reference将实值对象改为引用对象三

重构数据-Change Value to Reference将实值对象改为引用对象三 1.将实值对象改为引用对象 1.1.实值对象和引用对象区别 下面通过客户Customer和订单Order两个对象介绍下它们的区别 值对象&#xff1a;当一个客户Customer下了多个订单Order后&#xff0c;每个订单类都将创建一…...

计算机网络——通信专业面试问题学习笔记

文章目录1、计算机网络这门课学了什么&#xff1f;目录里有多少章&#xff1f;2、Internet的概念与发展史3、什么是交换&#xff1f;三种交换方式4、OSI的七层协议, TCP/IP的四层协议, 五层协议5、WAN 、LAN 、MAN、PAN这些能分的清楚吗&#xff1f;全称分别都是什么&#xff1…...

代码随想录算法训练营第三十天 | 332.重新安排行程 51. N皇后 37. 解数独 总结

打卡第30天&#xff0c;回溯算法第二刷。 今日任务 332.重新安排行程51.N皇后37.解数独总结 332.重新安排行程 给你一份航线列表 tickets &#xff0c;其中 tickets[i] [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。 所有这些机票都属于一个从…...

Windows权限提升—MySQL数据库提权

Windows权限提升—MySQL数据库提权1. 前言2. 数据库提权介绍2.1. 常见数据库端口2.2. MySQL数据库提权条件2.3. MySQL数据库提权类型3. MySQL中UDF提权3.1. UDF提权介绍3.2. UDF提权思路3.3. UDF提权步骤3.3.1. 获取外连数据库3.3.1.1. 外连数据库3.3.1.2. 连接数据库3.3.1.3. …...

使用旧电脑玩Linux

今天给大家讲讲使用旧电脑玩Linux&#xff0c;大家应该都知道旧电脑的硬件一般比较落后&#xff0c;特别是一些非常老的电脑&#xff0c;目前还在使用的是机械硬盘&#xff0c;如是要跑windows可想而知&#xff0c;但是Linux系统对硬件性能的要求可比windows低的多了&#xff0…...

React Native 导航系统实战(React Navigation)

导航系统实战&#xff08;React Navigation&#xff09; React Navigation 是 React Native 应用中最常用的导航库之一&#xff0c;它提供了多种导航模式&#xff0c;如堆栈导航&#xff08;Stack Navigator&#xff09;、标签导航&#xff08;Tab Navigator&#xff09;和抽屉…...

【Linux】C语言执行shell指令

在C语言中执行Shell指令 在C语言中&#xff0c;有几种方法可以执行Shell指令&#xff1a; 1. 使用system()函数 这是最简单的方法&#xff0c;包含在stdlib.h头文件中&#xff1a; #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...

3.3.1_1 检错编码(奇偶校验码)

从这节课开始&#xff0c;我们会探讨数据链路层的差错控制功能&#xff0c;差错控制功能的主要目标是要发现并且解决一个帧内部的位错误&#xff0c;我们需要使用特殊的编码技术去发现帧内部的位错误&#xff0c;当我们发现位错误之后&#xff0c;通常来说有两种解决方案。第一…...

聊聊 Pulsar:Producer 源码解析

一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台&#xff0c;以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中&#xff0c;Producer&#xff08;生产者&#xff09; 是连接客户端应用与消息队列的第一步。生产者…...

ETLCloud可能遇到的问题有哪些?常见坑位解析

数据集成平台ETLCloud&#xff0c;主要用于支持数据的抽取&#xff08;Extract&#xff09;、转换&#xff08;Transform&#xff09;和加载&#xff08;Load&#xff09;过程。提供了一个简洁直观的界面&#xff0c;以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...

k8s业务程序联调工具-KtConnect

概述 原理 工具作用是建立了一个从本地到集群的单向VPN&#xff0c;根据VPN原理&#xff0c;打通两个内网必然需要借助一个公共中继节点&#xff0c;ktconnect工具巧妙的利用k8s原生的portforward能力&#xff0c;简化了建立连接的过程&#xff0c;apiserver间接起到了中继节…...

智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制

在数字化浪潮席卷全球的今天&#xff0c;数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具&#xff0c;在大规模数据获取中发挥着关键作用。然而&#xff0c;传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时&#xff0c;常出现数据质…...

零基础在实践中学习网络安全-皮卡丘靶场(第九期-Unsafe Fileupload模块)(yakit方式)

本期内容并不是很难&#xff0c;相信大家会学的很愉快&#xff0c;当然对于有后端基础的朋友来说&#xff0c;本期内容更加容易了解&#xff0c;当然没有基础的也别担心&#xff0c;本期内容会详细解释有关内容 本期用到的软件&#xff1a;yakit&#xff08;因为经过之前好多期…...

力扣-35.搜索插入位置

题目描述 给定一个排序数组和一个目标值&#xff0c;在数组中找到目标值&#xff0c;并返回其索引。如果目标值不存在于数组中&#xff0c;返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solution {public int searchInsert(int[] nums, …...

上位机开发过程中的设计模式体会(1):工厂方法模式、单例模式和生成器模式

简介 在我的 QT/C 开发工作中&#xff0c;合理运用设计模式极大地提高了代码的可维护性和可扩展性。本文将分享我在实际项目中应用的三种创造型模式&#xff1a;工厂方法模式、单例模式和生成器模式。 1. 工厂模式 (Factory Pattern) 应用场景 在我的 QT 项目中曾经有一个需…...