NativeScript / sample-Groceries

:green_apple: :pineapple: :strawberry: A NativeScript-built iOS and Android app for managing grocery lists
Apache License 2.0
486 stars 345 forks source link

TypeError: Cannot set property 'page' of undefined #261

Closed NickIliev closed 7 years ago

NickIliev commented 7 years ago

From @zaszlo on June 30, 2017 8:32

details below:

I started with the {N} provided groceries sample application and incrementally modified it to make it use my api. In the process I must have done something wrong but can't figure it out where so that when I login and would get to the groceries list I get this error:

JS: ERROR TypeError: Cannot set property 'page' of undefined JS: ERROR CONTEXT [object Object]

This is interesting because in the login page the same ui/page is imported and called so I don't get it why on the groceries page this breaks.

Checked

I tried to find related issues here and on stackoverflow and comparing my code with the original sample but.. I couldn't figured it out.

Which platform(s) does your issue occur on?

Android

Please provide the following version numbers that your issue occurs with:

Please tell us how to recreate the issue in as much detail as possible.

My VisualStudio doesn't show anything to be wrong in my code, so I don't know what I do wrong.

Is there code involved? If so, please share the minimal amount of code needed to recreate the problem.

import { Component, ElementRef, OnInit, ViewChild } from "@angular/core";
import { Router } from "@angular/router";
import { action } from "ui/dialogs";
import { Color } from "color";
import { Page } from "ui/page";
import { TextField } from "ui/text-field";
import * as SocialShare from "nativescript-social-share";
import { isAndroid } from "platform";

import { GroceryListComponent } from "./grocery-list/grocery-list.component";
import { GroceryService } from "./shared";
import { LoginService, alert } from "../shared";

@Component({
  selector: "gr-groceries",
  moduleId: module.id,
  templateUrl: "./groceries.component.html",
  styleUrls: ["./groceries-common.css", "./groceries.component.css"],
  providers: [GroceryService]
})
export class GroceriesComponent implements OnInit {
  grocery: string = "";
  isAndroid;
  isShowingRecent = false;
  isLoading = false;

  @ViewChild("groceryTextField") groceryTextField: ElementRef;

  constructor(private router: Router,
    private store: GroceryService,
    private loginService: LoginService,
    private page: Page) {}

  ngOnInit() {
    console.log("ngOnInit begin");
    this.page.actionBarHidden = true;
    this.page.className = "list-page";
    console.log("ngOnInit end");
  }

  public layoutLoaded(args){
        console.log("layoutLoaded begin");
        this.isAndroid = !!this.page.android;
        console.log("layoutLoaded end");
    }

  // Prevent the first textfield from receiving focus on Android
  // See http://stackoverflow.com/questions/5056734/android-force-edittext-to-remove-focus
  handleAndroidFocus(textField, container) {
    if (container.android) {
      container.android.setFocusableInTouchMode(true);
      container.android.setFocusable(true);
      textField.android.clearFocus();
    }
  }

  showActivityIndicator() {
    this.isLoading = true;
  }
  hideActivityIndicator() {
    this.isLoading = false;
  }

  add(target: string) {
    // If showing recent groceries the add button should do nothing.
    if (this.isShowingRecent) {
      return;
    }

    let textField = <TextField>this.groceryTextField.nativeElement;

    if (this.grocery.trim() === "") {
      // If the user clicked the add button, and the textfield is empty,
      // focus the text field and return.
      if (target === "button") {
        textField.focus();
      } else {
        // If the user clicked return with an empty text field show an error.
        alert("Enter a grocery item");
      }
      return;
    }

    // Dismiss the keyboard
    // TODO: Is it better UX to dismiss the keyboard, or leave it up so the
    // user can continue to add more groceries?
    textField.dismissSoftInput();
    /*
    this.showActivityIndicator();
    this.store.add(this.grocery)
      .subscribe(
        () => {
          this.grocery = "";
          this.hideActivityIndicator();
        },
        () => {
          alert("An error occurred while adding an item to your list.");
          this.hideActivityIndicator();
        }
      );
     */
  }

  toggleRecent() {
    if (!this.isShowingRecent) {
      this.isShowingRecent = true;
      return;
    }

    this.showActivityIndicator();
    /*
    this.store.restore()
      .subscribe(
        () => {
          this.isShowingRecent = false;
          this.hideActivityIndicator();
        },
        () => {
          alert("An error occurred while adding groceries to your list.");
          this.hideActivityIndicator();
        }
      );
      */
  }

  showMenu() {
    action({
      message: "What would you like to do?",
      actions: ["Share", "Log Off"],
      cancelButtonText: "Cancel"
    }).then((result) => {
      if (result === "Share") {
        this.share();
      } else if (result === "Log Off") {
        this.logoff();
      }
    });
  }

  share() {
    let items = this.store.items.value;
    let list = [];
    for (let i = 0, size = items.length; i < size ; i++) {
      list.push(items[i].title);
    }
    SocialShare.shareText(list.join(", ").trim());
  }

  logoff() {
    this.loginService.logoff();
    this.router.navigate(["/login"]);
  }
}

I guess these settings could be affecting it.

groceries.module.ts

import { NativeScriptModule } from "nativescript-angular/nativescript.module";
import { NativeScriptFormsModule } from "nativescript-angular/forms";
import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core";
import { groceriesRouting } from "./groceries.routing";
import { GroceriesComponent } from "./groceries.component";
import { GroceryListComponent } from "./grocery-list/grocery-list.component";
import { ItemStatusPipe } from "./grocery-list/item-status.pipe";

@NgModule({
  imports: [
    NativeScriptModule,
    NativeScriptFormsModule,
    groceriesRouting
  ],
  declarations: [
    GroceriesComponent,
    GroceryListComponent,
    ItemStatusPipe
  ],
  schemas: [NO_ERRORS_SCHEMA]
})
export class GroceriesModule {}

app.module.ts

import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core";
import { NativeScriptModule } from "nativescript-angular/nativescript.module";
import { NativeScriptHttpModule } from "nativescript-angular/http";
import { NativeScriptRouterModule } from "nativescript-angular/router";

import { authProviders, appRoutes } from "./app.routing";
import { setStatusBarColors, BackendService, LoginService } from "./shared";
import { AppComponent } from "./app.component";

import { LoginModule } from "./login/login.module";
import { GroceriesModule } from "./groceries/groceries.module";

@NgModule({
    bootstrap: [
        AppComponent
    ],
    imports: [
        NativeScriptModule,
        NativeScriptHttpModule,
        NativeScriptRouterModule,
        NativeScriptRouterModule.forRoot(appRoutes),
        LoginModule,
        GroceriesModule
    ],
    declarations: [
        AppComponent
    ],
    providers: [
        BackendService,
        LoginService,
        authProviders
    ],
    schemas: [
        NO_ERRORS_SCHEMA
    ]
})
export class AppModule { }

package.json

{
  "description": "NativeScript Application",
  "license": "SEE LICENSE IN <your-license-filename>",
  "readme": "NativeScript Application",
  "repository": "<fill-your-repository-here>",
  "nativescript": {
    "id": "org.nativescript.iAngulAndro",
    "tns-android": {
      "version": "3.0.1"
    }
  },
  "dependencies": {
    "@angular/animations": "~4.1.0",
    "@angular/common": "~4.1.0",
    "@angular/compiler": "~4.1.0",
    "@angular/core": "~4.1.0",
    "@angular/forms": "~4.1.0",
    "@angular/http": "~4.1.0",
    "@angular/platform-browser": "~4.1.0",
    "@angular/router": "~4.1.0",
    "email-validator": "^1.0.7",
    "nativescript-angular": "~3.0.0",
    "nativescript-social-share": "^1.3.2",
    "nativescript-theme-core": "~1.0.2",
    "reflect-metadata": "~0.1.8",
    "rxjs": "~5.3.0",
    "tns-core-modules": "~3.0.0",
    "zone.js": "~0.8.2"
  },
  "devDependencies": {
    "babel-traverse": "6.25.0",
    "babel-types": "6.25.0",
    "babylon": "6.17.4",
    "lazy": "1.0.11",
    "nativescript-dev-typescript": "~0.4.0",
    "typescript": "~2.2.1"
  }
}

vendor.ts

require("./vendor-platform");

require("reflect-metadata");
require("@angular/platform-browser");
require("@angular/core");
require("@angular/common");
require("@angular/forms");
require("@angular/http");
require("@angular/router");

require("nativescript-angular/platform-static");
require("nativescript-angular/forms");
require("nativescript-angular/router");

Copied from original issue: NativeScript/NativeScript#4482

NickIliev commented 7 years ago

From @zaszlo on June 30, 2017 9:54

By debugging I found out that this.page is not undefined, only this.page.android is and this is why on the login screen I don't get this error as there this.page is not used with .android. Now, what could be the reason for page.android to be undefined when developing only for android and android emulator otherwise renders the code as expected.

NickIliev commented 7 years ago

From @zaszlo on June 30, 2017 10:17

So got to this https://github.com/NativeScript/NativeScript/issues/3970 that says to don't use page.android in ngInit, instead call it in a function fired from the layout when it is loaded:

So beneath ngInit I put

  public layoutLoaded(args){
        console.log("layoutLoaded begin");
        this.isAndroid = !!this.page.android;
    }

and in groceries.component.html

<GridLayout #container
(loaded)="layoutLoaded($event)"
...

And now I get the same error between the two, don't know what is calling/causing it:

JS: ngOnInit begin
JS: ngOnInit end
JS: ERROR TypeError: Cannot set property 'page' of undefined
JS: ERROR CONTEXT [object Object]
JS: layoutLoaded begin

Also I find it strange that in the sample application the page.android would be called incorrectly, in the ngOnInit already.

NickIliev commented 7 years ago

@zaszlo you can overcome the whole this.page.android issue by using the out-of-the-box properties in the platform module

isAndroid isAndroid: boolean Defined in platform/platform.d.ts:11 Gets a value indicating if the app is running on the Android platform.

isIOS isIOS: boolean Defined in platform/platform.d.ts:16 Gets a value indicating if the app is running on the iOS platform.

respectivly you can import them as follows:

import { isAndroid, isIOS } from "platform";

//and later..

if (isAndroid) {
    // do the Android stuff
}

From the API reference here

You can also try to call this.page using ngAfterViewInit which will be triggered after ngOnInit

NickIliev commented 7 years ago

From @zaszlo on June 30, 2017 11:48

@NickIliev as I see with removing the this.page.android from the ngOnInit, the error message of Cannot set property 'page' of undefined doesn't come anymore from there, from the this.page.android but some other call that I can't see. From angular's part the ngOnInit is where it starts, and form the calls from the layout I guess that the layoutLoaded shoud be triggered first so I don't understand what produces the error message between the two:

JS: ngOnInit begin
JS: ngOnInit end
JS: ERROR TypeError: Cannot set property 'page' of undefined
JS: ERROR CONTEXT [object Object]
JS: layoutLoaded begin

It seems to me that it's not the this.page.android or not only that.

Also, I can't seem to stop in debug mode at the breakpoints, commented on https://stackoverflow.com/questions/42407703/visual-studio-code-wont-stop-at-breakpoint-in-nativescript-app

NickIliev commented 7 years ago

@zaszlo check if your layout page exists. You have several options from this point - you can debug the error (if using VSCode you can see this article about debugging) or you can compare your code with the code from the Groceries end branch.

As this repository is only for login issues, bugs and feature requests related to NativeScript framework itself and not for the Groceries tutorial I will move this issue to the Groceries repository, and we can continue this conversation there.

zaszlo commented 7 years ago

I have compared with the original files without getting from where my bug comes from. How to check if my layout page exists? I have the "groceries.component.html" that is the page's layout page right? I have qouted from its code already.

I have followed the articles and use VSCode but will not stop at breakpoints, once it did, I can't reproduce it :).. But this is another issue.

NickIliev commented 7 years ago

@zaszlo you can create a repository with the project up to this point (exclude node_modules and platforms folders) and share the link here so I can investigate the issue locally.

zaszlo commented 7 years ago

https://github.com/zaszlo/nativeSample here it is, but I guess you'll have to ignore somehow the api I have set it up with.

zaszlo commented 7 years ago

ok, I got it, it doesn't refer to ui/page but to another object's page property (filter).