Categories
MongoDB

Using MongoDB with Mongoose — Discriminators and Arrays

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.

Embedded Discriminators in Arrays

We can add embedded discriminators into arrays.

For example, we can write:

async function run() {
  const { createConnection, Types, Schema } = require('mongoose');
  const db = createConnection('mongodb://localhost:27017/test');
  const eventSchema = new Schema({ message: String },
    { discriminatorKey: 'kind', _id: false });

  const batchSchema = new Schema({ events: [eventSchema] });
  const docArray = batchSchema.path('events');
  const clickedSchema = new Schema({
    element: {
      type: String,
      required: true
    }
  }, { _id: false });
  const Clicked = docArray.discriminator('Clicked', clickedSchema);
  const Purchased = docArray.discriminator('Purchased', new Schema({
    product: {
      type: String,
      required: true
    }
  }, { _id: false }));

  const Batch = db.model('EventBatch', batchSchema);
  const batch = {
    events: [
      { kind: 'Clicked', element: '#foo', message: 'foo' },
      { kind: 'Purchased', product: 'toy', message: 'world' }
    ]
  };
  const doc = await Batch.create(batch);
  console.log(doc.events);
}
run();

We add the events array into the eventSchema .

Then we create the docArray by calling the batchSchema.path method so that we can create the discriminator with the docArray method.

Then we create the discriminators by calling docArray.discriminator method with the name of the model and the schema.

Next, we create the Batch model from the batchSchema so that we can populate the model.

We call Batch.create with the batch object that has an events array property to add the items.

The kind property has the type of object we want to add.

Then doc.events has the event entries, which are:

[
  { kind: 'Clicked', element: '#foo', message: 'foo' },
  { kind: 'Purchased', product: 'toy', message: 'world' }
]

Recursive Embedded Discriminators in Arrays

We can also add embedded discriminators recursively in arrays.

For example, we can write:

async function run() {
  const { createConnection, Types, Schema } = require('mongoose');
  const db = createConnection('mongodb://localhost:27017/test');
  const singleEventSchema = new Schema({ message: String },
    { discriminatorKey: 'kind', _id: false });

  const eventListSchema = new Schema({ events: [singleEventSchema] });

  const subEventSchema = new Schema({
    subEvents: [singleEventSchema]
  }, { _id: false });

  const SubEvent = subEventSchema.path('subEvents').
    discriminator('SubEvent', subEventSchema);
  eventListSchema.path('events').discriminator('SubEvent', subEventSchema);

  const Eventlist = db.model('EventList', eventListSchema);
  const list = {
    events: [
      { kind: 'SubEvent', subEvents: [{ kind: 'SubEvent', subEvents: [], message: 'test1' }], message: 'hello' },
      { kind: 'SubEvent', subEvents: [{ kind: 'SubEvent', subEvents: [{ kind: 'SubEvent', subEvents: [], message: 'test3' }], message: 'test2' }], message: 'world' }
    ]
  };

  const doc = await Eventlist.create(list)
  console.log(doc.events);
  console.log(doc.events[1].subEvents);
  console.log(doc.events[1].subEvents[0].subEvents);
}
run();

We create the singleEventSchema .

Then we use as the schema for the objects in the events array property.

Next, we create the subEventSchema the same way.

Then we create the SubEvent model calling the path and the discriminator methods.

This will create the schema for the subEvents property.

Then we link that to the events property with the:

eventListSchema.path('events').discriminator('SubEvent', subEventSchema);

call.

Now we have can subEvents properties in events array entrys that are recursive.

Next, we create the list object with the events array that has the subEvents property added recursively.

Then we call Eventlist.create to create the items.

The last 2 console logs should get the subevents from the 2nd event entry.

Conclusion

We can add discriminators to arrays and also add them recursively.

Categories
MongoDB

Using MongoDB with Mongoose - Nested Document Discriminators and Plugins

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.

Discriminators and Single Nested Documents

We can define discriminators on single nested documents.

For instance, we can write:

async function run() {
  const { createConnection, Types, Schema } = require('mongoose');
  const db = createConnection('mongodb://localhost:27017/test');
  const shapeSchema = Schema({ name: String }, { discriminatorKey: 'kind' });
  const schema = Schema({ shape: shapeSchema });

schema.path('shape').discriminator('Circle', Schema({ radius: String }));
  schema.path('shape').discriminator('Square', Schema({ side: Number }));

  const Model = db.model('Model', schema);
  const doc = new Model({ shape: { kind: 'Circle', radius: 5 } });
  console.log(doc)
}
run();

We call discriminator on the shape property with:

schema.path('shape').discriminator('Circle', Schema({ radius: String }));
schema.path('shape').discriminator('Square', Schema({ side: Number }));

We call the discriminator method to add the Circle and Square discriminators.

Then we use them by setting the kind property when we create the entry.

Plugins

We can add plugins to schemas.

Plugins are useful for adding reusable logic into multiple schemas.

For example, we can write:

const loadedAtPlugin = (schema, options) => {
  schema.virtual('loadedAt').
    get(function () { return this._loadedAt; }).
    set(function (v) { this._loadedAt = v; });

    schema.post(['find', 'findOne'], function (docs) {
    if (!Array.isArray(docs)) {
      docs = [docs];
    }
    const now = new Date();
    for (const doc of docs) {
      doc.loadedAt = now;
    }
  });
};

async function run() {
  const { createConnection, Types, Schema } = require('mongoose');
  const db = createConnection('mongodb://localhost:27017/test');
  const gameSchema = new Schema({ name: String });
  gameSchema.plugin(loadedAtPlugin);
  const Game = db.model('Game', gameSchema);

  const playerSchema = new Schema({ name: String });
  playerSchema.plugin(loadedAtPlugin);
  const Player = db.model('Player', playerSchema);

  const player = new Player({ name: 'foo' });
  const game = new Game({ name: 'bar' });
  await player.save()
  await game.save()
  const p = await Player.findOne({});
  const g = await Game.findOne({});
  console.log(p.loadedAt);
  console.log(g.loadedAt);
}
run();

We created th loadedAtPlugin to add the virtual loadedAt property to the retrieved objects after we call find or findOne .

We call schema.post in the plugin to listen to the find and findOne events.

Then we loop through all the documents and set the loadedAt property and set that to the current date and time.

In the run function, we add the plugin by calling the plugin method on each schema.

This has to be called before we define the model .

Otherwise, the plugin won’t be added.

Now we should see the timestamp when we access the loadedAt property after calling findOne .

Conclusion

We can add the discriminators to singly nested documents.

Also, we can add plugins to add reusable logic into our models.

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.