Changes in model not reflected in the view

In my Angular app (5.2.3), I have implemented a feature to display a login/logout button in the top right corner of the page. The functionality involves silently logging the user in using an external Open ID Connect provider and then displaying the user's name and logout button upon callback. However, despite successful backend operations, the view fails to update accordingly.

Here is the relevant component view code snippet:

<ul class="navbar-nav">
  <li class="nav-item" *ngIf="!loggedIn">
    <a href="#" class="nav-link" (click)="login()">Login</a>
  </li>
  <li class="nav-item" *ngIf="loggedIn">
    <a href="#" class="nav-link disabled" disabled>Hello, {{ name }}</a>
  </li>
  <li class="nav-item" *ngIf="loggedIn">
    <a href="#" class="nav-link" (click)="logoff()">Logout</a>
  </li>
</ul>
I have attempted various solutions sourced from StackOverflow threads without success. Here is the current state of the component:

import {
  Component,
  OnInit,
  SimpleChanges,
  ApplicationRef,
  ChangeDetectorRef,
  NgZone
} from '@angular/core';
import {
  OAuthService
} from 'angular-oauth2-oidc';
import {
  SessionService
} from '../session.service';

@Component({
  selector: 'app-navigation',
  templateUrl: './navigation.component.html',
  styleUrls: ['./navigation.component.css']
})
export class NavigationComponent implements OnInit {
  name: string;
  loggedIn: boolean;

  constructor(private oauthService: OAuthService,
    private sessionService: SessionService,
    private applicationRef: ApplicationRef,
    private zone: NgZone,
    private cd: ChangeDetectorRef) {
    //this.loggedIn = false;
  }

  ngOnInit() {
    this.sessionService.state.subscribe(isLoggedIn => {
      console.log('Detected a state change! User is logged in: ' + isLoggedIn);
      this.zone.run(() => {
        if (!isLoggedIn) {
          this.name = null;
          this.loggedIn = false;
          console.log('User not logged in. Name is set to null');
        } else {
          const claims: any = this.oauthService.getIdentityClaims();
          console.log(`Got claims. Name is now ${claims.name}`);
          this.name = claims.name;
          this.loggedIn = true;
        }
      });
      this.cd.detectChanges();
      this.applicationRef.tick();
    });
    this.sessionService.configureWithNewConfigApi();
  }

  public login() {}

  public logoff() {}
}
All console.log statements are successfully executed, but unfortunately, the view does not reflect the updates as intended.

Answer №1

It looks like your issue is quite similar to one I've encountered before and provided a solution for.

Trigger update of component view from service - No Provider for ChangeDetectorRef

To resolve this, you'll need to prompt Angular to update your view. One way to achieve this is by utilizing the ApplicationRef. Here's an example of how your service code could be structured:

import {Injectable, ApplicationRef } from '@angular/core';
  
@Injectable()
export class UserService {
  private isLoggedIn: boolean = false;
  constructor(private ref: ApplicationRef) {}
  
  checkLogin() {
    let stillLogged = //insert logic here making API calls or other actions.
    if (this.isLoggedIn != stillLogged) {
        this.isLoggedIn = stillLogged;
        this.ref.tick(); //triggers Angular to update the view.
    }
  }
}

More information on ApplicationRef can be found in this documentation page

Check out this live example where AppRef is used to update views when a client subscribes to push notifications on Safari browser.

If needed, you could also consider moving this.applicationRef.tick() inside this.zone.run(()=>{}) (especially if NgZone is required).

Answer №2

This is my proposal for utilizing observables.

Within my service:

export class MyAuthenticationService {
  authenticationSubject: ReplaySubject<any>;
  authentication$: Observable<any>;

  constructor(private http: HttpClient) {
    this.authenticationSubject = new ReplaySubject<any>();
    this.authentication$ = this.authenticationSubject.asObservable();
  }

  authenticate(): void {
    // implement your authentication logic and once it succeeds
    this.authenticationSubject.next({authenticated: true});
  }
}

In my HTML:

<ul class="navbar-nav">
  <li class="nav-item" *ngIf="!(myAuthSvc.authentication$ | async)?.authenticated">
    <a href="#" class="nav-link" (click)="authenticate()">Login</a>
  </li>
  <li class="nav-item" *ngIf="(myAuthSvc.authentication$ | async)?.authenticated">
    <a href="#" class="nav-link disabled" disabled>Hello, {{ name }}</a>
  </li>
  <li class="nav-item" *ngIf="(myAuthSvc.authentication$ | async)?.authenticated">
    <a href="#" class="nav-link" (click)="logout()">Logout</a>
  </li>
</ul>

Within my component:

@Component({
  ...
})
export class MyComponent {
  constructor(public myAuthSvc: MyAuthenticationService) {}

  ...
}

Answer №3

It seems like the issue arises from relying too heavily on information found on the internet to track changes, resulting in potential weird behavior. Have you considered simplifying things by using just the detectChanges() method? While it might not be the most optimal solution, it should do the trick...

An alternative approach, if the subscribe() function is within the zone, would be to use markForCheck()... however, as mentioned earlier, detectChanges alone should suffice.

@Component({
  selector: 'app-navigation',
  templateUrl: './navigation.component.html',
  styleUrls: ['./navigation.component.css']
})
export class NavigationComponent implements OnInit {
  name: string;
  loggedIn: boolean;

  constructor(private oauthService: OAuthService,
    private sessionService: SessionService,
    private applicationRef: ApplicationRef,
    private zone: NgZone,
    private cd: ChangeDetectorRef) {
    //this.loggedIn = false;
  }

  ngOnInit() {
    this.sessionService.state.subscribe(isLoggedIn => {
      console.log('Detected a state change! User is logged in: ' + isLoggedIn);
      if (!isLoggedIn) {
        this.name = null;
        this.loggedIn = false;
        console.log('User not logged in. Name is set to null');
      } else {
        const claims: any = this.oauthService.getIdentityClaims();
        console.log(`Got claims. Name is now ${claims.name}`);
        this.name = claims.name;
        this.loggedIn = true;
      }
      this.cd.detectChanges();
    });
    this.sessionService.configureWithNewConfigApi();
  }

  public login() {}

  public logoff() {}
}

I hope this provides some guidance.

Answer №4

After reviewing the code you shared, everything seems to be in order. However, it's possible that there could be a conflict with the ngIf directive causing your issue. One suggestion is to ensure that you initialize the loggedIn value at the beginning and consider using an *ngIF else template within your HTML file.

loggedIn: boolean = false;

The corresponding HTML code snippet would look like this:

<ul class="navbar-nav">
    <li class="nav-item" *ngIf="!loggedIn; else loggedInTemplate">
        <a href="#" class="nav-link" (click)="login()">Login</a>
    </li>
    <ng-template #loggedInTemplate>
        <li class="nav-item">
          <a href="#" class="nav-link disabled" disabled>Hello, {{ name }}</a>
        </li>
        <li class="nav-item">
          <a href="#" class="nav-link" (click)="logoff()">Logout</a>
        </li>
    </ng-template>
  </ul>

Without additional context, it's challenging to pinpoint any other potential issues. Unfortunately, without access to the sessionService code, I am unable to recreate the error scenario. Hopefully, my recommendations assist you in resolving the problem.

Answer №5

In order to ensure that the view reflects the correct logged in status, you can run the code within the ngZone. Here's how you can do it:

this.ngZone.run(() => {
  this.loggedIn = true;
});

Below is the updated code snippet:

import {
Component,
OnInit,
SimpleChanges,
ApplicationRef,
ChangeDetectorRef,
NgZone } from '@angular/core';
import {  OAuthService} from 'angular-oauth2-oidc';
import { SessionService } from '../session.service';

@Component({
 selector: 'app-navigation',
 templateUrl: './navigation.component.html',
 styleUrls: ['./navigation.component.css']
})
export class NavigationComponent implements OnInit {
  name: string;
  loggedIn: boolean;

  constructor(private oauthService: OAuthService,
    private sessionService: SessionService,
    private zone: NgZone) {
  }

  ngOnInit() {
    this.sessionService.state.subscribe(isLoggedIn => {
      console.log('Detected a state change! User is logged in: ' + isLoggedIn);

        if (!isLoggedIn) {
          this.name = null;
          this.loggedIn = false;
          console.log('User not logged in. Name is set to null');
        } else {
          const claims: any = this.oauthService.getIdentityClaims();
          console.log(`Got claims. Name is now ${claims.name}`);
          this.name = claims.name;
          this.zone.run(() => {
          this.loggedIn = true;
          });
        }
    });
    this.sessionService.configureWithNewConfigApi();
  }

  public login() {}

  public logoff() {}
}

I trust that implementing this approach will rectify the issue related to view updates.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Tips for calculating the total of keyup elements in an Angular application

There are "N" inputs in a formgroup that need to be summed: <input (keyup)="sum($event)" type="text" name="estoque_variacao{{i}}" class="form-control" id="estoque_variacao{{i}}" formControlName="estoque_variacao"> This is the Typescript code: sum( ...

Creating a table and filling it with data from any cell is a simple process

I have a question about creating an HTML table similar to the image shown here: image I want to populate the table with data from a JSON response received from a web service: { "ErrorCode": 0, "ErrorMessage": "ok", "Data": [ { ...

Verifying the legitimacy of the elements in an n-dimensional array

In my journey to create my own Tensor class (n-dimensional arrays) in typescript, I have devised a structure where the data is stored in a 1D array property, and the shape of the data is stored separately for indexing purposes. My goal is to develop a fun ...

Issue with Django and Angular 4 - The requested resource is missing the 'Access-Control-Allow-Origin' header

This is my django code snippet Whenever I test my delete function, this error occurs: The requested resource does not include the 'Access-Control-Allow-Origin' header. This means that the origin is not permitted access. The response resulted ...

I'm encountering an issue trying to apply array filtering with checkboxes using React hooks and TypeScript

Help needed! I'm facing an issue while trying to filter an array based on gender using checkboxes. The functionality works fine for the male checkbox but seems to fail when clicking on the female checkbox. Below is the code snippet from my App.tsx fil ...

Removing excess space at the bottom of a gauge chart using Echarts

After trying to implement a gauge chart using Baidu's Echarts, I noticed that the grid properties applied to other charts are working fine, but the bottom space is not being removed in the gauge chart. Even after applying radius(100%), the space at th ...

The array containing numbers or undefined values cannot be assigned to an array containing only numbers

Currently facing an issue with TypeScript and types. I have an array of IDs obtained from checkboxes, which may also be empty. An example of values returned from the submit() function: const responseFromSubmit = { 1: { id: "1", value: "true" }, 2: ...

The form data consistently replaces existing values with each new entry added

I am struggling to persist all the form values that are entered in my component using local storage. Despite setting and pushing the form values, I noticed that each time I push the data, it replaces the previously entered form data. My goal is to retain a ...

Ways to retrieve a Class Level Variable within the onCellValueChanged function in agGrid

Exploring Angular with Typescript for the first time. I'm trying to access Class Level Variables within the onCellValueChanged event of agGrid, but encountering the following error: Cannot read property 'updateDict' of undefined Here&apo ...

Tally up identical words without considering differences in capitalization or extra spaces

Let's take an example with different variations of the word "themselves" like "themselves", "Themselves", or " THEMSelveS " (notice the leading and trailing spaces), all should be considered as one count for themselves: 3 ...

Choosing the primary camera on a web application with multiple rear cameras using WebRTC

Having a bit of trouble developing a web app that can capture images from the browser's back camera. The challenge lies in identifying which camera is the main one in a multi-camera setup. The issue we're running into is that each manufacturer u ...

How to activate the close function of ionic-datepicker when tapping the hardware back button in Ionic 4

Utilizing ionic-datepicker in my app (ionic v-4), here is the corresponding template: <ion-datetime formControlName="meeting_date" display-format="MMM DD, YYYY"></ion-datetime> The datepicker (i.e ion-datetime) closes upon clicking cancel/do ...

Can you explain the concept of "Import trace for requested module" and provide instructions on how to resolve any issues that

Hello everyone, I am new to this site so please forgive me if my question is not perfect. My app was working fine without any issues until today when I tried to run npm run dev. I didn't make any changes, just ran the command and received a strange er ...

a feature in mongoose that automatically increments versions when creating a new document

Is it possible to set up an auto-increment feature for the versionKey (__v) field whenever a new document is created, or should I consider using a different field like 'version' in the schema? Here's an example of the schema used in my appl ...

Encountering issues while trying to incorporate a trading chart library into an Angular 7 project

ERROR in src/assets/datafeeds/udf/src/udf-compatible-datafeed-base.ts(243,74): error TS2339: 'errmsg' Property Not Found The property 'errmsg' does not exist on the type 'UdfErrorResponse | UdfSearchSymbolsResponse'. The p ...

Deduce the generic types of conditional return based on object property

My goal is to determine the generic type of Model for each property. Currently, everything is displaying as unknown[] instead of the desired types outlined in the comments below. playground class Model<T> { x?: T } type ArgumentType<T> = T ...

What is the best way to relocate the styles folder to the src folder while using nextjs, typescript, and tailwind

I am currently working with Next.js using TypeScript and Tailwind CSS. My goal is to relocate the styles folder into the src folder. I have already updated the baseUrl in my tsconfig.json file to point to the src directory, but I encountered the following ...

Struggling with manipulating arrays in Angular 7

Alright, let me give you the gist: Here's the deal - I've got a dataset chilling in sessionStorage, looking something like this: [{ "id": "123:456", "streetAddress": "1020 15th St", "point": { "lati": 35.74633, "longi": ...

Replacing text with new content when text is stored in a separate file

After organizing all the strings from my code, I compiled them into a file named constants.ts. export class Constants { public static API_URL = '/api/'; public static CREATE_CHILD_API_URL = Constants.API_URL + '%s' + '/create- ...

When transitioning to generics, the narrowing of types in TypeScript is sometimes lost

I am intrigued by this scenario where Test1 fails while Test2 succeeds. I wonder if there is a way to have Test1 pass without altering the generic signature: enum TableType { Shipment = "Shipment", Batch = "Batch", } type Test& ...