Categories
Angular

Angular - Routing Basics

Angular is a popular front-end framework made by Google. Like other popular front-end frameworks, it uses a component-based architecture to structure apps.

In this article, we’ll look at how to add routing to our Angular app.

Routing with Angular

We need routing to load components when we go to some URL.

To create an app with routing enabled, we run:

ng new routing-app --routing

The routes will be defined relative to the base path set as the value of the href value of the base tag.

First, we create some components that we want to map our URLs to.

To do that, we run:

ng generate component first
ng generate component second

In app.module.ts , we should have the AppRoutingModule :

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { FirstComponent } from './first/first.component';
import { SecondComponent } from './second/second.component';

@NgModule({
  declarations: [
    AppComponent,
    FirstComponent,
    SecondComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Then in app-routing.module.ts , we should have:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

const routes: Routes = [];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Now we can define the routes.

To do that, we write:

app-routing.module.ts

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { FirstComponent } from './first/first.component';
import { SecondComponent } from './second/second.component';

const routes: Routes = [
  { path: 'first-component', component: FirstComponent },
  { path: 'second-component', component: SecondComponent },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Then in app.coomponent.html , we write:

<nav>
  <ul>
    <li><a routerLink="/first-component" routerLinkActive="active">First
        Component</a></li>
    <li><a routerLink="/second-component" routerLinkActive="active">Second
        Component</a></li>
  </ul>
</nav>
<router-outlet></router-outlet>

The routerLink attribute has the path to the route we want to access when we click the link.

routerLink active sets the variable for setting whether the link is active.

Getting Route Information

To get route data, we can call the this.route.queryParams.subscribe method to get the query string key-value pairs.

For example in, app.component.ts , we can write:

import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'angular-example';
  constructor(
    private route: ActivatedRoute,
  ) { }

  name: string;

  ngOnInit() {
    this.route.queryParams.subscribe(params => {
      this.name = params['name'];
      console.log(this.name)
    });
  }
}

We inject the route dependency and then call the subscribe method in ngOnInit to watch query parameter changes when the component is loaded.

Then we get the query parameters from the params parameter.

When we go to http://localhost:4200/first-component?name=foo , we get the 'foo' from the name query parameter.

Wildcard Routes

We can define wildcard routes using the '**' string:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { FirstComponent } from './first/first.component';
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
import { SecondComponent } from './second/second.component';

const routes: Routes = [
  { path: 'first-component', component: FirstComponent },
  { path: 'second-component', component: SecondComponent },
  { path: '**', component: PageNotFoundComponent },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

So now if we go to any URL other than first-component or second-component , Angular loads the PageNotFoundComponent .

Conclusion

We can add routing with Angular with the routing module.

Categories
Angular

Angular - Route Redirects and Relative Paths

Angular is a popular front-end framework made by Google. Like other popular front-end frameworks, it uses a component-based architecture to structure apps.

In this article, we’ll look at how to add routing to our Angular app.

Setting up Redirects

We can set up redirects within our Angular app with the redirectTo property.

For example, we can write:

app-routing.module.ts

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { FirstComponent } from './first/first.component';
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
import { SecondComponent } from './second/second.component';

const routes: Routes = [
  { path: 'first-component', component: FirstComponent },
  { path: 'second-component', component: SecondComponent },
  { path: '', redirectTo: '/first-component', pathMatch: 'full' },
  { path: '**', component: PageNotFoundComponent },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

If we go to the / path, we go to the /first-component path because of the redirectTo property.

The pathMatch property is set to 'full' so the full path has to be matched before the redirect is done.

Nesting Routes

We can add nested routes by adding the children property.

For example, we can write:

app-routing.module.ts

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { ChildAComponent } from './child-a/child-a.component';
import { ChildBComponent } from './child-b/child-b.component';
import { FirstComponent } from './first/first.component';
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
import { SecondComponent } from './second/second.component';

const routes: Routes = [
  {
    path: 'first-component', component: FirstComponent, children: [
      {
        path: 'child-a',
        component: ChildAComponent,
      },
      {
        path: 'child-b',
        component: ChildBComponent,
      },
    ],
  },
  { path: 'second-component', component: SecondComponent },
  { path: '', redirectTo: '/first-component', pathMatch: 'full' },
  { path: '**', component: PageNotFoundComponent },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

to add the child routes with the children property.

Then in first.component.html , we add:

<p>first works!</p>
<router-outlet></router-outlet>

so that we can see the child routes.

Now when we go to /first-component/child-a , we get:

first works!

child-a works!

And if we go to /first-component/child-b , we get:

first works!

child-b works!

Using Relative Paths

We can define relative paths in our router links.

For example, we can write:

child-a.component.html

<nav>
  <ul>
    <li><a routerLink="../child-b">child b</a></li>
  </ul>
</nav>
<p>child-a works!</p>

Then the link will go to the /first-component/child-b path.

We can also do this programmatically.

For example, we can write:

child-a.component.html

<button (click)='goToChildB()'>child-b</button>
<p>child-a works!</p>

child-a.component.ts

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';

@Component({
  selector: 'app-child-a',
  templateUrl: './child-a.component.html',
  styleUrls: ['./child-a.component.css']
})
export class ChildAComponent implements OnInit {

  constructor(
    private router: Router,
    private route: ActivatedRoute
  ) { }

  ngOnInit() {
  }

  goToChildB() {
    this.router.navigate(['../child-b'], { relativeTo: this.route });
  }

}

We call goToChildB when we click on the child-b button.

In goToChildB , we call this.router.navigate to go to the ../child-b route.

The relativeTo property sets the path that the relative path is relative to.

Conclusion

We can go to a relative path with Angular’s router.

Also, we can set up redirects to go to a path.

Categories
Angular

Angular - Route Guards and Strategy

Angular is a popular front-end framework made by Google. Like other popular front-end frameworks, it uses a component-based architecture to structure apps.

In this article, we’ll look at how to add routing to our Angular app.

Preventing Unauthorized Access

We can access query parameters with Angular.

To do that, we run:

ng generate guard nav

to create our guard.

Then we have:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { FirstComponent } from './first/first.component';
import { NavGuard } from './nav.guard';
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
import { SecondComponent } from './second/second.component';

const routes: Routes = [
  {
    path: 'first-component', component: FirstComponent, canActivate: [NavGuard],

},
  { path: 'second-component', component: SecondComponent },
  { path: '', redirectTo: '/first-component', pathMatch: 'full' },
  { path: '**', component: PageNotFoundComponent },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

to add the guard with the `canActivate` property.

Then in `nav.guard.ts` , we can write something like:

import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot, } from '@angular/router';

@Injectable({
  providedIn: 'root'
})
export class NavGuard implements CanActivate {
  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): boolean {
    return true
  }
}

to add the canActivate method to the guard class.

Implementing CanActivate means the route will only be loaded if canActivate returns true .

Link Parameters Array

The routerLink prop can be set to an array.

This way, we can add URL parameters to the route path.

For example, we can write:

app.routing.module.ts

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { FirstComponent } from './first/first.component';
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
import { SecondComponent } from './second/second.component';

const routes: Routes = [
  { path: 'first-component', component: FirstComponent },
  { path: 'second-component/:id', component: SecondComponent },
  { path: '', redirectTo: '/first-component', pathMatch: 'full' },
  { path: '**', component: PageNotFoundComponent },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

app.componen.html :

<nav>
  <ul>
    <li><a routerLink="/first-component" routerLinkActive="active">First
        Component</a></li>
    <li><a [routerLink]="['/second-component', 1]" routerLinkActive="active">Second
        Component</a></li>
  </ul>
</nav>
<router-outlet></router-outlet>

We added the :id URL parameter to the /second-component path.

And we changed the routerLink to an array in the 2nd link.

Now when we click on the 2nd link, we go to /second-component/1 .

Routing Strategy

We can change the routing strategy to render URLs in different formats.

We should pick one early and stick with it so that the URL formats won’t change.

One strategy is the HashLocationStrategy to add a hash to the URL between the base URL and the path.

To use this strategy, we write:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { FirstComponent } from './first/first.component';
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
import { SecondComponent } from './second/second.component';

const routes: Routes = [
  { path: 'first-component', component: FirstComponent },
  { path: 'second-component/:id', component: SecondComponent },
  { path: '', redirectTo: '/first-component', pathMatch: 'full' },
  { path: '**', component: PageNotFoundComponent },
];

@NgModule({
  imports: [RouterModule.forRoot(routes, { useHash: true })],
exports: [RouterModule]
})
export class AppRoutingModule { }

in app-routing.module.ts .

We added the useHash property and set it to true .

Now our URLs should be something like http://localhost:4200/#/first-component .

If we leave out the useHash option or set it to false , then Angular render paths without the hash sign.

Conclusion

We can add guards to prevent unauthorized access to routes.

Also, we can change routing strategy to what we want.

Categories
MongoDB

Using MongoDB with Mongoose — Dynamic References

To make MongoDB database manipulation easy, we can use the Mongoose NPM package to make working with MongoDB databases easier.

In this article, we’ll look at how to use Mongoose to manipulate our MongoDB database.

Dynamic References via refPath

We can join more than one model with dynamic references and the refPath property.

For example, we can write:

async function run() {
  const { createConnection, Types, Schema } = require('mongoose');
  const db = createConnection('mongodb://localhost:27017/test');
  const commentSchema = new Schema({
    body: { type: String, required: true },
    subject: {
      type: Schema.Types.ObjectId,
      required: true,
      refPath: 'subjectModel'
    },
    subjectModel: {
      type: String,
      required: true,
      enum: ['BlogPost', 'Product']
    }
  });
  const Product = db.model('Product', new Schema({ name: String }));
  const BlogPost = db.model('BlogPost', new Schema({ title: String }));
  const Comment = db.model('Comment', commentSchema);
  const book = await Product.create({ name: 'Mongoose for Dummies' });
  const post = await BlogPost.create({ title: 'MongoDB for Dummies' });
  const commentOnBook = await Comment.create({
    body: 'Great read',
    subject: book._id,
    subjectModel: 'Product'
  });
  await commentOnBook.save();
  const commentOnPost = await Comment.create({
    body: 'Very informative',
    subject: post._id,
    subjectModel: 'BlogPost'
  });
  await commentOnPost.save();
  const comments = await Comment.find().populate('subject').sort({ body: 1 });
  console.log(comments)
}
run();

We have the commentSchema that has the subject and subjectModel properties.

The subject is set to an object ID. We have the refPath property that references the model that it can reference.

The refPath is set to the subjectModel , and the subjectModel references the BlogPost and Product models.

So we can link comments to a Product entry or a Post entry.

To do the linking to the model we want, we set the subject and subjectModel when we create the entry with the create method.

Then we call populate with subject to get the subject field’s data.

Equivalently, we can put the related items into the root schema.

For example, we can write:

async function run() {
  const { createConnection, Types, Schema } = require('mongoose');
  const db = createConnection('mongodb://localhost:27017/test');
  const commentSchema = new Schema({
    body: { type: String, required: true },
    product: {
      type: Schema.Types.ObjectId,
      required: true,
      ref: 'Product'
    },
    blogPost: {
      type: Schema.Types.ObjectId,
      required: true,
      ref: 'BlogPost'
    }
  });
  const Product = db.model('Product', new Schema({ name: String }));
  const BlogPost = db.model('BlogPost', new Schema({ title: String }));
  const Comment = db.model('Comment', commentSchema);
  const book = await Product.create({ name: 'Mongoose for Dummies' });
  const post = await BlogPost.create({ title: 'MongoDB for Dummies' });
  const commentOnBook = await Comment.create({
    body: 'Great read',
    product: book._id,
    blogPost: post._id,
  });
  await commentOnBook.save();
  const comments = await Comment.find()
    .populate('product')
    .populate('blogPost')
    .sort({ body: 1 });
  console.log(comments)
}
run();

Then we set the product and blogPost in the same object.

We rearranged the commentSchema to have the products and blogPost references.

Then we call populate on both fields so that we can get the comments.

Conclusion

We can reference more than one schema within a schema.

Then we can call populate to get all the fields.

Categories
MongoDB

Using MongoDB with Mongoose — Query Limits and Child Refs

To make MongoDB database manipulation easy, we can use the Mongoose NPM package to make working with MongoDB databases easier.

In this article, we’ll look at how to use Mongoose to manipulate our MongoDB database.

limit vs. perDocumentLimit

Populate has a limit option, but it doesn’t support limit on a per-document basis.

For example, we can write:

async function run() {
  const { createConnection, Types, Schema } = require('mongoose');
  const connection = createConnection('mongodb://localhost:27017/test');
  const personSchema = Schema({
    _id: Schema.Types.ObjectId,
    name: String,
    age: Number,
    stories: [{ type: Schema.Types.ObjectId, ref: 'Story' }]
  });
  const storySchema = Schema({
    author: { type: Schema.Types.ObjectId, ref: 'Person' },
    title: String,
    fans: [{ type: Schema.Types.ObjectId, ref: 'Person' }]
  });
  const Story = connection.model('Story', storySchema);
  const Person = connection.model('Person', personSchema);
  const author = new Person({
    _id: new Types.ObjectId(),
    name: 'James Smith',
    age: 50
  });
  await author.save();
  const fan = new Person({
    _id: new Types.ObjectId(),
    name: 'Fan Smith',
    age: 50
  });
  await fan.save();
  const story1 = new Story({
    title: 'Mongoose Story',
    author: author._id,
    fans: [fan._id]
  });
  await story1.save();
  const story = await Story.findOne({ title: 'Mongoose Story' })
    .populate({
      path: 'fans',
      options: { limit: 2 }
    })
    .exec();
  console.log(story.fans)
}
run();

to query up to numDocuments * limit .

Mongoose 5.9.0 or later supports the perDocumentLimit property to add a per-document limit.

For example, we can write:

async function run() {
  const { createConnection, Types, Schema } = require('mongoose');
  const connection = createConnection('mongodb://localhost:27017/test');
  const personSchema = Schema({
    _id: Schema.Types.ObjectId,
    name: String,
    age: Number,
    stories: [{ type: Schema.Types.ObjectId, ref: 'Story' }]
  });
  const storySchema = Schema({
    author: { type: Schema.Types.ObjectId, ref: 'Person' },
    title: String,
    fans: [{ type: Schema.Types.ObjectId, ref: 'Person' }]
  });
  const Story = connection.model('Story', storySchema);
  const Person = connection.model('Person', personSchema);
  const author = new Person({
    _id: new Types.ObjectId(),
    name: 'James Smith',
    age: 50
  });
  await author.save();
  const fan = new Person({
    _id: new Types.ObjectId(),
    name: 'Fan Smith',
    age: 50
  });
  await fan.save();
  const story1 = new Story({
    title: 'Mongoose Story',
    author: author._id,
    fans: [fan._id]
  });
  await story1.save();
  const story = await Story.findOne({ title: 'Mongoose Story' })
    .populate({
      path: 'fans',
      perDocumentLimit: 2
    })
    .exec();
  console.log(story.fans)
}
run();

Refs to Children

If we call push to items to children, then we can get the refs to the child items.

For example, we can write:

async function run() {
  const { createConnection, Types, Schema } = require('mongoose');
  const connection = createConnection('mongodb://localhost:27017/test');
  const personSchema = Schema({
    _id: Schema.Types.ObjectId,
    name: String,
    age: Number,
    stories: [{ type: Schema.Types.ObjectId, ref: 'Story' }]
  });
  const storySchema = Schema({
    author: { type: Schema.Types.ObjectId, ref: 'Person' },
    title: String,
    fans: [{ type: Schema.Types.ObjectId, ref: 'Person' }]
  });
  const Story = connection.model('Story', storySchema);
  const Person = connection.model('Person', personSchema);
  const author = new Person({
    _id: new Types.ObjectId(),
    name: 'James Smith',
    age: 50
  });
  await author.save();
  const fan = new Person({
    _id: new Types.ObjectId(),
    name: 'Fan Smith',
    age: 50
  });
  await fan.save();
  const story1 = new Story({
    title: 'Mongoose Story',
    author: author._id,
  });
  story1.fans.push(fan);
  await story1.save();
  const story = await Story.findOne({ title: 'Mongoose Story' })
    .populate('fans')
    .exec();
  console.log(story.fans)
}
run();

We call push on story.fans to add an entry to the fans array field.

Now when we query the story, we get the fans array with the fan in it.

Conclusion

We can limit how many documents are returned and add entries to array fields so that we can access the refs to child entries.