Building an Angular app with MSAL and RxJs
In this post I describe step-by-step, the design and implementation of an Angular + MSAL + NgRx demo application that authenticates a user with Microsoft Identity in a Single Page Application.
This is a follow-up to my previous post in which I introduced the app at a very high level, so if you haven't seen it yet check it out before reading on!
As a reminder, the code relating to this post is on GitHub.
You can follow along with this article assembling the demo piece by piece, or simply download the whole app and inspect the files as we discuss them below.
Getting started
At the time of writing the demo I wanted to be using the newest version of Angular, which was 20.1.3, so as prerequisites you'll need Node.js v22.15+, NPM v11.5.1+, and Angular CLI v20.1.3+. So at a command prompt or in Windows Terminal, type
ng new OAuth-NgRx-Demo
Just for kicks, I created the project as zoneless. I didn't want to introduce SSR for this demo, and I also chose SCSS for styling.
After the project has been created, change directory to the new project and open the folder in Visual Studio code or some other IDE of your choice. E.g.
cd OAuth-NgRx-Demo
code .
Before we move on, I should point out that I'm using a naming convention in my files that harks back to earlier versions of Angular. In some instances it almost appears like I'm "fighting the framework" by renaming or manipulating the files that the new version of Angular CLI generates. You don't have to follow my convention, and in all likelihood I too will move to the new convention in due course.
With the boiler-plate code generated by Angular CLI, you should be able to start the app with `ng serve` and browse on http://localhost:4200. You should also be able to run the few standard unit tests with `ng test`.
Environment
Angular appears to be deprecating the use of environment files, so it's likely that Angular CLI has not generated an 'environments' folder and files. I still use them, so for for this demo copy the folder from GitHub and place it in the 'src' folder. Follow the instructions here to ensure that file substitution is implemented.
TL;DR; Open angular.json and copy this block into the configurations.development object:
"fileReplacements": [
{
"replace": "/src/environments/environment.ts",
"with": "/src/environments/environment.dev.ts"
}
]
In the 'src/environments' folder we need an 'environment.ts' at minimum, but I'm actually using 'environment.dev.ts' for development.
You need to have the following properties in each environment file (in addition to the standard production boolean):
msalConfig: {
clientId: 'ENTER_CLIENT_ID',
tenantId: 'ENTER_TENANT_ID',
},
apiKey: '',
usePopupAuthentication: false, // Set to true if you want to use popup authentication instead of redirect
microsoftLoginUrl: 'https://login.microsoftonline.com/',
microsoftGraphApiBaseUrl: 'https://graph.microsoft.com/v1.0/',
As noted in my introductory post for this demo, you can set your preference for login/logout style with the usePopupAuthentication flag at this time.
Now open main.ts and add the following before the bootstrap method:
if (environment.production) {
enableProdMode();
}
Done? Now we can really get started!
Services
I must credit the major work behind these services to odelattregh. My work here adds a bit to the functionality, but mainly introduces abstraction and of course, testing.
There are two services needed.
The WindowsService class
The first is a rather basic service to expose the window frame state - is our window in a frame or not? If the app is running in an frame, we do not want to load the app. The reason for this is that MSAL will create hidden iframes during logon and logoff, and running in a frame will interfere with the normal operation of the process.
I don't want to dwell on this service so, let's quickly implement it and forget about it!
Navigate to the `app` folder, and create a new subfolder called `services`. Then navigate into the services folder, and generate a new service (and corresponding 'spec' unit test file:
ng generate service Windows.Service
Replace the code in the service class and the unit test with the code from the GitHub repo.
Note that you'll probably have to import signal from @angular/core for the project to transpile. In fact there will be multiple instances where pasting code will require imports to be updated. If you're using an IDE like Visual Studio Code, in most cases the appropriate import will be detected.
I recommend also that you attempt to re-run the tests to verify that all is well after every change, or added file.
The AuthService class
The second service is crucial and I want to spend a bit more time on it: the authentication service. So generate a new service like so:
ng generate service Auth.Service
If you try to run the unit tests now, chances are, you'll hit an error:
Error: NG0908: In this configuration Angular requires Zone.js
Remember we are running our app as zoneless! So we have to modify the TestBed configuration in auth.service.spec.ts:
beforeEach(() => {
TestBed.configureTestingModule({
providers: [provideZonelessChangeDetection()]
});
service = TestBed.inject(AuthService);
});
Now the tests should pass (even though we've not actually done anything in the service yet).
Before we move on, let's consider what we're trying to achieve.

We're starting at the bottom of the image with the service. At the low level it's what interacts with systems outside the application: databases, APIs etc.
In AuthService, we're interacting with Microsoft's authentication API and Graph API to conduct the login/logout process, obtain token and provide information such as the user's authentication status and information about the user profile (username etc.).
So our service will have public properties and methods such as
- ActiveAccount: Observable<AccountInfo | undefined>
- checkIfAuthenticated(): void
- logIn(): void
- logOut(): void
- getProfile(): Observable<UserProfile>
Note that return types in the services are always Observables or Signals (if not void). This ensures that changes will be automatically updated in the state and thereafter in the components.
We'll return to the details of state management shortly, but for now suffice to say that the service is called only by the effects functions in state management. Components should never directly reference or call the service. The authentication state and user profile information are maintained in the browser 'store' and the component should only interact with these referencing the state facade.
For now, let's move on with the implementation of AuthService.
The Angular MSAL libraries are at the heart of this demo, so now we install NPM packages @azure/msal-angular and @azure/msal-browser.
npm install @azure/msal-angular
or
ng add @azure/msal-angular
Instead of labouring over the contents of the files, overwrite the auth.service.ts and auth.service.spec.ts files with the contents of those files from GitHub.
At this point, the application is still not ready because we've not introduced the MSAL configuration.
In app.config.ts add the following block to the providers array (this is intermediate code, so for now don't copy the content for this file from GitHub):
provideHttpClient(withInterceptorsFromDi(), withFetch()),
{
provide: HTTP_INTERCEPTORS,
useClass: MsalInterceptor,
multi: true
},
MsalGuard,
importProvidersFrom(
MsalModule.forRoot(
MSALInstanceFactory(),
MSALGuardConfigFactory(),
MSALInterceptorConfigFactory()
),
),
This will require a number of imports from @azure/msal-angular and @azure/msal-browser. You'll also have to copy the factory functions from GitHub.
One error will remain in the app.config.ts, and that will be for msalConfig. We define this object in a new file called auth.config.ts.
Copy auth.config.ts file from GitHub into the 'src/app' folder.
Ordinarily there shouldn't be any reason to change this configuration. But there are parameters to change the pre and post redirect urls, provide a logging callback function, and specify the minimum log level applied to MSAL messages (on the browser console).
Return to app.config.ts, where you should now be able to add an import for msalConfig for auth.config.ts.
Finally you'll also have an error of an unknown import userProfile in the auth.service.ts. This is a good time to implement our models and fix this error.
There's nothing special about the files in 'app/models'. The 'status.ts' contains an enum of the states (for our state management), and the 'userProfile.ts' is a custom object for some properties of our user profile, as obtained from Microsoft Graph. Copy the entire 'models' folder from GitHub, and resolve the reference error in auth.service.ts.
AuthService itself has a few items to note. A cursory examination of the code here and elsewhere reveals my preference for constructor dependency injection over declarative injection (which seems to be the recommended preference nowadays).
So my preference will be:
constructor(private myService: SomethingService) {}
instead of
private myService: SomethingService = inject(SomethingService);
The constructor injection does not appear to be possible for the MsalGuardConfiguration, which is exposed as a token by the @azure/msal-angular library, hence the need to call the inject method.
One of the things to be aware of is that MSAL needs to be initialised to work correctly. This precipitated the need to create an internal flag 'isInitialised', and an 'initialise' method. The method actually does more than call the 'initialize' method on the service instance (which returns a promise). It also sets up subscriptions on the msalBroadcastService observables so we are notified of the progress and outcome of the authentication processes.
AuthService unit testing
The unit tests are self-explanatory - with much of the file taken up in creating the test objects and spies to simulate MSAL. Note that only the successful login scenarios are tested.
Ideally more tests could be added to cover failed logIn, logOut and getProfile scenarios.
This should conclude everything we need to do with the services in our demo.
However, you should be able to build, test and run the app at this stage.
The App component
Before moving on, we really should modify our root app component to remove boiler-plate code from Angular CLI that we don't need, and prepare it for our enhancements.
For starters, replace the 'styles.scss' and 'app.scss' files with the content of those files in GitHub.
Then update app.html content with the following (this is intermediate code and is not in the GitHub repository):
<nav>
<div class="left-side">
<h1><a [routerLink]="['/']">🏠</a>{{ title }}</h1>
</div>
<div class="right-side">
</div>
</nav>
<main class="main">
<div class="content">
<div class="left-side">
<!--isIframe is to avoid reload during acquireTokenSilent() because of hidden iframe -->
@if (!isInIframe) {
<router-outlet />
} @else {
<p>You cannot run this app in a frame!</p>
}
</div>
</div>
</main>
Now update the app.spec.ts with the following (again, this is intermediate code and is not in the GitHub repository)
import { ComponentFixture, ComponentFixtureAutoDetect, TestBed } from "@angular/core/testing";
import { App } from "./app";
import { provideZonelessChangeDetection } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { WindowsService } from "./services/windows.service";
describe('AppComponent', () => {
let fixture: ComponentFixture;
let component: App;
beforeEach(async () => {
await TestBed.configureTestingModule({
providers: [
provideZonelessChangeDetection(),
{ provide: ActivatedRoute, useValue: { snapshot: { data: {} } } },
WindowsService,
{ provide: ComponentFixtureAutoDetect, useValue: true },
],
imports: [App]
}).compileComponents();
fixture = TestBed.createComponent(App);
component = fixture.componentInstance;
});
it('should create the app', () => {
expect(component).toBeTruthy();
});
it('should have the correct title', () => {
expect((component as any).title).toBe('OAuth NgRx Demo');
});
});
And the app.ts:
import { RouterOutlet, RouterLink } from '@angular/router';
import { WindowsService } from './services/windows.service';
@Component({
standalone: true,
selector: 'app-root',
providers: [WindowsService],
imports: [RouterOutlet, RouterLink],
templateUrl: './app.html',
styleUrl: './app.scss'
})
export class App implements OnInit {
protected title = 'OAuth NgRx Demo';
protected isInIframe: boolean = false;
constructor(private windowsService: WindowsService) {
effect(() => {
this.isInIframe = this.windowsService.isInIframe();
});
}
ngOnInit(): void {
console.debug('[APP COMPONENT] Initialising');
}
}
We're now again at a state where we should be able to run and browse the application (and indeed execute unit tests). This is how the app should currently look:

Great!
Looking back at the diagram, at the top we have at least one component, and at the bottom we've got (at least) one service. We've laid the foundations for starting on state management.
User state
Now, we start work on the NgRx state management; we need to build up the pieces in the middle: effects, actions, reducers, selectors, and a state facade.
The facade is not strictly necessary, but it does provide a simple abstraction over the actions and selectors, that can be injected into components for use across the application.
Add the following to the project using ng add or npm install
- @ngrx/store
- @ngrx/entity
- @ngrx/effects
- @ngrx/store-devtools
Different things in an application will use different state management objects. In this demo we're only concerned with the state of the authentication and user data - so in 'src/app' we'll create a subfolder path of 'src/app/state/user'.
If we want to develop this app further with calls to one or more custom APIs and manage different types of data we can create additional folders under 'state', using the same pattern to keep things consistent and clear.
For now, we'll create empty files with the following names in the 'user' folder:
- user.state.ts
- user.facade.ts
- user.reducers.ts
- user.actions.ts
- user.selectors.ts
- user.effects.ts
The 'user.state.ts' is an interface of the object(s) whose state we want to manage. We can also define the object's initial state here.
import { AccountInfo } from "@azure/msal-browser";
import { UserProfile } from "../../models/userProfile";
import { Status } from "../../models/status";
export interface UserState {
profile: UserProfile | undefined;
accountInfo: AccountInfo | undefined;
status: Status;
error: string | null;
}
export const initialState: UserState = {
profile: undefined,
accountInfo: undefined,
status: Status.Initialised,
error: null,
};
We'll now take a [very] brief look of what each of the other files do as we implement each piece of state management. (For the full details go to https://ngrx.io/docs and particularly https://ngrx.io/guide/store. This article's already long enough!)
Actions
The user.action.ts defines our user 'actions' or events. You use an 'Action' to define types of events. Actions are defined as functions; here are a few example actions:
export const login = createAction(
'[User] Login'
);
export const loginSuccess = createAction(
'[User] Login Success',
props<{ account: AccountInfo }>()
);
export const loginFailure = createAction(
'[User] Login Failure',
props<{ error: string | null }>()
);
Above, we've defined three events that we want to trigger modifications on user state: the login action (that initiates authentication), loginSuccess action which occurs on a successful authentication (resulting in a valid AccountInfo object), and a loginError action on a failed authentication (with an error message).
There are several other actions to consider in the app, but that's the gist. Now copy the content of user.actions.ts from GitHub into your empty file.
Reducers
Next, we look at reducers. Reducers are pure functions (always produce the same output for a given input). The purpose of a reducer is to take the provided action, the current state, and return a modified state or the same state without any other side effects.
import * as UserActions from './user.actions';
export const userReducers = createReducer(
initialState,
...
// Login reducers
on(UserActions.login, state => ({
...state,
status: Status.Loading,
error: null
})),
on(UserActions.loginSuccess, (state, { account }) => ({
...state,
accountInfo: account,
status: Status.Success,
error: null
})),
on(UserActions.loginFailure, (state, { error }) => ({
...state,
status: Status.Error,
error
})),
...
);
So as a counterpart to our example login actions, our reducers above will change the status property of the UserState object, and on loginSuccess will set the accountInfo. On loginFailure, it sets the error property.
Between actions and reducers we can make it extremely clear what we expect to happen to our UserState on every defined event.
Now, copy the entirety of the user.reducers.ts file from GitHub.
Before we move on, the app needs to register the reducer. Open app.config.ts:
In the providers array, add
provideStore({ userState: userReducers }),
Selectors
Selectors are also pure functions that return data from the state. For example an selector called 'selectDataError' might return the current error message (as an Observable).
export const selectDataError = createSelector(
selectUserState,
(state) => state.error
);
A selector can contain complex logic manipulating the content of state to return a particular shape of data. This is not the case in this demo and for now, simply copy the content of user.selectors.ts from GitHub.
After copying, you'll notice that the file will show an error referencing something called AppState. This is simply an app-level object, that pulls all our different 'state objects' together. So create new file 'app.state.ts' under the 'src/app/state' folder and copy the following into it:
import { UserState } from "./user/user.state";
export interface AppState {
userState: UserState;
}
If/when you want to add new states to the application, they could be added as further properties on this object.
Effects
Effects are probably the most complex piece of the NgRx puzzle because it is where all interactions and side-effects with the 'outside world' take place.
For example, when the component calls (or dispatches) an action, the action might trigger a change of state. An effect of that change might be a call to an external API.
E.g.
@Injectable()
export class UserEffects {
private actions$: Actions = inject(Actions);
private authService: AuthService = inject(AuthService);
...
login$ = createEffect(() => {
return this.actions$.pipe(
ofType(UserActions.login),
mergeMap(() => {
this.authService.logIn();
return this.authService.ActiveAccount$.pipe(
map(account => UserActions.loginSuccess({ account: account! })),
catchError(error => of(UserActions.loginFailure({ error })))
)
}
)
)
});
...
}
Here the effect is listening to an Action stream where the action is of type 'login'. The AuthService is injected into the effects class and is called when the login action is received.
Note how the loginSuccess and loginFailure actions are called by the effect. It's not an issue here, but it's possible in some complex situations for an effect to trigger an action which triggers another effect, cascading multiple changes to different states. Caution must be taken here because in complex scenarios there is a a risk of triggering infinite action-effect loops.
Another thing to note is something of an oddity.
Note how I'm injecting the service and actions stream declaratively instead of via [my preference of] constructor injection.
When it came to the corresponding unit tests, I found that I could not get the action stream to initialise in the effects unit tests before the effect was created, unless I injected the Actions in this way. (The AuthService can still be injected by constructor but I used the same injection mechanism for consistency.) The issue seems to be that the createEffect method is executed by the test runner as soon as the file is parsed - even before the constructor is executed. Some clever defensive coding might mitigate this problem but the additional complexity this adds to the UserEffects class seems to be pointless if it can be solved simply by adjusting the class' injection method.
In any case, for now copy the contents of user.effects.ts from GitHub.
Before moving on from effects, open app.config.ts and add the following to the providers array to register the effects:
provideEffects([UserEffects]),
Facade
As noted above, the facade is simply a convenient abstraction through which a component, or indeed other services can interact with state management without direct knowledge of the details of the implementation or directly invoking the store.
The facade exposes the observables via the store and selectors, and dispatches actions. Then we simply inject the facade wherever we need.
Copy the content of user.facade.ts from GitHub.
State management unit tests
The unit tests for actions, effects, reducers, selectors and facade can all be copied directly from GitHub at this point.
The tests make heavy use of Jasmine Spies for isolation, and mocks to simulate various data and scenarios. Although the unit test code can be a bit dense, it is mostly fairly self-explanatory. There are a few instances where the behaviour deviates from anticipated, as noted above regarding the tests for effects.
One of the things I have not yet been able to figure out is how to assert the content of an observable in the unit tests relating to authentication effects (user.effects.spec.ts). For example in the following we execute the effect by subscribing to the login$ observable:
it('should dispatch loginSuccess after logIn and ActiveAccount$ emits', () => {
const account: AccountInfo = {
homeAccountId: 'abc', environment: '', tenantId: '', username: '',
localAccountId: ''
};
const spySubj = authServiceSpy.ActiveAccount$ as Subject;
authServiceSpy.logIn.and.callFake(() => {
spySubj.next(account);
});
actions$ = of(UserActions.login());
// subscribe to execute effect
effects.login$.subscribe();
expect(authServiceSpy.logIn).toHaveBeenCalledOnceWith();
});
No matter what I try, I am unable to extract and assert a value from the subscription: in fact, the code never actually executes the subscribe callback at all. I feel I'm missing something here; it could be a consequence of using the authServiceSpy and a fake to update the spySubj but as a I say, I'm not sure. If I do figure it out later, I'll add a comment to this post! For now, I've added a 'NEED MORE ATTENTION!!!' in the title of that unit test group.
Nevertheless, the state management unit tests are still a robust way to ensure that the state object is responding to actions (and reducers), and returning the expected data (via selectors), as well as making the expected external calls (via effects). The great thing is that we can do all this and validate our state management before using any of it in an actual component.
Components
We're on the home stretch now. We need to add several components to complete our demo.
ng generate component Home.Component
ng generate component Login.Component
ng generate component LoginFailed.Component
ng generate component Profile.Component
I use this naming structure because I prefer the suffix .component on all my files. Later versions of Angular don't automatically add this. [The downside is that the folder for each component is then also suffixed with .component. So I manually rename my folders and remove the suffix.]
Note also that by default all components in Angular 20+ are generated as 'standalone'. That is, they do not require an NgModule class and must therefore specify all their own dependencies.
Before moving on, we need to establish the relationship between these components in the routes array. Add the following to the app.routes.ts. (This is intermediate code - do not copy from GitHub.)
export const routes: Routes = [
{ path: '', component: HomeComponent},
{ path: 'profile', component: ProfileComponent },
{ path: 'login-failed', component: LoginFailedComponent },
];
So by default we'll load the Home component. If we fail login, we expect (by some redirect) to end up on a path which loads LoginFailed component.
The home component
This component shows a short paragraph which, if the user is not authenticated contains a log in link. When logged in, it shows a welcome message with the user's name. To do this, we simply inject in our state management facade (UserFacade), and expose it's properties in our template with 'AsyncPipe'.
export class HomeComponent implements OnInit {
protected authenticatedAccount$: Observable = of(undefined);
protected isAuthenticated$: Observable = of(false);
constructor(private userFacade: UserFacade) {
...
this.authenticatedAccount$ = userFacade.accountInfo$;
this.isAuthenticated$ = userFacade.accountInfo$.pipe(map(ai => !!ai));
}
...
Then in the template we can use these properties:
@if(isAuthenticated$ | async) {
<p>Welcome back, {{ (authenticatedAccount$ | async)?.name }}! 🎉</p>
} @else {
<p>Welcome to the demo app! Please <a id="loginLink" href="#" (click)="logIn()">log in</a> to continue.</p>
}
So now, copy the contents of home.component.ts, home.component.spec.ts, home.component.scss, and home.component.html files from GitHub into your files.
Open home.component.spec.ts. Notice the extraction of the InitialiseComponent method
async function InitialiseComponent(mockUserFacade: jasmine.SpyObj): Promise<{ fixture: ComponentFixture, component: HomeComponent }> {
await TestBed.configureTestingModule({
providers: [
provideZonelessChangeDetection(),
provideMockStore(),
{ provide: UserFacade, useValue: mockUserFacade },
],
imports: [HomeComponent]
})
.compileComponents();
let fixture = TestBed.createComponent(HomeComponent);
let component = fixture.componentInstance;
await fixture.whenStable();
return { fixture, component };
}
NB: This is a common pattern to note across all component tests. When dealing with Observable properties one should not expect the observables to 'fire' with updates as they do in actual usage.
This is because there is no change detection running during unit tests and it's necessary in several tests to 'arrange' the expected values for the observable, then reset/initialise the TestBed, before the 'act' and 'assert' phases. Extracting the initialisation in a InitialiseComponent method makes this easier when writing several tests.
Login component
This is a non-navigable component, used only to render some content on screen.
In this case, we want it to render some navigation: when we are not logged in, we want a login button, and when we are, we want a logout button and a button to take us to the profile page.
In terms of logic and content the LoginComponent is very similar to HomeComponent, with the added dependency on RouterLink for navigation.
Copy the contents of all four login.component files from GitHub.
Hopefully, at this point it's already becoming clear as to the advantage that state management and the UserFacade makes to the clarity and simplicity of components.
LoginFailed component
There's little implementation in this component - it's just a page for the failed login process to redirect to. We could add a button or link to retry login here, but I'll leave that as an exercise for the reader!
For now, simply copy the contents of all four login-failed.component files from GitHub.
Profile component
This is our only 'secured' page; we have to be logged in before we can get a link to it. On the page we want to render data from Microsoft Graph, which we get from the 'getProfile' method in our AuthService. Once again, we simply inject the UserFacade as we don't want to directly reference the AuthService, or even the Store from our component.
Since this is an API call we should anticipate that it could take some time with network latency. Therefore we also want to show a 'loading' indicator/message, and an error message should the need arise. We expose various properties of the facade as observables and then use them in the template.
@if (profile$ | async; as profile) {
<p><strong>Id: </strong> {{profile?.id}}</p>
<p><strong>First Name: </strong> {{profile?.givenName}}</p>
<p><strong>Last Name: </strong> {{profile?.surname}}</p>
<p><strong>Email: </strong> {{profile?.userPrincipalName}}</p>
} @else if (loading$ | async) {
<p>Loading profile...</p>
} @else if (error$ | async) {
<p>Error getting profile data.</p>
<p>{{(error$ | async)}}</p>
} @else {
<p>No profile data available.😒</p>
}
Other component files
You'll need to copy the contents of the html template file carefully for each component, as well as the scss (styling) files.
Finally, be sure to copy all the component test files over to your project from GitHub.
Now that we've created all our components, we need to complete the integration into the App component.
Update the app.html file, adding in the LoginComponent [html] selector:
<div class="right-side">
<app-login></app-login>
</div>
Open app.ts, and add LoginComponent to the imports array (remember all components are 'standalone') and inject the UserFacade to the constructor. Also add an OnInit event handler to initiate an authentication check, so it looks like:
@Component({
...
imports: [LoginComponent, RouterOutlet, RouterLink],
...
})
export class App implements OnInit {
...
constructor(private userFacade: UserFacade, private windowsService: WindowsService) {
effect(() => {
this.isInIframe = this.windowsService.isInIframe();
});
}
ngOnInit(): void {
console.debug('[APP COMPONENT] Initialising, checking authentication status');
this.userFacade.checkIsAuthenticated();
}
That's all the work on the components done!
The AuthRedirectGuard class
Finally, we want to ensure that the user is authenticated before they can load and view the profile page. To prevent unauthorised access to the page by people hyperlinking directly to the page, we need a guard class.
Although MSAL provides a Guard class already (see MsalGuard in app.config.ts and auth.config.ts), it isn't used for components which aren't directly involved in the login/logout process. So currently in our demo, if we are unauthenticated and navigate to http://localhost:4200/profile we will still see the page, albeit without data.
What we really want, is to redirect to a 'safe' page - in our case, the home page.
So create a new folder under 'src/app' called 'guards', and navigate into the folder. Add a guard as follows:
ng g guard AuthRedirect
When prompted choose the 'canActivate' type of guard.
I adjust the names to authredirect.guard.ts and authredirect.guard.spec.ts (personal preference - you can leave as is).
The generated guard creates a CanActivateFn method, but my preference is to create a full class - which is probably a bit retro-Angular. In any case, replace the content of authredirect.guard.ts with the code from GitHub.
The basis of the guard is to listen to the accountInfo$ observable from UserFacade and convert it into a simple isAuthenticated$$ boolean signal. If we're authenticated then the canActivate method returns true. If not, a subscription on the same observable will trigger a redirect away from the protected page.
To implement the guard, modify the routes in app.routes.ts to specify AuthRedirectGuard on the profile page:
export const routes: Routes = [
{ path: '', component: HomeComponent},
{ path: 'profile', component: ProfileComponent, canActivate: [AuthRedirectGuard] },
{ path: 'login-failed', component: LoginFailedComponent },
];
Before we leave the guard, copy the authredirect.guard.spec.ts contents from GitHub. The tests in there are self-explanatory, but note the use of jasmine.clock() to manage the time interval before a redirect when a user is not authenticated.
Running the demo
That's it! You got through this marathon of a post. Tests should all be running and passing.
You should now be able to follow the steps in my previous post to get your full implementation of the demo running.
Hope this helps!