ホームページ >ウェブフロントエンド >jsチュートリアル >angular10 コンポーネントの関連概念を 1 つの記事で見てみましょう
この記事では、angular10 のコンポーネントを理解し、コンポーネント要素、コンポーネントのライフサイクル、コンポーネント間の通信、および動的コンポーネントの基本的な使用方法を紹介します。一緒に見てみましょう。
#【関連チュートリアルの推奨事項:「angular チュートリアル」】
//从angular主模块中引入Component(组件装饰器或组件注解) import { Component } from '@angular/core'; //装饰器中以json的形式声明元数据 @Component({ //它指定了一个叫 57b7f9effa9cadc72edb88082c9f873b 的元素。 该元素是 index.html 文件里的一个占位符 //为什么这个组件跟入口index建立了联系呢?因为入口main.ts中绑定了主模块为appModule selector: 'app-root', //在模板中找对应的标签,找到后创建并插入该组件实例 templateUrl: './app.component.html', // html模板 styleUrls: ['./app.component.css'], // css样式,可以引入多个css文件 // 这个属性(内联模板)和templateUrl(外联模板)二选一,template后面可以直接跟html字符串 // 注意在模板语法(反引号)中是使用插值表达式,不能使用${}插入值 template: `4a249f0d628e2318394fd9b75b4636b1{{title}}473f0a7621bec819994bb5020d29372a` }) //组件控制器,写逻辑代码的地方 export class AppComponent { title = 'myAngular'; //构造函数可以用来进行属性的声明和初始化语句 //在angular里面有个特别重要的点要记住:只能通过构造函数注入依赖 constructor() {} }
ng generate component <project-name> // 创建一个组件 ng g c <project-name> // 缩写
コンポーネントの作成に一般的に使用されるその他のオプション
ng g c <project-name> --skip-tests // 创建一个组件,且不用安装测试文件 ng g c <project-name> --inline-style // 缩写-s,内联样式 ng g c <project-name> --inline-template // 缩写-t,内联模板 ng g c <project-name> --module= <module-name> // 指定创建的组件在哪个模块引用,在多个模块的项目中会用到
angular cli を使用してコンポーネントを自動的に生成することに加えて、コンポーネントを手動で作成することもできます (非推奨)。これについてはここでは紹介しません。
コンポーネントのライフ サイクル (高レベルのコンテンツ、マスター ngOnInit、ngOnDestroy、ngOnChanges、ngAfterViewInit())ライフ サイクルの意味コンポーネント インスタンスのライフ サイクルは、Angular がコンポーネント クラスをインスタンス化し、コンポーネント ビューとそのサブビューをレンダリングするときに始まります。ライフサイクルには常に変更検出が伴います。データにバインドされたプロパティが変更されたときに Angular がチェックし、必要に応じてビューとコンポーネントのインスタンスを更新します。ライフサイクルは、Angular がコンポーネント インスタンスを破棄し、レンダリングされたテンプレートを DOM から削除すると終了します。ディレクティブには、Angular が実行中にインスタンスを作成、更新、破棄するのと同様のライフサイクルがあります。
アプリケーション:
アプリケーションはライフ サイクル フック メソッドを使用して、コンポーネントまたはディレクティブのライフ サイクルで主要なイベントをトリガーし、新しいインスタンスを初期化し、必要に応じて変更検出を開始できます。更新に応答し、インスタンスを削除する前にクリーンアップしてください。
ライフ サイクル イベントの実装方法各コンポーネントまたはディレクティブは、1 つ以上のライフ サイクル フックを実装できます。これにより、コンポーネントまたはディレクティブに対して操作を実行できます。必要に応じてディレクティブのインスタンスを作成します。
各インターフェイスには固有のフック メソッドがあり、その名前はインターフェイス名と ng プレフィックスで構成されます。たとえば、OnInit インターフェイスのフック メソッドは ngOnInit() と呼ばれます。このメソッドをコンポーネントまたはディレクティブ クラスに実装する場合、Angular は最初にコンポーネントまたはディレクティブの入力プロパティをチェックした後にこのメソッドを呼び出します
import { Component } from '@angular/core'; @Component({ selector: 'app-root', styleUrls: ['./app.component.css'], template: `<h1>{{title}}</h1>` }) // 实现OnInit生命周期,可以实现多个生命周期 export class AppComponent implements OnInit{ title = 'myAngular'; constructor() {} ngOnInit(){ console.log("Angular在首次检查完组件的输入属性后,然后调用它,只调用一次") } }
ライフサイクルの概要 重点生命周期详解 初始化组件和指令 ngOnInit 在实例销毁时进行清理 ngOnDestroy 这里是释放资源的地方,这些资源不会自动被垃圾回收。如果你不这样做,就存在内存泄漏的风险。 页面埋点 可以在组件中通过ngOnInit和ngOnDestroy方法统计页面的时长, 更好的做法可以通过指令实现ngOnInit和ngOnDestroy生命周期,用来统计页面时长 也可以通过路由守卫来记录页面出入栈所需要的时间 使用变更检测钩子 ngOnchanges 一旦检测到该组件或指令的输入属性发生了变化,Angular 就会调用它的 ngOnChanges() 方法。 因为这个方法会频繁执行,所以要注意判断的计算量,会影响性能。 一、父子组件的通信(基础) 父传子 1、父组件通过属性绑定向子组件传值 2、子组件通过@Input接收数据 子传父 1、子组件通过自定义事件,向父组件发送数据 2、父组件通过监听自定义事件,接收子组件的传值 子组件怎么监听输入属性值的变化?(2种方法) 1、可以使用一个输入属性@Input()的 setter,以拦截父组件中值的变化。 2、通过ngOnChange()来截听输入属性值的变化 当需要监视多个、交互式输入属性的时候,本方法比用属性的 setter 更合适。 父组件怎么读取子组件的属性和调用子组件的方法?(2种方法) 1、通过本地变量代表子组件 父组件不能使用数据绑定来读取子组件的属性或调用子组件的方法。但可以在父组件模板里,新建一个本地变量来代表子组件,然后利用这个变量来读取子组件的属性和调用子组件的方法,如下例所示。 思考:父组件可以通过这种方式读取子组件的私有属性和私有方法吗? 父组件 子组件 2、父组件调用@viewChild() (基础,推荐使用) 这个本地变量方法是个简单便利的方法。但是它也有局限性(只能在模板html中使用),因为父组件-子组件的连接必须全部在父组件的模板中进行。父组件本身的ts代码对子组件没有访问权。 当父组件类需要访问子组件时,可以把子组件作为 ViewChild,注入到父组件里面。 父组件类中访问子组件的属性和方法: 注意:(使用场景很多,必须掌握) ngAfterViewInit() 生命周期钩子是非常重要的一步。被注入的计时器组件只有在 Angular 显示了父组件视图之后才能访问,所以它先把秒数显示为 0. 然后 Angular 会调用 ngAfterViewInit 生命周期钩子,但这时候再更新父组件视图的倒计时就已经太晚了。Angular 的单向数据流规则会阻止在同一个周期内更新父组件视图。应用在显示秒数之前会被迫再等一轮。 使用 setTimeout() 来等下一轮,然后改写 seconds() 方法,这样它接下来就会从注入的这个计时器组件里获取秒数的值。 二、组件通过服务来通信(发布订阅者模式,基础,必须掌握) 父组件和它的子组件共享同一个服务,利用该服务在组件家族内部实现双向通信。 不仅局限于父子组件,只要组件与组件共享同一个服务,就可以实现数据通信。 思考: 这种发布订阅者模式适合全局状态管理吗? 三、可以通过本地缓存来实现通信(Cookie,LocalStorage、SessionStorage) 四、页面路由传参也可以实现单向通信 这部分内容,我会在路由章节整理。 组件通信总结 所以组件通信大概有如下几种: 1、父子组件通信(1、@Input() @output 2、本地变量#val 3、@viewChild()) 2、通过服务 3、页面缓存 4、页面级组件传参(两个页面等同于两个组件) 组件的模板不会永远是固定的。应用可能会需要在运行期间按需加载一些新的组件。
通过下面的例子可以了解动态组件的基本使用 1、创建组件,被引入的组件 2、创建容器组件,用于加载动态组件 在AppContainerComponent组件中,通过@ViewChild装饰器来获取视图中的模板元素,如果没有指定第二个查询参数,则默认返回
组件实例或相应的DOM元素,但这个示例中,我们需要获取ViewContainerRef实例也就是视图容器。可以在视图容器中创建、插入、删除组件等。 3、动态创建组件 在创建组件之前,需要注入ComponentFactoryResolver服务对象,该服务是动态加载组件的核心,可以将一个组件实例呈现到另一个
组件视图上。 4、为组件属性赋值 通过如下的方式为组件属性进行赋值 5、销毁组件 在使用组件后,记得要销毁组件。 6、组件注册 为了能够动态创建组件需要将组件在模块的entryComponents中声明。因为在entryComponents中声明的组件Angular都会创建一个
ComponentFactory并将其存储在ComponentFactoryResolver中,这是动态加载必需的步骤。 更多编程相关知识,请访问:编程入门!!
#フック
##ngOnChanges()タイミング 使用 ノート
ngOnInit()バインドされた入力プロパティの値が変更されるときに呼び出されます。最初の呼び出しは、ngOnInit()
入力属性時に Angular がデータ バインディングの応答を設定またはリセットする前に行われる必要があります。このメソッドは、現在および以前のプロパティ値の SimpleChanges オブジェクトを受け入れます。
これは非常に頻繁に発生するため、ここで行うことはパフォーマンスに大きな影響を与えます。
ngDoCheck() ngOnChanges() の最初のラウンドが完了した後に呼び出されます。一度だけ呼び出されます。
Angular が最初にデータ バインディングを表示し、ディレクティブ/コンポーネントの入力プロパティを設定した後、ディレクティブ/コンポーネントを初期化します。コンポーネントが初期データを取得するのに適した場所です。
一度だけ呼び出すことが重要です。
ngAfterContentInit()変更検出が実行されるたびに呼び出されます。 ngOnChanges() および ngOnInit() の後、変更検出が最初に実行されます。
Angular が独自に検出できない、または検出したくない変更が発生した場合に、検出して対応します。
これは ngOnChanges と同じくらい頻繁に発生します。
ngAfterContentChecked()ngDoCheck() が初めて呼び出されるとき、それは 1 回だけ呼び出されます。
Angular が外部コンテンツをコンポーネント ビューまたはディレクティブが配置されているビューに投影した後に呼び出されます。
1 回のみ呼び出されます
ngAfterViewInit()ngAfterContentInit() と ngDoCheck() が呼び出された後は毎回
毎回呼び出されますAngular コンポーネントまたはディレクティブに投影されたコンテンツをチェックした後に呼び出されます。
コンポーネントビューとサブビューの変更検出完了後に毎回呼び出されます。 ngAfterContentChecked() の後に初めて呼び出されるときは、1 回だけ呼び出されます。
コンポーネント ビューとそのサブビューを初期化した後に呼び出されます。
ngAfterViewChecked()
ngAfterViewInit() は 1 回だけ呼び出され、ngAfterContentChecked() の後には毎回呼び出されます。
各ディレクティブ/コンポーネントが Angular によって破棄される前に呼び出され、クリーンアップされます。メモリ リークを防ぐために、ここでオブザーバブルのサブスクライブを解除し、イベント ハンドラーを切り離します。
ngOnDestroy()
Angular がディレクティブ/コンポーネントを破棄する前に呼び出されます。 監視可能なオブジェクトからサブスクライブ解除、
タイマーのクリア、 このコマンドによってグローバルまたはアプリケーション サービスに登録されたすべてのコールバックの登録を解除します。 非常に重要
@Directive({selector: '[appSpy]'})
export class SpyDirective implements OnInit, OnDestroy {
constructor(private logger: LoggerService) { }
ngOnInit() { this.logIt(`onInit`); }
ngOnDestroy() { this.logIt(`onDestroy`); }
private logIt(msg: string) {
this.logger.log(`Spy #${nextId++} ${msg}`);
}
}
// 可以监听输入属性的变化
ngOnChanges(changes: SimpleChanges) {
for (const propName in changes) {
const chng = changes[propName];
const cur = JSON.stringify(chng.currentValue);
const prev = JSON.stringify(chng.previousValue);
this.changeLog.push(`${propName}: currentValue = ${cur}, previousValue = ${prev}`);
}
}
组件之间的通信(重要,必须掌握)
import { Component } from '@angular/core';
@Component({
selector: 'app-parent',
template: `
<app-child [msg]="msg"></app-child>
`
})
export class ParentComponent {
msg = '父组件传的值';
}
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-child',
template: `
<p>{{msg}}</p>
`
})
export class ChildComponent {
@Input() msg: String;
}
import { Component, Input, EventEmitter, Output} from '@angular/core';
,
@Component({
selector: 'app-child',
template: `
<p>{{msg}}</p>
<button (click)="vote()" >发送</button>
`
})
export class ChildComponent {
@Input() msg: String;
@Output() voted = new EventEmitter<boolean>();
vote() {
this.voted.emit("子组件传的值");
}
}
import { Component } from '@angular/core';
@Component({
selector: 'app-parent',
template: `
<app-child [msg]="msg" (voted)="voted($event)"></app-child>
`
})
export class ParentComponent {
msg = '父组件传的值';
voted(val){ //监听自定义事件的传值
console.log(val)
}
}
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-child',
template: '<h3>"{{name}}"</h3>'
})
export class ChildComponent {
@Input()
get name(): string { return this._name; }
set name(name: string) {
this._name = (name && name.trim()) || '<no name set>';
}
private _name = '';
}
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
@Component({
selector: 'app-version-child',
template: `
<h3>Version {{major}}.{{minor}}</h3>
<h4>Change log:</h4>
<ul>
<li *ngFor="let change of changeLog">{{change}}</li>
</ul>
`
})
export class VersionChildComponent implements OnChanges {
@Input() major: number;
@Input() minor: number;
changeLog: string[] = [];
ngOnChanges(changes: SimpleChanges) { //ngOnchange适合监听子组件多个输入属性的变化
const log: string[] = [];
for (const propName in changes) {
const changedProp = changes[propName];
const to = JSON.stringify(changedProp.currentValue);
if (changedProp.isFirstChange()) {
log.push(`Initial value of ${propName} set to ${to}`);
} else {
const from = JSON.stringify(changedProp.previousValue);
log.push(`${propName} changed from ${from} to ${to}`);
}
}
this.changeLog.push(log.join(', '));
}
}
import { Component } from '@angular/core';
import { CountdownTimerComponent } from './countdown-timer.component';
@Component({
selector: 'app-countdown-parent-lv',
template: `
<h3>Countdown to Liftoff (via local variable)</h3>
<button (click)="timer.start()">Start</button> //调用子组件方法
<button (click)="timer.stop()">Stop</button>
<div class="seconds">{{timer.seconds}}</div> //读取子组件属性
<app-countdown-timer #timer></app-countdown-timer>
`,
styleUrls: ['../assets/demo.css']
})
export class CountdownLocalVarParentComponent { }
import { Component, OnDestroy } from '@angular/core';
@Component({
selector: 'app-countdown-timer',
template: '<p>{{message}}</p>'
})
export class CountdownTimerComponent implements OnDestroy {
intervalId = 0;
message = '';
seconds = 11;
ngOnDestroy() { this.clearTimer(); }
start() { this.countDown(); }
stop() {
this.clearTimer();
this.message = `Holding at T-${this.seconds} seconds`;
}
private clearTimer() { clearInterval(this.intervalId); }
private countDown() {
this.clearTimer();
this.intervalId = window.setInterval(() => {
this.seconds -= 1;
if (this.seconds === 0) {
this.message = 'Blast off!';
} else {
if (this.seconds < 0) { this.seconds = 10; } // reset
this.message = `T-${this.seconds} seconds and counting`;
}
}, 1000);
}
}
import { AfterViewInit, ViewChild } from '@angular/core';
import { Component } from '@angular/core';
import { CountdownTimerComponent } from './countdown-timer.component'; //引入子组件
@Component({
selector: 'app-countdown-parent-vc',
template: `
<h3>Countdown to Liftoff (via ViewChild)</h3>
<button (click)="start()">Start</button>
<button (click)="stop()">Stop</button>
<div class="seconds">{{ seconds() }}</div>
<app-countdown-timer></app-countdown-timer>
`,
styleUrls: ['../assets/demo.css']
})
export class CountdownViewChildParentComponent implements AfterViewInit {
//通过 @ViewChild 属性装饰器,将子组件 CountdownTimerComponent 注入到私有属性 timerComponent 里面。
@ViewChild(CountdownTimerComponent)
private timerComponent: CountdownTimerComponent;
seconds() { return 0; }
// angular创建了组件的子视图后会调用它,注意获取子组件的属性,要在子组件视图加载之后
ngAfterViewInit() {
// 访问子组件属性
setTimeout(() => this.seconds = () => this.timerComponent.seconds, 0);
}
start() { this.timerComponent.start(); } // 访问子组件的方法
stop() { this.timerComponent.stop(); }
}
<!--parent.component.html-->
<p style="width: 1000px;margin: auto">
<p class="card" style="width: 500px;float: left">
<p class="card-header"> 父组件</p>
<p class="card-body">
<h5 class="card-title">父组件</h5>
<p class="form-group">
<label for="serviceoutput">父组件服务输入:</label>
<input type="text"
class="form-control"
id="serviceoutput"
placeholder="服务输入"
[(ngModel)]="serviceInput"
>
</p>
<button class="btn btn-primary" (click)="clickService()">Service方式</button>
</p>
</p>
<app-child></app-child>
</p>
<!--child.component.html-->
<p class="card" style="width: 500px;">
<p class="card-header">子组件</p>
<p class="card-body">
<h5 class="card-title">子组件</h5>
<p class="form-group">
<label for="serviceoutput">子组件服务输入:</label>
<input type="text"
class="form-control"
id="serviceoutput"
placeholder="服务输入"
[(ngModel)]="serviceInput"
>
</p>
<button class="btn btn-primary" (click)="clickService()">Service方式</button>
</p>
</p>
//服务重点
//meditor.service.ts
import {Injectable} from '@angular/core';
import {Subject} from 'rxjs/Subject';
import {Observable} from 'rxjs/Observable';
@Injectable()
export class MeditorService {
private subject = new Subject<MeditorMsg>();
constructor() {}
// 获取订阅者
public getObservable(): Observable<MeditorMsg> {
return this.subject.asObservable();
}
// 推送信息
public push(msg: MeditorMsg) {
this.subject.next(msg);
}
}
// 中间者信息
export interface MeditorMsg {
id: string;
body: any;
}
subscription: Subscription = null; //初始化一个订阅对象
//子组件构造函数,用于监听数据推送
constructor(private meditor: MeditorService) {
this.subscription = meditor.getObservable().subscribe(
msg => {
console.log(msg);
if (msg.id === 'parent') { //id为parent,获取父组件数据
this.serviceInput = msg.body;
}
}
);
}
// 子组件将数据推送到中间着,给订阅者
clickService() {
this.meditor.push({id: 'parent', body: this.serviceInput});
}
//父组件构造函数,用于监听数据推送
constructor(private meditor: MeditorService) {
this.subscription = meditor.getObservable().subscribe(
msg => {
console.log(msg);
if (msg.id === 'child') { //id为child,获取子组件数据
this.serviceInput = msg.body;
}
}
);
}
// 父组件将数据推送到中间着,给订阅者
clickService() {
this.meditor.push({id: 'parent', body: this.serviceInput});
}
// 注意:订阅一个对象,就是在生命周期结束前,要取消订阅。
ngOnDestroy() {
this.subscription.unsubscribe();
}
<!-- catch_namae_type.ts -->
// 目的是好维护
// 项目当中用到的页面缓存,需要在这里进行声明;key-value保持一致
// 声明规则,不同类型的缓存使用前缀 session_/ local_ / cookie_
// 动态设置缓存不用在这里声明,但是需要在key后面加'_noSetType_'标识
export const CatchNameType = {
session_userInfo: 'session_userInfo', // 用户信息
session_toekn: 'session_token', // token
local_loginInfo: 'local_loginInfo', // 本地缓存用户名密码
};
<!--catch.ts-->
import { Injectable } from '@angular/core';
// 定义这个类,主要是看全局定义了哪些本地缓存
import { CatchNameType } from './catch_namae_type';
// -------------------------------------------------------缓存工具类(三类方法)
// Cookie (方法有:set/get/remove)
// SStorage(sessionStorage) (方法有:set/get/remove/clear)
// LStorage(localStorage) (方法有:set/get/remove/clear)
@Injectable({
providedIn: 'root',
})
export class Catch {
// cookie
public static Cookie = {
/**
* cookie 存贮
* @param key 属性
* @param value 值
* @param String expire 过期时间,单位天
*/
set(key: string, value: any, expire: any): void {
if (Catch.is_set_catch_name_type(key)) {
const d = new Date();
d.setDate(d.getDate() + expire);
document.cookie = `${key}=${value};expires=${d.toDateString()}`;
}
},
get(key: string): string {
const cookieStr = unescape(document.cookie);
const arr = cookieStr.split('; ');
let cookieValue = '';
// tslint:disable-next-line: prefer-for-of
for (let i = 0; i < arr.length; i++) {
const temp = arr[i].split('=');
if (temp[0] === key) {
cookieValue = temp[1];
break;
}
}
return cookieValue;
},
remove(key: string): void {
document.cookie = `${encodeURIComponent(key)}=;expires=${new Date()}`;
},
};
// sessionStorage
public static SStorage = {
set(key: string, value: any): void {
if (Catch.is_set_catch_name_type(key)) {
sessionStorage.setItem(key, JSON.stringify(value));
}
},
get(key: string): any {
const jsonString =
sessionStorage.getItem(key) === 'undefined'
? undefined
: sessionStorage.getItem(key);
return jsonString ? JSON.parse(jsonString) : null;
},
remove(key: string): void {
sessionStorage.removeItem(key);
},
clear(): void {
sessionStorage.clear();
},
};
// localStorage
public static LStorage = {
set(key: string, value: any): void {
if (Catch.is_set_catch_name_type(key)) {
localStorage.setItem(key, JSON.stringify(value));
}
},
get(key: string): any {
const jsonString =
localStorage.getItem(key) === 'undefined'
? undefined
: localStorage.getItem(key);
return jsonString ? JSON.parse(jsonString) : null;
},
remove(key: string): void {
localStorage.removeItem(key);
},
clear(): void {
localStorage.clear();
},
};
// 设置缓存的时候是否在catch_name_type里面声明
static is_set_catch_name_type(key: string): boolean {
let allow = false;
// 对动态设置缓存不进行检查
if (key.indexOf('_noSetType_') !== -1) {
allow = true;
console.log('动态设置缓存', key);
return allow;
}
// 对命名规则进行检查
const nameRule =
key.indexOf('session_') !== -1 ||
key.indexOf('local_') !== -1 ||
key.indexOf('cookie_') !== -1;
if (!nameRule) {
allow = false;
console.log('命名规则错误', key);
return allow;
}
// 静态设置的缓存需要配置类型
Object.values(CatchNameType).forEach((item) => {
if (item === key) {
allow = true;
}
});
if (!allow) {
console.log('缓存操作失败,请检查配置缓存类型');
}
return allow;
}
}
动态组件
@Component({
template: `<span>hello world</span>`
})
export class DynamicComponent { }
@Component({
selector: 'app-container',
template: `
<div #dynamicContainer></div>
<button (click)="createComponent()">Create</button>
`
})
export class AppContainerComponent {
// 声明容器
@ViewChild("dynamicContainer", { read: ViewContainerRef }) container: ViewContainerRef;
}
//依赖组件类型获取对应的factory,从名字上可以看出该factory是用来初始化组件的
const componentFactory = this.ComponentFactoryResolver(DynamicComponent);
//调用视图容器的createComponent方法并将组件添加到容器上。该方法内部会调用factory的create方法来初始化组件。
const modalContainerRef = this.container.createComponent(componentFactory);
modalContainerRef.instance.property = ***;
modalContainerRef.destroy();
以上がangular10 コンポーネントの関連概念を 1 つの記事で見てみましょうの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。