analog
analog copied to clipboard
[RFC]: Explore concept of global imports for components
Which scope/s are relevant/related to the feature request?
Don't known / other
Information
In most cases, when building Angular components, there are some directives/pipes that you end up using over and over such as:
- NgIf
- NgFor
- Async Pipe
The concept of global imports would make these imports implicitly available by default by adding them to an imports.ts
file defined in the project that is added to the imports
array of all components/directives. There would be some way to opt-out of this behavior also.
BEFORE:
import { AsyncPipe, CurrencyPipe, NgForOf } from '@angular/common';
import { Component, inject, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { CartService } from '../../cart.service';
@Component({
selector: 'app-shipping',
standalone: true,
imports: [NgForOf, CurrencyPipe, AsyncPipe],
template: `
<h3>Shipping Prices</h3>
<div class="shipping-item" *ngFor="let shipping of shippingCosts | async">
<span>{{ shipping.type }}</span>
<span>{{ shipping.price | currency }}</span>
</div>
`,
})
export default class ShippingComponent implements OnInit {
private cartService = inject(CartService);
shippingCosts!: Observable<{ type: string; price: number }[]>;
ngOnInit(): void {
this.shippingCosts = this.cartService.getShippingPrices();
}
}
AFTER:
src/imports.ts
import { AsyncPipe, CurrencyPipe, NgForOf } from '@angular/common';
export const imports = [
AsyncPipe,
CurrencyPipe,
NgForOf,
// other imports
];
import { Component, inject, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { CartService } from '../../cart.service';
@Component({
selector: 'app-shipping',
standalone: true,
imports: [
// global imports included
],
template: `
<h3>Shipping Prices</h3>
<div class="shipping-item" *ngFor="let shipping of shippingCosts | async">
<span>{{ shipping.type }}</span>
<span>{{ shipping.price | currency }}</span>
</div>
`,
})
export default class ShippingComponent implements OnInit {
private cartService = inject(CartService);
shippingCosts!: Observable<{ type: string; price: number }[]>;
ngOnInit(): void {
this.shippingCosts = this.cartService.getShippingPrices();
}
}
Implementation-wise this would most likely be done with a TypeScript transformer.
Notes
- Language service integration?
- More flexible imports file?
Describe any alternatives/workarounds you're currently using
Tooling
I would be willing to submit a PR to fix this issue
- [ ] Yes
- [ ] No
I think I would rather just use Angular Modules over magic app-wide configuration with a non-Modules workaround injecting code into my standalone components?
It feels counter-intuitive to use standalone modules and then say you want an external module to handle common imports.
Component({ selector: 'app-shipping', standalone: true, imports: [ ...CommonImports, ...FeatureImports, // Component's imports ],
(Sorry about the formatting, typed this from my phone.)
I understand it's opt-in rather than out, but auto import or code generation options should make that pretty cheap.
It feels like it solves the same issue, just explicitly. You can always just make the import arrays globally available if you don't want the extra imports in the component files?
The opt-out question on this feels like it's going to make things uglier of you ever don't want the magic globals for some reason.
Plus you're losing some portability. If you're sharing code with a friend informally across different repos, y'all would have to compare and manage different globals?
It could be too much magic. What I want is a set of things to be available without me importing anything.
Standalone components are great. They've also made Angular even more verbose unless you use arrays for common imports. We don't have a defined pattern for that yet.
The talk at ng-conf by Pawel mentioned something similar using template syntax but I imagine it's going to be bound to first-party directives.
If you're using Analog, you're opting for this potential feature, so if you want to stop using it, there is a cost to switch.
Opt-out could be some sort of filter.
This looks like a lot of effort for almost zero or even negative benefit. The Angular language service would need to be extended to support this in tooling, and it probably won't work in IntelliJ since they use their own version of it. This RFC doesn't address unit testing either.
What is so hard about doing this?
import { AsyncPipe, CurrencyPipe, NgForOf } from '@angular/common';
export const COMMON = [
AsyncPipe,
CurrencyPipe,
NgForOf,
// other imports
];
import { Component, inject, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { CartService } from '../../cart.service';
import { COMMON } from './imports'
@Component({
selector: 'app-shipping',
standalone: true,
imports: [
COMMON // "global" imports
],
template: `
<h3>Shipping Prices</h3>
<div class="shipping-item" *ngFor="let shipping of shippingCosts | async">
<span>{{ shipping.type }}</span>
<span>{{ shipping.price | currency }}</span>
</div>
`,
})
export default class ShippingComponent implements OnInit {
private cartService = inject(CartService);
shippingCosts!: Observable<{ type: string; price: number }[]>;
ngOnInit(): void {
this.shippingCosts = this.cartService.getShippingPrices();
}
}
This is then easily sharable with unit tests as well.
import { COMMON } from './imports'
beforeEach(() => {
TestBed.configureTestingModule({
imports: [COMMON]
})
})
@antischematic it says "Explore" in the title for a reason. It may be unreasonable to do in practice, but we may end up with something more suitable as a result. If you have any suggestions other than "do what we've always done" I'd be happy to hear it.
The idea is nice. I know sth like this from ASP.NET. However, as we get Auto Imports for Standalone Components soon, perhaps we don't need such global deps. Or do I miss sth. here?