The one difference we can all agree on between developing in Angular or in React is the level of predictability.
Jumping from an Angular project to another should be seamless if it follows the same principles described in the style guide.
And Angular CLI helps this level of predictability even further by giving us a standard and encapsulated way of managing our environments, generating new elements and keeping up to date with the best way of doing all this.
General websites of any level of complexity but with no need for custom webpack configurations are a very good case and account for probably 75%+ of use cases.
So for example: if you want to develop an app with nativecript or electron maybe using angular cli will not help you. For this you can refer to angular-seed-advanced.
This blog post is not a tutorial, it's a collection of bits of information that can be useful to someone looking for ways on how to organize their angular cli applications.
So if you haven't tried out Angular and Angular CLI I suggest you do that first, you don't need to master it, just to have a basic understanding of what you want to achieve with it and some ideas on how to do it.
With Angular's component based architecture our global mixins and variables must be imported into each .sass file individually (same with other css processors)
So that we can do things like:
@import "~bootstrap/scss/variables"
@import "custom" // <-- bootstrap configuration goes here
@import "~bootstrap/scss/mixins"
@include media-breakpoint-down(md)
//conditions for small medium and under screens here
and
@import "our-color-variables.scss"
.some-button
color: $our-picton-blue
You can start realizing the problem here, having import these files everywhere becomes a nuisance.
So we can join them all in a single file file called _all_variables_and_mixins.scss
like so:
// bootstrap
@import "~bootstrap/scss/variables";
@import "custom"; // <-- bootstrap configuration goes here
@import "~bootstrap/scss/mixins";
//our
@import "our-color-variables.scss";
@import "our-button-mixins.scss";
@import "our-animation-mixins.scss";
that we then import pretty much in every .sass file like so:
@import "all-variables-and-mixins"
-
only import variables and mixins into
_all_variables_and_mixins.scss
since these don't increase your CSS bundle, importing css classes on the other hand does and you would start seeing repeated classes. -
to import .scss files into others without resorting to
../../../../..
in the@import
declarations you must first configure global paths for folders where you keep files that you want to make accessible from anywhere in the app structure on.angular-cli.json
like so:
//...
"apps": [
{
//...
"stylePreprocessorOptions": {
"includePaths": [
"assets/styles" // <-- globally accessible files via @import
]
},
//...
}
]
//...
Tools for making sure the build is as small as possible and our pages as fast as possible to compile are built in to Angular CLI, all that is needed is to make use of them.
The command to achieve the smallest possible and fastest to render build is:
ng build --aot --env=prod --prod
--prod
minify hash, and gzip.--env=prod
use your prod environment constants file.--aot
compile angular templates via AOT
And to analyze our build files we can use the flag:
--stats-json
generate stats.json that can then be visualized in webpack visualizer
Encapsulating components with their own modules makes it much easier to understand what dependencies they are using.
ng generate component component-name
Which generates a folder structure like such:
├── home/
│ ├── home.module.ts
│ ├── home.component.ts|html|css|spec.ts
│ └── shared/
│ ├── shared.module.ts
│ ├── bar/
│ │ └── bar.component.ts|html|css|spec.ts
│ ├── footer/
│ │ └── footer.component.ts|html|css|spec.ts
│ ├── top-banner/
│ │ └── top-banner.component.ts|html|css|spec.ts
And importing all the dependencies for all the shared components in home/shared/shared.module.ts
ng generate module component-name
ng generate component component-name
and then manually declare the components as exports in component-name.module.ts like so:
@NgModule({
...
exports: [ComponentNameComponent]
...
})
Which generates a folder structure like such:
├── home/
│ ├── home.module.ts
│ ├── home.component.ts|html|css|spec.ts
│ └── shared/
│ ├── shared.module.ts
│ ├── bar/
│ │ ├── bar.module.ts
│ │ └── bar.component.ts|html|css|spec.ts
│ ├── footer/
│ │ ├── footer.module.ts
│ │ └── footer.component.ts|html|css|spec.ts
│ ├── top-banner/
│ │ ├── top-banner.module.ts
│ │ └── top-banner.component.ts|html|css|spec.ts
This way our components are wrapped in their own module where we can include dependencies only to the component making it easier to understand what components are using what.
Including scripts in the index.html
from third party services is a very common use case that can be achieved by injecting them into src/main.ts
instead like so:
if (environment.production) {
document.write(
`
<!-- Google Analytics -->
<script>
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
ga('create', '${environment.keys.googleanalyticsKey}', 'auto');
ga('send', 'pageview');
</script>
<script async src='https://www.google-analytics.com/analytics.js'></script>
<!-- End Google Analytics -->
`
);
enableProdMode();
}
Note ${environment.keys.googleanalyticsKey}
where we set the key according to the environment.
This last one is maybe more of an opinion that I feel like sharing since most of the examples on how to use angular routing have various approaches and this one is a combination of many of those.
Firstly maybe it's better to have a view of our app structure
├── app.module.ts
├── app.routes.ts
├── app.component.ts|html|css|spec.ts
├── home/
│ ├── home.module.ts
│ ├── home.routes.ts
│ ├── home.component.ts|html|css|spec.ts
│ │ ├─index/
│ │ ├─about/
│ │ ├─team/
│ ...
├── blog/ <-- lazy loaded
│ ...
│ ├── blog.routes.ts
│ ...
├── style-guide/ <-- lazy loaded
│ ...
│ ├── style-guide.routes.ts
│ ...
So for any component, lazy or not, that has it's own set of child page components will have it's own [name].routes.ts with it's respective routes which looks something like:
import { ModuleWithProviders } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HomeComponent } from './home.component';
import { IndexComponent } from './index/index.component';
import { AboutComponent } from './about/about.component';
import { TeamComponent } from './team/team.component';
export const homeRoutes: Routes = [
{
path: '',
component: HomeComponent,
children: [
{
path: '',
component: IndexComponent
},
{
path: 'about',
component: AboutComponent
},
{
path: 'team',
component: TeamComponent
}
]
}
];
export const routing: ModuleWithProviders = RouterModule.forChild(homeRoutes);
and that must be imported in it's component's module like so:
import { routing } from './home.routes';
...
@NgModule({
imports: [
...
routing
],
...
})
Then in app.module.ts
and app.routes.ts
we decide if the component is lazy or not.
So app.routes.ts
:
import { ModuleWithProviders } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
const appRoutes: Routes = [
// { path: '', component: HomeComponent }, <- in the home module
// lazy loaded routes
{ path: 'blog', loadChildren: './blog/blog.module#BlogModule' },
{ path: 'style-guide', loadChildren: './style-guide/style-guide.module#StyleGuideModule' },
// any other route
{ path: '**', redirectTo: '' }
];
export const appRoutingProviders: any[] = [];
export const routing: ModuleWithProviders = RouterModule.forRoot(appRoutes);
And app.module.ts
:
...
import { routing, appRoutingProviders } from './app.routes';
import { HomeModule } from './home/home.module'; // <-- non lazy loaded component
...
@NgModule({
...
imports: [
...
routing,
HomeModule, // <-- non lazy loaded component
...
],
providers: [appRoutingProviders],
...
})
export class AppModule { }