Angular学习笔记(三)

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

目录

Observable和RxJS

  Observable概览

    定义观察者

    订阅

    创建可观察对象

    多播

  RxJS 库

    创建可观察对象的函数

    操作

    错误处理

    可观察对象的命名约定

  Angular 中的可观察对象

    HTTP

    Async 管道

    路由器 (router)

    响应式表单 (reactive forms)

  实际运用

    输入提示(type-ahead)建议

    指数化退避

NgModule

  模块元数据

  模块类别汇总

  提供依赖

    使用惰性加载模块限制提供者的作用域

    多级注入器和服务实例

    forRoot() 模式

  惰性加载特性模块

    使用CLI分布创建惰性加载模块

    预加载模块

    预加载组件数据

安全

  无害化处理

  避免直接使用 DOM API

  信任安全值

  内容安全政策

  强制执行可信类型

  使用 AOT 模板编译器

  服务器端 XSS 保护

  HTTP 级漏洞

代码风格

  单一职责

  命名

  应用程序结构与 NgModule

  组件

  指令

  服务

  数据服务

  生命周期钩子


Observable和RxJS

  Observable概览

    观察者(Observer)模式是一个软件设计模式,它有一个对象,称之为主体 Subject,负责维护一个依赖项(称之为观察者 Observer)的列表,并且在状态变化时自动通知它们。
    该模式和发布/订阅模式非常相似(但不完全一样)。

    可观察对象是声明式的 —— 也就是说,虽然你定义了一个用于发布值的函数,但是在有消费者订阅它之前,这个函数并不会实际执行。订阅之后,当这个函数执行完或取消订阅时,订阅者就会收到通知。

    可观察对象可以发送多个任意类型的值 —— 字面量、消息、事件。无论这些值是同步发送的还是异步发送的,接收这些值的 API 都是一样的。
    由于准备(setup)和清场(teardown)的逻辑都是由可观察对象自己处理的,因此你的应用代码只管订阅并消费这些值就可以了,做完之后,取消订阅。
    无论这个流是按键流、HTTP 响应流还是定时器,对这些值进行监听和停止监听的接口都是一样的。


    定义观察者

       用于接收可观察对象通知的处理器要实现 Observer 接口。这个对象定义了一些回调函数来处理可观察对象可能会发来的三种通知:
         next  必要。用来处理每个送达值。在开始执行后可能执行零次或多次。
         error 可选。用来处理错误通知。错误会中断这个可观察对象实例的执行过程。
         complete 可选。用来处理执行完毕(complete)通知。当执行完毕后,这些值就会继续传给下一个处理器。

	   const myObserver = {
         next: (x: number) => console.log('Observer got a next value: ' + x),
         error: (err: Error) => console.error('Observer got an error: ' + err),
         complete: () => console.log('Observer got a complete notification'),
       };

    订阅

       只有当有人订阅 Observable 的实例时,它才会开始发布值。订阅时要先调用该实例的 subscribe() 方法,并把一个观察者对象传给它,用来接收通知。

	   myObservable.subscribe(myObserver);
	   // 等同于
	   myObservable.subscribe(
         x => console.log('Observer got a next value: ' + x),
         err => console.error('Observer got an error: ' + err),
         () => console.log('Observer got a complete notification')
       );
	   // 无论哪种情况,next 处理器都是必要的,而 error 和 complete 处理器是可选的。

    创建可观察对象

      // This function runs when subscribe() is called
      function sequenceSubscriber(observer: Observer<number>) {
        // synchronously deliver 1, 2, and 3, then complete
        observer.next(1);
        observer.next(2);
        observer.next(3);
        observer.complete();

        // unsubscribe function doesn't need to do anything in this
        // because values are delivered synchronously
        return {unsubscribe() {}};
      }
      // Create a new Observable that will deliver the above sequence
      const sequence = new Observable(sequenceSubscriber);
      // execute the Observable and print the result of each notification
      sequence.subscribe({
        next(num) { console.log(num); },
        complete() { console.log('Finished sequence'); }
      });

      创建一个用来发布事件的可观察对象。

      function fromEvent<T extends keyof HTMLElementEventMap>(target: HTMLElement, eventName: T) {
        return new Observable<HTMLElementEventMap[T]>((observer) => {
          const handler = (e: HTMLElementEventMap[T]) => observer.next(e);

          // Add the event handler to the target
          target.addEventListener(eventName, handler);

          return () => {
            // Detach the event handler from the target
            target.removeEventListener(eventName, handler);
          };
        });
      }
      const ESC_CODE = 'Escape';
      const nameInput = document.getElementById('name') as HTMLInputElement;
      const subscription = fromEvent(nameInput, 'keydown').subscribe((e: KeyboardEvent) => {
        if (e.code === ESC_CODE) {
          nameInput.value = '';
        }
      });

    多播

      典型的可观察对象会为每一个观察者创建一次新的、独立的执行。
      当观察者进行订阅时,该可观察对象会连上一个事件处理器,并且向那个观察者发送一些值。
      当第二个观察者订阅时,这个可观察对象就会连上一个新的事件处理器,并独立执行一次,把这些值发送给第二个可观察对象。

      多播用来让可观察对象在一次执行中同时广播给多个订阅者。借助支持多播的可观察对象,你不必注册多个监听器,而是复用第一个(next)监听器,并且把值发送给各个订阅者。
      支持多播的可观察对象需要做更多的准备工作,但对某些应用来说,这非常有用。

      使用Subject实现多播1:转发多播

        const subject = new Subject<string>();
        subject.subscribe((v) => console.log(`observerA: ${v}`));
        subject.subscribe((v) => console.log(`observerB: ${v}`));
        from(['7', '8', '9']).subscribe(subject);
        // Log
        // observerA: 7
        // observerB: 7
        // observerA: 8
        // observerB: 8
        // observerA: 9
        // observerB: 9

      使用Subject实现多播2:直接多播

        const subject = new Subject<string>();
        subject.subscribe((v) => console.log(`observerA: ${v}`));
        subject.subscribe((v) => console.log(`observerB: ${v}`));
        subject.next('a');
        subject.next('b');
        subject.next('c');
        // Log
        // observerA: a
        // observerB: a
        // observerA: b
        // observerB: b
        // observerA: c
        // observerB: c

  RxJS 库

    响应式编程是一种面向数据流和变更传播的异步编程范式。
    RxJS(响应式扩展的 JavaScript 版)是一个使用可观察对象进行响应式编程的库,它让组合异步代码和基于回调的代码变得更简单。
    RxJS 提供了一种对 Observable 类型的实现,直到 Observable 成为了 JavaScript 语言的一部分并且浏览器支持它之前,它都是必要的。
    这个库还提供了一些工具函数,用于创建和使用可观察对象。这些工具函数可用于:
      把现有的异步代码转换成可观察对象
      迭代流中的各个值
      把这些值映射成其它类型
      对流进行过滤
      组合多个流

    创建可观察对象的函数

      RxJS 提供了一些用来创建可观察对象的函数。这些函数可以简化根据某些东西创建可观察对象的过程,比如事件、定时器、 Promise 等等。

      import { from, Observable, interval, fromEvent } from 'rxjs';
      import { ajax } from 'rxjs/ajax';

      // Create an Observable out of a promise
      const data = from(fetch('/api/endpoint'));
      // Subscribe to begin listening for async result
      data.subscribe({
        next(response) { console.log(response); },
        error(err) { console.error('Error: ' + err); },
        complete() { console.log('Completed'); }
      });

      // Create an Observable that will publish a value on an interval
      const secondsCounter = interval(1000);
      // Subscribe to begin publishing values
      const subscription = secondsCounter.subscribe(n =>
        console.log(`It's been ${n + 1} seconds since subscribing!`));

      const el = document.getElementById('my-element')!;
      // Create an Observable that will publish mouse movements
      const mouseMoves = fromEvent<MouseEvent>(el, 'mousemove');
      // Subscribe to start listening for mouse-move events
      const subscription = mouseMoves.subscribe(evt => {
        // Log coords of mouse movements
        console.log(`Coords: ${evt.clientX} X ${evt.clientY}`);
        // When the mouse is over the upper-left of the screen,
        // unsubscribe to stop listening for mouse movements
        if (evt.clientX < 40 && evt.clientY < 40) {
          subscription.unsubscribe();
        }
      });

      // Create an Observable that will create an AJAX request
      const apiData = ajax('/api/data');
      // Subscribe to create the request
      apiData.subscribe(res => console.log(res.status, res.response));

    操作

      操作符是基于可观察对象构建的一些对集合进行复杂操作的函数。RxJS 定义了一些操作符,比如 map()、filter()、concat() 和 flatMap()。
      操作符接受一些配置项,然后返回一个以来源可观察对象为参数的函数。当执行这个返回的函数时,这个操作符会观察来源可观察对象中发出的值,转换它们,并返回由转换后的值组成的新的可观察对象。

      import { of } from 'rxjs';
      import { map } from 'rxjs/operators';
      const nums = of(1, 2, 3);
      const squareValues = map((val: number) => val * val);
      const squaredNums = squareValues(nums);
      squaredNums.subscribe(x => console.log(x));

      你可以使用管道来把这些操作符链接起来。管道让你可以把多个由操作符返回的函数组合成一个。
      pipe() 函数以你要组合的这些函数作为参数,并且返回一个新的函数,当执行这个新函数时,就会顺序执行那些被组合进去的函数。

      // Create a function that accepts an Observable.
      const squareOddVals = pipe(
        filter((n: number) => n % 2 !== 0),
        map(n => n * n)
      );
      // Create an Observable that will run the filter and map functions
      const squareOdd = squareOddVals(nums);
      // Subscribe to run the combined functions
      squareOdd.subscribe(x => console.log(x));

      pipe() 函数也同时是 RxJS 的 Observable 上的一个方法,所以你可以用下列简写形式来达到同样的效果:

      const squareOdd = of(1, 2, 3, 4, 5)
        .pipe(
          filter(n => n % 2 !== 0),
          map(n => n * n)
        );
      // Subscribe to get values
      squareOdd.subscribe(x => console.log(x));

      常用操作符

        创建 from, fromEvent, of
        组合 combineLatest, concat, merge, startWith , withLatestFrom, zip
        过滤 debounceTime, distinctUntilChanged, filter, take, takeUntil
        转换 bufferTime, concatMap, map, mergeMap, scan, switchMap
        工具 tap
        多播 share

    错误处理

      除了可以在订阅时提供 error() 处理器外,RxJS 还提供了 catchError 操作符,它允许你在管道中处理已知错误。
      假设你有一个可观察对象,它发起 API 请求,然后对服务器返回的响应进行映射。如果服务器返回了错误或值不存在,就会生成一个错误。如果你捕获这个错误并提供了一个默认值,流就会继续处理这些值,而不会报错。
      catchError 提供了一种简单的方式进行恢复,而 retry 操作符让你可以尝试失败的请求。

      import { Observable, of } from 'rxjs';
      import { ajax } from 'rxjs/ajax';
      import { map, catchError } from 'rxjs/operators';

      // Return "response" from the API. If an error happens,
      // return an empty array.
      const apiData = ajax('/api/data').pipe(
        map((res: any) => {
          if (!res.response) {
            throw new Error('Value expected!');
          }
          return res.response;
        }),
		retry(3), // Retry up to 3 times before failing
        catchError(() => of([]))
      );

      apiData.subscribe({
        next(x: T) { console.log('data: ', x); },
        error() { console.log('errors already caught... will not run'); }
      });

    可观察对象的命名约定

      推荐可观察对象的名字以$符号结尾

  Angular 中的可观察对象

    Angular 使用可观察对象作为处理各种常用异步操作的接口。比如:
      HTTP 模块使用可观察对象来处理 AJAX 请求和响应。
      路由器和表单模块使用可观察对象来监听对用户输入事件的响应。

    HTTP

      Angular 的 HttpClient 从 HTTP 方法调用中返回了可观察对象。
      相对于基于承诺(Promise)的 HTTP API,它有一系列优点:
        可观察对象不会修改服务器的响应(和在 Promise 上串联起来的 .then() 调用一样)。反之,你可以使用一系列操作符来按需转换这些值。
        HTTP 请求是可以通过 unsubscribe() 方法来取消的
        请求可以进行配置,以获取进度事件的变化
        失败的请求很容易重试

    Async 管道

      AsyncPipe 会订阅一个可观察对象或 Promise ,并返回其发出的最后一个值。当发出新值时,该管道就会把这个组件标记为需要进行变更检查的(译注:因此可能导致刷新界面)

    路由器 (router)

      Router.events 以可观察对象的形式提供了其事件。你可以使用 RxJS 中的 filter() 操作符来找到感兴趣的事件,并且订阅它们,以便根据浏览过程中产生的事件序列作出决定。

        constructor(router: Router) {
          // Create a new Observable that publishes only the NavigationStart event
          this.navStart = router.events.pipe(
            filter(evt => evt instanceof NavigationStart)
          ) as Observable<NavigationStart>;
        }
        ngOnInit() {
          this.navStart.subscribe(() => console.log('Navigation Started!'));
        }

      ActivatedRoute 是一个可注入的路由器服务,它使用可观察对象来获取关于路由路径和路由参数的信息。比如,ActivatedRoute.url 包含一个用于汇报路由路径的可观察对象。

        this.activatedRoute.url.subscribe(url => console.log('The URL changed to: ' + url));

    响应式表单 (reactive forms)

     响应式表单具有一些属性,它们使用可观察对象来监听表单控件的值。
      FormControl 的 valueChanges 属性和 statusChanges 属性包含了会发出变更事件的可观察对象。
      订阅可观察的表单控件属性是在组件类中触发应用逻辑的途径之一。

        nameChangeLog: string[] = [];
        heroForm!: FormGroup;

        ngOnInit() {
          this.logNameChange();
        }
        logNameChange() {
          const nameControl = this.heroForm.get('name');
          nameControl?.valueChanges.forEach(
            (value: string) => this.nameChangeLog.push(value)
          );
        }

  实际运用

    输入提示(type-ahead)建议

      import { fromEvent, Observable } from 'rxjs';
      import { ajax } from 'rxjs/ajax';
      import { debounceTime, distinctUntilChanged, filter, map, switchMap } from 'rxjs/operators';

      const searchBox = document.getElementById('search-box') as HTMLInputElement;

      const typeahead = fromEvent(searchBox, 'input').pipe(
        map(e => (e.target as HTMLInputElement).value),
        // 确认它达到了最小长度
        filter(text => text.length > 2),
        // 控制时间间隔,防抖(这样才能防止连续按键时每次按键都发起 API 请求,而应该等到按键出现停顿时才发起)
        debounceTime(10),
        // 如果输入值没有变化,则不要发起请求(比如按某个字符,然后快速按退格)
        distinctUntilChanged(),
        // 如果已发出的 AJAX 请求的结果会因为后续的修改而变得无效,那就取消它
        switchMap(searchTerm => ajax(`/api/endpoint?search=${searchTerm}`))
      );

      typeahead.subscribe(data => {
        // Handle the data from the API
      });

    指数化退避

      指数化退避是一种失败后重试 API 的技巧,它会在每次连续的失败之后让重试时间逐渐变长,超过最大重试次数之后就会彻底放弃。
      如果使用 Promise 对象和其它跟踪 AJAX 调用的方法会非常复杂,而使用可观察对象,这非常简单:

      import { of, pipe, range, throwError, timer, zip } from 'rxjs';
      import { ajax } from 'rxjs/ajax';
      import { map, mergeMap, retryWhen } from 'rxjs/operators';

      export function backoff(maxTries: number, delay: number) {
        return pipe(
          retryWhen(attempts =>
            zip(range(1, maxTries + 1), attempts).pipe(
              mergeMap(([i, err]) => (i > maxTries) ? throwError(err) : of(i)),
              map(i => i * i),
              mergeMap(v => timer(v * delay)),
            ),
          ),
        );
      }

      ajax('/api/endpoint')
        .pipe(backoff(3, 250))
        .subscribe(function handleData(data) { /* ... */ });

NgModule

  模块元数据

    declarations
      属于此模块的可声明类(组件、指令和管道)的列表。
        编译模板时,你需要确定一组选择器,用于触发其对应的指令。
        模板是在 NgModule(声明模板组件的 NgModule)的上下文中编译的,它使用以下规则确定选择器集:
          declarations 中列出的指令的所有选择器。
          从导入的 NgModules 导出的指令的所有选择器。
      每个组件都应该(且只能)声明(declare)在一个 NgModule 类中。
      这些可声明的类在当前模块中是可见的,但是对其它模块中的组件是不可见的 —— 除非把它们从当前模块导出,并让对方模块导入本模块。
    imports
      你要用的其他 NgModule,这样你才可以使用它们的可声明对象。
    exports
      导入模块可以使用的声明列表(组件、指令和管道类)。
      导出的声明是模块的公共 API。如果另一个模块中的组件导入此模块并且此模块导出 UserComponent,则另一个模块中的组件可以用此模块的 UserComponent。
    providers
      列出了该应用所需的服务。当直接把服务列在这里时,它们是全应用范围的。当你使用特性模块和惰性加载时,它们是范围化的。
      依赖注入提供者的列表。
      Angular 会使用 NgModule 的注入器注册这些提供者。如果是用于引导的 NgModule,则它是根注入器。
      这些服务可用于注入到作为此注入器子项的任何组件、指令、管道或服务中。
      惰性加载的模块有自己的注入器,它通常是应用程序根注入器的子。
      惰性加载的服务的范围为延迟模块的注入器。如果惰性加载的模块还提供了 UserService,则在该模块的上下文中创建的任何组件(例如通过路由器导航)都会获取服务的本地实例,而不是根应用程序注入器中的实例。
      外部模块中的组件会继续接收其注入器提供的实例。
    bootstrap
      自动引导的组件列表。
      通常此列表中只有一个组件,即应用程序的根组件。
      Angular 可以用多个引导组件启动,每个组件在宿主网页中都有自己的位置。

  模块类别汇总

    Domain
      围绕特性、业务领域或用户体验进行组织。
      领域模块用来组织与特定功能有关的代码,里面包含构成此功能的所有组件、路由和模板。
      领域模块中的顶级组件是该特性或领域的根,是你要导出的唯一组件。各种私有的支撑子组件都是它的后代。
      领域模块主要由可声明对象组成,很少会在此提供服务。如果一定要提供,那么这些服务的生命周期应和该模块的生命周期一致。
    Routed
      所有惰性加载模块都要用带路由的模块。
      使用该模块的顶级组件作为路由器导航路由的目标。
      带路由的模块不会导出任何内容,因为它们的组件永远不会出现在外部组件的模板中。
      不要把惰性加载的带路由的模块导入到另一个模块中,因为这会触发一个急性加载,从而破坏了惰性加载它的目的。
      不要在带路由的模块及其导入的相关模块中提供全应用范围内的单例服务。
    Routing
      使用路由定义模块来为领域模块提供路由配置,从而将路由相关的关注点从其伴生领域模块中分离出来。
        定义路由
        把路由器配置文件添加到模块的导入表中
        往模块的提供者列表中添加路由守卫和解析器(resolver)提供者
      路由定义模块的名字应该和其伴生模块的名字平行,但使用-routing后缀。
      如果伴生模块是根模块 AppModule,那么 AppRoutingModule 就会通过其导入表中的 RouterModule.forRoot(routes) 来添加路由器配置。
      所有其他的子路由定义模块都会导入 RouterModule.forChild(routes)。
      在路由定义模块中,要重新导出 RouterModule,以便其伴生模块中的组件可以访问路由器指令,比如 RouterLink 和 RouterOutlet
    Service
      服务模块提供实用服务,比如数据访问和消息传递。
      理想的服务模块完全由提供者组成,没有可声明对象。
      只能使用根模块 AppModule 来导入各种服务模块。
    Widget
      小部件模块可以为其它模块提供某些组件、指令或管道。
      小部件模块应该完全由可声明对象组成,其中大部分都是导出的。
    Shared
      共享模块可以为其它的模块提供组件,指令和管道的集合。
      共享模块不应该包含服务提供者,它所导入或重新导出的任何模块也都不应该包含提供者。

模块可声明对象提供者导出被谁导入
领域模块罕见顶级组件其它领域 AppModule
带路由的模块罕见
路由是(路由守卫)RouterModule其它领域模块(为获取路由定义)
服务模块AppModule
小部件模块罕见其它领域模块
共享模块其它领域模块

  提供依赖

    使用服务类的@Injectable()装饰器providedIn属性的值,模块中提供服务的首选方式。
    当没有人注入它时,该服务就可以被摇树优化掉。
      providedIn: null       可注入物不会在任何范围内自动提供,必须添加到@NgModule 、 @Component或@Directive的 providers 数组中
      providedIn: 'root'     指定 Angular 应该在根注入器中提供该服务,应用程序级单例服务
      providedIn: 'platform' 由页面上所有应用程序共享的特殊单例平台注入器
      providedIn: 'any'      在每个惰性加载的模块中提供一个唯一实例,而所有急性加载的模块共享一个实例
      providedIn: Type<any>  将可注入物与 @NgModule 或其他 InjectorType 相关联。此选项已弃用。


    也可以在模块/组件/指令/管道中使用providers属性声明一个提供者
      providers: [UserService] 在模块/组件/指令/管道中共享一个实例,生命周期与模块/组件/指令/管道一致
      在这种情况下,Angular 将 providers 值展开为完整的提供者对象,如下所示:

        providers: [{ provide: UserService, useClass: UserService }]

      useClass
        这个提供者键名能让你创建并返回指定类的新实例。
        你可以用这种类型的提供者来作为通用类或默认类的替代实现。
        [{ provide: Logger, useClass: BetterLogger }]
        
      useExisting
        允许你将一个令牌映射到另一个。
        实际上,第一个令牌是与第二个令牌关联的服务的别名,创建了两种访问同一个服务对象的方式。
        [ NewLogger,{ provide: OldLogger, useExisting: NewLogger}]
          当组件请求新的或旧的记录器时,注入器都会注入一个 NewLogger 的实例。通过这种方式,OldLogger 就成了 NewLogger 的别名。
          确保你没有使用 OldLogger 将 NewLogger 别名为 useClass ,因为这会创建两个不同 NewLogger 实例。
        自定义验证器指令时,需要使用useExisting

      useFactory
        允许你通过调用工厂函数来创建依赖对象。
        使用这种方法,你可以根据 DI 和应用程序中其他地方的可用信息创建动态值。

        const heroServiceFactory = (logger: Logger, userService: UserService) =>
          new HeroService(logger, userService.user.isAuthorized);
        export const heroServiceProvider =
          { provide: HeroService,
            useFactory: heroServiceFactory,
            deps: [Logger, UserService]
          };

       useValue
         允许你将固定值与某个 DI 令牌相关联。
         可以用此技术提供运行时配置常量,例如网站基址和特性标志。
         你还可以在单元测试中使用值提供者来提供模拟数据以代替生产级数据服务。
         
         可以定义和使用一个 InjectionToken 对象来为非类的依赖选择一个提供者令牌。

           export const APP_CONFIG = new InjectionToken<AppConfig>('app.config');
           providers: [{ provide: APP_CONFIG, useValue: HERO_DI_CONFIG }]
           constructor(@Inject(APP_CONFIG) config: AppConfig) {
             this.title = config.title;
           }

           AppConfig类型为接口,不能直接注入,需要使用useValue的方式注入实际值。

    使用惰性加载模块限制提供者的作用域

      当 Angular 的路由器惰性加载一个模块时,它会创建一个新的注入器。这个注入器是应用的根注入器的一个子注入器。
      这个子注入器会操纵所有特定于此模块的提供者,如果有的话。
      任何在惰性加载模块的上下文中创建的组件(比如路由导航),都会获取由子注入器提供的服务的局部实例,而不是应用的根注入器中的实例。
      而外部模块中的组件,仍然会收到来自于应用的根注入器创建的实例。

    多级注入器和服务实例

      服务都是某个注入器范围内的单例,这意味着在给定的注入器中最多有一个服务实例。
      Angular DI 具有多级注入体系,这意味着嵌套的注入器可以创建自己的服务实例。
      子模块注入器和组件注入器彼此独立,并为已提供的服务创建它们自己的单独实例。当 Angular 销毁 NgModule 或组件实例时,它也会销毁该注入器和该注入器的服务实例。

      ModuleInjector
        使用 @NgModule() 或 @Injectable() 注解在此层次结构中配置 ModuleInjector
        ModuleInjector 由 @NgModule.providers 和 NgModule.imports 属性配置。ModuleInjector 是可以通过 NgModule.imports 递归找到的所有 providers 数组的扁平化。
        子 ModuleInjector 是在惰性加载其它 @NgModules 时创建的。
        
        在 root 之上还有两个注入器,一个是额外的 ModuleInjector,一个是 NullInjector()
        root ModuleInjector -> 平台 ModuleInjector -> NullInjector()

      ElementInjector
        Angular 会为每个 DOM 元素隐式创建 ElementInjector。
        可以用 @Component() 装饰器中的 providers 或 viewProviders 属性来配置 ElementInjector 以提供服务
          providers的范围大于viewProviders,viewProviders中声明的服务在内容投影里不可见
        可以用 @Directive() 装饰器中的 providers属性来配置 ElementInjector 以提供服务。

      解析规则
        当组件声明依赖项时,Angular 会尝试使用它自己的 ElementInjector 来满足该依赖。 如果组件的注入器缺少提供者,它将把请求传给其父组件的 ElementInjector。
        这些请求将继续转发,直到 Angular 找到可以处理该请求的注入器或用完祖先 ElementInjector。
        如果 Angular 在任何 ElementInjector 中都找不到提供者,它将返回到发起请求的元素,并在 ModuleInjector 层次结构中进行查找。如果 Angular 仍然找不到提供者,它将引发错误。
        如果你已在不同级别注册了相同 DI 令牌的提供者,则 Angular 会用遇到的第一个来解析该依赖。

        可以使用 @Optional(),@Self(),@SkipSelf() 和 @Host() 来修饰 Angular 的解析行为。
        @Optional()
          允许 Angular 将你注入的服务视为可选服务。这样,如果无法在运行时解析它,Angular 只会将服务解析为 null,而不会抛出错误。
        @Self()
          使用 @Self() 让 Angular 仅查看当前组件或指令的 ElementInjector。
        @SkipSelf()
          Angular 在父 ElementInjector 中而不是当前 ElementInjector 中开始搜索服务。
        合用 @SkipSelf() 和 @Optional()
          如果值为 null 请同时使用 @SkipSelf() 和 @Optional() 来防止错误。

          class Person {
            constructor(@Optional() @SkipSelf() parent?: Person) {}
          }

        @Host()
          禁止在宿主组件以上的搜索。即使树的更上级有一个服务实例,Angular 也不会继续寻找。
          宿主组件通常就是请求该依赖的那个组件。不过,当该组件投影进某个父组件时,那个父组件就会变成宿主。
 

      forwardRef
        forwardRef() 函数建立一个间接地引用,Angular 可以随后解析,用来解决循环引用和引用自身的问题。
          AlexComponent中引用自身

		  providers: [{ provide: Parent, useExisting: forwardRef(() => AlexComponent) }],

    forRoot() 模式

      通常,你只需要用 providedIn 提供服务,用 forRoot()/forChild() 提供路由即可。
      
      如果模块同时定义了 providers(服务)和 declarations(组件、指令、管道),那么,当你同时在多个特性模块中加载此模块时,这些服务就会被注册在多个地方。
      这会导致出现多个服务实例,并且该服务的行为不再像单例一样。
      有多种方式来防止这种现象:
        用 providedIn 语法代替在模块中注册服务的方式
        把你的服务分离到它们自己的模块中
        在模块中分别定义 forRoot() 和 forChild() 方法

      使用 forRoot() 来把提供者从该模块中分离出去,这样你就能在根模块中导入该模块时带上 providers,并且在子模块中导入它时不带 providers。
        在该模块中创建一个静态方法 forRoot()。
        把这些提供者放进 forRoot() 方法中。

        static forRoot(config: UserServiceConfig): ModuleWithProviders<GreetingModule> {
          return {
            ngModule: GreetingModule,
            providers: [
              {provide: UserServiceConfig, useValue: config }
            ]
          };
        }

      forRoot() 和 Router
        RouterModule 中提供了 Router 服务,同时还有一些路由指令,比如 RouterOutlet 和 routerLink 等。
        应用的根模块导入了 RouterModule,以便应用中有一个 Router 服务,并且让应用的根组件可以访问各个路由器指令。
        任何一个特性模块也必须导入 RouterModule,这样它们的组件模板中才能使用这些路由器指令。
        通过使用 forRoot() 方法,应用的根模块中会导入 RouterModule.forRoot(...),从而获得一个 Router 实例,而所有的特性模块要导入 RouterModule.forChild(...),它就不会实例化另外的 Router。

      防止重复导入 GreetingModule

        constructor(@Optional() @SkipSelf() parentModule?: GreetingModule) {
          if (parentModule) {
            throw new Error(
              'GreetingModule is already loaded. Import it in the AppModule only');
          }
        }

  惰性加载特性模块

    主模块的imports中不引入指定模块
    主路由的路由表中使用loadChildren引入模块

      const routes: Routes = [
        {
          path: 'items',
          loadChildren: () => import('./items/items.module').then(m => m.ItemsModule)
        }
      ];
	  RouterModule.forRoot(routes)

    模块中的路由表指定相对路径

      const routes: Routes = [
        {
          path: '',
          component: CustomersComponent
        },
        {
          path: 'create',
          component: CustomerCreateComponent
        }
      ];
	  RouterModule.forChild(routes)

    使用CLI分布创建惰性加载模块

      ng new customer-app --routing
         --routing 标识生成了一个名叫 app-routing.module.ts 的文件.它是你建立惰性加载的特性模块时所必须的。
      ng generate module customers --route customers --module app.module
         --route 指定加载 customers 特性模块的路径也是 customers
         --module 将声明的路由 customers 添加到指定的模块中声明的 routes 数组中

    预加载模块

      预加载模块通过在后台加载部分应用来改善用户体验。这样一来,用户在激活路由时就无需等待下载这些元素。
      
      PreloadAllModules预加载所有惰性模块
        PreloadAllModules 策略不会加载被 canLoad 守卫所保护的特性区。

        import { PreloadAllModules } from '@angular/router';
        RouterModule.forRoot(
          appRoutes,
          {
            preloadingStrategy: PreloadAllModules
          }
        )

      使用自定义预加载策略

        自定义策略

		  @Injectable({ providedIn: 'root', })
          export class SelectivePreloadingStrategyService implements PreloadingStrategy {
            preloadedModules: string[] = [];
          
            preload(route: Route, load: () => Observable<any>): Observable<any> {
              if (route.data?.['preload'] && route.path != null) {
                // add the route path to the preloaded module array
                this.preloadedModules.push(route.path);
          
                // log the route path to the console
                console.log('Preloaded: ' + route.path);
          
                return load();
              } else {
                return of(null);
              }
            }
          }

        设置路由表和策略

		  const appRoutes: Routes = [
	        { path: 'crisis-center',
              loadChildren: () => import('./crisis-center/crisis-center.module').then(m => m.CrisisCenterModule),
              data: { preload: true }
            },
		  ...]
	      RouterModule.forRoot(appRoutes, {preloadingStrategy: SelectivePreloadingStrategyService})

    预加载组件数据

      要预加载组件数据,可以用 resolver 守卫。解析器通过阻止页面加载来改进用户体验,直到显示页面时的全部必要数据都可用。
      在新创建的服务中,实现由 @angular/router 包提供的 Resolve 接口:

        import { Resolve } from '@angular/router';
        /* An interface that represents your data model */
        export interface Crisis {
          id: number;
          name: string;
        }
        export class CrisisDetailResolverService implements Resolve<Crisis> {
          resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Crisis> {
            // observable 必须完成,否则导航不会继续
          }
        }

      把这个解析器导入此模块的路由模块

        {
          path: '/your-path',
          component: YourComponent,
          resolve: {
            crisis: CrisisDetailResolverService
          }
        }

      使用注入进来的 ActivatedRoute 类实例来访问与指定路由关联的 data 值

        import { ActivatedRoute } from '@angular/router';
        @Component({ … })
        class YourComponent {
          constructor(private route: ActivatedRoute) {}
          ngOnInit() {
            this.route.data
              .subscribe(data => {
                const crisis: Crisis = data.crisis;
                // …
              });
          }
        }

安全

  无害化处理

    HTML        值需要被解释为 HTML 时使用,比如当绑定到 innerHTML 时。
    样式          值需要作为 CSS 绑定到 style 属性时使用。
    URL          值需要被用作 URL 属性时使用,比如 <a href>。
    资源 URL  值需要作为代码进行加载并执行,比如 <script src> 中的 URL。
 
    Angular 会对前三项中种不可信的值进行无害化处理,但不能对第四种资源 URL 进行无害化

    示例:

      htmlSnippet = 'Template <script>alert("0wned")</script> <b>Syntax</b>'
      <p class="e2e-inner-html-interpolated">{{htmlSnippet}}</p>
	    插值的内容总会被编码 - 其中的 HTML 不会被解释
      <p class="e2e-inner-html-bound" [innerHTML]="htmlSnippet"></p>
        innerHTML的HTML内容会被正常解释,Angular 认为这些值是不安全的,并自动进行无害化处理。它会移除 script 元素,但保留安全的内容

  避免直接使用 DOM API

    除非你强制使用可信类型(Trusted Types),否则浏览器内置的 DOM API 不会自动保护你免受安全漏洞的侵害。
    比如 document、通过 ElementRef 拿到的节点和很多第三方 API,都可能包含不安全的方法。
    要避免直接和 DOM 打交道,而是尽可能使用 Angular 模板。

    在无法避免的情况下,使用内置的 Angular 无害化处理函数。
    使用 DomSanitizer.sanitize 方法以及适当的 SecurityContext 来对不可信的值进行无害化处理。
 

  信任安全值

    有时候,应用程序确实需要包含可执行的代码,比如使用 URL 显示 <iframe>,或者构造出有潜在危险的 URL。
    可以告诉 Angular,你已经审查了这个值,并确信它总是安全的。
    但是千万要小心!如果你信任了一个可能是恶意的值,就会在应用中引入一个安全漏洞。

    注入 DomSanitizer 服务,然后调用下面的方法之一,你就可以把一个值标记为可信任的。
      bypassSecurityTrustHtml
      bypassSecurityTrustScript
      bypassSecurityTrustStyle
      bypassSecurityTrustUrl
      bypassSecurityTrustResourceUrl

      this.dangerousUrl = 'javascript:alert("Hi there")';
      this.trustedUrl = sanitizer.bypassSecurityTrustUrl(this.dangerousUrl);
      this.dangerousVideoUrl = 'https://www.youtube.com/embed/' + id;
      this.videoUrl = sanitizer.bypassSecurityTrustResourceUrl(this.dangerousVideoUrl);

  内容安全政策

     内容安全策略(CSP)是防止 XSS 的深度防御技术。要启用 CSP,请将你的 Web 服务器配置为返回适当的 Content-Security-Policy HTTP 请求头。
     新版 Angular 所需的最小化策略是:
       default-src 'self'; style-src 'self' 'unsafe-inline';
       允许此页面加载所有来自同源的资源。
       允许此页面加载来自同源的全局样式('self'),并允许组件加载它们的样式
 

  强制执行可信类型

    建议使用可信类型来帮助保护你的应用程序免受跨站脚本攻击。
    可信类型是一项 Web 平台功能,可通过实施更安全的编码实践来帮助你防范跨站脚本攻击。
    可信类型还可以帮助简化应用程序代码的审计。

    要为你的应用程序强制实施可信类型,你必须将应用程序的 Web 服务器配置为使用以下 Angular 策略之一发出 HTTP 请求头:

策略详情
angular

此策略用于 Angular 内部经过安全审查的代码,并且当强制执行可信类型时,Angular 需要此策略才能正常运行。

任何由 Angular 清理的内联模板值或内容都被此政策视为安全的。

angular#unsafe-bypass

此策略用于要使用 Angular 的 DomSanitizer 的各个方法来绕过安全性的应用程序,比如 bypassSecurityTrustHtml。

任何使用了这些方法的应用程序都必须启用此策略。

angular#unsafe-jit

此策略供Just-In-Time (JIT) 编译器使用。

如果你的应用程序直接与 JIT 编译器交互或使用平台浏览器动态以 JIT 模式运行,你必须启用此策略。

angular#bundler创建惰性加载块文件时,Angular CLI 打包器会使用此策略。

    你应该在以下位置为可信类型配置 HTTP 请求头:
      生产环境基础设施服务器
      Angular CLI ( ng serve ),使用 angular.json 文件中的 headers 属性,用于本地开发和端到端测试
      Karma ( ng test ),使用 karma.config.js 文件中的 customHeaders 属性,进行单元测试

    以下是为可信类型和 Angular 配置的请求头示例:
      Content-Security-Policy: trusted-types angular; require-trusted-types-for 'script';
    以下是为可信类型和 Angular 应用程序专门配置的请求头示例,这些应用程序使用了 Angular DomSanitizer 中那些可以绕过安全性的方法。
      Content-Security-Policy: trusted-types angular angular#unsafe-bypass; require-trusted-types-for 'script';
    以下是使用 JIT,且专门为可信类型和 Angular 应用程序配置的请求头示例:
      Content-Security-Policy: trusted-types angular angular#unsafe-jit; require-trusted-types-for 'script';
    以下是专门为使用惰性加载模块的受信任类型和 Angular 应用程序配置的标头示例:
      Content-Security-Policy: trusted-types angular angular#bundler; require-trusted-types-for 'script';
 

  使用 AOT 模板编译器

    AOT 模板编译器可防止称为模板注入的一整类漏洞,并大大提高了应用程序性能。
    AOT 模板编译器是 Angular CLI 应用程序使用的默认编译器,你应该在所有生产部署中使用它。

  服务器端 XSS 保护

    在服务器上构造的 HTML 容易受到注入攻击。将模板代码注入到 Angular 应用程序中与注入可执行代码是一样的:它使攻击者可以完全控制该应用程序。
    为避免这种情况,请使用一种模板语言来自动转义值以防止服务器上的 XSS 漏洞。
    不要在服务器端使用模板语言生成 Angular 模板。这样做会带来引入模板注入漏洞的高风险。
 

  HTTP 级漏洞

    Angular 内置了一些支持来防范两个常见的 HTTP 漏洞:跨站请求伪造(XSRF)和跨站脚本包含(XSSI)。
    这两个漏洞主要在服务器端防范,但是 Angular 也自带了一些辅助特性,可以让客户端的集成变得更容易。
 

    跨站请求伪造(XSRF 或 CSRF)
      常见的反 XSRF 技术是服务器随机生成一个用户认证令牌到 cookie(默认名字为 XSRF-TOKEN) 中。
      客户端代码获取这个 cookie,并用它为接下来所有的请求添加自定义请求页头(默认名字为 X-XSRF-TOKEN)。
      服务器比较收到的 cookie 值与请求页头的值,如果它们不匹配,便拒绝请求。

      这个技术之所以有效,是因为所有浏览器都实现了同源策略。
      只有设置 cookie 的网站的代码可以访问该站的 cookie,并为该站的请求设置自定义页头。
      这就是说,只有你的应用程序可以获取这个 cookie 令牌和设置自定义页头。

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

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

    跨站脚本包含(XSSI)
      跨站脚本包含,也被称为 Json 漏洞,它可以允许一个攻击者的网站从 JSON API 读取数据。
      这种攻击发生在老的浏览器上,它重写原生 JavaScript 对象的构造函数,然后使用 <script> 标签包含一个 API 的 URL。

      只有在返回的 JSON 能像 JavaScript 一样可以被执行时,这种攻击才会生效。所以服务端会约定给所有 JSON 响应体加上前缀 ")]}',\n",来把它们标记为不可执行的,以防范这种攻击。
      Angular 的 HttpClient 库会识别这种约定,并在进一步解析之前,自动把字符串 ")]}',\n" 从所有响应中去掉。

代码风格

  单一职责

    风格 01-01 单一规则
      坚持每个文件只定义一样东西(比如服务或组件)。考虑把文件大小限制在 400 行代码以内。
    风格 01-02 小函数
      坚持定义简单函数。考虑限制在 75 行之内。

  命名

    风格 02-01 总体命名原则
      坚持所有符号使用一致的命名规则。
      坚持遵循同一个模式来描述符号的特性和类型。推荐的模式为 feature.type.ts
    风格 02-02 使用点和横杠来分隔文件名
      坚持在描述性名字中,用横杠来分隔单词。
      坚持使用点来分隔描述性名字和类型。
      坚持遵循先描述组件特性,再描述它的类型的模式,对所有组件使用一致的类型命名规则。推荐的模式为 feature.type.ts。
      坚持使用惯用的后缀来描述类型,包括 *.service、*.component、*.pipe、.module、.directive。必要时可以创建更多类型名,但必须注意,不要创建太多。
    风格 02-03 符号名与文件名
      坚持为所有东西使用一致的命名约定,以它们所代表的东西命名。
      坚持使用大写驼峰命名法来命名类。
      坚持匹配符号名与它所在的文件名。
      坚持在符号名后面追加约定的类型后缀(比如 Component、Directive、Module、Pipe、Service)。
      坚持在文件名后面追加约定的类型后缀(比如 .component.ts、.directive.ts、.module.ts、.pipe.ts、.service.ts)。
    风格 02-04 服务名
      坚持使用一致的规则命名服务,以它们的特性来命名。
      坚持为服务的类名加上 Service 后缀。比如,获取数据或英雄列表的服务应该命名为 DataService 或 HeroService。
      有些词汇显然就是服务,比如那些以“-er”后缀结尾的。比如把记日志的服务命名为 Logger 就比 LoggerService 更好些。需要在你的项目中决定这种特例是否可以接受。但无论如何,都要尽量保持一致。
    风格 02-05 引导
      坚持把应用的引导程序和平台相关的逻辑放到名为 main.ts 的文件里。
      坚持在引导逻辑中包含错误处理代码。
      避免把应用逻辑放在 main.ts 中,而应放在组件或服务里。
    风格 05-02 组件选择器
      坚持使用中线命名法(dashed-case)或叫烤串命名法(kebab-case)来命名组件的元素选择器。
    风格 02-07 为组件添加自定义前缀
      坚持使用带连字符的小写元素选择器值(比如 admin-users)。
      坚持为组件选择器添加自定义前缀。比如,toh 前缀表示 Tour of Heroes(英雄之旅),而前缀 admin 表示管理特性区。
    风格 02-06 指令选择器
      坚持使用小驼峰形式命名指令的选择器。
    风格 02-08 为指令添加自定义前缀
      坚持为指令的选择器添加自定义前缀(比如前缀 toh 来自 Tour of Heroes)。
      坚持用小驼峰形式拼写非元素选择器,除非该选择器用于匹配原生 HTML 属性。
      不要以 ng 作为指令名称的前缀,因为该前缀是为 Angular 保留的,使用它可能会导致难以诊断的错误。
    风格 02-09 管道名
      坚持为所有管道使用一致的命名约定,用它们的特性来命名。
      管道类名应该使用 UpperCamelCase(类名的通用约定),而相应的 name 字符串应该使用 lowerCamelCase。name 字符串中不应该使用中线(“中线格式”或“烤串格式”)。
    风格 02-10 单元测试文件名
      坚持测试规格文件名与被测试组件文件名相同。
      坚持测试规格文件名添加 .spec 后缀。
    风格 02-11 端到端(E2E)测试的文件名
      坚持端到端测试规格文件和它们所测试的特性同名,添加 .e2e-spec 后缀。
    风格 02-12 Angular NgModule 命名
      坚持为符号名添加 Module 后缀。
      坚持为文件名添加 .module.ts 扩展名。
      坚持用特性名和所在目录命名模块。
      坚持为 RoutingModule 类名添加 RoutingModule 后缀。
      坚持为 RoutingModule 的文件名添加 -routing.module.ts 后缀。

  应用程序结构与 NgModule

    风格 04-01 LIFT
      坚持组织应用的结构,力求:快速定位 (Locate) 代码、一眼识别 (Identify) 代码、 尽量保持扁平结构 (Flattest) 和尝试 (Try) 遵循 DRY (Do Not Repeat Yourself, 不重复自己) 原则。
      坚持四项基本原则定义文件结构,上面的原则是按重要顺序排列的。
    风格 04-02 定位
      坚持直观、简单和快速地定位代码。
    风格 04-03 识别
      坚持命名文件到这个程度:看到名字立刻知道它包含了什么,代表了什么。
      坚持文件名要具有说明性,确保文件中只包含一个组件。
      避免创建包含多个组件、服务或者混合体的文件。
    风格 04-04 扁平
      坚持尽可能保持扁平的目录结构。
      考虑当同一目录下达到 7 个或更多个文件时创建子目录。
      考虑配置 IDE,以隐藏无关的文件,比如生成出来的 .js 文件和 .js.map 文件等。
    风格 04-05 T-DRY(尽量不重复自己)
      坚持 DRY(Don't Repeat Yourself,不重复自己)。
      避免过度 DRY,以致牺牲了阅读性。
    风格 04-06 总体结构的指导原则
      坚持从零开始,但要考虑应用程序接下来的路往哪儿走。
      坚持有一个近期实施方案和一个长期的愿景。
      坚持把所有源代码都放到名为 src 的目录里。
      坚持如果组件具有多个伴生文件 (.ts、.html、.css 和 .spec),就为它创建一个文件夹。
    风格 04-07 按特性组织的目录结构
      坚持根据特性区命名目录。
    风格 04-08 应用的根模块
      坚持在应用的根目录创建一个 NgModule(比如 /src/app)。
      考虑把根模块命名为 app.module.ts。
    风格 04-09 特性模块
      坚持为应用中每个明显的特性创建一个 NgModule。
      坚持把特性模块放在与特性区同名的目录中(比如 app/heroes)。
      坚持特性模块的文件名应该能反映出特性区的名字和目录(比如 app/heroes/heroes.module.ts)。
      坚持特性模块的符号名应该能反映出特性区、目录和文件名(比如在 app/heroes/heroes.module.ts 中定义 HeroesModule)。
    风格 04-10 共享特性模块
      坚持在 shared 目录中创建名叫 SharedModule 的特性模块(比如在 app/shared/shared.module.ts 中定义 SharedModule)。
      坚持在共享模块中声明那些可能被特性模块引用的可复用组件、指令和管道。
      考虑把可能在整个应用中到处引用的模块命名为 SharedModule。
      考虑 不要在共享模块中提供服务。服务通常是单例的,应该在整个应用或一个特定的特性模块中只有一份。
      坚持在 SharedModule 中导入所有模块都需要的资产(比如 CommonModule 和 FormsModule)。
      坚持在 SharedModule 中声明所有组件、指令和管道。
      坚持从 SharedModule 中导出其它特性模块所需的全部符号。
      避免在 SharedModule 中指定应用级的单例服务提供者。如果是刻意要得到多个服务单例也行,不过还是要小心。
    风格 04-11 惰性加载文件夹
      某些边界清晰的应用特性或工作流可以做成惰性加载或按需加载的,而不用总是随着应用启动。
      坚持把惰性加载特性下的内容放进惰性加载目录中。典型的惰性加载目录包含路由组件及其子组件以及与它们有关的那些资产和模块。
    风格 04-12 永远不要直接导入惰性加载的目录
      避免让兄弟模块和父模块直接导入惰性加载特性中的模块。
    风格 04-13 不要往管道中添加过滤和排序逻辑
      避免往自定义管道中添加过滤或排序逻辑。
      坚持在把模型绑定到模板中时,把过滤和排序逻辑在组件或服务中进行预先计算。

  组件

    风格 05-03 把组件当做元素
      考虑给组件一个元素选择器,而不是属性或类选择器。
    风格 05-04 把模板和样式提取到它们自己的文件
      坚持当超过 3 行时,把模板和样式提取到一个单独的文件。
      坚持把模板文件命名为 [component-name].component.html,其中,[component-name] 是组件名。
      坚持把样式文件命名为 [component-name].component.css,其中,[component-name] 是组件名。
      坚持指定相对于模块的 URL,给它加上 ./ 前缀。
    风格 05-12 使用 input 和 output 属性装饰器
      坚持 使用 @Input() 和 @Output(),而非 @Directive 和 @Component 装饰器的 inputs 和 outputs 属性
      坚持把 @Input() 或者 @Output() 放到所装饰的属性的同一行。
    风格 05-13 避免为 inputs 和 outputs 指定别名
      避免除非有重要目的,否则不要为 input 和 output 指定别名。
    风格 05-14 成员顺序
      坚持把属性成员放在前面,方法成员放在后面。
      坚持先放公共成员,再放私有成员,并按照字母顺序排列。
    风格 05-15 把逻辑放到服务里
      坚持在组件中只包含与视图相关的逻辑。所有其它逻辑都应该放到服务中。
      坚持把可复用的逻辑放到服务中,保持组件简单,聚焦于它们预期目的。
    风格 05-16 不要给 output 属性加前缀
      坚持命名事件时,不要带前缀 on。
      坚持把事件处理器方法命名为 on 前缀之后紧跟着事件名。
      (savedTheDay)="onSavedTheDay($event)"
    风格 05-17 把表现层逻辑放到组件类里
      坚持把表现层逻辑放进组件类中,而不要放在模板里。
    风格 05-18 初始化输入属性
      TypeScript 的编译器选项 --strictPropertyInitialization,会确保某个类在构造函数中初始化其属性。当启用时,如果该类没有对任何未显式标为可选值的属性提供初始值,TypeScript 编译器就会报错。
      按照设计,Angular 把所有 @Input 都视为可选值。只要有可能,你就应该通过提供默认值来满足 --strictPropertyInitialization 的要求。
      如果该属性很难构造出默认值,请使用 ? 来把该属性显式标记为可选的。
      你可能希望某个 @Input 字段是必填的,也就是说此组件的所有用户都必须传入该属性。这种情况下,请使用默认值。
      仅仅使用 ! 来抑制 TypeScript 报错是不够的,应该避免它,因为这样做会阻止类型检查器来确保必须提供此输入值。

  指令

    风格 06-01 使用指令来增强已有元素
      坚持当你需要有表现层逻辑,但没有模板时,使用属性型指令。
    风格 06-03 HostListener 和 HostBinding 装饰器 vs. 组件元数据 host
      考虑优先使用 @HostListener 和 @HostBinding,而不是 @Directive 和 @Component 装饰器的 host 属性。
      坚持让你的选择保持一致。

  服务

    风格 07-01 服务总是单例的
      坚持在同一个注入器内,把服务当做单例使用。用它们来共享数据和功能。
    风格 07-02 单一职责
      坚持创建封装在上下文中的单一职责的服务。
      坚持当服务成长到超出单一用途时,创建一个新服务。
    风格 07-03 提供服务
      坚持在服务的 @Injectable 装饰器上指定通过应用的根注入器提供服务。
    风格 07-04 使用 @Injectable() 类装饰器
      坚持当使用类型作为令牌来注入服务的依赖时,使用 @Injectable() 类装饰器,而非 @Inject() 参数装饰器。

  数据服务

    风格 08-01 通过服务与 Web 服务器通讯
      坚持把数据操作和与数据交互的逻辑重构到服务里。
      坚持让数据服务来负责 XHR 调用、本地储存、内存储存或者其它数据操作。

  生命周期钩子

    风格 09-01 实现生命周期钩子接口
      坚持实现生命周期钩子接口。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值