Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

preface

The purpose of this article is to give you a quick introduction to The Angular8 and Baidu Maps API by implementing a step-by-step travel checklist project. We will gain:

  • Angular8 Basic usage, architecture
  • Use Baidu map API to achieve their own map application
  • Solve the cross-domain problem when calling Baidu map API
  • Basic encapsulation of localStorage for data persistence
  • Use of Ant Design UI, official website

Project introduction

An idea, can have such a program, record their own journey, see and feel. The home page of the project shows the tourist sites and routes that have been visited. The map route is realized by calling Baidu Map API. Of course, there are many such apis available, and people can use them according to their own preferences. Secondly, we can add future tourism planning and budget on the home page, which is convenient for later use. My mainland page mainly shows you have been and will go to the route, you can carry out relevant operations.

Function implementation

  1. The home page of the project shows the tourist sites and routes that have been visited, and the map routes are realized by Baidu API.
  2. Add future travel planning and budgeting forms to the home page
  3. My continent shows you the route you have been and will go to, you can do related operations.

The project address

Develop travel list project based on Angular8 and Baidu Map API

1. Start

  1. Erection of scaffolding

npm install -g @angular/cli 2. Create workspace and initial application ng new my-app 3. Create corresponding directory files based on the above architecture 4. Start the service

cd my-app
ng serve --open
Copy the code

The CLI automatically opens the browser port 4200, and the default page is displayed.

2. Introduce Baidu Map API

The API addresses of different map functions will be provided officially. Here are the addresses used in this project:

<script type="text/javascript" src="Http://api.map.baidu.com/api?v=2.0&ak= ak"></script>
<script type="text/javascript" src="http://api.map.baidu.com/library/CurveLine/1.5/src/CurveLine.min.js"></script>
Copy the code

If you do not have AK, please go to baidu map official website to apply, the steps are also very simple.

  • Baidu Map Example
  • Baidu Map Web service API
  • Baidu Map Javaxcript API

3. About the presents

A complete Angular should include:

  1. The module
  • Angular defines an NgModule, which declares a compilation context for a set of components focused on an application domain, a workflow, or a closely related set of capabilities. Each Angular application has a root module, usually named AppModule. The root module provides a boot mechanism to start the application. An application usually contains many functional modules.
  1. component
  • Every Angular app has at least one component, the root component, that connects the component tree to the DOM on the page. Each component defines a class that contains the application’s data and logic and is associated with an HTML template that defines a view for display in the target environment such as:
import { Component, OnInit } from '@angular/core';
import { LocationService } from '.. /.. /service/list';

@Component({
  selector: 'app-bar'.templateUrl: './index.html'.styleUrls: ['./index.scss']})export class AppBar implements OnInit {
    items;
    constructor(private locationService: LocationService) {
      this.items = this.locationService.getItems();
    }

    ngOnInit(){}}Copy the code
  1. Services and dependency injection
  • You can create service classes for data or logic that is not relevant to a particular view and that you want to share across components. Service classes are defined immediately after the @Injectable() decorator. This decorator provides metadata that allows your service to be injected as a dependency into the client component. Such as:
import { Injectable } from '@angular/core';

@Injectable({
    providedIn: 'root'
  })
export class Storage {}
Copy the code
  1. routing
  • Angular’s Router module provides a service that allows you to define paths to use when navigating between different application states and view hierarchies. As follows:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { HomeComponent } from './home';
import { NewMapComponent } from './newMap';
// Routes cannot start with '/'
const routes: Routes = [
  // The component loaded when matching the empty route
  { path: ' '.component: HomeComponent },
  { path: 'newMap'.component: NewMapComponent },
  // Load component or jump route when no route is matched
  {path: '* *'.redirectTo:'home'},]; @NgModule({imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule {}Copy the code

4. Baidu Map API and cross-domain problem solving

After entering the official website of Baidu Map, we go to the console to create an application. At this time, the corresponding application AK will be generated as follows:

  • In local debugging, the referer can be written as *, but when we use NG HTTP or FETCH to request API interface, cross-domain still occurs, and all kinds of data are collected online, but no effect is achieved. Here we use jquery $.getScript(URL), combined with jSONP callback, Can solve the problem.

  • Install the following jquery:

npm install jquery

  • Solutions are as follows:
  1. Encapsulating HTTP services:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { AK, BASE_URL } from '.. /config';
import * as $ from "jquery";

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

    params(data = {}) {
        letobj = {... data,ak: AK, output: 'json' };
        let paramsStr = '? ';
        for(let v in obj) {
            paramsStr += `${v}=${obj[v]}& `
        };
        return paramsStr.substr(0, paramsStr.length -1);
    }

    get(url, params) {
        return this.http.get(`${BASE_URL}${url}The ${this.params(params)}`)}getCors(url, params) {
        return new Promise((resolve, reject) = > {
            $.getScript(`${BASE_URL}${url}The ${this.params(params)}`.(res, status) = > {
                if(status === 'success') {
                    resolve(status)
                } else{ reject(status) } }); }}})Copy the code
  1. Define jSONP callback and receive data variables:
// home.component.ts
// Globally define the callback function to get the request data
let locationData = null;
window['cb'] = function(data) {
  locationData = data && data.results;
}
Copy the code
  1. Use:
// home.component.ts
async searchLocation(v) {
  return await this.http.getCors('/place/v2/search',
  { region:v, query: v, callback: 'cb' });
}

this.searchLocation(currentData.name).then(res= > {
  console.log(locationData) // The data returned
})
Copy the code

5. Introduce Ant Design

  1. After you run the following command, the ng-Zorro-ANTd initialization is automatically completed, including importing internationalization files, modules, and style files.

Ng add ng-zorro-antd 2

  • The main page is divided into three parts: header (app-bar), content (page routing), bottom (app-footer),
<div class="app-wrap">
  <app-bar></app-bar>
  <main class="main">
    <router-outlet></router-outlet>
  </main>
  <app-footer></app-footer>
</div>
Copy the code
  1. Define header and footer components
// app-bar.component.html
<div class="bar-warp">
  <div class="logo">Tourist map</div>
  <a [routerLink] ="[ '/home' ]" routerLinkActive="active">Home page</a>
  <nz-badge [nzCount] ="items.length" class="badge">
      <a [routerLink] ="[ '/newmap' ]" routerLinkActive="active">My mainland</a>
  </nz-badge>
</div>
//app-bar.component.ts
import { Component, OnInit } from '@angular/core';
import { LocationService } from ".. /.. /services/location/location.service";
@Component({
  selector: 'app-bar'.templateUrl: './app-bar.component.html'.styleUrls: ['./app-bar.component.scss']})export class AppBarComponent implements OnInit {
  items;

  constructor(private location:LocationService) {
    this.items = this.location.getItems();
   }

  ngOnInit(){}}//footer.component.html
<footer class="footer"> @developer: {{name}}</footer>//footer.component.ts
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-footer'.templateUrl: './footer.component.html'.styleUrls: ['./footer.component.scss']})export class FooterComponent implements OnInit {
  name = 'Wings of the Cloud'
  constructor(){}ngOnInit(){}}Copy the code

SCSS look at the source code to learn: Developing a travel checklist project based on the Angular8 and Baidu Map API

6. The page header component uses the LocationService. Let’s look at this service:

import { Injectable } from '@angular/core';
import { HttpClient } from "@angular/common/http";
import { StorageService } from ".. /storage/storage.service";
import { NzMessageService } from 'ng-zorro-antd';
/** ** Access list, add travel list, clear list function **@export
 * @class LocationService* /
@Injectable({
  providedIn: 'root'
})
export class LocationService {
  items = [
    {
      name: 'Beijing'.desc: 'Good Beijing, the scenery is really nice! '.price: '2000'.date: 'Fri Jul 05 2019 09:35:37 GMT+0800 '.hasDone: true.location: {
        lat: 39.910924.lng: 116.413387}}, {name: 'in suzhou'.desc: 'Suzhou is good, still want to go, good! '.price: '2000'.hasDone: true.date: '1562204417083'.location: { 
        lat: 31.303565.lng: 120.592412}}, {name: 'Shanghai'.desc: 'Shanghai is good. I still want to go there. '.price: '2000'.hasDone: true.date: '2018-12-29'.location: { 
        lat: 31.235929.lng: 121.48054}}, {name: 'wuhan'.desc: 'Wuhan is good, still want to go, good! '.price: '2000'.hasDone: false.date: '2018-12-29'.location: { 
        lat: 30.598467.lng: 114.311586}}];constructor(private http:HttpClient,
    private storage:StorageService,
    private message:NzMessageService
    ) { 
      if(storage.get('list')) {
        this.items = storage.get('list')}}getItems() {
      return this.items;
    }

    addToList(data) {
      // console.log(data)
      this.items.push(data);
      this.storage.set('list'.this.items)
      this.message.success('Added successfully');
    }

    clearList() {
      this.items = [];
      return this.items; }}Copy the code
  • The service provides access to the list getItems(), add the travel list (), and clear the list clearList(). @Injectable({providedIn: ‘root’}) was injected into the root component to share the service. Secondly, we use our own encapsulated Storage service for persistent data Storage. Storage service is as follows:
//storage.service.ts
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class StorageService {

  constructor(){}set(key:string,value:any) {
    localStorage.setItem(key,JSON.stringify(value));
  }
  get(key:string) {
    return JSON.parse(localStorage.getItem(key));
  }
  remove(key:string) {
    localStorage.removeItem(key); }}Copy the code

7. Achieved the core functions of Baidu map

// home.component.ts
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { LocationService } from ".. /.. /services/location/location.service";
import { RequestService } from ".. /.. /services/request/request.service";
import { HttpClient } from "@angular/common/http";
import { NzMessageService } from 'ng-zorro-antd';

// Declare variables
declare var BMap: any;
declare var BMapLib: any;

// Globally define the callback function to get the request data
let locationData = null;
window['cb'] = function(data) {
  locationData = data && data.results;
}
@Component({
  selector: 'app-home'.templateUrl: './home.component.html'.styleUrls: ['./home.component.scss']})export class HomeComponent implements OnInit {
  hasDoneList;
  validateForm: FormGroup;
  constructor(private fb: FormBuilder,
    private location:LocationService,
    private request:RequestService,
    private http:HttpClient,
    private message:NzMessageService
    ) {
      this.hasDoneList = this.location.getItems().filter(item= > {
        return item.hasDone && item
      });
      this.validateForm = this.fb.group({
        name: [null, [Validators.required]],
        price: [null, [Validators.required]],
        date: [null, [Validators.required]],
      });
  }

  ngOnInit() {
    
    // Baidu Map API function
    var map = new BMap.Map("baidu-map");          // Create a map instance
    map.centerAndZoom(new BMap.Point(118.454.32.955), 6);  // Create point coordinates
    map.enableScrollWheelZoom();
    let points = [];
    this.location.getItems().forEach(item= > 
      // && As long as && is preceded by true, the result returns the value after && regardless of whether it is followed by true or false
      item.hasDone &&  points.push(new BMap.Point(item.location.lng,item.location.lat))
    )

    var curve = new BMapLib.CurveLine(points, { strokeColor: "blue".strokeWeight: 3.strokeOpacity: 0.5 }); // Create an arc object
    map.addOverlay(curve); // Add to the map
    curve.enableEditing(); // Enable the edit function
  }

  async searchLocation(v) {
    return await this.request.getCors('/place/v2/search',
    {region: v, query: v, callback: 'cb'});
  }

  submitForm(): void {
    for (const i in this.validateForm.controls) {
      this.validateForm.controls[i].markAsDirty();
      this.validateForm.controls[i].updateValueAndValidity();
    }
    let currentData = this.validateForm.value; // reassign
    let date = new Date(currentData.date);
    currentData.date = date.getTime();
    this.searchLocation(currentData.name).then(res= > {
      if(! locationData.length) {this.message.error('Didn't find the city');
        return false
      }
      this.location.addToList({ ... currentData,location: locationData[0].location,
        desc: 'In the plan... '.hasDone: false})});this.validateForm.reset();
    
  }
  resetForm(e: MouseEvent): void {
    e.preventDefault();
    this.validateForm.reset();
    for (const key in this.validateForm.controls) {
      this.validateForm.controls[key].markAsPristine();
      this.validateForm.controls[key].updateValueAndValidity(); }}}Copy the code
<! -- home.component.html -->
<div class="home-warp">
  <section class="content-list">
    <div class="content-item">
      <nz-divider nzText="I've been there" nzOrientation="left"></nz-divider>
      <div class="visit-list">
        <div nz-row>
          <div class="list-button" nz-col nzSpan="6" *ngFor="let item of hasDoneList">
            <button nz-button nz-popover nzType="primary" [nzContent] ="item.desc" nzPlacement="bottom">
              {{item.name}}
            </button>
          </div>
        </div>
      </div>
    </div>
    <div class="content-item">
      <nz-divider nzText="Future planning" nzOrientation="left"></nz-divider>
      <div class="future-list">
        <form nz-form [formGroup] ="validateForm" class="login-form" (ngSubmit) ="submitForm()">
          <nz-form-item>
            <nz-form-label [nzSm] ="6" [nzXs] ="24" nzFor="name" nzRequired>place</nz-form-label>
            <nz-form-control [nzSm] ="14" [nzXs] ="24" nzErrorTip="Please enter the city name">
              <input nz-input id="name" formControlName="name" placeholder="City name" />
            </nz-form-control>
          </nz-form-item>

          <nz-form-item>
            <nz-form-label [nzSm] ="6" [nzXs] ="24" nzFor="price" nzRequired>The budget</nz-form-label>
            <nz-form-control [nzSm] ="14" [nzXs] ="24" nzErrorTip="Please enter your budget">
              <input type="number" nz-input id="price" formControlName="price" placeholder="Budget" />
            </nz-form-control>
          </nz-form-item>
          <nz-form-item>
            <nz-form-label [nzSm] ="6" [nzXs] ="24" nzFor="price" nzRequired>The date of</nz-form-label>
            <nz-form-control [nzSm] ="14" [nzXs] ="24" nzErrorTip="Please fill in the date">
              <nz-date-picker formControlName="date"></nz-date-picker>
            </nz-form-control>
          </nz-form-item>
          <nz-form-item nz-row class="register-area">
            <nz-form-control [nzSpan] ="14" [nzOffset] ="6">
              <button nz-button class="login-form-button" [nzType] ="'primary'" style="margin-right:20px;">submit</button>
              <button nz-button nzType="primary" (click) ="resetForm($event)">reset</button>
            </nz-form-control>
          </nz-form-item>
        </form>
      </div>
    </div>
  </section>
  <section class="map-warp" id="baidu-map"></section>
</div>
Copy the code
  1. In the ngOninit life cycle, initialize the map data, filter out hasDone true data according to the list server we defined earlier, and display it on the map.
  2. In the constructor constructor, initialize the already visited data, filter out hasDone true data, and display it on the page.
  3. Add a travel list using Ant Design’s form implementation
  4. Before submitting the form, we call the API of Baidu Map to generate the longitude and latitude corresponding to the city, and then add them to the list together. The purpose of this is to draw the road map. We need to provide the longitude and latitude data to Baidu API.
  5. Since it involves cross-domain, we need to define the jSONP callback and get the data as follows:
let locationData = null;
window['cb'] = function(data) {
  locationData = data && data.results;
}
Copy the code
  1. The addToList method of locationService adds the data to the manifest and stores it in the storage. If you want to see the full code, check it out on my Github.

My page

  • There is not much difficulty involved in displaying different styles depending on whether hasDone is true or false.
<div class="detail-warp">
  <h1>The new world</h1>
  <div class="detail-list" >
    <div *ngFor="let item of list" style="margin: 10px">
      <nz-card class="position-item" style="width:300px;" 
      [nzExtra] ="new"  [nzTitle] ="item.name"
        [nzExtra] ="extraTemplate">
        <p>{{item.date | date:'yyyy-MM-dd'}}</p>
        <p>{{item.desc}}</p>
        <p class="price">Budget: {{item. Price}}</p>
      </nz-card>
      <ng-template #new >
        <strong *ngIf=! "" item.hasDone" class="card-new">new</strong>
      </ng-template>
    </div>
  </div>
</div>
Copy the code
import { Component, OnInit } from '@angular/core';
import { LocationService } from ".. /.. /services/location/location.service";
@Component({
  selector: 'app-new-map'.templateUrl: './new-map.component.html'.styleUrls: ['./new-map.component.scss']})export class NewMapComponent implements OnInit {
  list;
  
  constructor(private location:LocationService) { 
    this.list = location.getItems();
  }

  ngOnInit(){}}Copy the code

conclusion

  • This project is a practical entry project based on Angular8, involving some advanced skills as well as baidu Map and JSONP cross-domain knowledge.
  • Part to be done: add edit function to my mainland page card, edit whether you have been here and remarks, etc

error

Can't bind to 'formGroup' since it isn't a known property of 'form'.
Copy the code
  • Solution: You need to import ReactiveFormsModule from @Angular/Forms. The FormGroupDirective is part of the ReactiveFormsModule.
  • Import the ReactiveFormsModule module in app.module.ts
  • import { FormsModule, ReactiveFormsModule } from '@angular/forms';
Uncaught TypeError: Cannot read property 'gc' of undefined
Copy the code
  • Cause of the problem: Baidu map, error, the page lacks rendering map div elements
  • Solution: The page initializes a div with a bound ID value
ERROR in src/app/home/index.ts(38.21): error TS2552: Cannot find name 'BMap'. Did you mean 'map'? 
src/app/home/index.ts(40.29): error TS2552: Cannot find name 'BMap'. Did you mean 'map'? 
src/app/home/index.ts(44.51): error TS2552: Cannot find name 'BMap'. Did you mean 'map'? 
src/app/home/index.ts(47.23): error TS2304: Cannot find name 'BMapLib'.
Copy the code
  • Cause: map, BMapLib variables are not defined.
  • Declare variables before use.
declare var BMap: any;
declare var BMapLib: any;
Copy the code