如何使用 TypeScript 静态注入器模式注入`$rootScope`?

Posted

技术标签:

【中文标题】如何使用 TypeScript 静态注入器模式注入`$rootScope`?【英文标题】:how do I inject `$rootScope` using TypeScript static injector pattern? 【发布时间】:2016-02-22 04:40:12 【问题描述】:

所以,最后在获得一个大型模型以 Angular 和 typescript 进行编译之后,我得到了这个运行时错误:

10angular.js:12314 错误:[ng:cpws] 无法复制!制作副本 不支持 Window 或 Scope 实例。 http://errors.angularjs.org/1.4.1/ng/cpws

at angular.js:68
at copy (angular.js:857)
at copy (angular.js:932)
at copy (angular.js:888)
at Scope.$digest (angular.js:15666)
at Scope.$apply (angular.js:15935)
at bootstrapApply (angular.js:1624)
at Object.invoke (angular.js:4443)
at doBootstrap (angular.js:1622)
at bootstrap (angular.js:1642)

我没有更改任何“实现”,我只是重新构造了类以使用 TypeScript。在我的代码中没有任何地方可以使用 angular.copy(在整个应用程序的任何类中)。在多次将头撞在墙上之后,我偶然发现了这个问题。构造函数将 $rootScope 分配给一个局部变量(由于 TS,它现在位于原型上)。现在,这是一种旧代码,我只使用 $rootScope 的 ref 将其用作所有使用 $rootScope.broadcast(...) 的视图控制器的通用事件调度程序。当我使用标准角度样板注入它时,它曾经工作正常,我可以重构它......

/// <reference path="../../app/reference.ts" />
class UserModel 
  roles:string;
  activeRole:string;
  errorString:string;
  loginAttemptEmailValue:string;
  successString:string;

static $inject = ['utils', '$rootScope','$q', '$cookies', '$http','settings', '$location'];
  constructor(u,r,q,c,h,s,l) 
    this.utils = u;
    this.rootScope = r; // <---- the problem. comment-out this line and it works.
    this.q = q;
    this.cookies = c;
    this.http = h;
    this.settings = s;
    this.location = l;
  
...

angular.module('App').service('userModel', UserModel );

但这让我担心,当我去转换我的控制器时,我会遇到同样的问题,只是注入 $scope。

所以,我的问题是,我如何使用这个 TypeScript 静态注入器模式正确注入 $scope 而不会出现这个可怕的复制错误?

我尝试创建一个类级别的静态作用域变量,但在尝试分配给静态类型时当然会出现左侧赋值错误。

更新: 这是转译的js。

var UserModel = (function () 
    function UserModel(u, r, q, c, h, s, l) 
        var _this = this;

        this.utils = u;
        this.rootScope = r;
        this.q = q;
        this.cookies = c;
        this.http = h;
        this.settings = s;
        this.location = l;
    
    UserModel.prototype.hasActiveRole = function (val) 
        if (this.activeRole === val) 
            return true;
        
        return false;
    ;
    //...other implementation...
    UserModel.$inject = ['utils', '$rootScope', '$q', '$cookies', '$http', 'settings', '$location'];
    return UserModel;
)();
angular.module('App').service('userModel', UserModel);

【问题讨论】:

澄清请求:如果您注释掉this.rootScope = r;,那么您的应用仍然可以正常运行(减去广播)? 是的!它是使应用程序立即出错的那一行。我可以将其注释掉,稍后在程序执行时得到一个正常的 null 错误。 有道理,你能分享使用$rootScope的代码吗?你上面的作业是正确的。 对,我只用来广播。 this.rootScope.$broadcast('changeRole'); 可能是另一个广播“loggedOut”之类的。我目前正在尝试将控制器转换为 TS,看看这实际上有多大意义。 【参考方案1】:

所以,如果其他人碰到同样的砖墙,请在这里回答我自己的问题。我相信这种情况可能是 Angular 错误或缺少关于将 $rootScope 分配给服务中的局部变量的文档。

注入$scope$rootScope 并分配给本地引用属性似乎工作正常,在控制器中,但不是服务。我还注意到您无法$scope 导入服务,也许角度团队也打算阻止注入$rootScope,但忽略了这样做。

错误本身令人费解;我最好的猜想是编译中存在一个工件(在 Angular 的内部深处),其中服务的实例化尝试深度复制 $rootScope 属性而不是浅复制当且仅当它被分配给服务对象上的属性时.

【讨论】:

【参考方案2】:

有同样的问题。就是这个东西

at Scope.$digest (angular.js:15666)

您将 UserModel 作为服务传递给 Angular,并在每个公共属性上添加 Angular 手表,包括所有这些

this.utils = u;
this.rootScope = r;
this.q = q;
this.cookies = c;
this.http = h;
this.settings = s;
this.location = l;

所以当 $digest 开始时,它会尝试save value as last

watch.last = watch.eq ? copy(value, null) : value;

然后 copy() 发生 :) 错误 because of

if (isWindow(source) || isScope(source)) 
  throw ngMinErr('cpws',
    "Can't copy! Making copies of Window or Scope instances is not supported.");

我使用存储类的 rootScope 的外部变量临时解决了这个问题

let rootScope: ng.IRootScopeService;

class Settings() 

  static $inject = [
    '$rootScope'
  ];

  constructor($rootScope: ng.IRootScopeService) 

    rootScope = $rootScope;
  

稍后将使用 RxJs Observables 替换 $rootScope 使用作为 pub/sub

【讨论】:

【参考方案3】:

显然,您正试图深入观察您班级的整个实例。如果此对象的属性之一指向范围实例,则不应该这样做。如果您都想这样做,您可以尝试使用 getter 函数而不是普通属性:this.getRootScope = () =&gt; $rootScope,这可能有效,但听起来仍然不是一个好主意。因为出于性能原因,当您深度观察某物时,您希望被观察的对象尽可能小。如果你只是想检测一些数据的变化,那么同时寻找注入服务的变化是否有意义?显然,您将错误的对象传递给 $watch

(顺便说一句,我建议使用 ngAnnotate 而不是手写static $inject...。)

【讨论】:

没有手表。这只是将 $rootScope 注入服务的问题......除非有一些我不知道的自动绑定手表。 堆栈跟踪清楚地显示某处有一个手表,它正在跟踪整个userModel 对象。像这样:$scope.$watch(userModel, ..., true). 我看到了一个摘要,但没有手表。你指的是什么? 实际上,消费控制器的实现只关注一个属性,loginStatus。 this.scope.$watch(() => return this.userModel.loginStatus; , (oldStatus, newStatus ) => if( oldStatus !== newStatus ) this.handleLoginStatusChange(); );跨度> Digest是手表加工的地方。 angular.copy 被深度手表用来存储被监视表达式值的副本。如果copy 是直接从$digest 调用的,则可能意味着这一件事。

以上是关于如何使用 TypeScript 静态注入器模式注入`$rootScope`?的主要内容,如果未能解决你的问题,请参考以下文章

使用依赖注入取代硬连接资源(静态工厂单例),也可用于构造方法bulider模式

Angular 指令依赖注入 - TypeScript

静态注入器错误

设计模式 - 如何仅在某些情况下强制执行对象属性(构建器模式,依赖注入)

Angular 2 中的依赖注入在使用内联注入器和构造器注入时创建多个实例

如何在表单中注入一些东西