理解 Angular 的核心:关键概念、实用程序和最佳实践

以下是 Angular 主题的结构化组织,按其类别和关系分组:

1. 模板和内容投影

  • 模板
  • 容器
  • 内容不合
  • NgTemplateOutlet
  • 2. 结构指令和属性指令

  • 结构指令:*ngIf *ngFor 自定义指令
  • 属性指令:HostBinding 和 HostListener
  • 3. 组件交互和内容查询

  • 查看查询:@ViewChild 和 @ViewChildren
  • 内容查询:@ContentChild 和 @ContentChildren
  • 4.渲染和DOM操作

  • 渲染器 API: Renderer2
  • 元素参考:ElementRef
  • 5.动态组件加载

  • 动态组件创建:ComponentRef ComponentFactoryResolver ViewContainerRef
  • 6. 变化检测和视图

  • 变化检测:ChangeDetectorRef
  • 视图管理:ViewRef HostViewRef EmbeddedViewRef
  • 7.依赖注入和服务

  • 依赖注入:注入器
  • 8. 高级 Angular 实用程序

  • 不同的实用程序:KeyValueDiffers IterableDiffers
  • 1. 模板和内容投影

    Angular 中的 **模板和内容投影​​* 类别提供了定义可重用模板、动态投影内容以及有效操作 DOM 结构的方法。让我们详细探讨每一项:

    模板

    它是什么?

    `ng-template` 是一个定义可动态重用的 Angular 模板的指令。除非明确使用,否则它不会在 DOM 中渲染任何内容。

    使用案例

  • 定义可重复使用的模板以进行动态渲染。
  • 使用结构指令进行条件内容呈现。
  • 例子

    Hello, this is a reusable template!

    import { Component, TemplateRef, ViewContainerRef } from '@angular/core';
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
    })
    export class AppComponent {
      constructor(private viewContainerRef: ViewContainerRef) {}
    
      showTemplate(template: TemplateRef) {
        this.viewContainerRef.clear();
        this.viewContainerRef.createEmbeddedView(template);
      }
    }

    输出

    最初,什么都没有出现。单击按钮后,将动态呈现消息“您好,这是一个可重复使用的模板!”。

    容器

    它是什么?

    `ng-container` 是一个逻辑容器,它不会在 DOM 中呈现自身,但用于对元素或指令进行分组。

    使用案例

  • 对多个指令进行分组,而无需向 DOM 添加额外的节点。
  • 减少不需要的包装元素。
  • 例子

    
      

    Welcome!

    This is displayed conditionally without extra wrappers.

    export class AppComponent {
      show = true;
    }

    输出

    如果“show”为“true”,则会呈现:

    Welcome!
    This is displayed conditionally without extra wrappers.

    如果“show”为“false”,则不会渲染任何内容。

    内容不合

    它是什么?

    `ng-content` 允许你将内容从父组件投影到子组件中,从而实现内容投影。

    使用案例

  • 创建具有可定制内容的可重复使用组件。
  • 将父内容投影到子组件中。
  • 例子

    **父组件:**

    
      

    Custom Title

    This is custom content passed from the parent!

    **子组件:**

    @Component({
      selector: 'app-card',
      templateUrl: './card.component.html',
      styleUrls: ['./card.component.css']
    })
    export class CardComponent {}

    输出

    Custom Title
    This is custom content passed from the parent!

    NgTemplateOutlet

    它是什么?

    `NgTemplateOutlet` 是一个将 `ng-template` 动态嵌入到视图中的指令。

    使用案例

  • 有条件地渲染模板。
  • 动态重用模板。
  • 例子

    Welcome, Admin!

    Welcome, User!

    export class AppComponent {
      role = 'admin';
    }

    输出

    如果“role”为“admin”,则呈现:

    Welcome, Admin!

    如果“role”是“user”,则呈现:

    Welcome, User!

    主要区别和总结

  • ng-template 定义可重复使用的模板但不能直接渲染。
  • ng-container 按逻辑对元素进行分组,无需添加额外的节点。
  • ng-content 将父内容投影到子组件中。
  • NgTemplateOutlet 将模板动态嵌入到视图中。
  • 这些工具允许 Angular 开发人员以最少的 DOM 操作创建动态、可重用且高效的模板。

    Image description

    2. 结构指令和属性指令

    Angular 中的指令用于通过改变其结构或行为来修改 DOM。

    结构型指令

    结构指令通过动态添加或删除元素来改变 DOM 的结构。

    1. *ngIf

    它是什么?

    `*ngIf` 是一个内置结构指令,它根据布尔表达式有条件地在 DOM 中包含或排除元素。

    使用案例

  • 有条件地显示或隐藏元素。
  • 例子

    Welcome, User!

    export class AppComponent {
      isLoggedIn = false;
    
      toggleLogin() {
        this.isLoggedIn = !this.isLoggedIn;
      }
    }

    输出

    当 `isLoggedIn` 为 `true` 时,会显示消息“欢迎,用户!”。否则,不会呈现任何内容。

    2. *ngFor

    它是什么?

    `*ngFor` 是一个结构指令,它遍历一个集合并为每个项目呈现其模板。

    使用案例

  • 动态显示项目列表。
  • 例子

    • {{ i + 1 }}. {{ item }}
    export class AppComponent {
      items = ['Apple', 'Banana', 'Cherry'];
    }

    输出

    1. Apple
    2. Banana
    3. Cherry

    3. 自定义结构指令

    它是什么?

    您可以创建自己的结构指令,根据自定义逻辑来操作 DOM。

    例子

    **指示:**

    import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
    
    @Directive({
      selector: '[appUnless]'
    })
    export class UnlessDirective {
      @Input() set appUnless(condition: boolean) {
        if (!condition) {
          this.viewContainer.createEmbeddedView(this.templateRef);
        } else {
          this.viewContainer.clear();
        }
      }
    
      constructor(
        private templateRef: TemplateRef,
        private viewContainer: ViewContainerRef
      ) {}
    }

    **用法:**

    This text is shown unless the condition is true.

    export class AppComponent {
      isHidden = false;
    
      toggle() {
        this.isHidden = !this.isHidden;
      }
    }

    输出

    当 `isHidden` 为 `false` 时,显示该段落。否则,将其从 DOM 中删除。

    属性指令

    属性指令修改元素的行为或外观而不改变 DOM 结构。

    1. HostBinding 和 HostListener

    这些是什么?

  • HostBinding:将指令/组件的属性绑定到主机元素。
  • HostListener:监听主机元素上的事件并触发方法。
  • 使用案例

  • 动态改变样式或类别。
  • 处理宿主元素上的事件。
  • 例子

    **指示:**

    import { Directive, HostBinding, HostListener } from '@angular/core';
    
    @Directive({
      selector: '[appHighlight]'
    })
    export class HighlightDirective {
      @HostBinding('style.backgroundColor') backgroundColor: string;
    
      @HostListener('mouseenter') onMouseEnter() {
        this.backgroundColor = 'yellow';
      }
    
      @HostListener('mouseleave') onMouseLeave() {
        this.backgroundColor = 'transparent';
      }
    }

    **用法:**

    Hover over this text to see the highlight.

    输出

    当您将鼠标悬停在段落上时,其背景颜色会变为黄色。当您将鼠标移开时,它会恢复为透明。

    概括

  • 结构指令:通过添加或删除元素(*ngIf、*ngFor、自定义指令)来更改 DOM 的结构。
  • 属性指令:修改现有元素(HostBinding、HostListener)的行为或外观。
  • 这些指令提供了强大的工具来构建动态和交互式的 Angular 应用程序。

    Image description

    3. 组件交互和内容查询

    此类别提供用于动态管理和与 Angular 视图及其内容交互的工具,为操作模板和视图容器提供了灵活性。

    查看查询

    ViewChild 和 ViewChildren

    这些是什么?

    `ViewChild` 和 `ViewChildren` 是用于访问组件模板内的子组件、指令或 DOM 元素的装饰器。

    使用案例

  • 访问单个或多个子元素。
  • 与组件或元素动态交互。
  • 例子

    子视图

    **模板:**

    
    

    **成分:**

    import { Component, ViewChild } from '@angular/core';
    import { ChildComponent } from './child.component';
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
    })
    export class AppComponent {
      @ViewChild('childComp') child: ChildComponent;
    
      callChildMethod() {
        this.child.sayHello();
      }
    }

    **子组件:**

    import { Component } from '@angular/core';
    
    @Component({
      selector: 'app-child',
      template: '

    Hello from Child!

    ', }) export class ChildComponent { sayHello() { alert('Hello from Child Component!'); } }

    输出:

    单击按钮会触发子组件的“sayHello”方法,显示一条警报。

    查看儿童

    **模板:**

    
    

    **成分:**

    import { Component, QueryList, ViewChildren } from '@angular/core';
    import { ChildComponent } from './child.component';
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
    })
    export class AppComponent {
      items = [1, 2, 3];
    
      @ViewChildren('child') children: QueryList;
    
      callAllChildMethods() {
        this.children.forEach((child) => child.sayHello());
      }
    }

    输出:

    点击按钮将调用列表中所有子组件的“sayHello”方法。

    内容查询

    ContentChild 和 ContentChildren

    这些是什么?

    `ContentChild` 和 `ContentChildren` 是用于使用 `ng-content` 查询组件内投影内容的装饰器。

    使用案例

  • 访问并与投影到组件中的内容进行交互。
  • 例子

    内容子项

    **父模板:**

    
      

    Projected Content

    **容器组件:**

    import { Component, ContentChild, AfterContentInit } from '@angular/core';
    
    @Component({
      selector: 'app-container',
      template: '',
    })
    export class ContainerComponent implements AfterContentInit {
      @ContentChild('projectedContent') content: ElementRef;
    
      ngAfterContentInit() {
        console.log(this.content.nativeElement.textContent);
      }
    }

    输出:

    将“投影内容”记录到控制台。

    内容儿童

    **父模板:**

    
      

    {{ item }}

    **容器组件:**

    import { Component, ContentChildren, QueryList, AfterContentInit } from '@angular/core';
    
    @Component({
      selector: 'app-container',
      template: '',
    })
    export class ContainerComponent implements AfterContentInit {
      @ContentChildren('projected') contents: QueryList;
    
      ngAfterContentInit() {
        this.contents.forEach((content) => {
          console.log(content.nativeElement.textContent);
        });
      }
    }

    输出:

    将“items”数组中的每个项目记录到控制台。

    概括

  • ViewChild 和 ViewChildren:访问组件模板内的子组件、指令或 DOM 元素。
  • ContentChild 和 ContentChildren:访问父组件中通过 ng-content 提供的投影内容。
  • 这些工具支持与视图和投影内容的动态交互,提供对 Angular 模板的细粒度控制。

    Image description

    4.渲染和DOM操作

    此类别专注于使用 Angular 的 API(例如“Renderer2”和“ElementRef”)动态操作 DOM 元素并高效地渲染更改。这些工具可确保与 DOM 进行安全且符合框架要求的交互。

    渲染 API

    渲染器2

    什么是 Renderer2?

    `Renderer2` 是用于 DOM 操作的 Angular 服务,它提供了对直接 DOM 操作的抽象。这确保了代码在不同的渲染环境中兼容,例如服务器端渲染 (SSR)。

    使用案例

  • 添加、删除或修改 DOM 元素。
  • 动态改变元素的样式、类或属性。
  • 例子

    添加和删​​除元素

    **模板:**

    **成分:**

    import { Component, Renderer2, ElementRef, ViewChild } from '@angular/core';
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
    })
    export class AppComponent {
      @ViewChild('container') container: ElementRef;
      private createdElement: HTMLElement;
    
      constructor(private renderer: Renderer2) {}
    
      addElement() {
        this.createdElement = this.renderer.createElement('p');
        const text = this.renderer.createText('This is a dynamically added element.');
        this.renderer.appendChild(this.createdElement, text);
        this.renderer.appendChild(this.container.nativeElement, this.createdElement);
      }
    
      removeElement() {
        if (this.createdElement) {
          this.renderer.removeChild(this.container.nativeElement, this.createdElement);
          this.createdElement = null;
        }
      }
    }

    输出:

  • 单击“添加元素”可动态地将段落插入到 div 中。
  • 单击“删除元素”将删除添加的段落。
  • 元素样式

    **模板:**

    Style me!

    **成分:**

    import { Component, Renderer2, ElementRef, ViewChild } from '@angular/core';
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
    })
    export class AppComponent {
      @ViewChild('textElement') textElement: ElementRef;
    
      constructor(private renderer: Renderer2) {}
    
      applyStyles() {
        this.renderer.setStyle(this.textElement.nativeElement, 'color', 'blue');
        this.renderer.setStyle(this.textElement.nativeElement, 'font-weight', 'bold');
      }
    }

    输出:

    单击**应用样式**将段落文本更改为蓝色并加粗。

    元素参考

    元素引用

    什么是 ElementRef?

    `ElementRef` 是提供对 DOM 元素的直接访问的服务。它通常与 `@ViewChild` 或 `@ContentChild` 一起使用来操作 DOM 元素。

    使用案例

  • 访问本机 DOM 元素以获取自定义逻辑。
  • 检索元素属性(例如尺寸)。
  • 例子

    访问和修改 DOM 属性

    **模板:**

    Modify my text!

    **成分:**

    import { Component, ElementRef, ViewChild } from '@angular/core';
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
    })
    export class AppComponent {
      @ViewChild('textElement') textElement: ElementRef;
    
      changeText() {
        this.textElement.nativeElement.textContent = 'Text has been modified!';
      }
    }

    输出:

    单击**更改文本**可动态更新段落的文本内容。

    获取元素尺寸

    **模板:**

    **成分:**

    import { Component, ElementRef, ViewChild } from '@angular/core';
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
    })
    export class AppComponent {
      @ViewChild('boxElement') boxElement: ElementRef;
    
      logDimensions() {
        const element = this.boxElement.nativeElement;
        console.log(`Width: ${element.offsetWidth}, Height: ${element.offsetHeight}`);
      }
    }

    输出:

    单击**记录尺寸**将`div`的尺寸记录到控制台。

    最佳实践

  • 为了兼容性和安全性,优先使用 Renderer2 而不是直接 DOM 操作。
  • 谨慎使用 ElementRef,通过安全访问 DOM 元素来避免潜在的 XSS 漏洞。
  • 概括

  • Renderer2 提供了一种安全、符合框架的方式来操作 DOM。
  • ElementRef 可以直接访问 DOM 元素,但应小心使用。
  • 这些工具使开发人员能够创建动态、交互式的用户界面,同时遵循 Angular 的最佳实践。

    Image description

    5.Angular 中的动态组件加载

    Angular 允许开发人员在运行时动态加载组件。当处理编译时不知道要显示的组件的情况(例如自定义模式、弹出窗口或动态表单组件)时,此功能至关重要。在 Angular 中,动态组件加载是使用 `ComponentRef`、`ComponentFactoryResolver` 和 `ViewContainerRef` 实现的。

    关键概念和术语

  • ComponentRef:此接口提供对已创建组件的引用。它允许我们与组件的实例进行交互、操作其生命周期方法并向其传递数据。
  • ComponentFactoryResolver:此服务用于解析特定组件类型的 ComponentFactory。它充当创建组件实例的工厂。
  • ViewContainerRef:它是 Angular 应用程序中可以动态插入组件的地方。它充当组件实例的容器。
  • 示例用例

    1. 将组件动态加载到视图中

    **场景**:您的 Angular 应用中有一个按钮,单击它后,会动态加载一个模态组件。

    **步骤**:

  • 创建模态组件:
  • // modal.component.ts
       import { Component, Input } from '@angular/core';
    
       @Component({
         selector: 'app-modal',
         template: `
           
         `
       })
       export class ModalComponent {
         @Input() title: string;
    
         close() {
           // Logic to close the modal
         }
       }
  • 组件创建方法:
  • // app.component.ts
       import { Component, ComponentFactoryResolver, ViewChild, ViewContainerRef } from '@angular/core';
       import { ModalComponent } from './modal.component';
    
       @Component({
         selector: 'app-root',
         template: ``
       })
       export class AppComponent {
         @ViewChild('container', { read: ViewContainerRef, static: true }) container: ViewContainerRef;
    
         constructor(private componentFactoryResolver: ComponentFactoryResolver) {}
    
         loadComponent() {
           const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
           const componentRef = this.container.createComponent(factory);
           componentRef.instance.title = 'Dynamic Modal';
         }
       }

    2. 将数据传递给动态组件

    **场景**:您想要将数据传递给动态加载的组件。

    **例子**:

    // app.component.ts
    import { Component, ComponentFactoryResolver, ViewChild, ViewContainerRef } from '@angular/core';
    import { ModalComponent } from './modal.component';
    
    @Component({
      selector: 'app-root',
      template: ``
    })
    export class AppComponent {
      @ViewChild('container', { read: ViewContainerRef, static: true }) container: ViewContainerRef;
    
      constructor(private componentFactoryResolver: ComponentFactoryResolver) {}
    
      loadComponentWithData() {
        const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
        const componentRef = this.container.createComponent(factory);
        componentRef.instance.title = 'Dynamic Modal with Data';
      }
    }

    3. 访问生命周期钩子

    **场景**:与动态加载组件的生命周期钩子交互,如“ngOnInit”。

    **例子**:

    // modal.component.ts
    import { Component, Input, OnInit } from '@angular/core';
    
    @Component({
      selector: 'app-modal',
      template: `
        
      `
    })
    export class ModalComponent implements OnInit {
      @Input() title: string;
    
      ngOnInit() {
        console.log('Modal Component Initialized');
      }
    
      close() {
        // Logic to close the modal
      }
    }
    
    // app.component.ts
    import { Component, ComponentFactoryResolver, ViewChild, ViewContainerRef } from '@angular/core';
    import { ModalComponent } from './modal.component';
    
    @Component({
      selector: 'app-root',
      template: ``
    })
    export class AppComponent {
      @ViewChild('container', { read: ViewContainerRef, static: true }) container: ViewContainerRef;
    
      constructor(private componentFactoryResolver: ComponentFactoryResolver) {}
    
      loadComponent() {
        const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
        const componentRef = this.container.createComponent(factory);
        componentRef.instance.title = 'Dynamic Modal with Lifecycle';
      }
    }

    详细说明

  • ComponentRef:ComponentRef 允许您与创建的组件实例进行交互。您可以调用组件的方法 (componentRef.instance.methodName()) 并访问其属性。示例:componentRef.instance.title = 'New Title'; 为动态创建的模式设置新标题。
  • ComponentFactoryResolver:它为特定组件类型提供 ComponentFactory。ComponentFactory 对象用于创建组件的新实例。示例:const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent); 工厂包含创建 ModalComponent 实例所需的信息。
  • ViewContainerRef:此容器用于在运行时插入组件。它允许动态创建、附加和移除组件。示例:const componentRef = this.container.createComponent(factory); ViewContainerRef 上的 createComponent 方法用于实例化组件。此处的工厂是您要加载的组件的 ComponentFactory。
  • 概括

    Angular 中的动态组件加载提供了一种在运行时创建和与组件交互的强大方法。使用 `ComponentRef`、`ComponentFactoryResolver` 和 `ViewContainerRef`,开发人员可以无缝加载组件、传递数据并与其生命周期挂钩进行交互。这种方法对于创建可适应各种场景和要求的模块化、灵活的 Angular 应用程序至关重要。

    Image description

    6. Angular 中的变化检测和视图

    Angular 通过 **变更检测** 和 **视图管理** 提供了跟踪和响应数据和视图变更的机制。理解这些概念对于构建动态且反应灵敏的 Angular 应用程序至关重要。本指南涵盖了 `ChangeDetectorRef`、`ViewRef`、`HostViewRef` 和 `EmbeddedViewRef`,并为每个概念提供了示例和说明。

    1.变化检测

    **变更检测** 是 Angular 用来跟踪数据变化并在发生这些变化时自动更新视图的过程。Angular 使用变更检测树来监控组件之间的变化并相应地更新 UI。

    变更检测器参考

    `ChangeDetectorRef` 是一个直接 API,允许手动控制变更检测过程。它提供了强制检测视图中变更的方法。

  • 方法:markForCheck():标记组件以进行变更检测。变更检测机制将在下次运行时考虑该组件。detectChanges():手动触发组件的变更检测。detach():将组件从变更检测树中分离,这意味着不会自动检查该组件。reattach():将先前分离的组件重新附加到变更检测树。
  • 例子:

    // app.component.ts
    import { Component, ChangeDetectorRef } from '@angular/core';
    
    @Component({
      selector: 'app-root',
      template: `
        

    {{message}}

    ` }) export class AppComponent { message: string = 'Initial Message'; constructor(private cdr: ChangeDetectorRef) {} updateMessage() { this.message = 'Updated Message'; } forceUpdate() { this.cdr.detectChanges(); // Forces change detection to update the view } }
  • 说明:在此示例中,detectChanges() 用于手动触发变更检测。调用 updateMessage() 时,它会更改消息属性。如果单击 forceUpdate(),detectChanges() 会确保更新视图以反映此更改。ChangeDetectorRef 允许开发人员手动控制变更检测的发生时间,这在处理复杂的 UI 或可能无法自动触发变更检测的第三方库时特别有用。
  • 2.视图管理

    Angular 组件由 `View` 对象构建。可以使用 `ViewRef`、`HostViewRef` 和 `EmbeddedViewRef` 来操作这些视图。

    视图引用

    “ViewRef” 提供对视图 API 和生命周期钩子的访问。它表示组件的视图并允许对该视图进行操作。

  • 方法:detach():将视图从变化检测循环中分离。attach():将分离的视图重新附加到变化检测循环。destroy():销毁视图并清理资源。
  • 例子:

    // app.component.ts
    import { Component, ViewChild, ViewContainerRef, ComponentFactoryResolver, ViewRef } from '@angular/core';
    import { ModalComponent } from './modal.component';
    
    @Component({
      selector: 'app-root',
      template: `
        
      `
    })
    export class AppComponent {
      @ViewChild('container', { read: ViewContainerRef, static: true }) container: ViewContainerRef;
    
      constructor(private componentFactoryResolver: ComponentFactoryResolver) {}
    
      loadComponent() {
        const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
        const componentRef = this.container.createComponent(factory);
        const viewRef = componentRef.hostView;
    
        viewRef.detach(); // Detaching view from change detection
        console.log('View Detached');
    
        setTimeout(() => {
          viewRef.attach(); // Re-attaching view after 2 seconds
          console.log('View Re-attached');
        }, 2000);
      }
    }
  • 说明:在此示例中,在加载并附加组件 (ModalComponent) 后,使用 viewRef.detach() 分离 ViewRef。这会暂时将视图从变更检测周期中移除,从而阻止自动检查视图。2 秒后,使用 viewRef.attach() 重新附加视图,以便再次检查其更新。
  • 主机视图引用

    “HostViewRef” 表示主机组件(包含其他子组件的组件)的视图。它允许访问主机组件的 DOM。

  • 方法:detach():将宿主视图从变更检测周期中分离。attach():重新连接宿主视图。destroy():销毁宿主视图及其所有子视图。
  • 例子:

    // app.component.ts
    import { Component, ViewChild, ViewContainerRef, ComponentFactoryResolver, HostViewRef } from '@angular/core';
    import { ModalComponent } from './modal.component';
    
    @Component({
      selector: 'app-root',
      template: `
        
      `
    })
    export class AppComponent {
      @ViewChild('container', { read: ViewContainerRef, static: true }) container: ViewContainerRef;
    
      constructor(private componentFactoryResolver: ComponentFactoryResolver) {}
    
      loadComponent() {
        const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
        const componentRef = this.container.createComponent(factory);
        const hostViewRef: HostViewRef = componentRef.hostView;
    
        hostViewRef.detach(); // Detaching host view
        console.log('Host View Detached');
    
        setTimeout(() => {
          hostViewRef.attach(); // Re-attaching host view after 2 seconds
          console.log('Host View Re-attached');
        }, 2000);
      }
    }
  • 说明:与 ViewRef 类似,HostViewRef 允许分离和附加主机视图。通过分离主机视图,将不会自动检查对视图的更改。这对于临时模式等场景或需要管理大量 DOM 操作而不影响性能的情况很有用。重新附加主机视图可在变更检测周期内将组件恢复到正常运行状态。
  • 嵌入视图引用

    `EmbeddedViewRef` 表示动态创建的视图。它提供方法来管理另一个组件中嵌入视图的生命周期和 DOM。

  • 方法:detach():将嵌入视图从变更检测循环中分离。attach():重新附加嵌入视图。destroy():销毁嵌入视图并清理资源。
  • 例子:

    // app.component.ts
    import { Component, ViewChild, ViewContainerRef, ComponentFactoryResolver, EmbeddedViewRef } from '@angular/core';
    import { ModalComponent } from './modal.component';
    
    @Component({
      selector: 'app-root',
      template: `
        
      `
    })
    export class AppComponent {
      @ViewChild('container', { read: ViewContainerRef, static: true }) container: ViewContainerRef;
    
      constructor(private componentFactoryResolver: ComponentFactoryResolver) {}
    
      loadComponent() {
        const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
        const componentRef = this.container.createComponent(factory);
        const embeddedViewRef: EmbeddedViewRef = componentRef.hostView;
    
        embeddedViewRef.detach(); // Detaching embedded view
        console.log('Embedded View Detached');
    
        setTimeout(() => {
          embeddedViewRef.attach(); // Re-attaching embedded view after 2 seconds
          console.log('Embedded View Re-attached');
        }, 2000);
      }
    }
  • 说明:与前面的示例类似,EmbeddedViewRef 可分离和附加视图。该示例演示了如何使用 embeddedViewRef.detach() 和 embeddedViewRef.attach() 来管理动态创建的组件的生命周期。这在有条件地加载组件(您希望暂时将其从 UI 中隐藏而不销毁它)等情况下非常有用。
  • 概括

    了解**变更检测**和**视图管理**对于开发高性能 Angular 应用程序至关重要。通过使用 `ChangeDetectorRef`、`ViewRef`、`HostViewRef` 和 `EmbeddedViewRef`,开发人员可以精确控制组件和视图的更新时间和方式。这些工具通过选择性地管理变更检测的发生时间,实现了动态加载、条件渲染和性能优化等高级场景。

    Image description

    7.Angular 中的依赖注入和服务

    依赖注入 (DI) 是 Angular 中一个强大的设计模式,允许开发人员以模块化和可测试的方式管理依赖项。Angular 使用“Injector”提供了一个强大的 DI 系统。了解 DI 以及它如何与服务协同工作对于构建可扩展且可维护的 Angular 应用程序至关重要。下面,我们将介绍“Injector”及其用法,并提供不同场景的示例。

    1.依赖注入

    **依赖注入 (DI)** 是一种技术,其中对象(依赖项)从外部源接收其依赖项,而不是在内部创建它们。这种方法减少了组件之间的耦合并增强了模块化,使代码库更易于测试和维护。

    注射器

    `Injector` 是 Angular DI 系统的核心。它负责解析依赖项并向组件提供服务。Angular 中的 DI 是分层的,遵循与组件树类似的层次结构。当在同一模块中声明服务时,这允许跨组件共享服务。

    喷射器的主要特点:

  • 依赖关系解析:注入器解析特定服务的依赖关系并在请求时提供该服务。
  • 分层 DI:每个 Angular 组件和模块都有自己的注入器,允许进行范围依赖解析。
  • 作用域:Injector 作用域定义了如何共享依赖项。常见的作用域包括 Root、Component 和 Directive 作用域。
  • 示例场景

    1. 基本 DI 示例

    **场景**:将简单服务注入到组件中。

    **服务**:

    // app.service.ts
    export class AppService {
      getMessage(): string {
        return 'Hello from AppService!';
      }
    }

    **成分**:

    // app.component.ts
    import { Component } from '@angular/core';
    import { AppService } from './app.service';
    
    @Component({
      selector: 'app-root',
      template: `

    {{message}}

    `, providers: [AppService] // Providing service in the component level }) export class AppComponent { message: string; constructor(private appService: AppService) { this.message = this.appService.getMessage(); } }
  • 解释:AppService 是一个提供消息的简单服务。在 AppComponent 中,AppService 是使用 Angular 的 DI 系统通过构造函数注入的。组件装饰器中的提供程序数组告诉 Angular 如何为该组件提供依赖项。通过注入 AppService,AppComponent 可以直接访问其方法和属性,而无需创建新实例。
  • 2. 跨组件的服务注入

    **场景**:在同一模块中的多个组件之间共享一个服务。

    **服务**:

    // app.service.ts
    export class AppService {
      getGreeting(): string {
        return 'Hello Angular!';
      }
    }

    **组件 1**:

    // component1.component.ts
    import { Component } from '@angular/core';
    import { AppService } from './app.service';
    
    @Component({
      selector: 'app-component1',
      template: `

    {{greeting}}

    ` }) export class Component1 { greeting: string; constructor(private appService: AppService) { this.greeting = this.appService.getGreeting(); } }

    **组件 2**:

    // component2.component.ts
    import { Component } from '@angular/core';
    import { AppService } from './app.service';
    
    @Component({
      selector: 'app-component2',
      template: `

    {{greeting}}

    ` }) export class Component2 { greeting: string; constructor(private appService: AppService) { this.greeting = this.appService.getGreeting(); } }
  • 解释:Component1 和 Component2 共享同一个 AppService,因为它们属于同一个模块。AppService 实例在这些组件之间共享,因为 Angular 的 DI 系统在提供程序列表中声明时会提供一个单例实例。这使得 Component1 和 Component2 可以使用 AppService,而无需自己实例化它,从而确保跨组件的服务数据一致性。
  • 3.动态注入和注入器 API

    **场景**:使用 `Injector` 动态解析依赖项。

  • 例子:
  • // dynamic-injection.component.ts
      import { Component, Injector } from '@angular/core';
      import { AppService } from './app.service';
    
      @Component({
        selector: 'app-dynamic-injection',
        template: ``
      })
      export class DynamicInjectionComponent {
        constructor(private injector: Injector) {}
    
        resolveService() {
          // Resolve AppService dynamically
          const service = this.injector.get(AppService);
          console.log(service.getGreeting());
        }
      }
  • 说明:在此示例中,DynamicInjectionComponent 演示了使用 Angular 的 Injector 动态解析服务。Injector 被注入到组件中,并在运行时使用 this.injector.get(AppService) 获取 AppService 的实例。当需要动态解析服务时,这种方法非常有用,尤其是在处理循环依赖关系或在不同上下文中解析服务时。
  • 4. 提供全局级别的服务(Root Injector)

    **场景**:在全球范围内提供服务,以便它可以在整个 Angular 应用程序中使用。

    **例子**:

    // app.module.ts
    import { NgModule } from '@angular/core';
    import { BrowserModule } from '@angular/platform-browser';
    import { AppService } from './app.service';
    import { AppComponent } from './app.component';
    import { DynamicInjectionComponent } from './dynamic-injection.component';
    
    @NgModule({
      declarations: [
        AppComponent,
        DynamicInjectionComponent
      ],
      imports: [
        BrowserModule
      ],
      providers: [AppService], // Providing service globally
      bootstrap: [AppComponent]
    })
    export class AppModule { }
  • 说明:通过将 AppService 添加到 NgModule 中的提供程序数组,它将成为整个 Angular 应用程序中的单例。这允许任何组件访问 AppService,而无需再次显式提供。使用 Injector,可以在整个应用程序中全局解析服务,这对于日志记录、配置或常用实用程序等服务非常有用。
  • 5. 分层注入

    **场景**:使用分层注入来覆盖嵌套组件内的服务。

    **例子**:

    // app.service.ts
    export class AppService {
      getValue(): string {
        return 'Value from root service';
      }
    }
    
    // child.service.ts
    export class ChildService {
      getValue(): string {
        return 'Value from child service';
      }
    }
    
    // parent.component.ts
    import { Component, Injector } from '@angular/core';
    import { AppService } from './app.service';
    import { ChildService } from './child.service';
    
    @Component({
      selector: 'app-parent',
      template: ``
    })
    export class ParentComponent {
      constructor(private injector: Injector) {}
    
      useChildService() {
        const childService = this.injector.get(ChildService);
        console.log(childService.getValue());
      }
    }
    
    // child.component.ts
    import { Component, Injector } from '@angular/core';
    import { AppService } from './app.service';
    import { ChildService } from './child.service';
    
    @Component({
      selector: 'app-child',
      template: `

    {{childValue}}

    ` }) export class ChildComponent { childValue: string; constructor(private injector: Injector) { const childService = this.injector.get(ChildService); this.childValue = childService.getValue(); } }
  • 解释:ParentComponent 和 ChildComponent 共享 Injector。通过使用 ParentComponent 中的 Injector,我们用 ChildService 覆盖根 AppService。ChildComponent 解析 ChildService 而不是 AppService,因为 ParentComponent 中的 Injector 提供了 ChildService。这展示了 Angular DI 系统的灵活性和层次性。
  • 概括

    依赖注入和服务是 Angular 中的基本概念,允许开发人员清晰地构建应用程序、独立测试服务并保持关注点分离。通过使用“Injector”、“AppService”和“ChildService”示例,本说明展示了如何动态解析服务、跨组件共享服务以及如何在嵌套上下文中覆盖服务。这种方法显著增强了 Angular 应用程序的模块化、可重用性和可维护性。

    Image description

    8. 高级 Angular 实用程序:Differ 实用程序

    在 Angular 中,“Differ”实用程序对于跟踪数据结构的变化并根据这些变化高效更新 DOM 至关重要。Angular 提供了两个主要的 diff 实用程序:“KeyValueDiffers”和“IterableDiffers”。这些实用程序使开发人员能够检测集合中的更改并自动更新视图,从而最大限度地降低性能开销。

    1. 不同实用程序概述

    Angular 的 diff 实用程序由 Angular 的变更检测机制内部使用。它们有助于高效地检测复杂数据结构(如数组和对象)中的变更。通过使用这些 different 实用程序,Angular 可以执行变更检测,而无需在每次检测到变更时迭代集合,从而提高性能。

    关键概念:

  • KeyValueDiffers:用于比较键的对象(地图、字典)的变化。
  • IterableDiffers:用于通过值比较元素的集合(数组、列表)的变化。
  • 2. KeyValueDiffers

    `KeyValueDiffers` 用于检测对象或映射中的变化。它检查键、值或两者的变化,并相应地更新视图。

    **例子**:

    import { Component, KeyValueDiffers } from '@angular/core';
    
    @Component({
      selector: 'app-key-value-diff',
      template: `
        
    • {{key}}: {{data[key]}}
    ` }) export class KeyValueDiffComponent { keys: string[]; data: { [key: string]: string }; constructor(private differs: KeyValueDiffers) { const differ = this.differs.find(this.data).create(); this.data = { 'name': 'John', 'age': '30' }; differ.diff(this.data); // Initial detection // Simulating changes setTimeout(() => { this.data['age'] = '31'; const changes = differ.diff(this.data); if (changes) { this.keys = Object.keys(this.data); } }, 2000); } }
  • 说明:在此示例中,KeyValueDiffComponent 使用 KeyValueDiffers 检测数据对象中的更改。differs.find(this.data).create() 行会为数据对象创建一个不同的实例。最初,数据对象包含姓名和年龄。2 秒后,年龄属性更新为 31。differ 会检测到此更改并通过比较键和值来更新视图。changes 对象有助于确定发生了哪些更改(例如,年龄从 30 变为 31)。
  • KeyValueDiffers 的用例:

  • 实时更新:通过 API 实时更新数据。
  • 检测更新:特定数据发生变化时通知用户。
  • 对象操作:检测嵌套对象或集合中的变化。
  • 3. IterableDiffers

    `IterableDiffers` 用于检测数组、列表和其他可迭代对象等集合中的变化。它通过检测集合中添加、移除或移动的项目来帮助高效管理 DOM。

    **例子**:

    import { Component, IterableDiffers } from '@angular/core';
    
    @Component({
      selector: 'app-iterable-diff',
      template: `
        
    • {{item}}
    ` }) export class IterableDiffComponent { items: string[]; differ: any; constructor(private differs: IterableDiffers) { this.items = ['apple', 'banana', 'orange']; this.differ = this.differs.find(this.items).create(); // Simulating changes setTimeout(() => { this.items.push('grape'); const changes = this.differ.diff(this.items); if (changes) { console.log('Items added:', changes.added); } }, 2000); } }
  • 说明:在此示例中,IterableDiffComponent 使用 IterableDiffers 检测 items 数组中的变化。differs.find(this.items).create() 行会为 items 数组创建一个 different 实例。最初,items 数组包含 ['apple', 'banana', 'orange']。2 秒后,新项目 ('grape') 会添加到数组中。differ 会检测到此变化,比较数组的新旧状态,并更新视图以显示添加的项目 ('grape')。changes 对象会提供有关发生的变化(例如,添加了项目)的见解。
  • IterableDiffers 的用例:

  • 动态列表:使用实时数据更新列表。
  • 动画:检测列表中的移动或重新排列。
  • 批量更新:处理来自 API 或用户交互的更新,其中一次发生多个更改。
  • 4. 结合 KeyValueDiffers 和 IterableDiffers

    应用程序通常需要对象和数组不同的实用程序。Angular 允许您在需要时组合使用这些不同的实用程序。

    **例子**:

    import { Component, IterableDiffers, KeyValueDiffers } from '@angular/core';
    
    @Component({
      selector: 'app-combined-diff',
      template: `
        

    Object Changes:

    • {{key}}: {{objData[key]}}

    Array Changes:

    • {{item}}
    ` }) export class CombinedDiffComponent { objData: { [key: string]: string }; listItems: string[]; objDiffer: any; listDiffer: any; constructor(private differs: KeyValueDiffers, private iterableDiffers: IterableDiffers) { this.objData = { 'name': 'Alice', 'age': '25' }; this.listItems = ['cat', 'dog', 'fish']; this.objDiffer = this.differs.find(this.objData).create(); this.listDiffer = this.iterableDiffers.find(this.listItems).create(); // Simulating changes setTimeout(() => { this.objData['age'] = '26'; this.listItems.push('bird'); const objChanges = this.objDiffer.diff(this.objData); const listChanges = this.listDiffer.diff(this.listItems); if (objChanges) { this.objKeys = Object.keys(this.objData); } if (listChanges) { console.log('Items added:', listChanges.added); } }, 2000); } }
  • 说明:在此示例中,CombinedDiffComponent 演示了 KeyValueDiffers 和 IterableDiffers 的用法。objData 是 KeyValueDiffers 监视的对象,listItems 是 IterableDiffers 监视的数组。使用 objDiffer 和 listDiffer 分别检测 objData 和 listItems 中的更改。组合用例说明了这些不同的实用程序如何共存以跟踪 Angular 中不同数据结构之间的更改。
  • 概括

    Angular 中的 `KeyValueDiffers` 和 `IterableDiffers` 实用程序对于高效处理数据结构的动态更新至关重要。它们允许 Angular 以最小的开销检测更改,从而提供流畅的 UI 更新。通过实际示例,我们了解了如何使用这些不同的实用程序来管理对象和数组中的更改,从而确保 Angular 框架即使在复杂的数据更新下也能保持响应和高性能。这些知识可帮助开发人员通过有效管理状态更改来优化他们的 Angular 应用程序。

    Image descriptionImage description

    以下是摘要:

    1. 模板和内容投影

  • ng-template:用于定义可重用的 HTML 块,而不直接将其渲染到 DOM。它通常用于条件渲染或组件自定义。
  • ng-container:一个包装元素,它不会呈现任何实际的 HTML,但允许将元素和指令组合在一起形成结构指令。
  • ng-content:用于内容投影。它允许您将内容插入到组件或指令模板中。
  • NgTemplateOutlet:用于动态更改组件中呈现的模板的指令。
  • 2. 结构指令和属性指令

  • 结构指令:*ngIf:根据条件控制元素的呈现。*ngFor:遍历集合以呈现每个项目。自定义指令:允许创建可重复使用的逻辑,可应用于整个应用程序的元素。
  • 属性指令:HostBinding 和 HostListener:用于绑定属性并监听主机元素上的事件。
  • 3. 组件交互和内容查询

  • 视图查询:@ViewChild 和 @ViewChildren:用于查询组件视图内的元素或指令。
  • 内容查询:@ContentChild 和 @ContentChildren:用于查询投影到组件视图中的内容。
  • 4.渲染和DOM操作

  • 渲染器 API:Renderer2:提供以与不同平台兼容的方式(如 Web 和服务器端渲染)操作 DOM 的方法。
  • 元素引用:ElementRef:提供对 DOM 中原生元素的引用,允许直接操作。
  • 5.动态组件加载

  • 动态组件创建:ComponentRef:表示动态创建的组件实例。ComponentFactoryResolver:用于在运行时解析和创建组件。ViewContainerRef:保存正在动态创建的组件实例,是将创建的组件附加到 DOM 所必需的。
  • 6. 变化检测和视图

  • 变化检测:ChangeDetectorRef:用于手动触发组件的变化检测。
  • 视图管理:ViewRef:提供对 Angular 视图(模板)的引用。HostViewRef:表示组件的宿主视图(组件模板渲染的主视图)。EmbeddedViewRef:表示嵌入视图,通常用于组件内显示投影内容。
  • 7.依赖注入和服务

  • 依赖注入:注入器:用于提供和管理 Angular 应用程序中的依赖项的核心服务。它允许组件访问服务和其他可注入类。
  • 8. 高级 Angular 实用程序

  • 不同实用程序:KeyValueDiffers:检测对象中的变化。IterableDiffers:检测数组等集合中的变化。
  • 这些概念中的每一个都在 Angular 生态系统中发挥着至关重要的作用,有助于实现高效且可维护的开发。将这些概念融入您的 Angular 专业知识中,将使您全面了解如何构建模块化、可维护且性能良好的应用程序。