Angular 17 - QA

Angular Component, Core Directives and Pipes

Angular Component, Core Directives and Pipes

Angular 17 新增了哪些新的方法
?
新增了 @for @empty | @if @else | @switch @case @default

@for @empty 和 *ngFor 的区别
?
更加简单,默认支持 $index, $count, $first, $last, $odd, $even,必须要给 track,可以是方法

@if @else 和 *ngIf 的区别
?
写法更方便

*ngContainer 的作用是什么
?
作为一个占位符,和 React<></>类似,通常可以结合其他指令一起使用。不会放到 dom 结构中

ngTemplate 是什么
?
可以定义一个模板元素,可以配合其他语法使用,例如@for @if等。可以用于模板片段,通过ngTemplateOutlet在其他组件中复用

1
2
3
4
5
6
7
// define
<ng-template #defaultImg let-url="description">
<p>{{url}}</p>
<img ngSrc="/assets/empty-image.png" alt="" height="225" width="398">
</ng-template>
// use
<ng-container *ngTemplateOutlet="templateRef; context: {description: course.description}"/>

build-in pipe 有哪些
?
date: 'MMM/dd/YYYY', currency: 'CNY', number:'3.3-5', slice:0:2, json, keyvalue

Angular Local Template Querying In Depth

Angular 在组件中如何查找子组件
?
可以使用装饰器 @ViewChild(ComponentClass or \referId),通过class匹配时,多个class也只返回第一个。装饰器可以拿到对应组件下面的所有成员变量。
如果使用 ElementRef加上refId,可以拿到对应的 native dom。也可以使用 @ViewChildren找到多个子项

1
2
3
4
5
6
7
8
@ViewChild('card'|CardComponent)
card: CardComponent

@ViewChildren(CardComponent)
card: QueryList[CardComponent]

@ViewChildren(CardComponent: {read: ElementRef})
card: QueryList[ElementRef]

什么时候可以操作对应的 dom 或者 component
?
可以在 Angular 提供的 ngAfterViewInit方法中操作,这个方法会在视图加载完成后调用。但是不要在这个方法中修改视图相关的数据,这样会导致渲染的时候出现错误

ngContent 如何使用
?
ngContent 可以作为一个占位符,类似于 React 中使用 {this.children()}。作用是将当前组件作为一个高阶组件,接收外部传入的子组件并正确的放置在对应的地方。其中 select 属性可以选择元素或者 class。一个页面可以允许多个 <ngContent/>

Angular Content Projection In Depth

@ViewChild注解和@ContentChild注解的区别
?
当视图中使用了ng-content标签,那么从父组件传入的部分无法被 @ViewChild捕获,因此只能使用@ContentChild来查询该组件。而@ContentChild语法只能用于查询传入的部分视图,无法获取当前 View 中的其他组件。

@ViewChild,@ViewChildren, @ContentChild,@ContentChildren区别
?
children表明是查询多个组件

Angular Directives In Depth

Angular Directive 是什么,怎么用?
?

  1. 增强现有组件功能,可以在该中为现有组件添加或修改属性或者事件
  2. 可以接受输入元素和方法 @Input@Output
  3. 可以自定义Directive实现类似ngIf的操作,通过templateRefViewContainer来操作视图的添加和删除
  4. 将指令通过exportAs导出后,通过#RefId='direID'的方式可以将它引入文件中,其他地方就可以调用其内部方法
  5. 通过步骤3导出后,可以通过@ViewChild查询到该指令
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <course-image [src]="course.iconUrl" *ngxUnless="!course.iconUrl"/>
    @Directive({
    selector: '[ngxUnless]',
    standalone: true
    })
    export class NgxUnlessDirective {
    constructor(private templateRef: TemplateRef<any>, private container: ViewContainerRef) {
    }
    private visible: boolean = false;

    @Input()
    set ngxUnless(condition: boolean) {
    if (!condition && !this.visible) {
    this.container.createEmbeddedView(this.templateRef);
    this.visible = true;
    }
    if (condition && this.visible) {
    this.container.clear();
    this.visible = false;
    }
    }
    }

Angular View Encapsulation In Depth

Angular是怎么保持各个组件之间的 css style 独立的?
?

  1. 基于组件分为不同的 css 文件,实现不同组件样式文件之间的独立
  2. 默认使用 Emulated 模式,会生成唯一的 className 来区分
  3. 支持使用 CSS 模块化

Angular提供了哪些 View Encapsulation?
?

  1. Emulated,默认,Angular会自动生成唯一标志来使 CSS互不影响
  2. Native,使用浏览器自带的Shadow DOM的API来实现样式隔离,但是浏览器支持有限
  3. None,不使用 Encapsulation,会导致组件之间样式污染

有哪些 build-in 的样式选择器?
?

  1. :host用于选中当前组件
  2. ng-deep 会让class变成全局属性,影响到其他组件,通常会在前面添加 :host将其限制在该组件和其子组件中
  3. :host-context(.css) 会限制 css 应用范围在当前宿主组件中

Angular Injectable Service In Depth

Angular 中如何实现 Http 请求
?
Angular 中自带了 httpClient+ RxJS 用于网络请求,并且build-in指令asyc可以将Observable对象转为实际元素,如下:

1
2
3
4
5
6
7
8
9
10
11
export class AppComponent implements OnInit {  
courses$: Observable<Course[]>;
constructor(private client: HttpClient) {
}

ngOnInit() {
this.courses$ = this.client.get<Course[]>('/api/courses');
}
}
%% HTML %%
<course-card *ngFor="let course of (courses$ | async)"/>

Angular 的 Service 适合什么场景?
?

  1. 数据共享,例如 Redux,可以通过 public来共享变量,也可以通过Observerble来观察数据变化
  2. 业务逻辑
  3. API交互
  4. 状态管理,帮助和跟踪应用程序的状态变化

Angular Dependency Injection In Depth

Angular 中 DI 有什么好处?
?

  1. 方便 Mock 对应的依赖,特别是在测试环境下,例如:模拟网络请求
  2. 单例模式,使用工厂模式,可以自定义工厂

Angular 中怎么使用自定义工厂?
?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// Complex
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
providers: [
{provide: COURSE_SERVICE, useFactory: courseServiceProvider, deps: [HttpClient]}
]
})
export class AppComponent implements OnInit {

courses$: Observable<Course[]>;

constructor(@Inject(COURSE_SERVICE) private service: CourseService) {
}
}
// Simple
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
providers: [CourseService]
})
export class AppComponent implements OnInit {

courses$: Observable<Course[]>;

constructor(private service: CourseService) {
}
}
// Factory
export function courseServiceProvider(client: HttpClient) {
console.log('create new service');
return new CourseService(client)
}

export const COURSE_SERVICE = new InjectionToken('COURSE_SERVICE');

Angular DI 的 Provider 作用?
?
如果当前组件需要自定义 Provider,那么生成的这个service的生命周期将会和组件绑定。如果不想影响全局其他的该service,则可以使用自定义Provider的方式。

Angular DI 的 shakeable provider 怎么实现?
?
如果直接使用 new InjectionToken(TOKEN) 的方式生成的元素会默认打包到最后的 js 文件中,通过传入第二个参数,指定 factory,这样只有使用了才会打包对应的配置文件

Angular DI 中的 @Host, @Self, @Skipself, Optional 有什么作用
?

  1. 当 Director 需要所绑定的组件提供依赖的时候,可以通过host来指定
  2. 当组件需要自身提供依赖实例的时候,可以使用 Self
  3. 当组件需要父级组件提供依赖实例的时候,可以使用 SkipSelf
  4. 当不确定是否有父级组件提供依赖实例的时候,可以使用 Optional

Angular Change Detection

Angular 的 change detection 是怎样的
?
Angular 有两种 change detection,一种是 Default,也被称为 CheckAlways,即任何可能导致视图发生变化的行为(输入、点击、事件等)触发时,会检查当前组件以及子组件是否发生变化,因此可能会导致出现部分性能损耗。
另一个方式是 OnPush,当组件的输入属性,即@Input发生变化时;当组件事件发生时;当手动调用ChangeDetectorRef.markForCheck()时,才会对比视图是否发生变化,但是需要注意的是对比@Input时是浅对比,不是深对比。使用 OnPush 需要注意,如果有 Observerble 属性的对象,那么需要通过在 template 中添加 async的方式来渲染,如果只在 .ts文件中通过订阅赋值的方式,则页面不会变化。

Angular 中还有什么方法可以避免更新检测
?
如果某个属性传入后不会发生变化,那么可以使用 attribute decorator来指定,可以避免后续变化的更新检测。也可以手动通过调用 ChangeDetectorRef.markForCheck() 来触发检测。

1
2
3
4
5
6
 constructor(
@Attribute("type") type: string
) {}

<course-card *ngFor="let course of (courses$ | async)"
[course]="course" (courseChanged)="save($event)" type="'beginner'">

Angular lifecycle hook

Angular 生命周期的执行顺序是什么
?
1. constructor: 注入依赖,进行实例化
2. ngOnChanges: 当被绑定的输入属性(@Input)发生变化时调用,首次绑定时也会调用。
3. ngOnInit: 在组件初始化完成后立即调用,用于初始化数据和执行逻辑。
4. ngDoCheck: 在每个变更检测周期中调用,用于自定义的变更检测逻辑。
5. ngAfterContentInit: 在组件内容投影完成后调用。
6. ngAfterContentChecked: 在每个内容投影完成后调用。
7. ngAfterViewInit: 在组件的视图初始化完成后调用。
8. ngAfterViewChecked: 在每个视图检测周期后调用。
9. ngOnDestroy: 在组件被销毁前调用,用于清理资源和取消订阅。

Angular Partial Template Loading

如何让 Angular 延迟加载
?
@defer 装饰器会让对应的组件延迟加载,配合 @placeholder(minimum 3s) 可以实现延时加载的效果