Giter Club home page Giter Club logo

angular-complete-guide-routing's Introduction

Section 11 - Changing Pages With Routing

This is one of several repos that I created for the course "Angular - The Complete Guide (2022 Edition)". Click here for a complete list of repos created for this course.

Chapters

124. Module Introduction 

125. Why do we need a Router? 

126. Understanding the Example Project 

In our app, we got three sections:

    1. Home
    2. Servers
        2.a View and Edit Servers
        2.b A Service is used to load and update Servers
    3. Users
        3.a View Users

This app will be improved by adding routing but definitely feel free to play around with it - besides routing, everything should be working fine.

127. Setting up and Loading Routes 

// 127 defining our routes in app.module.ts (they also need to be registered to the imports array)

    const appRoutes: Routes = [
    // aka localhost:4200
    { 
        path: '',
        component: HomeComponent 
    },
    // aka localhost:4200/users
    { 
        path: 'users',
        component: UsersComponent 
    },
    // aka localhost:4200/users
    { 
        path: 'servers',
        component: ServersComponent 
    }
    ];

// ...also...

  imports: [
    BrowserModule,
    FormsModule,
    RouterModule.forRoot(appRoutes) // 127
  ],

<!-- 127 in app.component.html...added the router-outlet (the area that will load the page/component associated with the route)-->

  <div class="row">
    <div class="col-xs-12 col-sm-10 col-md-8 col-sm-offset-1 col-md-offset-2">
      <router-outlet></router-outlet>
    </div>
  </div>

<!-- 
bad bootstrap css was bugging me 
https://getbootstrap.com/docs/4.0/components/navs/#tabs
-->
    <ul class="nav nav-tabs">
    <li role="presentation" class="nav-item">
        <a href="#" class="nav-link active">Home</a></li>
    <li role="presentation" class="nav-item">
        <a href="#" class="nav-link">Servers</a></li>
    <li role="presentation" class="nav-item">
        <a href="#" class="nav-link">Users</a></li>
    </ul>

128. Navigating with Router Links

<!--
128 router links
-->
    <ul class="nav nav-tabs">
    <li role="presentation" class="nav-item">
        <a 
        routerLink="/" 
        class="nav-link active">Home</a></li>
    <li role="presentation" class="nav-item">
        <a 
        routerLink="/servers" 
        class="nav-link">Servers</a></li>
    <li role="presentation" class="nav-item">

        <!-- 128 router link, property binding with non-string elements -->
        <a 
        [routerLink]="['/users']" 
        class="nav-link">Users</a></li>
    </ul>

129. Understanding Navigation Paths

<!-- 
    129 added link to servers.component.html...
    routerLink attribute does not have the leading slash (produces an error because there is no route "/servers/servers" )...
    <a routerLink="servers">Reload Page</a>
    
    ...adding the slash fixes the problem...
-->
    <a routerLink="/servers">Reload Page</a>

130. Styling Active Router Links

<!-- 
130 added routerLinkActive and (to home page) routerLinkActiveOptions

question: how can I make this conditional...meaning...still apply the 'nav-link'
posted my q over at: https://stackoverflow.com/questions/71859165/how-to-apply-a-css-class-to-a-link-when-its-route-is-not-active
-->
    <ul class="nav nav-tabs">
    <li role="presentation" class="nav-item">
        <a 
        [routerLinkActive]="['nav-link','active']" 
        [routerLinkActiveOptions]="{exact: true}"
        routerLink="/" 
        >Home</a>
    </li>
    <li role="presentation" class="nav-item">
        <a 
        routerLink="/servers" 
        [routerLinkActive]="['nav-link','active']" 
        >Servers</a>
    </li>
    <li role="presentation" class="nav-item">
        <a 
        [routerLink]="['/users']" 
        [routerLinkActive]="['nav-link','active']" 
        >Users</a>
    </li>
    </ul>

131. Navigating Programmatically

// home.component.ts
    export class HomeComponent implements OnInit {

    constructor(
        // 131 added this arg so that we can use it in method onLoadServers()
        private router: Router
    ) { }

    ngOnInit() {
    }

    // 131 added method + router.navigate()
    onLoadServers(){
        this.router.navigate(['/servers']); 
    }

    }


132. Using Relative Paths in Programmatic Navigation

<!-- 132 servers.component.html, added a new button with a click listener that fires method onReload()-->
    <button 
      class="btn btn-primary" 
      (click)="onReload()"
    >Reload Page</button>

// servers.component.ts...

  /* 
  131 servers.component.ts, added arguments (and object properties) router and route (for use in function onReload()).
  * Argument "router" is of type @angular/router/Router and gives us access to the navigate() method.
  * Argument "route" is of type @angular/router/ActivatedRoute and gives a means to pass in a relative path to the 2nd arg of our call to this.router.navigate().
  
  */
  constructor(
    private serversService: ServersService,
    private router: Router,
    private route: ActivatedRoute
  ) { }

  // 132 servers.component.ts, added method that is called by click listener
  onReload(){
    // this.router.navigate(['/servers'],{relativeTo: this.route});
  }

133. Passing Parameters to Routes

// 133 app.module.ts added this route with a dynamic placeholder named "id"
  { 
    path: 'users/:id',
    component: UsersComponent 
  },

134. Fetching Route Parameters

// user.component.ts
    // 134 user.component.ts, injected the activated route here in order to access path data (specifically in this case the user id)
    constructor(
        private route: ActivatedRoute 
    ) { }

    // 134 user.component.ts, using route to get the param value for ID
    ngOnInit() {
        console.log('user.component ngOnInit > "id" is "' + this.route.snapshot.params['id'] + '"');
        this.user = {
        id: this.route.snapshot.params['id'],
        name: this.route.snapshot.params['name']
        }
    }

<!-- 134 user.component.html, added variable output via {{string interpolation}} -->
    <p>User with ID {{user.id}} loaded.</p>
    <p>User name is {{user.name}}.</p>    

Outcome: http://localhost:4200/users/1/Vig displays the id of "1" and the user name of "Vig" as expected.

135. Fetching Route Parameters Reactively

<!-- 135 user.component.html, a router link to user 10 (Anna) -->
    <a
        [routerLink]="['/users',10,'Anna']"
    >
        Load Anna
    </a>

// 135, user.component.ts... 
    ngOnInit() {
        console.log('user.component ngOnInit > "id" is "' + this.route.snapshot.params['id'] + '"');
        this.user = {
        id: this.route.snapshot.params['id'],
        name: this.route.snapshot.params['name']
        }
        // 135, user.component.ts, added the params 'observable' 
        // an observable is a feature added by a 3rd party package that allow you to work with async tasks
        this.route.params.subscribe(
        // the subscribe method here takes three args...
        // arg.1. - a function that will be fired when new data is sent through the observable
        (params: Params) => {
            // this will update the user property when the params change
            this.user.id = params['id'];
            this.user.name = params['name'];
        }
        // arg.2. - tbd
        // arg.3. - tbd
        );
    }

136. An Important Note about Route Observables

// user.component.ts...
    paramsSubscription: Subscription; // 136 added subscription as a property...

    // 136 ...and added a call to unsubscribe in ngOnDestroy
    // note that this happens automatically thx to angular
    // but if you add your own observables you have to unsubscribe on your own
    ngOnDestroy(){
        this.paramsSubscription.unsubscribe();
    }

137. Passing Query Parameters and Fragments

// 137 route added app.module.ts
    {
        path: 'servers/:id/edit',
        component: EditServerComponent
    }

<!-- 
137, servers.component.html, added 
* routerLink 
* queryParams 
* fragment 
-->
    <a
    [routerLink]="['/servers',5,'edit']"
    [queryParams]="{allowEdit:'1'}"
    [fragment]="'loading'"
    href="#"
    class="list-group-item"
    *ngFor="let server of servers">
    {{ server.name }}
    </a>

<!-- 137, home.component.html, changed click event from onLoadServers() to onLoadServer(1) -->
    <button 
        class="btn btn-primary" 
        (click)="onLoadServer(1)"
    >Load Servers</button>

// 137, home.component.ts, added method onLoadServer
onLoadServer(id: number){
    // navigate() function call...
    this.router.navigate(
        // passing in the id...
        ['/servers',id,'edit'],
        // passing in query param 'allowEdit' with a value of '1'...
        {queryParams: {allowEdit: '1'}, 
        // passing in the fragment '#loading'...
        fragment: 'loading'}
    ); 
}

138. Retrieving Query Parameters and Fragments

// edit-server.component.ts...
    constructor(
        private serversService: ServersService,
        // 138 injecting the ActivatedRoute (for use in ngOnInit)
        private route: ActivatedRoute
    ) { }

  ngOnInit() {
    this.route.queryParams.subscribe();
    this.route.fragment.subscribe();
    ...

139. Practicing and some Common Gotchas

<!-- 139, users.component.html, added [routerLink]-->
    <a
        [routerLink]="['/users',user.id, user.name]"
        href="#"
        class="list-group-item"
        *ngFor="let user of users">
        {{ user.name }}
    </a>

<!-- 139, servers.component.html, modified [routerLink]-->
    <a
        [routerLink]="['/servers',server.id]"
        [queryParams]="{allowEdit:'1'}"
        [fragment]="'loading'"
        href="#"
        class="list-group-item"
        *ngFor="let server of servers">
        {{ server.name }}
    </a>

// 139, app.module.ts, added this route
    {path: 'servers/:id',component: ServerComponent},

// 139, server.component.ts, injected ActivatedRoute via the constructor
    constructor(
        private serversService: ServersService,
        private route: ActivatedRoute 
    ){ 

    }

    // ...and in the same file...

    ngOnInit() {
        // 139, set this const equal to the param, and added + which converts params['id'] to a number
        const id = +this.route.snapshot.params['id']; 

        this.server = this.serversService.getServer(1);

        // 139 added this subscribe method...get a new server when the id changes
        this.route.params.subscribe(
            (params: Params) => {
                this.server = this.serversService.getServer(+params['id']);
            }
        );
    }
// 139, user.component.ts, removing the unsubscribe method made things work properly
  ngOnDestroy(){
    // this.paramsSubscription.unsubscribe();
  }

140. Setting up Child (Nested) Routes

Refactored the routes defined in app.module.ts, defining several child routes...

    {path: '',component: HomeComponent}, 
    {path: 'users',component: UsersComponent, children: [
        {path: ':id/:name',component: UserComponent}
    ]},
    {path:'servers',component: ServersComponent, children: [
        {path: ':id',component: ServerComponent},
        {path: ':id/edit',component: EditServerComponent} 
    ]},

Added router-outlet to users.component.html & servers.component.html.
    This adds a new hook which will be used on all child routes of the servers (or users) component
    Allows us to load nested/child routes.
    It's magic.

    <router-outlet></router-outlet>

141. Using Query Parameters - Practice

Added a button to server.component.html w a click listener calling onEdit()...

    <button
        class="btn btn-primary" 
        (click)="onEdit()"
    >Edit Server</button>

In server.component.ts...

    // injecting router for use in onEdit()...
    constructor(
        private serversService: ServersService,
        private route: ActivatedRoute, 
        private router: Router // < this is new
    ){ 
    }

    // method called by cooresponding .html file...
    onEdit(){
        this.router.navigate(['edit'],{relativeTo: this.route}); 
        // ^ navigates to relative path 'edit'
    }   

servers.component.html...

        <!-- 
        141, servers.component.html, added an elvis operator that 
        only sets the allowEdit query param to 1 if the server id is 3 
        -->
        <a
            [routerLink]="['/servers',server.id]"
            [queryParams]="{allowEdit:server.id === 3 ? '1' : '0'}" 
            [fragment]="'loading'"
            href="#"
            class="list-group-item"
            *ngFor="let server of servers">
            {{ server.name }}
        </a>  

edit-server.component.ts...

    we want to be able to retrieve our query params...

    // 141 setting property this.allowEdit based on the value of the query param allowEdit
    this.route.queryParams.subscribe(
        (queryParams: Params) => {
            this.allowEdit = queryParams['allowEdit'] === '1' ? true : false;
        }
    );    

<!-- edit-server.component.html, conditional view/edit display logic added -->

    <h4 *ngIf="!allowEdit">You Shall Not Pass (or edit this Server)</h4>
    <div *ngIf="allowEdit">   
        ...code continues...

doesn't work (doesn't preserve relevant query param(s) when clicking 'Edit Server' button)

    will fix in 142

142. Configuring the Handling of Query Parameters

/* 
142, server.component.ts, added a third element to the 
second argument in navigate() called "queryParamsHandling" 
that...lets us handle query params (naturally lol).  In this 
example we are preserving or maintaining our query params 
when navigating to 'edit'.  Makes sense!
*/ 
    onEdit(){
        this.router.navigate(
            ['edit'],
            {
                relativeTo: this.route, 
                queryParamsHandling: 'preserve'
            }
        ); 
    }

143. Redirecting and Wildcard Routes

ng g c page-not-found

// 143, app-module.ts, added routes
    {path: 'not-found', component: PageNotFoundComponent},
    {path: 'something', redirectTo: '/not-found'}

    192.168.1.77:4200/something/ now redirects to 
    192.168.1.77:4200/not-found/

    ...changed to...
    {path: 'not-found', component: PageNotFoundComponent},
    {path: '**', redirectTo: '/not-found'}
    making sure that the ** redirect is at the end of the routes

    with this now all undefined routes redirect to not-found!

144. Important: Redirection Path Matching

    Important: Redirection Path Matching
    In our example, we didn't encounter any issues when we tried to redirect the user. But that's not always the case when adding redirections.

    By default, Angular matches paths by prefix. That means, that the following route will match both /recipes  and just / 

    { path: '', redirectTo: '/somewhere-else' } 

    Actually, Angular will give you an error here, because that's a common gotcha: This route will now ALWAYS redirect you! Why?

    Since the default matching strategy is "prefix" , Angular checks if the path you entered in the URL does start with the path specified in the route. Of course every path starts with ''  (Important: That's no whitespace, it's simply "nothing").

    To fix this behavior, you need to change the matching strategy to "full" :

    { path: '', redirectTo: '/somewhere-else', pathMatch: 'full' } 

    Now, you only get redirected, if the full path is ''  (so only if you got NO other content in your path in this example).

145. Outsourcing the Route Configuration 

// 145, app.module.ts, moved appRoutes constant to app-routing-module.ts

    /* also, in "imports", 
        (a) removed RouterModule.forRoot(appRoutes)
        (b) added AppRoutingModule 
    */
    imports: [
        BrowserModule,
        FormsModule,
        // RouterModule.forRoot(appRoutes),
        AppRoutingModule
    ],

// created app-routing-module.ts

    import { EditServerComponent } from './servers/edit-server/edit-server.component';
    import { HomeComponent } from './home/home.component';
    import { NgModule } from '@angular/core';
    import { PageNotFoundComponent } from './page-not-found/page-not-found.component'; // 127
    import { ServerComponent } from './servers/server/server.component';
    import { ServersComponent } from './servers/servers.component';
    import { Routes, RouterModule } from '@angular/router';
    import { UserComponent } from './users/user/user.component';
    import { UsersComponent } from './users/users.component';

    const appRoutes: Routes = [
        {path: '',component: HomeComponent}, 
        {path: 'users',component: UsersComponent, children: [
            {path: ':id/:name',component: UserComponent}
        ]},
        {path:'servers',component: ServersComponent, children: [
            {path: ':id',component: ServerComponent},
            {path: ':id/edit',component: EditServerComponent} 
        ]},
        {path: 'not-found', component: PageNotFoundComponent},
        {path: '**', redirectTo: '/not-found'}
    ];

    @NgModule({
        imports: [
            RouterModule.forRoot(appRoutes)
        ],
        exports: [
            RouterModule
        ]
    })

    export class AppRoutingModule {

    }

146. An Introduction to Guards

    code which is executed 
        a. when a route is loaded, or 
        b. when you want to leave a route  

147. Protecting Routes with canActivate

    auth-guard.service.ts 

        created

        extends CanActivate
            this forces you to have a canActivate() method

        canActivate() method

            args
                ActivatedRouteSnapshot
                RouterStateSnapshot

            returns one of
                Observable, Promise or boolean

            logic flow
                return logic flow...
                    1. the result of the isAuthenticated() method
                    2. passes the boolean from #1 to chained method .then() 
                    3. returns true if authenticated, otherwise navigates to '/'

        @Injectable()
            required so that we can inject another service into this service
            in this case we are injecting authService (for use in canActivate())

    auth.service.ts
        created
        loggedIn property
        methods for logging in, logging out 
        method isAuthenticated

    how we implement this fanciness

        app-routing-module.ts, routes array, 'servers'...

            {path:'servers', canActivate:[AuthGuard], component: ServersComponent, children: [
                {path: ':id',component: ServerComponent},
                {path: ':id/edit',component: EditServerComponent} 
            ]},

            in this example the guard(s) are applied to child routes as well as the parent route

    works great!

148. Protecting Child (Nested) Routes with canActivateChild

    auth-guard-service...

        added method canActivateChild() 

        canActivateChild
            (
                route: ActivatedRouteSnapshot,
                state: RouterStateSnapshot
            ):
            Observable<boolean> | Promise<boolean> | boolean {
                return this.canActivate(route, state);
            }

        this method fires for all routes that are children of the specified route(s) 
            (specified in this case in app-routing-module.ts)

    modified the 'servers' route in app-routing-module.ts...

        ...
        {
            path:'servers', 
            //  we no longer need this canActivate:[AuthGuard], 
            canActivateChild:[AuthGuard], // this was added
            component: ServersComponent, 
            children: 
                [
                    {path: ':id',component: ServerComponent},
                    {path: ':id/edit',component: EditServerComponent} 
                ]
        },
        ...      

149. Using a Fake Auth Service

    home.component.html gets two buttons...
    <button class="btn btn-default" (click)="onLogin()">Login</button>
    <button class="btn btn-default" (click)="onLogout()">Logout</button>

    ...and added the methods to home.component.ts...
    onLogin(){
        console.log('home.component onLogin');
        this.authService.login();
    }
    onLogout(){
        console.log('home.component onLogout');
        this.authService.logout();
    }
    ...and in the constructor...
    constructor(
        private router: Router, 
        private authService: AuthService // << don't forget to import AuthService
    ) { }

150. Controlling Navigation with canDeactivate

    edit-server.component.ts

        new property
            changesSaved = false;

        new property (via injection via the constructor)
            private router: Router 

        new logic added to onUpdateServer
            this.changesSaved = true;
            this.router.navigate(['../'],{relativeTo: this.route});
            can-deactivate.guard.service.ts

    created can-deactivate-guard.service.ts

        import { 
            ActivatedRouteSnapshot, 
            CanDeactivate, 
            RouterStateSnapshot, 
            UrlTree 
        } from "@angular/router";
        import { Observable } from "rxjs";

        export interface CanComponentDeactivate {
            canDeactivate: () => 
                // returns an Observable, a Promise or a boolean
                Observable<boolean> 
                | Promise<boolean> 
                | boolean;
        } 

        // ? why do an interface here (as opposed to just using a variation of the implementation code below) ?

        export 
            class CanDeactivateGuard 
            implements CanDeactivate<CanComponentDeactivate> {

            canDeactivate(
                component: CanComponentDeactivate, 
                currentRoute: ActivatedRouteSnapshot, 
                currentState: RouterStateSnapshot, 
                // returns an Observable, a Promise or a boolean
                nextState?: RouterStateSnapshot): 
                    // returns an Observable, a Promise or a boolean
                    Observable<boolean> 
                    | Promise<boolean> 
                    | boolean
                    {
                        return component.canDeactivate();
                    }
        }   

    app-routing.module.ts

        changed this route
            {
                path: ':id/edit',
                component: EditServerComponent
            } 
        to this
            {
                path: ':id/edit',
                component: EditServerComponent, 
                canDeactivate: [CanDeactivateGuard]
            } 

    app.module.ts

        added CanDeactivateGuard to providers array of @NgModule

    implement the interface in edit-server.component.ts @ 8:07

        canDeactivate(): boolean | Observable<boolean> | Promise<boolean> {
            if(!this.allowEdit){
                return true;
            }
            if(
                (this.serverName !== this.server.name || this.serverStatus !== this.server.status) 
                && !this.changesSaved
            ){
                return confirm('Do you want to discard the changes?');
            }else{
                return true;
            }
        }

    changed a few lines of code in edit-server.component.ts method ngOnInit...

        const id = +this.route.snapshot.params['id']; // << new
        this.server = this.serversService.getServer(id);
        // EXERCISE: subscribe route params to update the id if params change << TODO

150.5 edit-server.component.ts >> ngOnInit EXERCISE: subscribe route params to update the id if params change

    edit-server...

        this.route.params.subscribe(
            (params: Params) => {
            this.server = this.serversService.getServer(+params['id']);
            this.serverName = this.server.name;
            this.serverStatus = this.server.status;
            console.log('edit-server > ngOnInit params.subscribe id ' + params['id']);
            }
        )

151. Passing Static Data to a Route

    ng g c error-page

    changed a route in app-routing-module.ts
        // was...{path: 'not-found', component: PageNotFoundComponent},
        // ...is...
        {
            path: 'not-found', 
            component: ErrorPageComponent, 
            data: 
            {
                message: 'This is a "page not found" message hard-coded into the route at app-routing-module.ts'
            }
        },

        ...subscribe action happening in error-page.component.ts...
            ngOnInit(): void {
                /* we could grab the error message like this...
                this.errorMessage = this.route.snapshot.data['message'];
                ...but if this could possibly change while you are on the page we should subscribe... */
                this.route.data.subscribe(
                    (data: Data) => {
                        this.errorMessage = data['message'];
                        console.log('error-page > ngOnInit data...');
                        console.log(data);
                    }
                );
            }        

        and in error-page.component.html we display the message...
            <p>{{ errorMessage }}</p>

152. Resolving Dynamic Data with the resolve Guard

    added file server-resolver.service.ts

        import { Injectable } from "@angular/core";
        import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from "@angular/router";
        import { Observable } from "rxjs";
        import { ServersService } from "../servers.service";

        // this interface is used by the resolve() method below
        interface Server {
            id: number;
            name: string;
            status: string;
        }

        // when injecting a service into another service, add @Injectable()
        // we are doing this in the constructor() method below
        @Injectable()

        export class ServerResolver 

            // this implements the Resolve interface provided by @angular/router
            // Resolve is a generic type, here it wraps the datatype that we get in the end
            implements Resolve<{

                // this is the type definition of the thing that the resolver will give us 
                id: number, 
                name: string, 
                status: string

            }> {

            // here we inject the ServersService (for use in method resolve() below)
            constructor(private serversService: ServersService) {}

            // the Resolve interface requires that we implement the resolve() method
            resolve(
                route: ActivatedRouteSnapshot, 
                state: RouterStateSnapshot

            // here we are returning either (a) an observable, (b) a promise 
            // or (c) the server (which we added as interface (above))
            ): Observable<Server> 
                | Promise<Server> 
                | Server 
            {

                // here we call serversService.getServer() which will return a server
                return this.serversService.getServer(
                    +route.params['id']
                );
            }
        }

    now that this is created we have to add it...

        in app.module.ts
            added 'ServerResolver' to providers array
                providers: [ServersService, AuthService, AuthGuard, CanDeactivateGuard, ServerResolver],

    app-routing.module.ts
        added resolve to children array of servers tab routes
            // Servers tab
            {
                path:'servers', 
                canActivateChild:[AuthGuard], 
                component: ServersComponent, 
                children: 
                    [
                        // 152 added resolve...
                        {
                            path: ':id',
                            component: ServerComponent, 
                            resolve: {
                                server: ServerResolver // 'server' property name is up to us
                            }
                        },
                    ]
            },

    server.component.ts

        ngOnInit() {
            /* 152, commented out...
            const id = +this.route.snapshot.params['id']; 
            console.log('server > ngOnInit() > id ' + id);
            this.server = this.serversService.getServer(1);
            this.route.params.subscribe(
                (params: Params) => {
                    this.server = this.serversService.getServer(+params['id']);
                }
            );
            ...and replaced it with...
            */
            this.route.data.subscribe( // 'subscribe' is 'listen for changes'
                (data: Data) => 
                {
                    // 'server' has to match the route > resolve property name 'server' 
                    this.server = data['server']; 
                }
            );
        }

152.5 Took The Summer Off Because, Kids!

153. Understanding Location Strategies

    Notes from the lecture
    * the server has to be configured so that 404s contain index.html
    * enable hash routes

    # added useHash to app-routing.module.ts
    imports: [
        RouterModule.forRoot(appRoutes, {useHash: true})
    ],    

    then I changed it back

154. Wrap Up



Percent Complete

At this end of this section I am __% complete with this course.

More Info

Error When Running npm i

Solution!

With help from https://github.com/spiraldev/ and https://gist.github.com/ted-piotrowski/33d7a23ce9f67231620d8edd825bf89e

# uninstall the things
sudo apt-get remove nodejs
sudo apt-get remove npm
sudo rm /etc/apt/sources.list.d/*

# install nvm
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash

# close and reopen terminal now or...
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"  # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"  # This loads nvm bash_completion

# verify installation
command -v nvm

# made a VM snapshot at this point

# next steps...
# https://dev.to/ms314006/how-to-install-npm-through-nvm-node-version-manager-5gif
# nvm use system ... no system version defined

nvm ls-remote
# revealed latest LTS: v16.14.2   (Latest LTS: Gallium)

nvm install v16.14.2

# check the version of nvm currently in use 
nvm current

# let's install this stuff...
ng i
# getting this...
# npm WARN old lockfile 
# npm WARN old lockfile The package-lock.json file was created with an old version of npm,
# npm WARN old lockfile so supplemental metadata must be fetched from the registry.
# npm WARN old lockfile 
# npm WARN old lockfile This is a one-time fix-up, please be patient...
# npm WARN old lockfile 

ng s
# we are gtg!

angular-complete-guide-routing's People

Contributors

christophervigliotti avatar spiraldev avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.