Some of you might be familiar with the concept of container components
. If not, they are components meant to 'wrap' around your presentation components as a data layer to separate concerns and improve the speed of testing.
There's also a lot of other benefits to this structure such as easier debugging and reasoning about. What I also like about this structure is you can leverage it with NgRx and really squeeze out some efficiencies by switching to OnPush detection strategy
for your presentation components as all their data at this point should be coming in through @Input
or as immutable new objects from the Store
.
Here's a great article by Lars Gyrup Brink Nielsen (@layzeedk) that I helped review on this very subject: Container Components with Angular
In the article, Lars takes you on a well-written journey of refactoring the Tour of Heroes example project.
If you're looking for really great info on advanced Angular topics, I suggest giving this fellow a follow:
[Deleted User]
I can't explain the subject better than Lars, but I thought I would it would be fun to add another practical example from another perspective since his involves refactoring an existing project.
So what if you're starting from new project and no refactoring is needed? This is the way I would quickly generate container components in a brand new project to set the structure off on a good foot.
So let's say I have a feature module called auth.module.ts and I want to generate a component called login.
Generating the Container Component
First I want to generate the container component, and register it to the auth module. Because it is a container component, it's most likely I won't need a separate HTML file, nor will I need styles. Just the *.ts
and *.spec.test
files.
So to do that in one fell swoop, we leverage the Angular CLI
like so:
> ng g c auth/login -t -s -m=auth.module
Let's dissect that a little. Most of you are familiar with the ng
part.
-
g
is the short alias forgenerate
. -
c
is the short alias forcomponent
. - next we specify the path as to where we want the component files generated.
-
-t
is the short alias for--inline-template
; the optional flag that says to skip the HTML file so we can go with inline template (more on that in a sec). -
-s
is the short alias for--inline-style
; the optional flag that says to skip the style file so we can go with inline styles (also more on that in a sec). -
-m
is the short alias for--module
and we assign this component to the auth module
If we wrote that command out without the short aliases it would look like:
> ng generate component auth/login --inline-template --inline-style --module=auth.module
That will produce a file structure like so:
auth\
login\
-login.component.spec.ts
-login.component.ts
A difference between what I have here and the Lars' article, is that the file is still *.component.ts
instead of *.container.ts
. It really doesn't matter as long as you pick a convention and stick with it. Since the presentation component I am about to generate will have UI
in the file name and selector, I think keeping this a *.component.ts
is ok.
Generating the Presentation Component
So when generating the login presentation component, we have two options, generating the component in it's own sub-directory or in the same directory level as the container component. Personally, I like generating them in sub-directories because it will be easier to mentally reason about when looking at the file structure if you have a container component with multiple presentation components. For example, I could refactor the login presentation component to a login-form sub-component, or a forgot-password component, etc.
> ng g c auth/login/login-ui
This results in the following file structure:
auth\
login\
-login.component.spec.ts
-login.component.ts
login-ui\
-login-ui.component.html
-login-ui.component.scss
-login-ui.component.spec.ts
-login-ui.component.ts
In the container component, we write the login-ui component selector in the inline template, then it's wired up, just specify inputs and outputs where needed.
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-login',
template: `<app-login-ui [inputs]="$inputs | async"></app-login-ui>`,
styleUrls: []
})
export class LoginComponent implements OnInit {
inputs$: Observable<boolean>;
constructor(private store: Store) { }
ngOnInit() {
this.inputs$ = this.store.pipe(select(inputs));
}
}
It might seem like extra work, but this pattern really makes things easier in the long run on complex apps.
Let me know what you guys think of the pattern in the comments!
Cheers!
Top comments (4)
Great article thanks, I'll Be looking into using container components from now on. By the way, you can probably use store.select directly, thus making pipe unnecessary?
Hi thanks! You can indeed go directly with store.select, but the benefit of piping in custom selectors and using the
createSelector
fromNgRx Store
is that you get memoized selectors.Memoized selectors cache the data and provide efficient re-rendering if needed, because a call doesn't have to be made all the way back to the resource if the data hasn't changed and another component needs it.
Ok, good to know :) thanks!
I only now discovered your article, Stephen. Thanks for mentioning my work and for helping me write it 😊 Great article. To the point.