【问题标题】:How to call Cordova plugin using Angular without Ionic如何在没有 Ionic 的情况下使用 Angular 调用 Cordova 插件
【发布时间】:2018-11-14 23:20:29
【问题描述】:

我在 Angular 6 中构建了一个 PWA,并打算使用 Google Maps API 进行地理定位。但是,我很快意识到 PWA 中的地理定位服务仅在用户与 UI 交互以故意请求其位置时才起作用。但是,我的应用程序应该在用户开车时在后台跟踪他们的位置。因此,当他们锁定屏幕或转到其他应用程序时,它仍应跟踪他们。作为记录,这是一个私人应用程序,用户完全知道他们正在被跟踪。因此,我使用 Cordova 将 PWA 转换为混合应用程序。到目前为止,一切都适用于我已经拥有的东西(模拟很好,等等),但我似乎无法弄清楚如何添加地理定位部分。我已经安装了this 插件,它似乎已安装并且可用。我见过的所有示例都使用 Ionic(此时我不需要),并且适用于用户单击按钮以获取其位置的场景,但我需要它在地理定位内容位于服务中并启动的情况下他们登录后在后台运行。我似乎找不到显示如何执行此操作的内容。以下是我认为我应该做的事情:

(这不完整,我只是将 GitHub 的示例代码粘贴到此处,并打算在我知道它实际上被调用时用“stuff”填充它)

_services/geolocation.service.ts:

import { Injectable } from '@angular/core';
import { Component, ViewChild } from '@angular/core';

declare var cordova: any;

@Injectable()
export class GeolocationService {
  startBackgroundGeolocation() {
    console.log("Geolocation service called...");
    cordova.plugins.BackgroundGeolocation.configure({
      locationProvider: cordova.plugins.BackgroundGeolocation.ACTIVITY_PROVIDER,
      desiredAccuracy: cordova.plugins.BackgroundGeolocation.HIGH_ACCURACY,
      stationaryRadius: 50,
      distanceFilter: 50,
      notificationTitle: 'Background tracking',
      notificationText: 'enabled',
      debug: true,
      interval: 10000,
      fastestInterval: 5000,
      activitiesInterval: 10000,
      url: 'http://192.168.0.3:3000/location',
      httpHeaders: {
        'X-FOO': 'bar'
      },
      // customize post properties
      postTemplate: {
        lat: '@latitude',
        lon: '@longitude',
        foo: 'bar' // you can also add your own properties
      }
    });

    cordova.plugins.BackgroundGeolocation.on('location', function(location) {
      // handle your locations here
      // to perform long running operation on iOS
      // you need to create background task
      cordova.plugins.BackgroundGeolocation.startTask(function(taskKey) {
        // execute long running task
        // eg. ajax post location
        // IMPORTANT: task has to be ended by endTask
        cordova.plugins.BackgroundGeolocation.endTask(taskKey);
      });
    });

    cordova.plugins.BackgroundGeolocation.on('stationary', function(stationaryLocation) {
      // handle stationary locations here
    });

    cordova.plugins.BackgroundGeolocation.on('error', function(error) {
      console.log('[ERROR] cordova.plugins.BackgroundGeolocation error:', error.code, error.message);
    });

    cordova.plugins.BackgroundGeolocation.on('start', function() {
      console.log('[INFO] cordova.plugins.BackgroundGeolocation service has been started');
    });

    cordova.plugins.BackgroundGeolocation.on('stop', function() {
      console.log('[INFO] cordova.plugins.BackgroundGeolocation service has been stopped');
    });

    cordova.plugins.BackgroundGeolocation.on('authorization', function(status) {
      console.log('[INFO] cordova.plugins.BackgroundGeolocation authorization status: ' + status);
      if (status !== cordova.plugins.BackgroundGeolocation.AUTHORIZED) {
        // we need to set delay or otherwise alert may not be shown
        setTimeout(function() {
          var showSettings = confirm('App requires location tracking permission. Would you like to open app settings?');
          if (showSettings) {
            return cordova.plugins.BackgroundGeolocation.showAppSettings();
          }
        }, 1000);
      }
    });

    cordova.plugins.BackgroundGeolocation.on('background', function() {
      console.log('[INFO] App is in background');
      // you can also reconfigure service (changes will be applied immediately)
      cordova.plugins.BackgroundGeolocation.configure({ debug: true });
    });

    cordova.plugins.BackgroundGeolocation.on('foreground', function() {
      console.log('[INFO] App is in foreground');
      cordova.plugins.BackgroundGeolocation.configure({ debug: false });
    });

    cordova.plugins.BackgroundGeolocation.on('abort_requested', function() {
      console.log('[INFO] Server responded with 285 Updates Not Required');
      cordova.plugins.BackgroundGeolocation.stop();
      // Here we can decide whether we want stop the updates or not.
      // If you've configured the server to return 285, then it means the server does not require further update.
      // So the normal thing to do here would be to `cordova.plugins.BackgroundGeolocation.stop()`.
      // But you might be counting on it to receive location updates in the UI, so you could just reconfigure and set `url` to null.
    });

    cordova.plugins.BackgroundGeolocation.on('http_authorization', () => {
      console.log('[INFO] App needs to authorize the http requests');
    });

    cordova.plugins.BackgroundGeolocation.checkStatus(function(status) {
      console.log('[INFO] cordova.plugins.BackgroundGeolocation service is running', status.isRunning);
      console.log('[INFO] cordova.plugins.BackgroundGeolocation services enabled', status.locationServicesEnabled);
      console.log('[INFO] cordova.plugins.BackgroundGeolocation auth status: ' + status.authorization);

      // you don't need to check status before start (this is just the example)
      if (!status.isRunning) {
        cordova.plugins.BackgroundGeolocation.start(); //triggers start on start event
      }
    });

    // you can also just start without checking for status
    // cordova.plugins.BackgroundGeolocation.start();

    // Don't forget to remove listeners at some point!
    // cordova.plugins.BackgroundGeolocation.events.forEach(function(event) {
    //   return cordova.plugins.BackgroundGeolocation.removeAllListeners(event);
    // });
  }

}

然后从 app.component:

    import { Component, OnInit } from '@angular/core';
    import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
    import {
      transition,
      trigger,
      query,
      style,
      animate,
      group,
      animateChild
    } from '@angular/animations';
    import { GeolocationService } from './_services/geolocation.service';

    declare const device;

    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css'],
      animations: [
        trigger('routeAnimation', [
          transition('* => *', [
            query(
              ':enter',
              [style({ opacity: 0 })],
              { optional: true }
            ),
            query(
              ':leave',
               [style({ opacity: 1 }), animate('0.3s', style({ opacity: 0 }))],
              { optional: true }
            ),
            query(
              ':enter',
              [style({ opacity: 0 }), animate('0.3s', style({ opacity: 1 }))],
              { optional: true }
            )
          ])
        ])
      ]
    })

    export class AppComponent implements OnInit{
      title = 'HLD Phlebotomist App';

  constructor(private geolocationService: GeolocationService) { }

      ngOnInit() { 
        document.addEventListener("deviceready", function() {
          this.geolocationService.startBackgroundGeolocation();
          alert(device.platform);
        }, false); 
      } 
    }

但是,当我在 android 模拟器中运行它时,我得到“未捕获的类型错误:无法读取未定义的属性 'startBackgroundGeolocation'”。不知道为什么。有人可以帮我理解这里的结构吗?我认为我的问题是我不完全了解如何“调用”Cordova 插件。

【问题讨论】:

    标签: android angular cordova google-geolocation


    【解决方案1】:

    我在这里写这篇文章是因为我很难在一个地方找到这些答案,并将它们从各种博客帖子和 stackoverflow 帖子拼凑在一起,所有这些似乎都不完整。

    (1) 非常重要的引导

    事实证明,在某些情况下,Angular 和核心应用程序会在移动设备(Android 和 iOS)准备好提供对系统资源(如相机)的访问权限之前启动,从而导致持续出现错误和头痛。这是我一直在考虑的事情,直到最终在我们的应用中添加 SSO 作为第一步,这让我在超过 75% 的加载场景中都遇到了这个问题

    这里的解决方案非常简单,在您的 Angular 应用程序的 main.ts 中添加一个 javascript document.addEventListener,等待 cordova 在引导 Angular 之前说设备已准备好。

    document.addEventListener('deviceready', bootstrap, false);
    

    我们实际上更进一步,添加了一个条件块,仅当我们在环境变量中打开它时才注入cordova脚本标签。这使得在我们的测试环境中使用 ng serve 测试非 cordova 功能变得更加容易。

    const bootstrap = () => platformBrowserDynamic().bootstrapModule(AppModule).catch(e => console.error(e));
    const bootstrapCordova = () => {
      /** Dynamically load cordova JS **/
      console.log('bootstrapped cordova');
    
      const script = document.createElement('script');
      script.type = 'text/javascript';
      script.src = 'cordova.js';
    
      document.head.appendChild(script);
    
      if (script['readyState']) {
        // IE 2018 and earlier (?). They're migrating to using Chromium under the hood, so this may change
        script['onreadystatechange'] = () => {
          if (script['readyState'] === "loaded" || script['readyState'] === "complete") {
            document.addEventListener('deviceready', bootstrap, false);
    
          }
        };
      } else {
        // All other browsers
        document.addEventListener('deviceready', bootstrap, false);
      }
    };
    environment.cordova ? bootstrapCordova() : bootstrap();
    

    (2) 识别类型

    由于 cordova 及其插件没有作为 Angular 的一部分进行引导,因此我们需要将 cordova 类型添加到我们的 Angular 项目中。虽然可以在不使用“窗口”函数的情况下以特定方式编写代码,但我们发现键入解决了很多问题很重要。值得庆幸的是,有人为几乎每一个核心 cordova 插件贡献了打字稿类型。我们尝试在类型文件中安装这些类型,但发现 Angular 在编译时会抱怨有类型但没有库。郑重声明,我们不认为下面的方法应该奏效,但它们一次又一次地是唯一可复制的步骤,可以避免抱怨。

    首先,我们将 Angular 应用程序维护在与 cordova 不同的文件空间中,然后在运行 ng build --prod 时将应用程序写入 www 文件夹。这意味着我们的 cordova 应用程序和 Angular 应用程序每个都有一个唯一的 package.json 来管理 npm 依赖项。为了访问 angular 中的cordova类型,我们需要将cordova和我们正在使用的所有插件添加到我们的 angular 项目的 package.json 中。

    "cordova": "latest",
    "cordova-plugin-camera": "latest",
    "cordova-plugin-inappbrowser": "latest"
    

    第二 - 一旦你安装了这些依赖项,我们需要为我们的项目生成一个类型文件。值得注意的是,在 npm 中不推荐使用 "typings" 包以支持 "@types" 。但是我们无法弄清楚如何使用“@types”实现我们想要的最终结果,所以现在我们使用“typings”

    npm install –g typings
    typings search cordova
    typings install dt~cordova --global --save
    

    这将在您运行命令的地方生成一个打字文件夹,因此我们建议在您的 Angular 项目的根目录中执行此操作。完成后,将类型文件添加到您的 tsconfig.app.json 中,您就可以开始编码了。

    "types": [
      "./typings/globals/cordova"
    ]
    

    三级文件级导入

    在我们的研究中,我们看到了很多在代码级别实现类型化的方法,但只有一种在我们的项目中有效。逻辑表明我们应该能够做一个简单的事情:

    import {*} from 'typings/globals/cordova'
    

    虽然我们的 ide 对这个 ng build 很好,但 --prod 却不是。相反,我们通过以下方式引用类型文件:

    /// <reference path="../../../../typings/globals/cordova/index.d.ts" />
    

    完成此操作后,我们就可以从 IDE 中的完整智能感知中受益。

    export class SplashComponent implements AfterViewInit, OnDestroy {
      private browser: InAppBrowser;
      private cordova: Cordova;
    
      constructor(private route: Router) {
        this.cordova = window['cordova']
    this.browser = this.cordova.InAppBrowser;
      }
    
      ngAfterViewInit(){
          this.currentBrowser = this.browser.open('https://www.google.com', '_blank', 'location=yes');
    }
    
    }
    

    (3) 构建科尔多瓦

    如果您在其他任何地方都没有见过它,那么处理您在 cordova 中构建时遇到的任何剩余问题的最佳方法是删除您的平台并重新添加它。

    cordova platform rm android
    cordova platform add android
    

    使用此工作流程,我们能够消除我们在应用中遇到的几乎 100% 的运行时错误,并且我们的应用性能飙升。

    希望如果您遇到这些问题,您会发现这篇文章对您有所帮助,因为它包含了我希望存在的所有东西。

    注意事项: 处理此代码的“窗口”元素的正确 Angular 方法是使用注入令牌。我没有在这里包含它,因为这是一个很长的帖子。我可能会稍后更新它以包含此内容。

    【讨论】:

    • 如果我理解正确,cordova 项目中是否可以有 angular 项目?然后你运行“ng serve”,只运行 Angular 项目(预先设置环境变量)。你用什么来运行科尔多瓦项目,以便科尔多瓦使用角度代码?我将 angular 和 cordova 保存在单独的项目中,并使用输出路径 = cordova 项目构建 angular。
    【解决方案2】:

    这里的问题是 Cordova CLI 正在提取插件的 2.x 版本,但文档是针对 3.x 的。在 2.x 中,插件文件是 backgroundGeolocation.js,而 3.x 将其更改为 BackgroundGeolocation.js,并添加了一个 BackgroundGeolocation.d.ts 文件以支持 TypeScript 实现。由于它区分大小写,因此它被错误的名称调用并且失败了。将@latest 添加到我的cordova plugin add &lt;plugin-name&gt; 命令会拉取正确的版本并开始工作。

    【讨论】:

      猜你喜欢
      • 2018-05-24
      • 2019-12-16
      • 2016-12-06
      • 1970-01-01
      • 1970-01-01
      • 2019-11-13
      • 2013-09-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多