使用文件替换来替换服务实现

发布于 2025-01-11 14:45:11 字数 1968 浏览 0 评论 0原文

Angular 允许通过 fileReplacements文件替换 > 在 angular.json 中。一个常见的用例是替换文件environment.ts以针对特定构建,例如使用ng build --configuration staging以及configurations中的以下条目angular.json:

"staging": {
  ...,
  "fileReplacements": [
    {
      "replace": "src/environments/environment.ts",
      "with": "src/environments/environment.staging.ts"
    }
  ]
}

上面的效果很好,并且它的用例非常清晰。

但是,让我们假设这种情况,我想通过文件替换来替换服务(和其他文件)。一项特定服务的示例:

// service.interface.ts
export interface IService { doSomething(): void; }

// service.mock.ts
@Injectable()
export interface Service implements IService {
  doSomething(): void { throw Error(); }
}

// service.bar.ts
@Injectable()
export interface Service implements IService {
  doSomething(): void { // bar specific implementation }
}

// service.foo.ts
@Injectable()
export interface Service implements IService {
  doSomething(): void { // foo specific implementation }
}

// app.component.ts
import { Service } from './service.mock';
...
export class AppComponent {
  constructor(private service: Service) {
    this.service.doSomething();
  }
}

angular.json 中的 configurations 如下所示:

"foo": {
  ...
  "fileReplacements": [
    {
      "replace": "src/app/service.mock.ts",
      "with": "src/app/service.foo.ts"
    }
  ]
},
"bar": {
  ...
  "fileReplacements": [
    {
      "replace": "src/app/service.mock.ts",
      "with": "src/app/service.bar.ts"
    }
  ]
}

通过使用 ng build --configuration foong build --configuration bar< /code> 我现在可以确定通过 AppComponent 中的 this.service.doSomething(); 调用哪个实现 - foo 或 < em>bar 具体一个。长话短说:这个解决方案感觉有点奇怪。请记住,我可能有几个服务采用这种方式,还有路由和其他文件(导致相当长的 fileReplacements)。

我想知道:1.)文件替换是否适用于此用例? 2.) 是否有任何严重的缺点或缺点? 3.)有哪些可能的替代方案,或者更确切地说,什么是更像 Angular 的方式(也许类似于依赖注入)?

如果需要更多详细信息,请告诉我。

Angular allows file replacements via fileReplacements in the angular.json. One common use case is to replace the file environment.ts in order to target a certain build, e.g. with ng build --configuration staging and the following entry in configurations within the angular.json:

"staging": {
  ...,
  "fileReplacements": [
    {
      "replace": "src/environments/environment.ts",
      "with": "src/environments/environment.staging.ts"
    }
  ]
}

The above works well and its use case is pretty clear.

However, let's assume the situation, that I want to replace services (and other files) via file replacements. An example for one specific service:

// service.interface.ts
export interface IService { doSomething(): void; }

// service.mock.ts
@Injectable()
export interface Service implements IService {
  doSomething(): void { throw Error(); }
}

// service.bar.ts
@Injectable()
export interface Service implements IService {
  doSomething(): void { // bar specific implementation }
}

// service.foo.ts
@Injectable()
export interface Service implements IService {
  doSomething(): void { // foo specific implementation }
}

// app.component.ts
import { Service } from './service.mock';
...
export class AppComponent {
  constructor(private service: Service) {
    this.service.doSomething();
  }
}

And configurations in the angular.json looks like:

"foo": {
  ...
  "fileReplacements": [
    {
      "replace": "src/app/service.mock.ts",
      "with": "src/app/service.foo.ts"
    }
  ]
},
"bar": {
  ...
  "fileReplacements": [
    {
      "replace": "src/app/service.mock.ts",
      "with": "src/app/service.bar.ts"
    }
  ]
}

By using ng build --configuration foo or ng build --configuration bar I can now determine which implementation gets called via this.service.doSomething(); in AppComponent - either the foo or the bar specific one. Long story short: this solution feels a bit odd. And keep in mind, that I might have a couple of services going this way, also routing and other files as well (resulting in a pretty long fileReplacements).

I was wondering: 1.) Are file replacements intended for this use case?; 2.) Are there any critical disadvantages or downsides?; and 3.) What are possible alternatives or rather what is the more Angular-like way (maybe something like dependency injection)?

Let me know, if further details are needed.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

波浪屿的海角声 2025-01-18 14:45:11

文件替换的优点是您不会将所有未使用的服务与代码捆绑在一起。如果所有未使用的服务都被捆绑在一起并不困扰您,那么 DI 可能是您的一个很好的解决方案(继续阅读)。


您可以将变量添加到环境文件中,然后在 NgModule 中创建一个提供程序。 这是一个工作示例

您将创建 x 个所有服务都将扩展基本服务。然后,该基本服务将具有一些与之关联的抽象数据,甚至可能是您的孩子可以使用的一些方法/属性。

// Some base class
@Injectable({ providedIn: 'root' })
export abstract class Service {
  abstract name: string;
}

// Your foo service
@Injectable({ providedIn: 'root' })
export class FooService extends Service {
  name = 'foo';
}

// Your bar service
@Injectable({ providedIn: 'root' })
export class BarService extends Service {
  name = 'bar';
}

然后,您可以在模块中创建一个提供程序,甚至可以在其他一些提供服务的组件中创建一个提供程序。然后,当您将其注入组件/服务/指令等时,工厂将被执行,并根据您设置的环境变量决定向调用者提供哪个服务。

import { environment } from './environment';

@NgModule({
  imports: [BrowserModule, FormsModule],
  declarations: [AppComponent, HelloComponent],
  bootstrap: [AppComponent],
  providers: [
    {
      provide: Service,
      useFactory: (foo: FooService, bar: BarService) => {
        if (environment.somevar === 'mock') {
          throw new Error();
        } else if (environment.somevar === 'foo') {
          return foo;
        } else if (environment.somevar === 'bar') {
          return bar;
        }
      },
      deps: [FooService, BarService]
    },
  ],
})
export class AppModule {}

我们不是在 FooServiceBarService 上进行 DI 注入,而是在基础服务上进行。创建后将运行工厂并返回正确的类。

export class AppComponent implements OnInit {
  constructor(private readonly myService: Service) { }

  ngOnInit() {
    console.log('Using Service:', this.myService.name);
  }
}

The advantage of file replacement is that you won't have all the unused services bundled with the code. If it doesn't bother you that all of the unused services do get bundled then DI might be a good solution for you (Read on).


You could add a variable to your environment file(s) and then create a provider in your NgModule. Here is a working example

You would create x number of services that all would extend a base service. This base service would then have some abstract data associated with it and may even some methods/properties that your children could use.

// Some base class
@Injectable({ providedIn: 'root' })
export abstract class Service {
  abstract name: string;
}

// Your foo service
@Injectable({ providedIn: 'root' })
export class FooService extends Service {
  name = 'foo';
}

// Your bar service
@Injectable({ providedIn: 'root' })
export class BarService extends Service {
  name = 'bar';
}

You would then create a provider in your module or even some other component that would provide Service. When you then inject it into your components/services/directives etc. the factory will get executed and decide which service to provide to the caller based on your environment variable that is setup.

import { environment } from './environment';

@NgModule({
  imports: [BrowserModule, FormsModule],
  declarations: [AppComponent, HelloComponent],
  bootstrap: [AppComponent],
  providers: [
    {
      provide: Service,
      useFactory: (foo: FooService, bar: BarService) => {
        if (environment.somevar === 'mock') {
          throw new Error();
        } else if (environment.somevar === 'foo') {
          return foo;
        } else if (environment.somevar === 'bar') {
          return bar;
        }
      },
      deps: [FooService, BarService]
    },
  ],
})
export class AppModule {}

Instead of doing DI injection on FooService or BarService we do it on the base service. Which when created will run the factory and return the proper class.

export class AppComponent implements OnInit {
  constructor(private readonly myService: Service) { }

  ngOnInit() {
    console.log('Using Service:', this.myService.name);
  }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文