Anuglar2生命周期事件为rxjs Observable [英] Anuglar2 lifecycle events as rxjs Observable

查看:53
本文介绍了Anuglar2生命周期事件为rxjs Observable的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

是否有一种获取r2js Observable之类的angular2生命周期事件(如OnDestroy)的方式?

Is there a build in way to get angular2 lifecycle events like OnDestroy as rxjs Observable?

我想订阅这样的可观察文章:

I would like to subscribe to a observable like that:

ngOnInit() {
  MyService.myCustomFunction()
    .takeUntil(NgOnDestroy)  //NgOnDestroy would be the lifecycle observable
    .subscribe(() => {
      //any code
    });
 }

似乎比以下内容更直观,更易读

Which seems to be intuitive and better to read than:

private customObservable: Observable;

ngOnDestroy() {
  this.customObservable.unsubscribe();
}

ngOnInit() {
  this.customObservable = MyService.myCustomFunction()
    .subscribe(() => {
      //any code
    });
 }

推荐答案

虽然没有内置的方法,但是如果您不想等待,可以设置一个装饰器或基类来做到这一点.

There isn't a built in way, but you could set up a decorator or base class to do it if you don't want to wait.

基类

此解决方案可与AOT一起使用..但是,在较旧的Angular版本中,存在一个错误,即使用AOT时未注册基类的生命周期事件.它至少似乎在4.4.x +中有效.您可以在此处获取更多信息,以查看您的版本是否会受到影响: https://github.com /angular/angular/issues/12922

This solution works with AOT. However, in older versions of Angular, there was a bug where lifecycle events on base classes weren't getting registered when using AOT. It at least seems to work in 4.4.x+. You can get more information here to see if your version will be affected: https://github.com/angular/angular/issues/12922

示例

import { SimpleChanges, OnChanges, OnInit, DoCheck, AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked, OnDestroy } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import 'rxjs/add/operator/takeUntil';
import 'rxjs/add/operator/take';

const onChangesKey = Symbol('onChanges');
const onInitKey = Symbol('onInit');
const doCheckKey = Symbol('doCheck');
const afterContentInitKey = Symbol('afterContentInit');
const afterContentCheckedKey = Symbol('afterContentChecked');
const afterViewInitKey = Symbol('afterViewInit');
const afterViewCheckedKey = Symbol('afterViewChecked');
const onDestroyKey = Symbol('onDestroy');

export abstract class LifeCycleComponent implements OnChanges, OnInit, DoCheck, AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked, OnDestroy {
    // all observables will complete on component destruction
    protected get onChanges(): Observable<SimpleChanges> { return this.getObservable(onChangesKey).takeUntil(this.onDestroy); }
    protected get onInit(): Observable<void> { return this.getObservable(onInitKey).takeUntil(this.onDestroy).take(1); }
    protected get doCheck(): Observable<void> { return this.getObservable(doCheckKey).takeUntil(this.onDestroy); }
    protected get afterContentInit(): Observable<void> { return this.getObservable(afterContentInitKey).takeUntil(this.onDestroy).take(1); }
    protected get afterContentChecked(): Observable<void> { return this.getObservable(afterContentCheckedKey).takeUntil(this.onDestroy); }
    protected get afterViewInit(): Observable<void> { return this.getObservable(afterViewInitKey).takeUntil(this.onDestroy).take(1); }
    protected get afterViewChecked(): Observable<void> { return this.getObservable(afterViewCheckedKey).takeUntil(this.onDestroy); }
    protected get onDestroy(): Observable<void> { return this.getObservable(onDestroyKey).take(1); }

    ngOnChanges(changes: SimpleChanges): void { this.emit(onChangesKey, changes); };
    ngOnInit(): void { this.emit(onInitKey); };
    ngDoCheck(): void { this.emit(doCheckKey); };
    ngAfterContentInit(): void { this.emit(afterContentInitKey); };
    ngAfterContentChecked(): void { this.emit(afterContentCheckedKey); };
    ngAfterViewInit(): void { this.emit(afterViewInitKey); };
    ngAfterViewChecked(): void { this.emit(afterViewCheckedKey); };
    ngOnDestroy(): void { this.emit(onDestroyKey); };

    private getObservable(key: symbol): Observable<any> {
        return (this[key] || (this[key] = new Subject<any>())).asObservable();
    }

    private emit(key: symbol, value?: any): void {
        const subject = this[key];
        if (!subject) return;
        subject.next(value);
    }
}

用法

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

import { LifeCycleComponent } from './life-cycle.component';
import { MyService } from './my.service'

@Component({
  template: ''
})
export class TestBaseComponent extends LifeCycleComponent implements OnInit {
  constructor(private myService: MyService) {
    super();
  }

  ngOnInit() {
    super.ngOnInit();
    this.myService.takeUntil(this.onDestroy).subscribe(() => {});
  }
}

由于要继承,因此请确保如果您倾向于实现生命周期接口之一,则还可以调用基类方法(例如ngOnInit() { super.ngOnInit(); }).

Since you are inheriting make sure that if you feel inclined to implement one of the life-cycle interfaces that you also invoke the base class method (e.g. ngOnInit() { super.ngOnInit(); }).

此解决方案不适用于AOT .我个人更喜欢这种方法,但是对于某些项目,它不能与AOT配合使用.

This solution does not work with AOT. Personally I like this approach better but it not working with AOT is kind of a deal breaker for some projects.

示例

/**
 * Creates an observable property on an object that will
 * emit when the corresponding life-cycle event occurs.
 * The main rules are:
 * 1. Don't name the property the same as the angular interface method.
 * 2. If a class inherits from another component where the parent uses this decorator
 *    and the child implements the corresponding interface then it needs to call the parent method.
 * @param {string} lifeCycleMethodName name of the function that angular calls for the life-cycle event
 * @param {object} target class that contains the decorated property
 * @param {string} propertyKey name of the decorated property
 */
function applyLifeCycleObservable(
    lifeCycleMethodName: string,
    target: object,
    propertyKey: string
): void {
    // Save a reference to the original life-cycle callback so that we can call it if it exists.
    const originalLifeCycleMethod = target.constructor.prototype[lifeCycleMethodName];

    // Use a symbol to make the observable for the instance unobtrusive.
    const instanceSubjectKey = Symbol(propertyKey);
    Object.defineProperty(target, propertyKey, {
        get: function() {
            // Get the observable for this instance or create it.
            return (this[instanceSubjectKey] || (this[instanceSubjectKey] = new Subject<any>())).asObservable();
        }
    });

    // Add or override the life-cycle callback.
    target.constructor.prototype[lifeCycleMethodName] = function() {
        // If it hasn't been created then there no subscribers so there is no need to emit
        if (this[instanceSubjectKey]) {
            // Emit the life-cycle event.
            // We pass the first parameter because onChanges has a SimpleChanges parameter.
            this[instanceSubjectKey].next.call(this[instanceSubjectKey], arguments[0]);
        }

        // If the object already had a life-cycle callback then invoke it.
        if (originalLifeCycleMethod && typeof originalLifeCycleMethod === 'function') {
            originalLifeCycleMethod.apply(this, arguments);
        }
    };
}

// Property Decorators
export function OnChangesObservable(target: any, propertyKey: string) {
    applyLifeCycleObservable('ngOnChanges', target, propertyKey);
}
export function OnInitObservable(target: any, propertyKey: string) {
    applyLifeCycleObservable('ngOnInit', target, propertyKey);
}
export function DoCheckObservable(target: any, propertyKey: string) {
    applyLifeCycleObservable('ngDoCheck', target, propertyKey);
}
export function AfterContentInitObservable(target: any, propertyKey: string) {
    applyLifeCycleObservable('ngAfterContentInit', target, propertyKey);
}
export function AfterContentCheckedObservable(target: any, propertyKey: string) {
    applyLifeCycleObservable('ngAfterContentChecked', target, propertyKey);
}
export function AfterViewInitObservable(target: any, propertyKey: string) {
    applyLifeCycleObservable('ngAfterViewInit', target, propertyKey);
}
export function AfterViewCheckedObservable(target: any, propertyKey: string) {
    applyLifeCycleObservable('ngAfterViewChecked', target, propertyKey);
}
export function OnDestroyObservable(target: any, propertyKey: string) {
    applyLifeCycleObservable('ngOnDestroy', target, propertyKey);
}

用法

import { Component, OnInit, Input, SimpleChange } from '@angular/core';
import { Observable } from 'rxjs/Observable';

import {
    OnChangesObservable,
    OnInitObservable,
    DoCheckObservable,
    AfterContentInitObservable,
    AfterContentCheckedObservable,
    AfterViewInitObservable,
    AfterViewCheckedObservable,
    OnDestroyObservable
 } from './life-cycle.decorator';
import { MyService } from './my.service'

@Component({
    template: ''
})
export class TestDecoratorComponent implements OnInit {

    @OnChangesObservable
    onChanges: Observable<SimpleChanges>;
    @OnInitObservable
    onInit: Observable<void>;
    @DoCheckObservable
    doCheck: Observable<void>;
    @AfterContentInitObservable
    afterContentInit: Observable<void>;
    @AfterContentCheckedObservable
    afterContentChecked: Observable<void>;
    @AfterViewInitObservable
    afterViewInit: Observable<void>;
    @AfterViewCheckedObservable
    afterViewChecked: Observable<void>;
    @OnDestroyObservable
    onDestroy: Observable<void>;

    @Input()
    input: string;

    constructor(private myService: MyService) {
    }

    ngOnInit() {
        this.myService.takeUntil(this.onDestroy).subscribe(() => {});
        this.onChanges
            .map(x => x.input)
            .filter(x => x != null)
            .takeUntil(this.onDestroy)
            .subscribe((change: SimpleChange) => {
            });
    }
}

有关此解决方案的一些规则,我认为可以遵循:

There are a few rules about this solution that I think are reasonable to follow:

  1. 将属性命名为任何东西,但angular方法的名称将被调用以通知您的对象生命周期事件(例如,请勿将属性命名为ngOnInit).这是因为装饰器会将属性创建为getter,并且必须在类上创建该方法以拦截生命周期事件.如果您忽略此设置,则会收到运行时错误.
  2. 如果您继承自使用生命周期属性装饰器的类,并且子类为相应事件实现了angular接口,则子类必须在父类上调用该方法(例如ngOnInit() { super.ngOnInit(); }).如果您忽略此选项,则您的可观察项将不会发出,因为父类上的方法已被遮盖.
  3. 您可能会想这样做而不是实现角度接口:this.onInit.subscribe(() => this.ngOnInit()).别.它不是魔术. Angular只是检查功能的存在.因此,将您调用的方法命名为订阅,而不是使用角度接口.如果您忽略此设置,则将创建一个无限循环.
  1. Name your property anything but the name of the method angular will call to notify your object of the life-cycle event (e.g. don't name the property ngOnInit). This is because the decorator will create the property as a getter and will have to create that method on the class to intercept the life-cycle event. If you ignore this then you will get a runtime error.
  2. If you inherit from a class that uses the life-cycle property decorator and the child class implements the angular interface for the corresponding event then the child class must call the method on the parent class (e.g. ngOnInit() { super.ngOnInit(); }). If you ignore this then your observable wont emit because the method on the parent class is shadowed.
  3. You may be tempted to do something like this instead of implementing the angular interface: this.onInit.subscribe(() => this.ngOnInit()). Don't. Its not magic. Angular just checks for the presence of the function. So name the method you call in subscribe something other than what the angular interface would have you do. If you ignore this then you will create an infinite loop.

如果愿意,您仍然可以为生命周期事件实现标准的角度接口.装饰器将覆盖它,但是它将在可观察的对象上发出,然后调用您的原始实现.另外,您也可以订阅相应的可观察对象.

You can still implement the standard angular interfaces for the lifecycle events if you want to. The decorator will overwrite it but it will emit on the observable and then invoke your original implementation. Alternatively you could just subscribe to the corresponding observable.

-

要注意的一个好处是,由于ngOnChanges现在是可观察的,因此它基本上允许您的@Input属性是可观察的.您可以使用地图设置过滤器,以在属性值(例如this.onChanges.map(x => x.myInput).filter(x => x != null).subscribe(x => { ... });)上创建流.

One benefit to note is that it basically allows your @Input properties to be observable since ngOnChanges is now observable. You could setup a filter with a map to create a stream on the property's value (e.g. this.onChanges.map(x => x.myInput).filter(x => x != null).subscribe(x => { ... });).

在此编辑器中输入了很多上面的代码作为示例,因此可能存在语法错误.这是我在玩游戏时设置的正在运行的示例.打开控制台以查看事件触发.

A lot of the code above was typed in this editor for an example so there may be syntax errors. Here is a running example I setup when playing around with it. Open the console to see the events fire.

https://codepen.io/bygrace1986/project/editor/AogqjM

这篇关于Anuglar2生命周期事件为rxjs Observable的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆