它是什么
让我们从forwardRef的官方Angular 文档开始 。 它说如下:
允许引用尚未定义的引用。
例如,当声明需要为DI引用的令牌但尚未定义该令牌时,将使用forwardRef。 当我们尚未定义创建查询时使用的令牌时,也会使用它。
该定义讨论了对类的引用,并以引用类的标记为例。 在Angular中,我们定义了这样的依赖关系:
const dependency = { provide: SomeTokenClass, useClass: SomeProviderClass };
在上面的示例中,为provide和token指定了一个令牌。 因此,根据定义,我们知道可以将forwardRef用作令牌,如下所示:
const dependency = { provide: forwardRef(()=>{ SomeTokenClass }), useClass: SomeProviderClass };
但是,我们在useClass配方中也引用了SomeProviderClass类。 我们也可以对提供商使用这种方法吗? 文档没有提及,但是我们知道useClass配方拥有对类的引用 ,并且我们了解到forwardRef可以应用于引用。 因此答案是肯定的,我们也可以将这种方法应用于提供者配方:
const dependency = { provide: forwardRef(()=>{ SomeTokenClass }), useClass: forwardRef(()=>{ SomeProviderClass }) };
但是,仅当配方隐含对类的引用时才可以应用它,对于useClass或useExisting就是这种情况,如以下示例所示:
const dependency = { provide: forwardRef(()=>{ SomeTokenClass }), useExisting: forwardRef(()=>{ SomeOtherClassToken }) };
另外,如果您使用Inject装饰器按类引用注入令牌,则也可以应用该函数:
export class ADirective { constructor(@Inject(forwardRef(() => Token)) service) {
使用范例
Angular文档显示以下示例:
class Door { lock: Lock; // Door attempts to inject Lock, // despite it not being defined yet. // forwardRef makes this possible.
constructor(@Inject(forwardRef(() => Lock)) lock: Lock) { this.lock = lock; } } // Only at this point Lock is defined. class Lock {}
但是对我来说这是一个不自然的例子。 尽管可以理解要点,但是当我需要在实际应用程序中使用forwardRef时,很难通过查看来理解它。 我可以简单地将Lock类放在Door之上,这样就可以解决问题。 碰巧的是,Angular源提供了一个更好的真实单词用法示例。
您可能知道Angular表单具有ngModel和formControl指令,您可以在表单输入上使用它们。 这些控件中的每个控件都定义了一个提供程序,该提供程序允许通过公共令牌NgModel访问指令实例。 因此,例如,如果您要访问与自定义指令中的输入关联的form指令,则可以执行以下操作:
@Directive({ selector: '[mycustom]' }) export class MyCustom { constructor(@Inject(NgControl) directive) {
...
<input type="text" ngModel mycustom>
为了使NgModel和formControl指令都定义一个formControlBinding提供程序并将其注册在指令装饰器描述符中。 这是formControl指令执行此操作的方式:
export const formControlBinding : any = { provide: NgControl, useExisting: FormControlDirective }; @Directive({ selector: '[formControl]', providers: [ formControlBinding ], ... }) export class FormControlDirective { ... }
和NgModel指令:
export const formControlBinding : any = { provide: NgControl, useExisting: NgModel };
@Directive({ selector: '[ngModel]', providers: [ formControlBinding ], ... }) export class NgModel { ... }
这里有趣的实现是formControlBinding是在指令类装饰器之外定义的。 因此,当JS运行时评估定义formControlBinding对象的代码时,尚未评估NgModel类定义,并且如果我们将提供者对象登录到控制台,我们将看到以下内容:
Object {useExisting: undefined, token: function}
嗯,useExisting指向undefined,因此Angular将无法解析其他标记。 这就是为什么Angular在这里使用forwardRef的原因:
export const formControlBinding: any = { provide: NgControl, useExisting: forwardRef (() => FormControlDirective ) }; export class FormControlDirective { ... }
export const formControlBinding: any = { provide: NgControl, useExisting: forwardRef (() => FormControlDirective ) }; export class FormControlDirective { ... }
...
export const formControlBinding: any = { provide: NgControl, useExisting: forwardRef (() => NgModel ) }; export class NgModel { ... }
export const formControlBinding: any = { provide: NgControl, useExisting: forwardRef (() => NgModel ) }; export class NgModel { ... }
但是,如果我们在Angular的类装饰器中定义formControlBinding而不使用forwardRef的话,它会起作用吗:
@Directive({ selector: '[ngModel]', providers: [ { provide: NgControl, useExisting: NgModel } ], ... }) export class NgModel { ... }
好吧,如果您看一下代码,则似乎在装饰器中在类定义之前引用了NgModel。 但是您应该记住, 定义了类后 ,所有类装饰器都将应用于该类。 因此,即使没有forwardRef,上述实现也可以工作。 但是,通过在装饰器中内联提供程序,我们将不再将其导出,因此无法在应用程序中重用。
为什么forwardRef起作用?
现在,您的脑海中可能浮现出一个问题,forwardRef是如何工作的。 实际上,这与JavaScript中的闭包如何工作有关。 当您在闭包函数中捕获变量时 ,它将捕获变量引用 ,而不是变量值 。 这是一个小例子,以证明这一点:
let a; function enclose() { console.log(a); } enclose(); // undefined a = 5; enclose(); // 5
您可以看到,尽管在创建封闭函数时未定义变量a,但它捕获了变量引用。 因此,稍后将变量更新为5时,它将记录正确的值。
而forwardRef只是一个将类引用捕获到闭包中的函数,并且在执行该函数之前定义了类。 Angular编译器在运行时使用函数resolveForwardRef解开令牌或提供程序类型。
您发现文章中的信息有帮助吗?
From: https://hackernoon.com/what-is-forwardref-in-angular-and-why-we-need-it-6ecefb417d48