在Angular 8和9中,提供和注入“Window”与使用“Window”的区别是什么?

16

我有两个Angular项目,分别使用以下版本:

  • 9.0.0-next.6
  • 8.1.0

在9版本中,我使用以下代码来提供和注入window对象:

@NgModule({
  providers: [
    {
      provide: Window,
      useValue: window
    },
  ]
})

export class TestComponent implements OnInit {
  constructor(@Inject(Window) private window: Window)
}

这个方法运行良好。


采用这种方法到版本8时,编译时会引发警告和错误:

警告:无法解析TestComponent的所有参数…

我通过使用单引号来解决它,像这样:

@NgModule({
  providers: [
    {
      provide: 'Window',
      useValue: window
    },
  ]
})

export class TestComponent implements OnInit {
  constructor(@Inject('Window') private window: Window)
}
这两个版本有什么不同之处?
Angular 8和9之间的区别是什么导致了这种情况?

我希望通过悬赏,能够得到一个答案,让我和其他人更好地学习和理解Angular中的providersdi在不同版本的框架中是如何工作的。 - lampshade
2个回答

17
为了让您的应用程序能够与服务器端渲染配合使用,我建议您不仅使用窗口令牌,还要以适合SSR的方式创建此令牌,而无需引用window。 Angular内置了DOCUMENT令牌,可用于访问document。以下是我针对我的项目使用window令牌的解决方案:
import {DOCUMENT} from '@angular/common';
import {inject, InjectionToken} from '@angular/core';

export const WINDOW = new InjectionToken<Window>(
    'An abstraction over global window object',
    {
        factory: () => {
            const {defaultView} = inject(DOCUMENT);

            if (!defaultView) {
                throw new Error('Window is not available');
            }

            return defaultView;
        },
    },
);

编辑:鉴于这是人们经常需要的东西,我们使用了这种技术创建了一个微型开源库,其中包含用于全局对象的注入令牌,因此您可以使用它:

https://github.com/ng-web-apis/common

它有一个姊妹库,可供在Angular Universal中与SSR一起使用:

https://github.com/ng-web-apis/universal

总的来说,请查看我们在Angular中使用的本机API的中心:

https://ng-web-apis.github.io/


非常感谢您的回答。这对我非常有帮助,我将来会采用类似的解决方案。 - lampshade
@lampshade 请查看我们创建的这个小型库,它包含许多全局对象令牌,例如window、navigator、location等:https://github.com/ng-web-apis/common - waterplea
作为一种替代方案,您可以省略明确的错误"throw"并返回"defaultView"。因此,使用组件可以检查"this.window"是否不为假,以便可以轻松跳过与"Window"相关的逻辑。 - im.pankratov
我从未遇到过它不可用的情况,包括SSR。这更像是一种TypeScript的做法,以使其满意。 - waterplea

7

考虑 ValueProvider 接口:

export declare interface ValueProvider extends ValueSansProvider {
    /**
     * An injection token. Typically an instance of `Type` or `InjectionToken`, but can be `any`.
     */
    provide: any;
    /**
     * When true, injector returns an array of instances. This is useful to allow multiple
     * providers spread across many files to provide configuration information to a common token.
     */
    multi?: boolean;
}
provide属性的类型为any。这意味着任何对象(包括Window构造函数)都可以放在里面。只有引用才重要,以确定应使用哪个提供程序将参数注入到构造函数中。
不应将原生Window构造函数视为注入令牌的良好实践。这在编译时会失败,因为Window存在于运行时的浏览器环境中,它也存在于TypeScript的declare中,但是Angular 8编译器无法进行静态代码分析,以关联提供程序中的Window和构造函数参数中的Window,因为Window的赋值是由浏览器而不是代码完成的。虽然不确定为什么在Angular 9中起作用...
您应该创建自己的注入令牌来表示依赖项提供程序。此注入令牌应为:
  • 专用字符串(就像您使用'Window'一样)
  • 专用InjectionToken。例如:export const window = new InjectionToken<Window>('window');
此外,Angular代码应该是平台无关的(可在浏览器和Node.js服务器上执行),因此最好使用返回windowundefined/null的工厂函数,然后在组件中处理undefined/null情况。

1
非常好!谢谢。我刚刚查看了Angular文档(v8和v9),但是我没有找到任何一个使用字符串的例子。 :( 他们应该在文档中真正解释这个问题! - Zaphoid

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接