Angular Code Snippets - aakash14goplani/FullStack GitHub Wiki
- Subject vs BehaviorSubject
- URL TREE
- forChild() vs forRoot()
ng-template
vsng-content
vsng-container
and*ngtemplateoutlet
- Observables vs Promise
- Hot vs Cold Observables
- Single Import vs Multi Import
- How to modify HTML DOM using Angular - YouTube, Same YouTube content but written in Article
-
We have a user object with four properties:
user = { public email: string, public id: string, private token: string, private tokenExpirationDate: Date }
-
If we want to take note of changes in user object as a whole i.e. new user added or existing user deleted, we could set up a subscription to the user
-
If you don't care about every change to the user, and you're only interested with one property of user and don't want to have an ongoing subscription, just want to capture if that property changed, we could set up a
BehaviorSubject
-
The difference is that
BehaviorSubject
also gives subscribers immediate access to the previously emitted value even if they haven't subscribed at the point of time that value was emitted. explanation with example -
Example we can get access to be currently active user even if we only subscribe after that user has been emitted. We can fetch data at this point of time, even if the user logged in before that point of time which will have been the case, we get access to that latest user.
user = new Subject<User>(); user = new BehaviorSubject<User>(null); ... this.user.next(user);
-
Now the thing just is I don't want to set up an ongoing subscription and still I need to subscribe to get that user, so to make sure I only get the user once and thereafter I'm done, of course we could manually immediately call
unsubscribe()
thereafter or you use pipe and then a special operatorRxJS
gives you and that would be thetake()
operator. -
take()
is called as a function and you simply pass a number to it and I pass1
here and what this tellsRxJS
is that I only want to take one value from that observable and thereafter, it should automatically unsubscribe. So this manages the subscription for me.return this.authService.user.pipe( take(1), .... );
-
We can use HTTP request observable and set it up in there and attach our token which we get out of the user. The problem just is here, we're in subscribe of an observable and here, we create yet another observable. In the end, I want to return this observable here but returning from inside subscribe doesn't work, we have to return on the top level of this method.
-
The solution is that we pipe these two observables, the user observable and the HTTP observable together into one big observable. You simply add another operator here to pipe for the user observable and there, we can use
exhaustMap()
. -
exhaustMap()
waits for the first observable, for the user observable to complete which will happen after we took the latest user. Thereafter, it gives us that user, so inexhaustMap()
we pass in a function, there we get the data from that previous observable and now we return a new observable in there which will then replace our previous observable in that entire observable chain.return this.authService.user.pipe( take(1), exhaustMap(user => { return this.http.get<Recipe[]>( 'https://angular-practice-166c4.firebaseio.com/posts.json', { params: new HttpParams().set('auth', user.token) } ); }), map(finalObservable => { ... }) );
-
So we start of with a user observable and once we're done with that, this will be replaced in that observable chain with the inner observable we return inside of that function we pass to
exhaustMap()
. So in there, I will return this HTTP request where I get my recipes. This is now returned inside of exhaust map and therefore this entire observable chain now switches to this HTTP observable.
-
What we should make sure though is that user is not able to access feature pages or particular URL e.g.
/recipes
, if they are not authenticated. In the past with older Angular versions, you had to manually redirect, if not, navigate him awaycanActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | UrlTree | Observable<boolean | UrlTree> | Promise<boolean | UrlTree> { return this.authService.user.pipe( tap(isAuth => { if (!isAuth) { this.router.navigate(['/auth']); } }) ); }
-
Nothing wrong with this but in some edge cases, this could lead to race conditions with multiple redirects that kind of interfere with each other and therefore, instead of using this approach, you can now also use a different one and not return true or false here but return URL tree.
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> { return this.authService.user.pipe( map(userData => { const isAuth = !!userData; if (isAuth) { return true; } return this.router.createUrlTree(['/auth']); }) ); }
-
The one issue we have here is that we essentially set up an ongoing subscription here though. This user subject of course can emit data more than once and I don't want that here, this can lead to strange side effects if our guard keeps on listening to that subject, instead we want to look inside the user value for one time only and then not care about it anymore unless we run the guard again and therefore here just as in other places too, we should use take one to make sure that we always just take the latest user value and then unsubscribe for this guard execution so that we don't have an ongoing listener
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> { return this.authService.user.pipe( take(1), map(userData => { const isAuth = !!userData; if (isAuth) { return true; } return this.router.createUrlTree(['/auth']); }) ); }
-
We can also move the feature components related route configuration away from the app (main) routing module. For that, in the router module for that feature component, you could add
forChild()
. -
Now
forRoot()
is only used once essentially, here in the app routing module where we useforRoot()
to configure our root routes. In a feature module which you then plan on importing into your app module, you would useforChild()
and this will automatically merge the child routing configuration with the root routes. -
Feature Component
const appRoute: Routes = [ { path: '', component: ShoppingListComponent }, { path: '**', redirectTo: '/page-not-found' } */ { path: '**', component: PageNotFoundComponent, data: {message: 'Please double check the URL entered'} } ]; @NgModule({ imports: [ RouterModule.forChild(appRoute) ], exports: [ RouterModule ] }) export class ShoppingListRouting {}
-
Feature Module
@NgModule({ declarations: [ ShoppingListComponent, ShoppingEditComponent ], imports: [ SharedModule, FormsModule, ShoppingListRouting ] }) export class ShoppingListModule {}
-
App Routing
const recipesAppRoute: Routes = [ { path: '', redirectTo: '/recipes', pathMatch: 'full' }, // { path: 'recipes', loadChildren: './recipes/recipes.module#RecipesModule' } { path: 'auth', loadChildren: () => import('./auth/auth.module').then((mod) => mod.AuthModule) }, { path: 'recipes', loadChildren: () => import('./recipes/recipes.module').then((mod) => mod.RecipesModule) }, { path: 'shopping-list', loadChildren: () => import('./shopping-list/shopping-list.module').then((mod) => mod.ShoppingListModule) }, { path: '**', component: PageNotFoundComponent, data: {message: 'Please double check the URL entered'} } ]; @NgModule({ imports: [ RouterModule.forRoot(recipesAppRoute, { preloadingStrategy: PreloadAllModules }) ], exports: [ RouterModule ] }) export class AppRoutingModule {}
-
App Module
imports: [ BrowserModule, FormsModule, ReactiveFormsModule, HttpClientModule, AppRoutingModule, SharedModule ],
Single Import: import { Ingredients } from '../../shared/ingredients.model';
Multiple Import: import * as ShoppingListActions from './shopping-list.actions';