8 - Layout Lib and BehaviorSubjects In this section we make a reusable Layout and a BehaviorSubject to share User state
1. Add a new Layout lib and container component
Copy nx generate @nrwl/angular:lib layout
In the past, the cli would ask you to choose the stylesheet language. If this happens, make sure to choose sass. As of Nx 12, this will not happen anymore.
Add a layout container component
Copy nx generate @nrwl/angular:component containers/layout --project=layout
2. Add the MaterialModule and Router Module to the LayoutModule and export the Layout Component out of the module
libs/layout/src/lib/layout.module.ts
Copy import { NgModule } from '@angular/core' ;
import { CommonModule } from '@angular/common' ;
import { LayoutComponent } from './containers/layout/layout.component' ;
import { MaterialModule } from '@demo-app/material' ; // Added
import { RouterModule } from '@angular/router' ; // Added
@ NgModule ({
imports : [CommonModule , MaterialModule , RouterModule] , // Added
declarations : [LayoutComponent] ,
exports : [LayoutComponent]
})
export class LayoutModule {}
3. Add a material tool bar to the layout component
Note the <ng-content></ng-content> to transclude content into the component. Also note the flexLayout attribute.
libs/layout/src/lib/containers/layout/layout.component.html
Copy <mat-toolbar color="primary" fxLayout="row">
<span>Customer Portal</span>
</mat-toolbar>
<ng-content></ng-content>
3. Add a BehaviourSubject to Auth service
A BehaviorSubject is a special observable you can both subscribe to and pass values.
Add a BehaviorSubject to the Auth service.
libs/auth/src/lib/services/auth/auth.service.ts
Copy import { Injectable } from '@angular/core' ;
import { Authenticate , User } from '@demo-app/data-models' ;
import { HttpClient } from '@angular/common/http' ;
import { Observable , BehaviorSubject } from 'rxjs' ;
import { tap } from 'rxjs/operators' ;
@ Injectable ({
providedIn : 'root'
})
export class AuthService {
private userSubject$ = new BehaviorSubject < User >( null );
user$ = this . userSubject$ .asObservable ();
constructor ( private httpClient : HttpClient ) {}
login (authenticate : Authenticate ) : Observable < User > {
return this .httpClient
.post < User >( 'http://localhost:3000/login' , authenticate)
.pipe ( tap ((user : User ) => this . userSubject$ .next (user)));
}
}
4. Add the LayoutComponent logic to select the current logged in user
Re-export the AuthService from the Auth Libs index.ts file.
Copy export * from './lib/auth.module' ;
export { AuthService } from './lib/services/auth/auth.service' ;
Add logic to subscribe to User Subject in the Auth Service
libs/layout/src/lib/containers/layout/layout.component.ts
Copy import { Component , OnInit } from '@angular/core' ;
import { AuthService } from '@demo-app/auth'
import { Observable } from 'rxjs' ;
import { User } from '@demo-app/data-models' ;
@ Component ({
selector : 'demo-app-layout' ,
templateUrl : './layout.component.html' ,
styleUrls : [ './layout.component.scss' ]
})
export class LayoutComponent implements OnInit {
user$ : Observable < User >;
constructor ( private authService : AuthService ) {}
ngOnInit () {
this .user$ = this . authService .user$;
}
}
6. Add the new component to the Customer Portal apps main view
apps/customer-portal/src/app/app.component.html
Copy <demo-app-layout>
<router-outlet></router-outlet>
</demo-app-layout>
7. Add styles to styles.scss and layout.scss
apps/customer-portal/src/styles.scss
Copy @import '~@angular/material/prebuilt-themes/deeppurple-amber.css' ;
body {
margin : 0 ;
}
Add styles to the layout.component.scss
libs/layout/src/lib/containers/layout/layout.component.scss
Copy .right-nav {
margin-left: auto;
}
8. Add the Layout Module to the AppModule
apps/customer-portal/src/app/app.module.ts
Copy import { BrowserModule } from '@angular/platform-browser' ;
import { NgModule } from '@angular/core' ;
import { AppComponent } from './app.component' ;
import { NxModule } from '@nrwl/nx' ;
import { RouterModule } from '@angular/router' ;
import { authRoutes , AuthModule } from '@demo-app/auth' ;
import {BrowserAnimationsModule} from '@angular/platform-browser/animations' ; // Added
import { LayoutModule } from '@demo-app/layout' ;
@ NgModule ({
declarations : [AppComponent] ,
imports : [
BrowserModule ,
BrowserAnimationsModule ,
NxModule .forRoot () ,
RouterModule .forRoot ([{path : 'auth' , children : authRoutes}] , { initialNavigation : 'enabled' }) ,
AuthModule ,
LayoutModule
] ,
providers : [] ,
bootstrap : [AppComponent]
})
export class AppModule {}
9. Add a material tool bar logic
We will add the products link later but for now it will error if we try and click it.
libs/layout/src/lib/containers/layout/layout.component.html
Copy <mat-toolbar color="primary" fxLayout="row">
<span>Customer Portal</span>
<div class="right-nav">
<span *ngIf="user$ | async as user">
<button mat-button>{{(user$ | async)?.username}}</button>
<button mat-button [routerLink]="['/products']" routerLinkActive="router-link-active">Products</button>
<button mat-button>Logout</button>
</span>
<span *ngIf="!(user$ | async)">
<span mat-button [routerLink]="['/auth/login']" routerLinkActive="router-link-active">Login</span>
</span>
</div>
</mat-toolbar>
<ng-content></ng-content>
There might be some extra styles in the app.component.scss like this:
apps/customer-portal/src/app/app.component.scss
Copy /*
* Remove template code below
*/
:host {
display : block ;
font-family : sans-serif ;
min-width : 300 px ;
max-width : 600 px ;
margin : 50 px auto ;
}
.gutter-left {
margin-left : 9 px ;
}
The margin: 50px line in particular will push down the entire app layout, so it's a good idea to just remove everything in this file.
1. Convert Layout component into a pure container component
Add a toolbar presentational component.
Pass user into presentational component via inputs.