Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
108 views
in Technique[技术] by (71.8m points)

Hybrid app with Angular and Cordova: Dynamic content not rendered in IOS

I have converted an Angular project into a hybrid app following this guide: https://medium.com/@christof.thalmann/convert-angular-project-to-android-apk-in-10-steps-c49e2fddd29

For Android I did not run into many issues and the app is working as expected on that platform.

On IOS I ran into multiple difficulties. First of all in order contents display I needed to change the Angular LocationStrategy to HashLocation as describe in this SO topic:

Why Angular Router 8 router ...

Although I now do get content to render I still have trouble getting the dynamic content (i.e. the content requiring a call to a web server before rendering) to render properly.

My app has got a classic NavBar to switch from one component to another. If I activate one component by clicking on a Nav Button the static content gets rendered ok. However, the dynamic content is not there. I can verify this by looking at the source code: The holding the dynamic content is empty. If I click on the same Nav Button again the dynamic content is added. I get the same effect on an iPhone simulator and on a real device.

This is the html of one of the components

<div class="card bg-opaque border-light">
    <div class="card-body">
        <div>
            <h2 class="text-light text-opaque">Einsparungen von {{ user }}</h2>
        </div>

        <div *ngIf="monthScore" class="card border-primary bg-opaque-light"> <!-- THIS BLOCK NOT VISIBLE -->
            <div class="card-body text-center">
                <h3 class="card-title">Current Month</h3>
                <ul class="list-unstyled">
                    <li>
                        <strong>Savings:</strong> {{ monthScore.savings | number: '1.1-2' }} kg
                    </li>
                    <li>
                        <strong>Position:</strong> {{ monthScore.rank }}
                        <span *ngIf="!monthScore.rank">???</span>
                        <span *ngIf="monthScore.rank == 1">
                            <mat-icon class="text-warning">emoji_events</mat-icon>
                        </span>
                    </li>
                    <li>
                        <strong>Captured:</strong> {{ monthScore.captured | number: '1.1-2' }} kg
                    </li>
                </ul>
                <div *ngIf="showMonthButton" >
                    <button class="btn btn-outline-primary" (click)="toggleMonthGraph()">
                        <mat-icon>bar_chart</mat-icon>
                    </button>
                </div>
                <div *ngIf="!showMonthButton" (click)="toggleMonthGraph()">
                    <canvas
                        baseChart
                        [chartType]="'bar'"
                        [datasets]="monthChartData"
                        [labels]="monthChartLabels"
                        [options]="chartOptions"
                        [legend]="false">
                    </canvas>
                </div>
            </div>
        </div>

        <div *ngIf="yearScore" class="card border-primary bg-opaque-light"> <!-- THIS BLOCK NOT VISIBLE -->                <div class="card-body text-center">
                <h3 class="card-title">Current year</h3>
                <ul class="list-unstyled">
                    <li>
                        <strong>Savings:</strong> {{ yearScore.savings | number: '1.1-2' }} kg
                    </li> 
                    <li>
                        <strong>Position:</strong> {{ yearScore.rank }}
                        <span *ngIf="!yearScore.rank">???</span>
                        <span *ngIf="yearScore.rank == 1">
                            <mat-icon class="text-warning">emoji_events</mat-icon>
                        </span>
                    </li>
                    <li>
                        <strong>Captured:</strong> {{ yearScore.captured | number: '1.1-2' }} kg
                    </li>
                </ul>
                <div *ngIf="showYearButton" >
                    <button class="btn btn-outline-primary" (click)="toggleYearGraph()">
                        <mat-icon>bar_chart</mat-icon>
                    </button>
                </div>
                <div *ngIf="!showYearButton" (click)="toggleYearGraph()">
                    <canvas
                        baseChart
                        [chartType]="'bar'"
                        [datasets]="yearChartData"
                        [labels]="yearChartLabels"
                        [options]="chartOptions"
                        [legend]="false">
                    </canvas>
                </div>
            </div>
        </div>
    </div>
</div>

<app-inpage></app-inpage>

The .ts file:

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

import { SummaryService} from '../summary.service';
import { AuthService } from '../../auth/auth.service';
import { Score } from '../summary';
import { MAT_RIPPLE_GLOBAL_OPTIONS } from '@angular/material/core';

@Component({
  selector: 'app-score',
  templateUrl: './score.component.html',
  styleUrls: ['./score.component.scss']
})
export class ScoreComponent implements OnInit {

  monthScore: Score;
  yearScore: Score;
  user: string;

  // Histogramm per Consumer
  chartOptions = {
    responsive: true,
    scales: {
      xAxes: [{
          gridLines: {
              drawOnChartArea: false
          }
      }],
      yAxes: [{
          gridLines: {
              drawOnChartArea: false
          }
      }]
  }
  };

  yearChartData = [];
  yearChartLabels = [];
  yearChartTitle: string;
  showYearChart: boolean = false;
  showYearButton: boolean = true;

  monthChartData = [];
  monthChartLabels = [];
  monthChartTitle: string;
  showMonthChart: boolean = false;
  showMonthButton: boolean = true;

  constructor(private service: SummaryService, private authService: AuthService) { }

  ngOnInit(): void {

    this.user = this.authService.user

    this.getMonthScore();
    this.getYearScore();
  }

  getMonthScore(): void {
    this.service.getScore('month').subscribe(score => {
      this.monthScore = score;
      this.createMonthGraph();
    })
  }

  getYearScore(): void {
    console.log('GETTING SCORE')
    this.service.getScore('year').subscribe(score => {
      this.yearScore = score;
      this.createYearGraph();
    })
  }

  private createYearGraph(): void {
    this.service.getTimeline('year').subscribe(timelines => {
      let data: number[] = [];
      let label: string[] = [];
      for (let i = 0; i < timelines.length; i++){
        data.push(timelines[i].user_savings);
        label.push(timelines[i].period.toString());
      }

      this.yearChartData = [{data: data, label: 'Savings', barThickness: 2, backgroundColor: 'rgba(0, 0, 0, 0.5' }]
      this.yearChartLabels = label

    })
  }

  private createMonthGraph(): void {
    this.service.getTimeline('month').subscribe(timelines => {
      let data: number[] = [];
      let label: string[] = [];
      for (let i = 0; i < timelines.length; i++){
        data.push(timelines[i].user_savings);
        label.push(timelines[i].period.toString());
      }

      this.monthChartData = [{data: data, label: 'Savings', barThickness: 2, backgroundColor: 'rgba(0, 0, 0, 0.5' }]
      this.monthChartLabels = label

    })
  }

  toggleYearGraph(): void {
    this.showYearChart = !this.showYearChart;
    this.showYearButton = !this.showYearButton;
  }

  toggleMonthGraph(): void {
    this.showMonthButton = !this.showMonthButton;
  }
}

My config.xml

<?xml version='1.0' encoding='utf-8'?>
<widget id="com.ticumwelt.co2" version="0.2.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
    <name>Tracker</name>
    <description>
        An app to track your savings.
    </description>
    <author email="[email protected]" href="https://example.com">
        Developer Team
    </author>
    <content src="index.html" />
    <access origin="*" />
    <allow-intent href="http://*/*" />
    <allow-intent href="https://*/*" />
    <allow-intent href="tel:*" />
    <allow-intent href="sms:*" />
    <allow-intent href="mailto:*" />
    <allow-intent href="geo:*" />
    <platform name="android">
        <allow-intent href="market:*" />
    </platform>
    <platform name="ios">
        <allow-intent href="itms:*" />
        <allow-intent href="itms-apps:*" />
        <!-- iOS 8.0+ -->
        <!-- iPhone 6 Plus  -->
        <icon src="res/ios/icons/[email protected]" width="180" height="180" />
        <!-- iOS 7.0+ -->
        <!-- iPhone / iPod Touch  -->
        <icon src="res/ios/icons/icon-60.png" width="60" height="60" />
        <icon src="res/ios/icons/[email protected]" width="120" height="120" />
        <!-- iPad -->
        <icon src="res/ios/icons/icon-76.png" width="76" height="76" />
        <icon src="res/ios/icons/[email protected]" width="152" height="152" />
        <!-- Spotlight Icon -->
        <icon src="res/ios/icons/icon-40.png" width="40" height="40" />
        <icon src="res/ios/icons/[email protected]" width="80" height="80" />
        <!-- iOS 6.1 -->
        <!-- iPhone / iPod Touch -->
        <icon src="res/ios/icons/icon.png" width="57" height="57" />
        <icon src="res/ios/icons/[email protected]" width="114" height="114" />
        <!-- iPad -->
        <icon src="res/ios/icons/icon-72.png" width="72" height="72" />
        <icon src="res/ios/icons/[email protected]" width="144" height="144" />
        <!-- iPad Pro -->
        <icon src="res/ios/icons/icon-167.png" width="167" height="167" />
        <!-- iPhone Spotlight and Settings Ic

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

After a lot of trial and error and searching I found the solution.

I found a hint here:

https://github.com/angular/angular/issues/7381[1]

For a reason I fo not fully understand yet the app seems to switch zones during the async call to the server. Therefore the UI change mechanism is not triggered and the screen is not updated.

By wrapping the changes of variables into NgZone.run()the screen is updated correctly.

The updated .ts file

import { Component, OnInit, NgZone } from '@angular/core';

// ...

constructor(private service: SummaryService, private authService: AuthService, private zone: NgZone) { }

// ...

getMonthScore(): void {
    this.service.getScore('month').subscribe(score => {
      this.zone.run(() => {
        this.monthScore = score;
        this.createMonthGraph();
        console.log('GOT MONTH SCORE');
        console.log(score);

      });
      
    })
  }

This is required only when building an iOS app with Cordova. When building an Android app or using the browser does not seem necessary.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

1.4m articles

1.4m replys

5 comments

56.8k users

...