Once you internalize the Angular "Template Context", it becomes an indispensable practical framework for thinking—your primary compass for crafting top-tier Angular applications.
Tomas Trajan
@tomastrajan
Nov 22, 2022
15 min read
🧠 by Milad Fakurian 🖌️by Tomas Trajan
Foreword
Sure, this piece is quite extensive, but that’s not because Angular is inherently over-complicated. Rather, it’s about covering a broad spectrum and examining every possible perspective, so that by the end, you’ll walk away with a complete understanding.
You might skip straight to the section labeled “Template context” if you’re in a hurry, though I’d suggest going through everything in order 😉
TLDR; Within Angular, any declarables ( components, directives and pipes ) that reference one another in templates must belong to THE SAME “template context”, which is established via @NgModule (or by the component itself under the new stand-alone components STAC approach). A typical application features multiple “origin template contexts”: the eager ( root ) one for the initial load, plus one for every lazy loaded @NgModule. Conversely, each STAC comes with its own fully self-contained “template context”.
Update
Catch the recording of my ngIndia 2023 talk, where I dive into the Angular Template Context Mental Model!
Hi everyone!
A major wave of Angular excitement has recently swept through the community, and with good justification!
A new chapter of thrilling times for Angular has arrived!
Angular 13 marked the last release to drop the outdated ViewEngine rendering engine, making it the first purely IVY-based version.
With that hefty challenge behind them, the Angular team is now free to concentrate on rolling out impressive features such as fully typed reactive forms, enhanced developer experience, and the framework's own growth!
This area of focus primarily gave rise to the push enabling Angular applications to be built without @NgModules, instead leveraging the stand-alone components ( STAC ) approach, which debuted as a developer preview in Angular 14.
As Angular 15 has now been launched ( 17.11.2022 ), STACs have exited developer preview, gaining first-class backing from the Angular CLI and other framework elements.
Angular Stand-alone components mark a major milestone in the framework's evolution, which might revive earlier anxiety about a sweeping shift akin to migrating from Angular JS to Angular
Fortunately, that fear is completely unfounded!
What's more, we'll uncover that both the @NgModules-based strategy and the STAC approach rely on a shared foundational principle that remains consistently intact! Due to this, it offers proof to the contrary, Angular remains, and will continue to be, a highly stable and trustworthy technology.
I trust this has piqued your interest about that "shared foundational principle", and that's precisely what we'll dive into next!
However, before that, let's broaden our perspective and sketch out a bigger picture so everything clicks into place—starting with…
Mental models
A mental model is a way of explaining how someone perceives the way things operate in the real world. It captures the environment around us, the connections between its components, and a person's gut feelings about their own actions and outcomes. Mental models can influence behavior and define a strategy for tackling problems ( much like a personal algorithm ) and completing tasks Wikipedia
That formal description hints at its potential value, but let's examine it through a developer's lens using ideas we're all acquainted with.
Putting aside philosophical debates and aiming for a practical analogy, we can envision our brains as akin to a computer running software ( the mind ), where that software is trainable ( learning ).
Example: "A variable"
A handy piece of that "mind software" could be dubbed a "mental model". A prime instance of such a model, likely well-known to all of us, is "a variable", which typically gets explained like this…
variable holds a value ( typed / untyped / which type (depends on language) )
variable can be assigned a different value ( or not, if it's constant )
variable is reachable from certain spots but hidden from others ( e.g., scope, class property, … )
variable can be employed to fetch its value for calculations
the list goes on, varying with the specific language, scenario…
Armed with such a mental model, we can apply the idea of "variable" to reach objectives while coding features.
Each of us carries numerous mental models that we apply fluidly, often without realizing it, in everyday work!
Mental models frequently merge and stack on one another—take "variable" and "array", for instance, where arrays are often kept in variables but have their own mental model involving multiple values and processing methods like map or filter and beyond…
Limited mental models
Often, our mental model is just a simplified version, enough to accomplish tasks, and that's acceptable. Ultimately, the key is reaching the desired outcome, which can easily happen even with a surface-level grasp of the topic.
For instance, we could work with variables without any awareness of TypeScript's typing system. After all, we could label everything as any and still produce a functional app that users enjoy!
In such a scenario, we'd be relying on a constrained understanding, and while we might achieve results, those results would be less than ideal and might create problems later on, such as when we try to expand our untyped project or bring in a new developer to collaborate on it.
Drawing from my own work in Angular spanning development, consulting, and teaching across enterprise settings, it's common to see developers working with only a partial grasp of the "template context" concept. As with the earlier example, although apps get built successfully, they're frequently delivered in a flawed state that can restrict future growth and drive up maintenance expenses as business needs evolve!
A partial grasp of the "template context" concept often results in Angular apps that are in a poor state, which can hinder their progress and increase upkeep costs!
Templates
Let's shift to something more hands-on. In Angular, to render content, we rely on a component and, specifically, its template. Here, we use a declarative method to specify:
the content to show, e.g.,
<h1>Hello world</h1>how it updates, e.g.,
<p>{{ user.name }}</p>how to manage events, e.g.,
<button (click)="save()">Save</button>
This differs greatly from the outdated approach of imperative rendering, where you might directly assign to
.innerHTMLwhenever a display update was needed.
Here’s a sample of what a component might look like...
@Component({
selector: 'my-org-user-item',
template: `
<mat-card>
<mat-card-header>
<mat-card-title>{{ user.name }} {{ user.surname }}</mat-card-title>
<mat-card-subtitle>{{ user.role }}</mat-card-subtitle>
</mat-card-header>
<mat-card-actions align="end" *ngIf="user.role === Role.ADMIN">
<button mat-button (click)="edit()">Edit</button>
<button mat-button (click)="remove()">Remove</button>
</mat-card-actions>
</mat-card>
`,
})
export class UserItemComponent {
@Input() user: User;
edit() {
/* ... */
}
remove() {
/* ... */
}
}
With Angular, you have the option to move your template into its own HTML file and point to it via the
templateUrlattribute inside the@Component()decorator—though whether you do this is entirely up to personal taste.
Now, let’s zoom in on the template itself…
<mat-card>
<mat-card-header>
<mat-card-title>{{user.name}} {{user.surname}}</mat-card-title>
<mat-card-subtitle>
<mat-icon [svgIcon]="user.role"></mat-icon>
{{user.role}}, {{user.lastLoggedIn | date }}
</mat-card-subtitle>
</mat-card-header>
<mat-card-actions align="end" *ngIf="user.role === Role.ADMIN">
<button mat-button (click)="edit()">Edit</button>
<button mat-button (click)="remove()">Remove</button>
</mat-card-actions>
</mat-card>
So what exactly are we looking at? The template mixes familiar pieces such as plain HTML <button> with less obvious constructs like <mat-card> or <mat-icon>.
In addition, the <button> carries an attribute that shares a similar naming style with those elements, namely mat-button. There’s also the | date pipe, which formats the value held in the lastLoggedIn property of the user object.
From this, it becomes clear that a template within an Angular component is able to work with the following:
native HTML elements, e.g.
<button>, …reusable Angular components, e.g.
<mat-card>,<mat-icon>, …Angular directives, like
mat-buttonAngular pipes, for instance
| dateor| json
Every Angular component has built-in access to all standard HTML elements inside its own template
The question remains: how do components, directives, and pipes from other sources get used?
Declarables
Within Angular, any construct tied to templates—covering components, directives, and pipes—is labeled a declarable. This is because each one must be listed in the declarations:[ ] array of exactly one parent @NgModule, or alternatively be launched as a standalone component, directive, or pipe…
Had we written the previous component and its template without any additional preparation…
<mat-card>
<!-- other components -->
</mat-card>
Every Angular-based component (and other declarables) would trigger an error, as shown below.
Error: user-item.component.html:1:1 - error NG8001: 'mat-card' is not a known element:
1. If 'mat-card' is an Angular component, then verify that it is part of this module.
2. If 'mat-card' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '`@NgModule`.schemas' of this component to suppress this message.
For the time being, let's set aside the Web Component scenario and zero in solely on Angular. The error message provides a key clue: when
A comparable situation arises if you adopt the fresh Angular stand-alone components methodology — allow me to demonstrate…
Error: user-item.component.html:1:1 - error NG8001: 'mat-card' is not a known element:
1. If 'mat-card' is an Angular component, then verify that it is included in the '@Component.imports' of this component.
2. If 'mat-card' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@Component.schemas' of this component to suppress this message.
The error we encounter is essentially the same, flagging that if <mat-card> is an Angular component, it must be included in the imports of “this component”.
Whether we work with ngModules or the newer standalone components, using another Angular component in our template triggers the identical error—showing that the “template context” concept remains just as vital and enduring.
This same principle applies to directives and pipes alike. If we try to use them without them being “a part” of the cur module or standalone component, an error will surface.
Before we reach our final point, let’s take one more brief detour. We’ll explore the finer details of the “template context”, offering practical examples and best practices for building real-world Angular apps, and then address the context itself…
Context
Context is the surrounding text that clarifies a word's meaning. Without it, we can't decide if “dish” points to the meal (like roasted chicken) or the plate it's served on.
In daily life, we often encounter contextual details—information that only makes sense within a specific framework, otherwise appearing irrelevant or confusing…
Fortunately, when it comes to software development, context takes on a much more specific definition!
Broadly speaking, context refers to elements that are connected, mutually accessible, or fall under the same scope.
Everything is now in its place. We’ve covered:
WHY the shift to NgModules and STAC is ahead, whether it's a cause for concern, and what consequences to expect.
HOW templates, components, directives, and pipes must share a module or component to work together—otherwise, errors occur.
the remaining piece is the WHAT: leveraging template contexts to create optimal Angular applications.
Now, let’s reshape your thinking with a new mental model—the “template context”—if you’re ready to go along! 😅
Template context
We’ll kick off with something simple: a component that incorporates another component in its template...
@Component({
selector: 'my-org-root',
template: ` <my-org-hello [name]="name"></my-org-hello> `,
})
export class AppComponent {
name = 'Tomas Trajan';
}
The original NgModule approach
Suppose our AppComponent is associated with the AppModule, placing it inside that module’s declarations: [ ] array.
With
@NgModules, every component is tied to exactly one module, meaning it can appear in only a singledeclarations: [ ]array!
A basic version of our module might be structured as follows…
@NgModule({
declarations: [AppComponent], // declared in exactly one module
imports: [], // let's ignore this for now
bootstrap: [AppComponent], // we have to bootstrap our app
})
export class AppModule {} // our first root template context
Attempting to compile this would trigger an error much like the previous ones, indicating that my-org-hello doesn’t belong to the same “template context” (Angular module).
The remedy is straightforward: simply include that component in the same declarations: [ ] array.
@NgModule({
declarations: [AppComponent, HelloComponent], // <- added HelloComponent
imports: [],
bootstrap: [AppComponent],
})
export class AppModule {}
Excellent — the build succeeds, and our app behaves exactly as it should!
Since both AppComponent and HelloComponent belong to the same “template context” (here, that’s @NgModule), we are free to place <my-org-hello> directly inside the template of AppComponent.
Multiple modules
Let’s take this scenario a step further by relocating HelloComponent into its own separate module, called HelloModule.
@NgModule({
declarations: [HelloComponent], // <- declared in exactly one module
imports: [],
exports: [],
})
export class HelloModule {}
Because an Angular component can belong to the declarations: [ ] array of only one module, we must strip it from the declarations: [ ] of the AppModule, thereby excluding it from the module's “template context”.
Recall that each @NgModule establishes its own “template context”, within which declarables are mutually usable in templates. Our HelloComponent is thus now included in the fresh “template context” that the HelloModule creates. As a result, we end up with TWO “template contexts” that are at this point isolated from one another.
At this stage, the app breaks once more, displaying the same error as before: my-org-hello is treated as an unrecognized element.
Follow me on Twitter because you will get notified about new Angular blog posts and cool frontend stuff!😉
Privacy
To address this, we can include the HelloModule in the imports: [ ] array of the AppModule.
@NgModule({
declarations: [AppComponent], // <- removed HelloComponent
imports: [HelloModule], // <-added HelloModule
bootstrap: [AppComponent],
})
export class AppModule {}
The HelloModule has now been included in the AppModule’s “template context”. That would seem like a step in the right direction, yet the very same error still halts the app?
The HelloComponent sits exclusively inside the HelloModule’s “template context” — it appears in the module's declarations: [ ] list but is omitted from exports: [ ], which is why this is happening.
Now, let’s correct that and observe the outcome.
@NgModule({
declarations: [HelloComponent], // <- declared in exactly one module
imports: [],
exports: [HelloComponent], // <- added HelloComponent to exports
})
export class HelloModule {}
The application now works again!
Now, let’s pause and analyze the sequence of events:
AppComponentis declared within the “template context” ofAppModuleHelloComponentis declared within the “template context” ofHelloModuleFor
AppComponentto referenceHelloComponentin its template, the latter must reside in the same “template context” as the former, which is the one defined byAppModuleWe achieve that by placing
HelloModuleinto theimports: [ ]array ofAppModule—this grantsAppModulevisibility into the “template context” ofHelloModuleAdditionally,
HelloComponentmust be included in theexports: [ ]array ofHelloModule; otherwise, it remains hidden from the expanded “template context” ofAppModule, which now encompassesHelloModuleas well
Any component within a single “template context” stays inaccessible to other contexts unless it is explicitly exported via the
exports: [ ]array!
Single Component Angular Module ( SCAM ) Pattern
The aforementioned HelloModule, which contains just one HelloComponent listed in both its declarations: [ ] and exports: [ ], embodies a well-established pattern in Angular called Single Component Angular Module, or SCAM ( an unfortunate acronym🤷♂️ ).
Keep this in mind when we later examine the stand-alone components approach, as each STAC functions precisely like a SCAM!
Useful privacy
In the earlier scenario, our HelloComponent was private to HelloModule, yet that privacy served no real purpose—it merely triggered a compilation error that we resolved by exposing the component. However, this does not imply that such privacy lacks value!
Consider, for instance, a specialized HelloAnimationComponent that renders a waving hand beside the name shown by HelloComponent.
This component is not meant—and should not be—used beyond the “template context” of HelloModule, to avoid unwanted dependencies, and therefore ought to remain private.
@NgModule({
declarations: [HelloComponent, HelloAnimationComponent],
imports: [],
exports: [HelloComponent], // <- HelloAnimationComponentis NOT exported, it's private
})
export class HelloModule {}
Built-in modules
CommonModule and RouterModule are built-in modules in Angular that supply its own declarables, enabling their usage in our “template contexts.”
Consequently, these modules are no different from others and operate exactly according to the behavior described earlier.
Consider a practical case with the *ngIf directive, a highly useful tool that toggles portions of a component template based on whether the provided condition evaluates to true or false.
As documented, *ngIf belongs to both the declarations: [ ] and exports: [ ] arrays within CommonModule, which also encapsulates other standard directives and pipes such as *ngFor or | json.
@NgModule({
declarations: [
NgIf, // <- declared in exactly one module
// other declarables... (some potentially private only for internal use
],
exports: [
NgIf,
// other declarables which should be public...
],
})
export class CommonModule {} // just an example, part of Angular itself
Bringing CommonModule into the AppModule means every publicly exposed declarable inside it joins that “template context”, so the AppComponent template can reference them directly.
However, when you inspect a fresh Angular project, no CommonModule appears in the AppModule — yet using directives such as *ngIf or *ngFor still works without any error!? To pin down what’s happening here, we need to examine a new idea called…
Transitive “template context”
So far, we’ve established that placing a module (say HelloModule) inside the target module’s (e.g., AppModule) imports: [ ] array pulls all exported declarations: [ ] into that target module’s “template context”—but what occurs when we export additional modules alongside declarables?
Here’s a streamlined real-world case that captures this exact behavior, one that most Angular applications encounter in practice!
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule], // Angular module which sets up app infrastructure
}) // but also something else !
export class AppModule {}
@NgModule({
declarations: [
/* ... */
],
imports: [],
exports: [
CommonModule, // <!-- important ! we're re-exporting a module
// and other local declarables...
],
})
export class BrowserModule {} // just an example, part of Angular itself
@NgModule({
declarations: [
NgIf, // <- declared in exactly one module
// other declarables... (some potentially private only for internal use
],
exports: [
NgIf,
// other declarables which should be public...
],
})
export class CommonModule {} // just an example, part of Angular itself
By re-exporting a module ( say, the CommonModule ), all of its exported declarables ( for instance, NgIf ) automatically become available in the “template context” of the consuming module (the AppModule ).
Consequently, when gathering every declarable that should be available in the consumer’s “template context”, we have to traverse multiple template contexts — three in our current scenario, though the number can grow larger in real projects.
Lazy loading and bundling
The “template context” concept becomes critical when we consider bundle size optimization and lazy loading in an Angular application.
Let’s revisit our initial setup with AppModule and the HelloModule, and see what happens behind the scenes…
there are TWO distinct “template contexts”
since the
AppModuleserves as the application’s main entry point, it is loaded eagerly, which means it gets placed into the main.js file (the primary eager app bundle)even though the
HelloModulehas its own “template context” ( giving it privacy ), it is included in theimports: [ ]array of theAppModule; as a result, it is also packed into the same eager main.js file
Both “template contexts” reside within the identical eagerly-loaded bundle, yet a hierarchy exists. The AppModule is the “origin template context” of the main.js bundle. All other “template contexts” that land in this same main.js bundle were brought in either directly or transitively, via chains of module references.
Interestingly, the same logic applies to lazy loaded @NgModules! Each lazy loaded module becomes a new “origin template context” for its bundle and gets its own some-lazy-bundle.js file.
For both eager and lazy loaded bundles, multiple “template contexts” can be grouped into a single bundle file
Real world use-cases and best practices
In real-world applications, we wouldn’t typically create a dedicated HelloModule just to establish a separate “template context” within the eager portion of the app. A common approach is to keep AppModule extremely lean, delegating all layout-related declarables (header, navigation, footer, menus, etc.) to a CoreModule.
Those declarables are then exported, enabling their use in the template of the root AppComponent.
As a result, the AppModule (which imports CoreModule) defines the “origin template context” for the eagerly loaded section of the application.
Every lazy feature, in turn, has its own “origin template context”, typically defined by the lazy loaded @NgModule ( for example, SomeLazyFeatureModule ).
Shared declarables used in multiple template contexts
As we’ve observed, a typical application is made up of a relatively small eager “template context” assembled by the AppModule, along with numerous lazy loaded features, each having its own lazy “template context”.
Each lazy feature usually depends on a shared set of base components — things that are helpful across all features, starting with what Angular’s
CommonModuleprovides, and extending to custom buttons, form fields, and other generic widgets.
This creates a need to include those shared components in every lazy “template context”, and the standard solution is to create a SharedModule.
As previously established, any module can specify:
declarations - declarables confined to the local (module’s own) “template context”
imports - modules that bring their exported declarables into the local (module’s own) “template context”
exports - local declarables, plus any other re-exported modules, that get delivered into the consuming module’s “template context”
A prototypical implementation would be SharedModule
@NgModule({
// local template context
declarations: [
// local declarables
MyOrgAnimatedStarComponent, // can use *ngIf (CommonModule is in tpl ctx)
MyOrgRatingComponent, // can use <animated-star> (MyOrgRatingComponent is in tpl ctx)
], // can use <mat-card> (MatCardModule is in tpl ctx)
// CAN'T use <mat-toolbar> (export only)
// brings their exported declarables into local template context
imports: [
CommonModule, // brings *ngIf, *ngFor, ...
MatCardModule, // brings <mat-card>, ...
],
// makes available for consumer template context
export: [
// delivers to consumer "template context"
CommonModule, // consumer can use *ngIf, *ngFor, ...
MatCardModule, // consumer can use <mat-card>, ...
MatToolbarModule, // consumer can use <mat-toolbar>, ...
MyOrgRatingComponent, // consumer can use <my-org-rating>
], // consumer CAN'T use <my-org-animated-start>
// because it's not exported
})
export class SharedModule {} // defines "shared" "template context" (tpl ctx)
With this configuration, the dependency chain reaches three levels from the consumer’s perspective, for instance, when dealing with a lazily loaded UserModule. Suppose this UserModule includes SharedModule in its imports: [ ] array, yet omits CommonModule.
Under these circumstances, a component declared in UserModule (such as UserItemComponent) that relies on the *ngFor directive in its template would be resolved through the following sequence (where CTX denotes “template context”):
CTX 1 |
UserItemComponentreferences*ngForin its template.CTX 1 |
UserItemComponentis listed in thedeclarations: [ ]array ofUserModule, thus it falls under the “template context” of that lazily loadedUserModule.CTX 1 |
UserModuleincludesSharedModulein itsimports: [ ]array.CTX 2 |
SharedModuleplacesCommonModulein itsexports: [ ]array.CTX 3 |
CommonModuleexposesNgForthrough itsexports: [ ]array.CTX 3 |
CommonModulealso hasNgForin itsdeclarations: [ ]array, meaningNgForis owned byCommonModule(it can only be declared in one module) and is accessible solely by importingCommonModule.
Thus, in this scenario, we navigate through 3 distinct “template contexts” when assembling the template context for the lazily loaded UserModule and figuring out which directives are available in UserItemComponent’s template.
Stand-alone components approach
Now let’s run through the identical example, but with Angular STACs substituting for @NgModules.
@Component({
standalone: true, // <-mark component as standalone
selector: 'my-org-root',
template: ` <my-org-hello [name]="name"></my-org-hello> `,
})
export class AppComponent {
name = 'Tomas Trajan';
}
With a standalone
AppComponent, theAppModulebecomes unnecessary, and bootstrapping happens straight throughboostrapApplication(AppComponent)instead — no module in sight!
This approach encounters the same kind of failure as before, with an error resembling those earlier ones around my-org-hello not being in the same “template context” ( except here the problem lies with the component imports rather than the module ).
@Component({
standalone: true,
selector: 'my-org-root',
imports: [HelloComponent], // <-import another STAC
template: ` <my-org-hello [name]="name"></my-org-hello> `,
})
export class AppComponent {
name = 'Tomas Trajan';
}
The solution involved bringing in the missing HelloComponent (now a standalone component) and including it in the imports: [ ] array of the parent standalone component, which takes on the role of handling its own “template context” since no @NgModuless are present.
Evidently, NgModules and Stand-alone components represent two paths to the same outcome—both carry the identical duty of defining and overseeing the “template context” for the components they encompass!
That’s precisely why the earlier claim was so bold: the “template context” ranks as “The Most Important Thing You Need To Understand About Angular,” since it captures the very essence of Angular’s functioning, and this core principle is set to remain unchanged for the long term!
STACs are similar to SCAMs
Take a look at the following example…
// SCAM
@Component({
/* ... */
})
export class HelloComponent {}
@NgModule({
declarations: [HelloComponent], // always only one declarable, the component
exports: [HelloComponent], // always only one declarable, the component
imports: [
CommonModule,
// other standard modules...
// other SCAMs...
],
})
export class HelloModule {}
// is the same as
// STAC
@Component({
standalone: true,
imports: [
// other STACs...
// other standard modules...
// other SCAMs...
],
// with STACs we CAN'T specify exports: [] which fits the use case
// because we should only export the component itself
// and the component is exported implicitly (less verbose)
})
export class HelloComponent {}
Let’s take this basic case a step further and incorporate the *ngIf directive, which will conditionally display the hello message depending on user actions.
@Component({
standalone: true,
selector: 'my-org-root',
imports: [HelloComponent], // <-import another STAC
template: ` <my-org-hello [name]="name" *ngIf="showHello"></my-org-hello> `,
})
export class AppComponent {
name = 'Tomas Trajan';
showHello = true;
}
Once more, we’d hit an error, since we’re referencing a directive (a declarable) that doesn’t belong to our “template context”.
NG8103: The `*ngIf` directive was used in the template,
but neither the `NgIf` directive nor the `CommonModule` was imported.
Please make sure that either the `NgIf` directive or the `CommonModule`
is included in the `@Component.imports` array of this component
Because the parent @NgModule no longer exists, you must import CommonModule directly in the STAC component’s own imports: [ ] array.
@Component({
standalone: true,
selector: 'my-org-root',
imports: [HelloComponent, CommonModule], // <-import CommonModule
template: ` <my-org-hello [name]="name" *ngIf="showHello"></my-org-hello> `,
})
export class AppComponent {
name = 'Tomas Trajan';
showHello = true;
}
As one might anticipate, the STAC maintains its own “template context”, and when the CommonModule is brought into that same context, the *ngIf directive becomes available within it—allowing the template to leverage it without any extra effort.
Interestingly, Angular does more than just re-export the NgIf directive via CommonModule; it additionally offers this directive as a standalone entity ( SAD ?! 🤔😅 ), ready for direct use. Thanks to that, we’ll be in a position to place it straight into the imports: [ ] array of the parent STAC component.
@Component({
standalone: true,
selector: 'my-org-root',
imports: [HelloComponent, NgIf], // <-import NgIf (STAC)
template: ` <my-org-hello [name]="name" *ngIf="showHello"></my-org-hello> `,
})
export class AppComponent {
name = 'Tomas Trajan';
showHello = true;
}
Both approaches function identically; the sole distinction is that opting for CommonModule rather than NgIf results in a marginally larger bundle, since CommonModule includes implementations of additional common directives and pipes, such as *ngIf or | keyvalue.
Conversely, the majority of non-trivial applications will likely rely on most of these anyway, making this our first official encounter that reveals the trade-off between:
developer experience, which eliminates the necessity of manually curating an extensive, fully granular list of dependencies by leveraging entities like
CommonModulethat bundle them togetherminimal bundle size, which requires handling a long, fully granular dependency list—demanding more developer effort but yielding the smallest possible bundle
The STACs do NOT support a multi-level transitive approach to “template context” when used in isolation, as they lack the ability to define an
exports: [ ]array; consequently, each STAC must entirely outline its own template context*
- (*) of course, STACs and the
@NgModuleapproach can be seamlessly combined, and introducing@NgModules into the equation would restore multi-level transitivity capabilities
That was quite an intense journey! My guess is you were already acquainted with many facets of the template context, but I trust you discovered at least one or two novel insights nonetheless! 😅
Useful questions
Let’s examine several helpful questions to consider while developing your application!
is the current “template context” eager or lazy?
what is accessible in my present context? (do I possess everything required, or am I overloaded? (perf))
is a declarable accessible directly or in a transitive manner?
is a declarable introduced once or through several simultaneous imports? (this influences attempts to remove it from context when unneeded)
what will become inaccessible if I remove a specific import?
which declarables are utilized across multiple (or many) lazy-loaded “template contexts”? (ideal candidates for
SharedModule)do I aim to keep my “template contexts” as fine-grained as possible? (opt for STAC) or do I lean towards grouping for better DX? (
SharedModule)
Wrap up
I hope you relished grasping the “template context” mental model, otherwise known as “The Most Important Thing You Need To Understand About Angular” 😉
Drop a comment if you’re interested in extending this discussion to topics like how it applies to Angular libraries and additional real-world scenarios of managing “template context” in large-scale Angular projects!
Kindly support this guide by forwarding it to colleagues who might find these insights valuable🙏.
Feel free to reach out with any queries via article responses or Twitter DMs at @tomastrajan.
And always remember, the future is bright
Sure, that’s the gleaming tomorrow! (📸 via Marc Zimmer )
Liking the look of the code preview? Check out our new theme plugin
Skol - the ultimate IDE theme
Bring the aurora right into your editor. A straightforward yet striking dark theme that is easy on the eyes and looks fantastic.
Combine Angular with artificial intelligence for more robust interfaces
Video Training for Angular and AI
This practical course walks you through embedding AI capabilities in Angular applications, using Hash Brown to craft responsive, smart interfaces.
Covering live streaming chat, tool invocation, generative UI, and structured output, one technique at a time.
Enjoying this content and eager to dive into Angular's cutting-edge Signal Forms?
Angular Signal Forms: A Practical, In-Depth Workshop
Dive into Angular's cutting-edge Signal-Forms across 12 sequenced chapters, combining theory with practical exercises.
Explore form fundamentals, validation logic, custom control creation, nested form structures, migration paths, and much more.
Enjoying the content? Ready to take a deep dive into Angular's modern Signal Forms?
Angular Signal Forms: A Practical Deep Dive
Angular’s Signal-Forms are broken down into 12 sequential chapters, each pairing conceptual lessons with practical exercises.
You’ll cover everything from core form handling and validation rules to building bespoke controls, nesting subforms, and planning your upgrade path.
Stay in the loop
on fresh articles
Subscribe to Angular Experts Content Updates & News, and we will let you know the moment a new post about Angular, Ngrx, RxJs, or other exciting Frontend topics goes live!
Your email stays private with us—you have the option to opt out anytime!
Your feedback & thoughts
Feel free to ask anything or contribute with your own insights and experiences regarding the subject
Tomas Trajan
Google Developer Expert (GDE)
for Angular & Web Technologies
My mission is to guide dev teams toward successful Angular projects by offering training and consulting work centered on Architecture and NgRx-based State management!
An Angular & Web Technologies GDE who splits his time between consulting and teaching Angular. These days he helps international enterprise customers build core features and architectures, adopt best practices, share know-how, and streamline how they work.
Tomas is constantly pushing to deliver real value to both clients and the developer community. That effort shows in his steady output of popular articles, his talks at global conferences and meetups, and his work on open-source projects.
52
Blog posts
4.7M
Blog views
3.5K
Github stars
612
Trained developers
39
Given talks
8
Capacity to eat another cake
You might also like
Check out following blog posts from Angular Experts to learn even more about related topics like Angular !

Top 10 Angular Architecture Mistakes You Really Want To Avoid
In 2024, Angular keeps changing for better with ever increasing pace, but the big picture remains the same which makes architecture know-how timeless and well worth your time!

Tomas Trajan
@tomastrajan
Sep 10, 2024
15 min read

Angular Signal Inputs
Revolutionize Your Angular Components with the brand new Reactive Signal Inputs.

Kevin Kreuzer
@nivekcode
Jan 24, 2024
6 min read

Improving DX with new Angular @Input Value Transform
Embrace the Future: Moving Beyond Getters and Setters! Learn how to leverage the power of custom transformers or the build in booleanAttribute and numberAttribute transformers.

Kevin Kreuzer
@nivekcode
Nov 18, 2023
3 min read
Leverage our vast know-how for your team's success
The Angular Experts team has accumulated years of hands-on experience consulting for both large corporations and early-stage startups, delivering hands-on workshops and tutorials, and maintaining a rich repository of open source projects. We take great pride in our extensive background in modern front-end technology and would be delighted to help your business flourish
