前言
如果把前端比作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)。
template和templateUrl都是一个用途,都接收一个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做个详细介绍。
- 类
获取原生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 { }
未完待续……
- EmbeddedViewRef
- EnvironmentInjector
- EventEmitter
- Injector
- NgModuleRef
- NgProbeToken
- NgZone
- PlatformRef
- Query
- QueryList
- Renderer2
- RendererFactory2
- Sanitizer
- SimpleChange
- TemplateRef
- Testability
- TestabilityRegistry
- ViewContainerRef
- ViewRef
- 装饰器
- Attribute
- Component
- ContentChild
- ContentChildren
- Directive
- Host
- HostBinding
- HostListener
- Inject
- Injectable
- Input
- NgModule
- Optional
- Output
- Pipe
- Self
- SkipSelf
- ViewChild
* - ViewChildren
- 函数
- assertInInjectionContext
- assertPlatform
- booleanAttribute
- computed
- createComponent
- createEnvironmentInjector
- createNgModule
- createPlatform
- createPlatformFactory
- destroyPlatform
- effect
- forwardRef
- getNgModuleById
- getPlatform
- importProvidersFrom
- inject
- isDevMode
- isSignal
- isStandalone
- makeEnvironmentProviders
- makeStateKey
- mergeApplicationConfig
- numberAttribute
- provideZoneChangeDetection
- reflectComponentType
- resolveForwardRef
- runInInjectionContext
- setTestabilityGetter
- signal
- untracked
- 结构
- AbstractType
- AfterContentChecked
- AfterContentInit
- AfterViewChecked
- AfterViewInit
- ApplicationConfig
- BootstrapOptions
- ChangeDetectionStrategy
- ClassProvider
- ClassSansProvider
- ComponentMirror
- ConstructorProvider
- ConstructorSansProvider
- CreateComputedOptions
- CreateEffectOptions
- CreateSignalOptions
- DoBootstrap
- DoCheck
- EffectRef
- ExistingProvider
- ExistingSansProvider
- FactoryProvider
- FactorySansProvider
- ForwardRefFn
- GetTestability
- InjectOptions
- InjectableType
- InjectorType
- IterableChangeRecord
- IterableChanges
- IterableDiffer
- IterableDifferFactory
- KeyValueChangeRecord
- KeyValueChanges
- KeyValueDiffer
- KeyValueDifferFactory
- MissingTranslationStrategy
- ModuleWithProviders
- NgZoneOptions
- OnChanges
- OnDestroy
- OnInit
- PipeTransform
- Predicate
- RendererStyleFlags2
- RendererType2
- SchemaMetadata
- SecurityContext
- SimpleChanges
- StaticClassProvider
- StaticClassSansProvider
- TrackByFunction
- TypeDecorator
- TypeProvider
- ValueProvider
- ValueSansProvider
- ViewEncapsulation
- WritableSignal
- ng-container
- ng-content
- ng-template
1.1.3 @Component——angular一个装饰器,用于把某个类(AppComponents class)标记为 Angular 组件,并为它配置一些元数据,以决定该组件在运行期间该如何处理、实例化和使用。
changeDetectionviewProvidersmoduleIdtemplateselectortemplateUrlstyleUrlsstylesanimationsencapsulationinterpolationinputsimportspreserveWhitespacesstandaloneschemasoutputsprovidersexportAsquerieshostjithostDirectives
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 {
}
视图:
如效果图,我们先创建了一个子组件,又创建了一个父组件,俄顷,又在父组件应用了子组件。
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实例发射器,用于向父组件抛出回调以及参数。
具体效果如图:
点子组件的
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();
}
}
点击前
点击后
基于父子组件通信的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不了解,因为这里会涉及流,单播和多播的概念。可暂时了解用法。在了解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() { }
}
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 style和class 样式绑定
- style的单一和多样绑定 单一绑定,可带单位
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() {
}
}
单一绑定
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() {
}
}
多样绑定的两种方式
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的单一和多样绑定
单一绑定
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() {
}
}
多样绑定
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 属性绑定
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() {
}
}
- 原生属性绑定
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新出的双向绑定
父组件引入了子组件,且父组件定义了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);
}
}
- 属性的双向绑定
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 {
}
}
如上。我们先在父组件使用了子组件
app-child-componen,子组件之间插入了dom片段。之后,在子组件中放置了插槽需要放置的位置,显示如图。可以类比vue的匿名插槽。
1.4.2 多个插槽(自定义插槽)
根组件
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前
点击button后
父组件的投影中插入了三个模版
template。子组件通过ContentChildren装饰器,获取到投影内容中的所有关于模版的模版数组。这里必须用map浅拷贝才可以访问,否则不可直接访问模版。之后又添加了show属性,作为显示和隐藏第一个模版的开关。
注意。
ngAfterContentInit(): void { this.dataTemRefs = this.ChildrensTemplateRef.map(f => f).reverse(); }
这里使用了投影初始化成功的生命周期,在生命周期中,获取到父组件投影的模版数组,之后反转了数组,所以看到的渲染结果是从第三节开始的,并非从第一节开始
1.5 组件的生命周期
这里只介绍常用的生命周期