妈妈,打工可太难了,学习angular

112 阅读15分钟

前言

如果把前端比作AI机器人。那么HTML就是机器人的身体外骨骼,css就是皮肤及衣服,js就是芯片,框架就是机器人的品牌商……

1. 组件

1.1 单文件组件的构成介绍

1.1.1 基础内容

简单的angular组件一般包含了……@angular/core(angular核心库)、@Component(核心库的组件装饰器)、AppComponents(组件类)。 下面贴一个code片段:

import { Component, Input,Output, OnInit, ViewChild,  OnChanges, EventEmitter } from '@angular/core';

@Component({
selector,
template
templateUrl,
styleUrls,
styles
})

export class AppComponents implements OnInit, OnChanges {
    @Input() name: String = ''
    @Output() eventEmitter = new EventEmitter();
    
    constructor() { }
    
    OnInit() {}
    OnChanges() {
       this.eventEmitter.emit(1); 
    }
}

上面的code。首先,我们先从核心库中导入了Component(组件装饰器)、Input(输入属性装饰器)、Output(输出方法装饰器)、OnInit(初始化生命周期)、ViewChild(属性装饰器)、OnChanges(输入属性发生变化时触发的生命周期钩子), EventEmitter。


接着,我们使用了组件装饰器@Component,可以看到我们传递了一个对象,对象内包含了一些属性——template、selector、templateUrl、styleUrls、styles。这里只是列举了通常会用到的属性,具体用法接下来会讲解。

selector 可以理解为指令或组件的别名,接收一个Sring,在其他组件模版中使用指令或组件需要用到的名称(namespace)。

templatetemplateUrl都是一个用途,都接收一个String。template叫组件内联模版,它接收一个模版字符串。,templateUrl叫模版的url,它接收一个模版文件的url。两者用其一。

styleUrls——样式文件URL,styles——样式,两者都是个Array,可以传递多个样式文件或样式code,两者用其一

EventEmitter事件发射类,用来给父组件抛出事件,一般配合Output装饰使用


最后,我们导出了一个类,这里叫组件类,因为被已经被@Component装饰器标记为组件类。

然后,这个组件类继承了(又叫实现)OnInit、OnChanges,两个生命周期钩子。

在这个类中,我们使用了@Input装饰器,用来接收一个叫name的属性,且规定这个属性只能传递字符串。

俄顷,又使用了@Output装饰器,定义了eventEmitter发生器变量。

OnChanges生命周期钩子中,我们发射了一个值1

此刻,我们就可以在外部使用eventEmitter接收到1这个值了。


在这里,本小结基础已结束,我们只是对angular单文件组件有个大致概念。

下面是具体进阶内容。少侠,可选择性跳过。

1.1.2 @angular/core——angular的核心库。

本小节讲解的api涵盖庞杂,如果你是带着快速上手的目的,了解angular,只需要了解带*的文字即可,笔者也会优先整理带星号的内容。

@angular/core主要内容包含如下——我们可以将核心库理解为芯片,也就是我们的大脑。

mindmap
      `@angular/core`
          类
          装饰器
          函数
          结构

下面就类、装饰器、函数、结构常用的一些api做个详细介绍。

  1. ApplicationRef
  2. ChangeDetectorRef
  3. DestroyRef
  4. ElementRef*

获取原生DOM,需要注意的是,不太推荐这种写法,原因是直接和渲染层耦合,脱离了框架的初衷,且使用此方法,此应用将无法使用web Worker

下面写了一个例子。

<div>
  <button (click)="onAlert()" #dom>测试文本</button>
</div>
import { Component, ElementRef, ViewChild } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  title = 'web';
  @ViewChild('dom') dom!: ElementRef;
  constructor() {}
  onAlert() {
    this.dom.nativeElement.innerHTML = '22222';
    console.log(this.dom.nativeElement);
    
  }
}

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

未完待续……

  1. EmbeddedViewRef
  2. EnvironmentInjector
  3. EventEmitter
  4. Injector
  5. NgModuleRef
  6. NgProbeToken
  7. NgZone
  8. PlatformRef
  9. Query
  10. QueryList
  11. Renderer2
  12. RendererFactory2
  13. Sanitizer
  14. SimpleChange
  15. TemplateRef
  16. Testability
  17. TestabilityRegistry
  18. ViewContainerRef
  19. ViewRef
  • 装饰器
  1. Attribute
  2. Component
  3. ContentChild
  4. ContentChildren
  5. Directive
  6. Host
  7. HostBinding
  8. HostListener
  9. Inject
  10. Injectable
  11. Input
  12. NgModule
  13. Optional
  14. Output
  15. Pipe
  16. Self
  17. SkipSelf
  18. ViewChild*
  19. ViewChildren
  • 函数
  1. assertInInjectionContext
  2. assertPlatform
  3. booleanAttribute
  4. computed
  5. createComponent
  6. createEnvironmentInjector
  7. createNgModule
  8. createPlatform
  9. createPlatformFactory
  10. destroyPlatform
  11. effect
  12. forwardRef
  13. getNgModuleById
  14. getPlatform
  15. importProvidersFrom
  16. inject
  17. isDevMode
  18. isSignal
  19. isStandalone
  20. makeEnvironmentProviders
  21. makeStateKey
  22. mergeApplicationConfig
  23. numberAttribute
  24. provideZoneChangeDetection
  25. reflectComponentType
  26. resolveForwardRef
  27. runInInjectionContext
  28. setTestabilityGetter
  29. signal
  30. untracked
  • 结构
  1. AbstractType
  2. AfterContentChecked
  3. AfterContentInit
  4. AfterViewChecked
  5. AfterViewInit
  6. ApplicationConfig
  7. BootstrapOptions
  8. ChangeDetectionStrategy
  9. ClassProvider
  10. ClassSansProvider
  11. ComponentMirror
  12. ConstructorProvider
  13. ConstructorSansProvider
  14. CreateComputedOptions
  15. CreateEffectOptions
  16. CreateSignalOptions
  17. DoBootstrap
  18. DoCheck
  19. EffectRef
  20. ExistingProvider
  21. ExistingSansProvider
  22. FactoryProvider
  23. FactorySansProvider
  24. ForwardRefFn
  25. GetTestability
  26. InjectOptions
  27. InjectableType
  28. InjectorType
  29. IterableChangeRecord
  30. IterableChanges
  31. IterableDiffer
  32. IterableDifferFactory
  33. KeyValueChangeRecord
  34. KeyValueChanges
  35. KeyValueDiffer
  36. KeyValueDifferFactory
  37. MissingTranslationStrategy
  38. ModuleWithProviders
  39. NgZoneOptions
  40. OnChanges
  41. OnDestroy
  42. OnInit
  43. PipeTransform
  44. Predicate
  45. RendererStyleFlags2
  46. RendererType2
  47. SchemaMetadata
  48. SecurityContext
  49. SimpleChanges
  50. StaticClassProvider
  51. StaticClassSansProvider
  52. TrackByFunction
  53. TypeDecorator
  54. TypeProvider
  55. ValueProvider
  56. ValueSansProvider
  57. ViewEncapsulation
  58. WritableSignal
  59. ng-container
  60. ng-content
  61. ng-template

1.1.3 @Component——angular一个装饰器,用于把某个类(AppComponents class)标记为 Angular 组件,并为它配置一些元数据,以决定该组件在运行期间该如何处理、实例化和使用。

3. AppComponents——angular组件类。

如这个代码片段中。

1.2 组件的使用及通信

上小结介绍了单文件组件的基本构成,以及常用的api。本小节,接上小结,引入父子通信的方法

1.2.1 组件的基本封装及使用

先看一段code及效果。

子组件

import { Component } from '@angular/core';

@Component({
  selector: 'app-child-component',
  template: `<p>这是子组件内容</p>`,
  styles: ['']
})
export class ChildComponentComponent {

}

父组件

import { Component } from '@angular/core';

@Component({
  selector: 'app-parent-component',
  template: `<div>
  这是父组件内容:
  <app-child-component></app-child-component>
</div>`,
  styles: [``]
})
export class ParentComponentComponent {

}

视图:

image.png

如效果图,我们先创建了一个子组件,又创建了一个父组件,俄顷,又在父组件应用了子组件。

1.2.2 组件通信

  • 父子组件通信

父组件

import { Component } from '@angular/core';

@Component({
  selector: 'app-parent-component',
  template: `<div>
  {{content}} {{name}}
  <app-child-component [text]="text" (eventEmitter)="emittterCallback($event)"></app-child-component>
</div>`,
  styles: [``]
})
export class ParentComponentComponent {
  name:String = '父组件默认名称';
  text:String = '父级传递的text属性';
  content:String = '这是父组件内容:';
  constructor() {}

  emittterCallback(value:String) {
    this.name = value;
  }
}

在父组件中,我么先引用了子组件——app-child-component

并且给子组件传递了属性text,以及监听了子组件的回调方法eventEmitter

俄顷,在父组件类中,定义了name属性,接收子组件回调传递的值。

定义了text属性,传递给了子组件的text属性。

定义了content属性,在父组件展示

定义了emittterCallback回调监听,在监听中,接收到父组件值,赋值给了name属性,name发生了变化

子组件

import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-child-component',
  template: `<div>这是子组件内容:
    <div>{{text}}</div>
    <button (click)="onClick()">点击我</button>
  </div>`,
  styles: ['']
})
export class ChildComponentComponent {
  @Input() text:String = '';
  @Output() eventEmitter = new EventEmitter()

  onClick() {
    this.eventEmitter.emit('子组件抛出内容')
  }
}

在子组件中,我们先用input装饰器定义了text属性,用于接收父组件传递的值。

引入Ouput装饰器,定义了eventEmitter实例发射器,用于向父组件抛出回调以及参数。

具体效果如图:

屏幕录制2023-08-24 17.22.27.gif

点子组件的button时,触发了click事件,紧接着触发了发射器eventEmitter,父组件监听收到了最新的值,父组件name属性做了同步更新。

  • 通过viewChild属性装饰器通信

此处只讲属性装饰器的用法,具体展开前往1.1.2小节

viewChild装饰器,入参是一个字符串、组件、指令、模版。这样就可以拿到dom、组件实例、指令实例、模版等,换个说法,它的入参接收一个字符串或类。方便在js内自由组合,完成业务

子组件

import { Component, Input, Output, EventEmitter, ViewChild, ElementRef } from '@angular/core';

@Component({
  selector: 'app-child-component',
  template: `<div>这是子组件内容:
    <div>{{text}}</div>
    <button (click)="onClick()">点击我</button>
  </div>`,
  styles: ['']
})
export class ChildComponentComponent {
  @Input() text:String = '';
  @Output() eventEmitter = new EventEmitter()

  htmlpVal:String = '回调后的文本'
  childName:string = '子组件的变量'
  onClick() {
    this.eventEmitter.emit('子组件抛出内容')
  }

  // 作为父组件回调
  parentCallback() {
    return this.htmlpVal
  }
}

父组件

import { Component, ViewChild, ElementRef } from '@angular/core';
import {ChildComponentComponent} from '../child-component/child-component.component';

@Component({
  selector: 'app-parent-component',
  template: `<div>
  {{content}} {{name}}
  <button (click)="onclick" #buttonDom>点击我内容就会变</button>
  <div>{{childComponentComponent.childName}}</div>
  <app-child-component [text]="text" (eventEmitter)="emittterCallback($event)" #childComponentComponent></app-child-component>
</div>`,
  styles: [``]
})
export class ParentComponentComponent {
  name:String = '父组件默认名称';
  text:String = '父级传递的text属性';
  content:String = '这是父组件内容:';
  @ViewChild('buttonComponent') buttonDom?: ElementRef;
  @ViewChild(ChildComponentComponent) childComponentComponent?: ChildComponentComponent
  constructor() {}

  emittterCallback(value:String) {
    this.name = value;
  }

  onclick() {
    this.buttonDom!.nativeElement.innerHTML = this.childComponentComponent?.parentCallback();
  }
}

点击前

image.png

点击后

image.png

基于父子组件通信的code上,父组件添加了

  • <button (click)="onclick" #buttonDom>点击我内容就会变</button><div>{{childComponentComponent.childName}}</div>

  • @ViewChild('buttonComponent') buttonDom?: ElementRef;

  • @ViewChild(ChildComponentComponent) childComponentComponent?:ChildComponentComponent

子组件添加了

htmlpVal:String = '回调后的文本'

childName:string = '子组件的变量'

onClick() { this.eventEmitter.emit('子组件抛出内容') }

parentCallback() { return this.htmlpVal }

我们在父组件先获取了一个button元素buttonDom,又获取了子组件实例childComponentComponent。点击按钮时,触发父组件onclick方法,调用了子组件的parentCallback回调,将子组件属性赋值给了buttonDom的innerHTML。

在父组件<div>{{childComponentComponent.childName}}</div>中,我们也可以直接使用子组件的属性。

需要注意的是,此方法最好不要直接修改子组件内部的值。尽管允许这样做,但违反了angular框架的设计理念与代码质量的可维护性规则。可能会发生意想不到的错误。

  • 广播通信(service通知)

rxjs文章推荐

如果对于rxjs不了解,因为这里会涉及单播多播的概念。可暂时了解用法。在了解10.章节, rxjs模块后,可再次返回查看。

广播通信不局限于父子组件,也可以是毫无关系的两个组件通信。场景可以是一对一单播,也可以是一对多多播。

  • 单播,每次都会进行实例化,从一个消息开始推送给观察者。
  • 多播,采用单例模式,每次从观察者开始观察的那一刻,开始推送消息给观察者。
  • 从某种角度。单播,可以看做app的一个短视频,无论你何时进来,看到那里。只要你退出,再次观看,它都会从头放映。多播,可以看做一场直播。是不可逆的,只要你观看,我只能给你看正在进行的,且不能回放。

解释完这些概念,就开始了。

下面是一段code.

父组件

import { Component, OnInit } from '@angular/core';
import {AppServiceService} from '../app-service.service';
@Component({
  selector: 'app-parent-component',
  template: `<div>
    {{parentName}}
    <button (click)="onclick()">点击我给子组件发送消息</button>
  <app-child-component></app-child-component>
</div>`,
  styles: [``]
})
export class ParentComponentComponent implements OnInit {
  constructor(private appServiceService: AppServiceService) {}
  parentName:String = '';
  ngOnInit() {
    this.appServiceService.obs.subscribe(val => {
      this.parentName = val.name;
      console.log('我是父组件观察者,收到的值:',val)
    })
  }
  onclick() {
    this.appServiceService.obs.next({name: '收到父组件的消息了'});
  }
}

子组件

import { Component, OnInit } from '@angular/core';
import { AppServiceService } from '../app-service.service';

@Component({
  selector: 'app-child-component',
  template: `<div>
    <div>{{name}}</div>
    <button (click)="onClick()">点击我</button>
  </div>`,
  styles: ['']
})
export class ChildComponentComponent implements OnInit {
  constructor(private appServiceService: AppServiceService) {
    
  }
  name:String = ''

  ngOnInit(): void {
    this.appServiceService.obs.subscribe(val => {
      this.name = val.name
    })
  }
  onClick() {
    this.appServiceService.obs.next({name: '我是子组件发送的消息'})
  }
}

service

import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class AppServiceService {
  obs = new BehaviorSubject({name: '1111'})
  constructor() { }
}

屏幕录制2023-08-25 18.11.45.gif

Injectable为可注入装饰器。providedIn属性为'root',注入为应用实例级别,整个实例引用都可被注入。

可以看到在父组件和子组件分别都注入了服务AppServiceService,然后使用了服务中的属性Observable类型的 obs,并调用了next方法发送了一个值。

俄顷,分别在父组件和子组件,分别监听和发送了数据。当点击button时,我们通过obs的next属性,发送了一个对象,对象中的name的值为我们要发送的内容。这样不论是父组件还是子组件点击发送数据,父子组件都可以收到数据。

BehaviorSubject会接收最近发送的一次数据,采用模式为多播。

如果想要父子间通讯和子组件互相不影响呢?其实只需要,再实例化一个BehaviorSubject.

import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class AppServiceService {
  childObs = new BehaviorSubject({name: '子组件'})
  parentObs = new BehaviorSubject({name: '父组件'})
  constructor() { }
}

这样,父组件和子组件接收和发送消息就会互相不影响,因为是两个实例。

单播使用Observable即可,即Subject。rxjs部分会说到。

1.3 绑定

如果会vue的伙伴,这里可以类比vue语法去学习,效率更高些。

1.3.1 styleclass 样式绑定

  • style的单一和多样绑定 单一绑定,可带单位

image.png

import { Component, OnInit } from '@angular/core';
@Component({
  selector: 'app-parent-component',
  template: `<div>
    <span [style.font-size.px]="40">这是一段内容</span>
</div>`,
  styles: [``]
})
export class ParentComponentComponent implements OnInit {
  constructor() {}
  ngOnInit() {
  }
  onclick() {
  }
}

单一绑定 image.png

import { Component, OnInit } from '@angular/core';
@Component({
  selector: 'app-parent-component',
  template: `<div>
    <span [style.color]="'red'">这是一段内容</span>
</div>`,
  styles: [``]
})
export class ParentComponentComponent implements OnInit {
  constructor() {}
  ngOnInit() {
  }
  onclick() {
  }
}

多样绑定的两种方式

image.png

import { Component, OnInit } from '@angular/core';
@Component({
  selector: 'app-parent-component',
  template: `<div>
    <span [style]="styles">这是一段内容</span>
</div>`,
  styles: [``]
})
export class ParentComponentComponent implements OnInit {
  styles:String = 'font-size:30px;color: red;'
  constructor() {}
  ngOnInit() {
  }
  onclick() {
  }
}

import { Component, OnInit } from '@angular/core';
@Component({
  selector: 'app-parent-component',
  template: `<div>
    <span [style]="styles">这是一段内容</span>
</div>`,
  styles: [``]
})
export class ParentComponentComponent implements OnInit {
  styles = {fontSize: '30px', color: 'red'}
  constructor() {}
  ngOnInit() {
  }
  onclick() {
  }
}

  • class的单一和多样绑定

单一绑定

image.png

import { Component, OnInit } from '@angular/core';
@Component({
  selector: 'app-parent-component',
  template: `<div>
    <span [class.fontSetting]="true">这是一段内容</span>
</div>`,
  styles: [`.fontSetting{font-size: 30px;color:red;}`]
})
export class ParentComponentComponent implements OnInit {
  constructor() {}
  ngOnInit() {
  }
  onclick() {
  }
}

多样绑定

image.png

import { Component, OnInit } from '@angular/core';
@Component({
  selector: 'app-parent-component',
  template: `<div>
    <span [class]="['fontSizing', 'fontColor']">这是一段内容</span>
</div>`,
  styles: [`.fontSizing{font-size: 30px;}.fontColor{color:red;}`]
})
export class ParentComponentComponent implements OnInit {
  constructor() {}
  ngOnInit() {
  }
  onclick() {
  }
}

import { Component, OnInit } from '@angular/core';
@Component({
  selector: 'app-parent-component',
  template: `<div>
    <span [class]="{'fontSizing':true, 'fontColor': true}">这是一段内容</span>
</div>`,
  styles: [`.fontSizing{font-size: 30px;}.fontColor{color:red;}`]
})
export class ParentComponentComponent implements OnInit {
  constructor() {}
  ngOnInit() {
  }
  onclick() {
  }
}

本节内容简单看代码应该就可以理解。

1.3.2 属性绑定

  • attr 属性绑定

image.png

import { Component, OnInit } from '@angular/core';
@Component({
  selector: 'app-parent-component',
  template: `<div>
    <span [attr.name]="'test'">这是一段内容</span>
</div>`,
  styles: []
})
export class ParentComponentComponent implements OnInit {
  constructor() {}
  ngOnInit() {
  }
  onclick() {
  }
}
  • 原生属性绑定

image.png

import { Component, OnInit } from '@angular/core';
@Component({
  selector: 'app-parent-component',
  template: `<div>
    <button [disabled]="true">这是一段内容</button>
</div>`,
  styles: []
})
export class ParentComponentComponent implements OnInit {
  constructor() {}
  ngOnInit() {
  }
  onclick() {
  }
}

其他属性绑定同理,比如src、type。

1.3.3 双向绑定

  • 组件的双向绑定
  • 双向绑定出现,相当于一个语法糖。因为在普通开发,父组件传值给子组件,子组件如果需要更改父组件的值,那么就需要抛出回调函数,在父组件更改这个属性。但这样写很冗余,多了一个步骤,子组件每次更改都需要让父组件更改。

  • 如果父组件有多个子组件,子组件又多个需要更改的属性,那么就需要绑定多个回调,且所有子组件需要处理的逻辑,就会堆砌在父组件处理,久而久之父组件代码会很臃肿,并不优雅。

  • 为了处理这个问题,就可以借助angular新出的双向绑定

屏幕录制2023-08-28 11.31.57(1).gif

父组件引入了子组件,且父组件定义了name属性传递给了双向绑定的子组件,在父组件的页面中,也动态绑定了父组件的name属性,显示’默认值‘。

import { Component, OnInit } from '@angular/core';
@Component({
  selector: 'app-parent-component',
  template: `<div>
    <span>父组件:{{name}}</span>
    <app-child-component [(name)]='name'></app-child-component>
</div>`,
  styles: []
})
export class ParentComponentComponent implements OnInit {
  name:String = '默认值'
  constructor() {}
  ngOnInit() {
  }
  onclick() {
  }
}

子组件接收了name属性,且定义了nameChange,事件触发器。 注意。双向绑定,如果接收属性为name,那么触发器就必须为nameChange,否则双向绑定失效,这是固定写法。 当子组件点击了按钮,改变了name属性,且触发器也同时触发了,抛出的值也为name。这时,子组件和父组件的渲染层,同时发生了变化。

import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';

@Component({
  selector: 'app-child-component',
  template: `<div>
    <div>子组件:{{name}}</div>
    <button (click)="onClick()">改变值</button>
  </div>`,
  styles: ['']
})
export class ChildComponentComponent implements OnInit {
  @Input() name:String = '';
  @Output() nameChange = new EventEmitter()
  constructor() {
    
  }

  ngOnInit(): void {
  }
  onClick() {
    this.name = '值变了';
    this.nameChange.emit(this.name);
  }
}

  • 属性的双向绑定

屏幕录制2023-08-28 12.15.06.gif

import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';

@Component({
  selector: 'app-child-component',
  template: `<div>
    <span>内部属性:{{name}}</span>
    <button (click)="onClick()">改变值</button>
  </div>`,
  styles: ['']
})
export class ChildComponentComponent implements OnInit {
  private _name:String = '子组件默认是'

  set name(val:String) {
    this._name = val;
  }
  get name () {
    return this._name
  }
  constructor() {
    
  }

  ngOnInit(): void {
  }
  onClick() {
    this.name = '值变了';
  }
}

如图,在子组件中添加了代码。设置了set方法和get方法,一个用来修改私有属性_name,一个用来获取_name。这样,每当内部逻辑代码修改了name,name属性在渲染层就会动态获取,做到了双向绑定。注意,set和get方法最好仅仅用来做双向绑定,处理简单逻辑,不可添加业务代码,如果逻辑代码复杂,函数频繁触发,会严重影响性能,甚至会爆栈。

关于表单的双向绑定,表单一节会做讲解,此处不再赘述

1.4 组件的投影(插槽)

此小节,如果学过vue的同学,可以类比vue的插槽。

1.4.1 单个插槽(匿名插槽)

先看一段code

父组件

import { Component, Directive, OnInit, ViewChild } from '@angular/core';

@Component({
  selector: 'app-parent-component',
  template: `<div>
    <app-child-component>
      投影内容
      <h1>标题</h1>
    </app-child-component>
  </div>`,
  styles: [],
})
export class ParentComponentComponent implements OnInit {
  constructor() {}
  ngOnInit() {}
  onclick() {}
}

子组件

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-child-component',
  template: `<div >
      插槽位置:
    <br/>
    <ng-content></ng-content>
  </div>`,
  styles: ['']
})
export class ChildComponentComponent implements OnInit {

  constructor() {
    
  }

  ngOnInit(): void {
    
  }
}

image.png

如上。我们先在父组件使用了子组件app-child-componen,子组件之间插入了dom片段。之后,在子组件中放置了插槽需要放置的位置,显示如图。可以类比vue的匿名插槽。

1.4.2 多个插槽(自定义插槽)

image.png

根组件

import { Component, ElementRef, ViewChild } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `<div>
    根组件内容:
    <app-parent-component>
    父组件投影内容
    <app-child-component>子组件投影内容</app-child-component>
  </app-parent-component>
</div>`,
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  title = 'web';
  @ViewChild('dom') dom!: ElementRef;
  constructor() {}
  onAlert() {
    this.dom.nativeElement.innerHTML = '22222';
    console.log(this.dom.nativeElement);
    
  }
}

在根组件app-root中,嵌入,父组件app-parent-component元素,在父组件app-parent-component元素中嵌入了app-child-component,在子组件app-child-component嵌入投影内容为:子组件投影内容。

父组件

import { Component, Directive, OnInit, ViewChild } from '@angular/core';

@Component({
  selector: 'app-parent-component',
  template: `<div>
    父组件内容:
    <br/>
    <ng-content ></ng-content>
   <ng-content select="app-child-component"></ng-content>
  </div>`,
  styles: [],
})
export class ParentComponentComponent implements OnInit {
  constructor() {}
  ngOnInit() {}
  onclick() {}
}

在父组件中,先放置了一个默认插槽,又放了一个带选择器的插槽,选择器中填入了子组件的选择器,所以这里渲染的就是子组件,如果选择器是个指令,那么这里就是渲染的指令。

子组件

import { AfterContentInit, Component, EventEmitter, OnInit } from '@angular/core';

@Component({
  selector: 'app-child-component',
  template: `<div >
    子组件内容:
    <br/>
    <ng-content></ng-content>
  </div>`,
  styles: ['']
})

export class ChildComponentComponent implements OnInit {
  constructor() {
    
  }

  ngOnInit(): void {
    
  }
}

在子组件中放置了默认插槽,内容文字下方。

1.4.3 进阶

ng-content有个缺点,就是如果用了,只要父组件投影有内容,子组件的ng-content就会被初始化,哪怕在if指令内。那如果,不想被初始化,且可以动态控制呢?下面看个code……

父组件

import { Component, Directive, OnInit, ViewChild } from '@angular/core';

@Component({
  selector: 'app-parent-component',
  template: `<div>
   <app-child-component>
    <ng-template >第一节</ng-template>
    <ng-template >第二节</ng-template>
    <ng-template >第三节</ng-template>
   </app-child-component>
  </div>`,
  styles: [],
})
export class ParentComponentComponent implements OnInit {
  constructor() {}
  ngOnInit() {}
  onclick() {}
}

子组件

import { AfterContentInit, Component, ContentChild, ContentChildren, Directive, EventEmitter, Input, OnInit, Output, TemplateRef, ViewChild } from '@angular/core';


@Component({
  selector: 'app-child-component',
  template: `<div >
    <ng-container *ngIf="show"  [ngTemplateOutlet]="dataTemRefs[0]"></ng-container>
    <br/>
    <ng-container  [ngTemplateOutlet]="dataTemRefs[1]"></ng-container>
    <br/>
    <ng-container  [ngTemplateOutlet]="dataTemRefs[2]"></ng-container>
    <br/>
    <button (click)="show = !show">{{show? '点击隐藏第三节': '点击显示第三节'}}</button>
  </div>`,
  styles: ['']
})
export class ChildComponentComponent implements OnInit, AfterContentInit {
  @ContentChildren(TemplateRef) ChildrensTemplateRef!: Array<TemplateRef<unknown>>
  show:boolean = true
  dataTemRefs:any;
  constructor() {
    
  }

  ngOnInit(): void {
    
  }
  
  onclick() {

  }

  ngAfterContentInit(): void {
    this.dataTemRefs = this.ChildrensTemplateRef.map(f => f).reverse();
  }
}

点击button前

image.png

点击button后

image.png

父组件的投影中插入了三个模版template。子组件通过ContentChildren装饰器,获取到投影内容中的所有关于模版的模版数组。这里必须用map浅拷贝才可以访问,否则不可直接访问模版。之后又添加了show属性,作为显示和隐藏第一个模版的开关。

注意。

ngAfterContentInit(): void { this.dataTemRefs = this.ChildrensTemplateRef.map(f => f).reverse(); }

这里使用了投影初始化成功的生命周期,在生命周期中,获取到父组件投影的模版数组,之后反转了数组,所以看到的渲染结果是从第三节开始的,并非从第一节开始

1.5 组件的生命周期

这里只介绍常用的生命周期

2. 模块

2.1 模块的介绍与构成

3. 模版

3.1 基础语法

3.2 管道

3.3 进阶用法

4. 指令

4.1 内置指令

4.2 属性指令

4.3 结构指令

4.4 组合

5. 依赖注入

5.1 依赖注入的介绍与基本用法

5.2 进阶

6. 路由

6.1 基本用法

6.2 模块

6.3 进阶

7. 表单

7.1 基本用法

7.2 表单验证

7.3 动态表单

7.4 表单进阶

8. 生命周期

8.1 介绍

8.1 使用场景

9. http

9.1 http service

9.2 http拦截器

10. rxjs

10.1 rxjs基本概念

10.2 常见用法

11. 脚手架

11.1 angular 脚手架的搭建步骤及项目的创建

11.2 脚手架目录结构介绍

11.3 脚手架常见命令