How Angular calls CORS enabled Netlify Function

Reading Time: 3 minutes

 177 total views,  1 views today

Introduction

In previous post (article), I created a Netlify function to return menu data in JSON format. In this post, I will show how to deploy the Netlify function to production environment and call the endpoint in the Angular Spanish Menu app from Github Page

show me, don't tell me

Enable CORS in Netlify function

Github page and Netlify have different domain names, we must enable CORS in our Netlify function to ensure that it accepts HTTP request from any domain.

First, we modify netlify.toml to add HTTP header, Access-Control-Allow-Origin to specify allowed origins.

[[headers]]
  # Define which paths this specific [[headers]] block will cover.
  for = "/*"
  [headers.values]
    Access-Control-Allow-Origin = "*"

Deploy Netlify function to production

Second, we will deploy the function to production site such that our Angular app can access it in Github page.

  1. Login Netlify site
  2. Choose the site, navigate to Deploys menu and click Trigger deploy button to deploy the function manually
deploy netlify function to production

Update Angular environment variable

Third, we will update menuUrl variable in environment.prod.ts to point to the function at netlify.app.

export const environment = {
  production: true,
  menuUrl: 'https://<site name>.netlify.app/.netlify/functions/menu',
}

Deploy Angular to Github page

Next, we deploy our Angular app to Github page to verify integration with Netlify function actually works.

  • Install angular-cli-ghpages.
ng add angular-cli-ghpages
  • Add ng deploy configuration in angular.json
"deploy": {
   "builder": "angular-cli-ghpages:deploy",
   "options": {
       "baseHref": "https://railsstudent.github.io/ng-spanish-menu/",
       "name": "<user name>",
       "email": "<user email>",
       "message": "<github commit message>",
       "repo": "https://github.com/railsstudent/ng-spanish-menu.git",
       "noSilent": true
   }
}
  • Simplify deployment of our app by auhoring a new npm script
"github-page-deploy": "ng deploy"

npm run github-page-deploy deploys our app to Github page on our behalf

Finally, we can browse our application to verify integration with the production Netlify function.

amost there

Verify integration

Browse https://railsstudent.github.io/ng-spanish-menu/food

browser angular app in github page

Open Network tab to verify that our application calls menu function successfully. It is made possible because the value of access-control-allow-origin response header is “*”.

result of http response

The result of the HTTP response is a valid JSON object containing our food menu.

Conclusion

We made few configuration changes and our Angular application can load data from the Netlify function.

This is the end of the post and I hope you find this article useful and consider to use serverless function to prototype Angular application.

Good bye!

we made it to the end of the blog post

Resources:

  1. Repo: https://github.com/railsstudent/ng-spanish-menu
  2. Github Page: https://railsstudent.github.io/ng-spanish-menu/food
  3. Access-Control-Allow-Origin Policy – https://answers.netlify.com/t/access-control-allow-origin-policy/1813/6
  4. https://www.netlify.com/blog/2021/01/25/getting-started-with-netlify-functions-for-angular/

Build Angular app with Netlify function

Reading Time: 4 minutes

 518 total views,  4 views today

Introduction

I created a container component, FoodMenuComponent, to display a menu for users to order food and drink.

There are several ways to create the menu data in JSON format:

  1. Hardcoded data in the component
  2. Create static JSON data in assets folder and use a service to retrieve the data for the component
  3. Design a backend API and use a service to call the endpoint to retrieve the data
  4. Add a serverless function to return the menu data to the component

Option 1 makes component bloated because it contains both business logic and data. Business logic and data should belong to service and external data source respectively.

Option 2 is ideal for Angular beginners such that they don’t have to worry about backend design and implementation.

Option 3 requires developers to have knowledge of one or more backend frameworks, for example, Express and NestJS. Because of the additional skills, it demands more development efforts for a simple side project.

After weighing all alernatives, I chose Netlify function because I can leverage backend capability of cloud provider instead of building my own backend server.

Result

This is the end result of FoodMenuComponent

The menu asks two questions and each question has at least one choice for user to choose.

Let me show you how to create a Netlify function to return menu data that FoodMenuComponent consumes and displays on browser.

Install Netlify CLI

Firstly, we have to install Netlify CLI globally.

npm install netlify-cli -g

Create Netlify function Configuration

Secondly, we create netlify.toml that is the configuration file of Netlify. Add the following sections to toml file:

[build]
  publish = "dist/ng-spanish-menu"
  command = "ng build"
  functions = "./src/assets/functions"
[[redirects]]
  from = "/*"
  to = "/index.html"
  status = 200

and the most important line is functions = "./assets/functions".

functions = "./assets/functions" specifies the folder of Netlify functions and all functions should save to it.

Then, start Netlify CLI with Angular in development mode

ntl dev

and open the application at http://localhost:8888

Setting up First Severless Function

Thirdly, we define our first serverless function to return menu data. We can find the function in ./assets/functions/menu.ts.

// menu.ts 

import { Handler } from "@netlify/functions";

const data = {
  "menu": [
    {
      "id": "1",
      "question": "Which appetizer(s) do you wish to order?",
      "choices": [
        {
          "id": "a",
          "name": "Egg salad",
          "description": "Egg salad",
          "currency": "USD",
          "price": 4.99,
          "available": true
        },
        ... omitted for the sake of brevity ...
      ]
    },
    {
      "id": "2",
      "question": "Which dessert(s) do you wish to order?",
      "choices": [
        {
          "id": "a1",
          "name": "Ice cream",
          "description": "Ice cream",
          "currency": "USD",
          "price": 1.99,
          "available": true
        },
        ... omitted for the sake of brevity ...
      ]
    }
  ]
}

const handler: Handler = async () => {
  return {
    statusCode: 200,
    body: JSON.stringify(data),
  };
};

export { handler };

Testing Severless Function

Next, we launch Postman application to test the GET endpoint to verify it is working as expected.

Get Endpoint: http://localhost:8888/.netlify/functions/menu

If the request is successful, the response displays menu data.

Adding endpoint in Angular

Lastly. open environment.ts and add a new environment variable, menuUrl, to it. Next, we will create food service to retrieve menu data through this endpoint.

export const environment = {
  production: false,
  menuUrl: '/.netlify/functions/menu',
}

Creating food service

@Injectable({
  providedIn: 'root',
})
export class FoodService {
  constructor(private http: HttpClient) {}

  getFood(url: string): Observable<MenuItem[] | undefined> {
    return this.http.get<Menu>(url).pipe(
      pluck('menu'),
      catchError((err: Error) => {
        console.error(err)
        return of(undefined)
      }),
      share(),
    )
  }
}

getFood function requests menu data from our serverless function and returns the result as Observable<MenuItem[] | undefined>. When request fails, we return undefined since menu does not exist.

Calling service in Angular component with RxJS

Lastly, FoodMenuComponent can combine RxJS operators and food service to retrieve menu data and assign to Observable<Menu[] | undefined>.

@Component({
  selector: 'app-food-menu',
  templateUrl: './food-menu.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FoodMenuComponent implements OnInit, OnDestroy {
  menuItems$: Observable<MenuItem[] | undefined>

  ngOnInit(): void {
    this.menuItems$ = this.service
       .getFood(environment.menuUrl)
       .pipe(takeUntil(this.unsubscribe$))
  }

  menumItemTrackByFn(index: number, menuItem: MenuItem): string | number {
    return menuItem ? menuItem.id : index
  }

  choiceTrackByFn(index: number, choice: Choice) {
    return choice ? choice.id : index
  }
  ... Omitted for the sake of brevity ....
}

In the template of FoodMenuComponent, async pipe resolves menuItems$ into MenuItem array (menuItems) and Angular provides ngFor directive that can iterate an array to render values in each row.

<div class="container" *ngIf="menuItems$ | async as menuItems; else notAvailable">
  <app-food-menu-card *ngFor="let menuItem of menuItems; index as i; trackBy: menumItemTrackByFn">
    <app-food-question [question]="menuItem.question" head></app-food-question>
    <ng-container *ngFor="let choice of menuItem.choices; index as j; trackBy: choiceTrackByFn" body>
      <app-food-choice [choice]="choice" (foodChoiceAdded)="handleFoodChoice.emit($event)"></app-food-choice>
    </ng-container>
  </app-food-menu-card>
</div>
<ng-template #notAvailable>No menu</ng-template>

The container div iterates menuItems and passes each menuItem to <app-food-menu-card> element. Each menuItem has one question and each question displays some options for user to select. <app-food-question> is responsible for rendering menu item question and <app-food-choice> renders all available and unavailable options of the question.

We have reached the end of the post and FoodMenuComponent is working. You should see the same component that I showed at the beginning of the post.

We are finally at the end of the post! I hope you find this article useful and leverage serverless function in your Angular . Good bye!

Resources:

  1. Repo: https://github.com/railsstudent/ng-spanish-menu
  2. https://www.netlify.com/blog/2021/01/25/getting-started-with-netlify-functions-for-angular/