#StackBounty: #angular #angular-testing Issue with deep testing components in Angular and accessing html with debugElement

Bounty: 50

I have a hierarchy of components in my angular app which is as follows:

BookmarkContainer has the following child components:

  • BookmarksListComponent
  • CreateBookmarkComponent

BookmarksListComponent has a ngFor of BookmarkItemComponent

I am trying to access the template of BookmarkItemComponent from the test of the BookmarkContainer using DebugElement and its query() method.

Here is the code for BookmarkContainer:

@Component({
  selector: 'app-bookmarks-container',
  templateUrl: './bookmarks.container.html',
  styleUrls: ['./bookmarks.container.scss']
})
export class BookmarksContainerComponent implements OnInit {
  bookmarks$: Observable<Bookmark[]>;
  newBookmark: Bookmark;
  deletedBookmarkId: number;

  constructor(private bookmarkService: BookmarkService) {}

  ngOnInit(): void {
    this.setBookmarks();
  }

  private setBookmarks() {
    this.bookmarks$ = this.bookmarkService
      .currentUser()
      .pipe(
        mergeMap(session => this.bookmarkService.search({ account: session.user.uid }))
      );
  }
}

Here is the corresponding test:

describe('BookmarkContainerComponent', () => {
  let component: BookmarksContainerComponent;
  let fixture: ComponentFixture<BookmarksContainerComponent>;
  let httpTestingController: HttpTestingController;

  const aBookmark: Bookmark = {
    id: 42,
    title: 'a bookmark',
    url: 'http://www.example.com',
    account: '1'
  };
  const anotherBookmark: Bookmark = {
    id: 43,
    title: 'another bookmark',
    url: 'http://www.example.fr',
    account: '1'
  };

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule, ReactiveFormsModule],
      declarations: [BookmarksContainerComponent, BookmarksListComponent, BookmarkItemComponent, CreateBookmarkComponent],
    }).compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(BookmarksContainerComponent);
    component = fixture.componentInstance;
    httpTestingController = TestBed.inject(HttpTestingController);
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('should search bookmarks', fakeAsync(() => {
    const sessionRequest = httpTestingController.expectOne('/api/system/connect');
    sessionRequest.flush({ user: { uid: '1' } });

    const bookmarksRequest = httpTestingController.expectOne('/api/bookmarks_link/search');
    bookmarksRequest.flush([aBookmark, anotherBookmark]);

    //Here I am not sure how to access the li(s) from the BookmarkItemComponent
    const item = fixture.debugElement.query(By.directive(BookmarkItemComponent));

});

I have tried to access the li(s) from the BookmarkItemComponent but without success: item is always null. I suspect it has something to do with the level of nesting of the BookmarkItemComponent… Can someone please help?

edit:

Template for BookmarkContainer:

<app-bookmarks-list
  [bookmarks]="bookmarks$ | async"
  [newBookmark]="newBookmark"
  [updatedBookmark]="updatedBookmark"
  [deletedBookmark]="deletedBookmarkId"
  (deleteBookmarkEvt)="confirmDeleteBookmark($event)"
  (updateBookmarkEvt)="openUpdateForm($event)">
</app-bookmarks-list>
...

Template for BookmarksListComponent:

<ng-container *ngFor="let bookmark of bookmarks">
  <div class="bookmark">
    <div class="">
      <ul class="bookmark-list">
        <app-bookmark-item
          [bookmark]="bookmark"
          (deleteBookmarkEvt)="deleteBookmark($event)"
          (updateBookmarkEvt)="updateBookmark($event)"
        >
        </app-bookmark-item>
      </ul>
    </div>
  </div>
</ng-container>

Template for BookmarkItemComponent:

<li class="bookmark-item bookmark-item-{{ bookmark.id }}">
  <span class="bookmark-icon"></span>
  <span class="bookmark-text">
    <a href="{{ bookmark.url }}" target="_blank" title="{{ 'BOOKMARKS.OPEN' | translate}}">
    {{bookmark.title}}
    </a>
  </span>
  <span class="bookmark-actions">
    <a title="{{ 'GLOBAL.UPDATE' | translate}}" (click)="updateBookmark(bookmark)">
      <i class="glyphicon glyphicon-pencil"></i>
    </a>
    <a title="{{ 'GLOBAL.DELETE' | translate}}" (click)="deleteBookmark(bookmark.id)">
      <i class="glyphicon glyphicon-remove "></i>
    </a>
  </span>
</li>


Get this bounty!!!

#StackBounty: #angular #typescript #angular-universal Why can't we manipulate objects passed to component via @input() in angular

Bounty: 50

I recently upgraded my app from angular 7 to angular 11.
Everything was working fine and then I implemented angular universal to enable server side rendering.

After implementing server side rendering i got lots of error saying "Can’t change readonly ‘xyz’ member of object [Object Object]"

All these members are from object that i have paaased to child component from parent component via @input()

My Questions

  1. Is it wrong to manipulate objects passed as Input
  2. Why is it breaking for Angular universal (server side rendering) and not for client side rendering

Here’s one such component

export class BannerComponent {

  @Input() banners : Offer[]
  constructor(private analyticService : AnalyticService) { }

  ngOnChanges() {
    if(this.banners) {
      this.banners.forEach(banner => {
        if(!banner.bannerImage.startsWith("http"))
          banner.bannerImage = environment.imageHost + banner.bannerImage;
      })
    }    
  }

  recordEvent(banner : Offer) {
    this.analyticService.eventEmitter(banner.category.name, "Click on banner", banner.offerDetail + "-" + banner.merchant.name, AnalyticService.AVG_AFFILIATE_CLICK_VALUE);
  }

}


Get this bounty!!!

#StackBounty: #angular #typescript #ngrx Understanding why an effect is re-executing

Bounty: 100

I am working on updating a login component to use an NgRx store, but I’m encountering something that I’ve never seen before and I’m not sure where I can be wrong since very similar code is working for other components in the same app.
The gist of the problem is that an Effect is being called over and over again, which for some reason re-triggers the same HTTP requests.

I’ll post the code I believe to be relevant but I can add more if it’s required.

Service:

login(request: LoginRequest): Observable<LoginResponse> {
    return this.http.post<LoginResponse>(`${environment.backendUrl}authentication/login`, request);
}

Effect:

@Effect()
login$: Observable<Action> = this.actions$
    .pipe(
        ofType<Login>(AuthenticationActionTypes.Login),
        switchMap(action => {
            console.log("in before login"); //---> gets printed only once
            return this.authenticationService.login(action.body)
                .pipe(
                    map(loginResponse => {
                        console.log("in login response"); // ---> gets printed until I refresh the page

                        return new LoginSuccess(loginResponse);
                    }),
                    catchError(error => of(new LoginError(error)))
                )
        })
    );

Actions:

export enum AuthenticationActionTypes {
    Login = '[Authentication] Login',
    LoginSuccess = '[Authentication] Login Success',
    LoginError = '[Authentication] Login Error',
    // a few more irrelevant types
}

export class Login implements Action {
    readonly type = AuthenticationActionTypes.Login;

    constructor(public body: LoginRequest) { }
}

export class LoginSuccess implements Action {
    readonly type = AuthenticationActionTypes.LoginSuccess;

    constructor(public response: LoginResponse) { }
}

export class LoginError implements Action {
    readonly type = AuthenticationActionTypes.LoginError;

    constructor(public error: any) { }
}

Facade:

constructor(private readonly store: Store<AuthenticationState>,
    private readonly actions$: Actions<AuthenticationActions>) {
}

public login(email: string, password: string): void {
    this.store.dispatch(new Login({ email: email, password: password }));
}

public successfulLogin$(): Observable<LoginSuccess> {
    return this.actions$
        .pipe(
            ofType<LoginSuccess>(AuthenticationActionTypes.LoginSuccess),
            take(1)
        );
}

public loginFailed$(): Observable<LoginError> {
    return this.actions$
        .pipe(
            ofType<LoginError>(AuthenticationActionTypes.LoginError),
            take(1)
        );
}

Login component (ts)

onSubmitLogin() {
    const userData = this.loginForm.value;

    this.authenticationFacade.login(userData.username, userData.password);

    this.authenticationFacade.successfulLogin$()
        .subscribe(() => {
            console.log("successful subscription called"); // ----> gets printed only once

            // do some stuff here
        });

    this.authenticationFacade.loginFailed$()
        .subscribe(() => {
            // -> doesn't get called, added just in case
            this.credentialsValid = false;
            this.submitLogin = false;
        });

}

In case it makes any difference, the process starts from:

<form [formGroup]="loginForm" (ngSubmit)="onSubmitLogin()">
...
<button type="submit" ... />

I tried to debug through VS Code, which I cannot easily understand since it goes into hundreds of angular/ngrx internal methods, and while the login method in the service gets stopped (breakpoint) only once, the inner console.log inside the effect gets stopped over and over again.
I know the problem is likely that I’m either not unsubscribing from something or leaking some resource, but I need help understanding how to figure out where is the issue.
What’s weird is that if I look at the call stack in Firefox/Edge, each subsequent HTTP post request has the first 100-something calls in the stack and adds around 98 more calls on top, all from internal Angular/NgRX modules.


If I edit the login$ effect to include a take(1) like below, then the never-ending calls disappear but I get a second attempt at making the same POST request that is immediately canceled, which I’d like to avoid if possible.

return this.authenticationService.login(action.body)
    .pipe(
        take(1),
        map(loginResponse => ...

enter image description here


Get this bounty!!!

#StackBounty: #angular #seam How to deal with org.jboss.seam.security.NotLoggedInException?

Bounty: 50

I have an application running with Angular, rest and seam framework. I have 2 user type: 1 normal user and second is admin. When I login with the normal user all works fine, when I login with admin the login works fine but when calling rest api I get this exception in the server side: org.jboss.seam.security.NotLoggedInException

I have the current code:

Code when user login:

@Out(required=true, scope=ScopeType.SESSION)
    private User user;
    
    public AccountFacadeREST() {
    }

@GET
    @Path("Authenticate")
    @Produces("application/json")   
    @Transactional
    public User authenticatie(@QueryParam("username") String username, @QueryParam("password") String password) {
        user = userDao.getUser(username);
        if (user != null) {
            passwordSupport.setPassword(password);      
            user = userDao.authenticatie(username, passwordSupport.getPasswordHash(user.getUserName()));            
        }
        return user;
    }

Code where an action is trigger from the Angular app through rest api:

@PUT
    @Path("CreateExcel")
    @Consumes("application/json")   
    public Response createsExcel(User adminUser) {      
        adminSupport.createExcel();
        return Response.ok().entity("excel created successfully").build();
    }

The error:

javax.servlet.ServletException: org.jboss.resteasy.spi.UnhandledException: org.jboss.seam.security.NotLoggedInException
    at org.jboss.seam.servlet.ContextualHttpServletRequest.run(ContextualHttpServletRequest.java:74)
    at org.jboss.seam.resteasy.ResteasyResourceAdapter.getResource(ResteasyResourceAdapter.java:121)
    at org.jboss.seam.servlet.SeamResourceServlet.service(SeamResourceServlet.java:80)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at com.funder.filters.CorsFilter.doFilter(CorsFilter.java:26)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:83)
    at org.jboss.seam.web.RewriteFilter.doFilter(RewriteFilter.java:63)
    at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
    at org.jboss.seam.web.IdentityFilter.doFilter(IdentityFilter.java:40)
    at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
    at org.jboss.seam.web.LoggingFilter.doFilter(LoggingFilter.java:60)
    at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
    at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:73)
    at org.jboss.seam.web.ExceptionFilter.doFilter(ExceptionFilter.java:64)
    at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
    at org.jboss.seam.web.RedirectFilter.doFilter(RedirectFilter.java:45)
    at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
    at org.ajax4jsf.webapp.BaseXMLFilter.doXmlFilter(BaseXMLFilter.java:206)
    at org.ajax4jsf.webapp.BaseFilter.handleRequest(BaseFilter.java:290)
    at org.ajax4jsf.webapp.BaseFilter.processUploadsAndHandleRequest(BaseFilter.java:388)
    at org.ajax4jsf.webapp.BaseFilter.doFilter(BaseFilter.java:515)
    at org.jboss.seam.web.Ajax4jsfFilter.doFilter(Ajax4jsfFilter.java:56)
    at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
    at org.jboss.seam.servlet.SeamFilter.doFilter(SeamFilter.java:158)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:496)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
    at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:650)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:803)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:790)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1459)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:748)

Any idea how to solve this problem ?


Get this bounty!!!

#StackBounty: #javascript #angular #session #cookies ngx-cookie gets deleted when link is opened in new tab in Angular Application

Bounty: 50

I have used a PC to serve the website via LAN connection and use the IPv4 address instead of localhost for hosting the website on LAN.

I’m using the npm package http-server for serving the dist compiled angular application.

I’m using ngx-cookie-service for manipulating cookies in the angular application.
However, the cookies get deleted when I open a page via router.navigate(). Also, the cookies don’t always get deleted as well.

So I’m kinda confused as to what is happening with the cookies.
What can be the reason?

P.S. I have not implemented session in the backend though.

Angular version : 10+
ngx-cookie-service: 11.0.2
browser: chrome 86


Get this bounty!!!

#StackBounty: #angular #directory-structure #angular10 #angular-library How to change Angular 10 library build structure

Bounty: 50

I’m maintaining an Angular 10 Ivy library and I’m attempting to upgrade it to make use of Secondary Entrypoints, but I’m running into trouble with folder structure of the final build.

Ideally, the folder structure would emulate @angular’s folder structure of:

-node_modules
 |
  -- @angular
     |
     --core
     --common
     --cli
     --material
     ...

and I could import pieces of the library like: import { someModule } from '@angular/core';

My current library builds with a folder structure of

-node_modules
 |
  --myLibrary
    |
     --package.json
     --dist/myLibrary
       |
        --package.json
        --public_api.ts
        --myLibrary.d.ts
        --feature1
        --feature2
        --feature3
        ...

which forces me to import features like import { feature1Module} from 'myLibrary/dist/myLibrary/feature1';.

I know I can remove the second myLibrary folder by updating the dest path in my myLibrary/ng-package.json, but the I don’t know how to remove the dist folder to make everything build on the same level, as setting "dest": "../.." will fail to build.

{
  "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
  "dest": "../../dist/myLibrary",
  "assets": [
    "quill/*.scss"
  ]
}

Any and all help is appreciated.

Update:
The project structure is

-projects
 |
  --myLibrary
    |
     --package.json
     --ng-package.json
     --src
       |
        --public_api.ts
        --commonFunctions
     --feature1
       |
       --package.json
       --src
         |
         --public_api.ts
         --index.ts
         --feature1.component.ts
         --feature1.component.html
         --feature1.module.ts
     --feature2
     --feature3
     ...

My projects/myLibrary/src/public_api.ts only exports the functions in commonFunctions and each of the public_api.ts files in each of the feature folders only exports the modules and components of their respective features.


Get this bounty!!!

#StackBounty: #android #library #ios #angular #typescript Ionic capacitor library for voice-to-text transcription

Bounty: 50

I am part of a non-profit NGO, we are developing an app to fight against violence and one of the features of our app is the possibility to ask for help by saying a specific word or phrase
We are using ionic with capacitor

The requirements would be:

  1. Continue running with the phone blocked, if not indefinitely, at least for 10 minutes
  2. Prevent self-disconnection when you say a word, for example, the google app when it detects a word and then silence, decides it’s over
  3. Works on ios and android

That we have tried:
https://ionicframework.com/docs/native/speech-recognition

Thank you very much


Get this bounty!!!

#StackBounty: #javascript #angular #okta Custom login page with OKTA and Angular 10

Bounty: 50

I’m having some issues here where is not logging me in. Nothing on the console either.
Here is the form in the login.component.html

<form [formGroup]="form"  (ngSubmit)="onSubmit()">
  <p *ngIf="loginInvalid" class="text-danger">
     The username and password were not recognized
  </p>
          
  <div class="form-group">
     <label for="username">Username</label>
     <input type="email" class="form-control" id="username">
  </div>
  <div class="form-group">
     <label for="exampleInputPassword1">Password</label>
     <input type="password" class="form-control" id="exampleInputPassword1" required>
  </div>
  <div class="form-group form-check">
     <input type="checkbox" class="form-check-input" id="rememerPass">
     <label class="form-check-label" for="rememerPass">Remember Password</label>
  </div>
  <button type="submit" class="btn btn-primary w-100 mt-4 mb-3">Login</button>
  <button type="button" class="btn btn-secondary w-100 mb-2">Register</button>
  <button type="button" class="btn btn-link w-100">Forgot Password</button>
</form>

the login.component.ts

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { AuthService } from '../../auth.service';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.scss']
})

export class LoginComponent implements OnInit {
  form: FormGroup;
  public loginInvalid: boolean;
  private formSubmitAttempt: boolean;
  private returnUrl: string;

  constructor(
    private fb: FormBuilder,
    private route: ActivatedRoute,
    private router: Router,
    private authService: AuthService
  ) {
 }

  async ngOnInit() {
    this.returnUrl = this.route.snapshot.queryParams.returnUrl || '/dashboard';

    this.form = this.fb.group({
      username: ['', Validators.email],
      password: ['', Validators.required]
    });

    if (await this.authService.checkAuthenticated()) {
      await this.router.navigate([this.returnUrl]);
    }
  }

  async onSubmit() {
    console.log("event fired");

    this.loginInvalid = false;
    this.formSubmitAttempt = false;
    if (this.form.valid) {
      try {
        const username = this.form.get('username').value;
        const password = this.form.get('password').value;
        await this.authService.login(username, password);
        console.log("username: ",username, "password: ", password);
    
      } catch (err) {
        this.loginInvalid = true;
      }
    } else {
      this.formSubmitAttempt = true;
    }
  }
}

and the auth.service.ts

import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { OktaAuth } from '@okta/okta-auth-js';
import { BehaviorSubject } from 'rxjs';

@Injectable({
  providedIn: 'root'
})

export class AuthService {
  private authClient = new OktaAuth({
    issuer:"https://dev-#######.okta.com/oauth2/default",
    clientId:"##########################",
  });

  public isAuthenticated = new BehaviorSubject<boolean>(false);

  constructor(private router: Router) {
  }

  async checkAuthenticated() {
    const authenticated = await this.authClient.session.exists();
    this.isAuthenticated.next(authenticated);
    return authenticated;
  }

  async login(username: string, password: string) {
    const transaction = await this.authClient.signIn({username, password});

    if (transaction.status !== 'SUCCESS') {
      throw Error('We cannot handle the ' + transaction.status + ' status');
    }
    this.isAuthenticated.next(true);

    this.authClient.session.setCookieAndRedirect(transaction.sessionToken);
  }

  async logout(redirect: string) {
    try {
      await this.authClient.signOut();
      this.isAuthenticated.next(false);
      this.router.navigate([redirect]);
    } catch (err) {
      console.error(err);
    }
  }
}

Nothing happens, although the logout does work (implemented in the navbar {not shown} )

What am I missing? What else do you need to see?


Get this bounty!!!

#StackBounty: #javascript #angular #angular-material #virtualscroll #angular-cdk-virtual-scroll Scroll to bottom with cdk-virtual-scrol…

Bounty: 100

Goal

Display a list of messages and scroll to bottom when a new message is received.

Problem

With virtual scroll I have to set the [itemSize] property, but for me it is not a static value:

  • When a message is too long for one line it breaks in multiple, so its height changes.
  • I have different types of messages with different heights (system messages for example).

Also, I am using ng-content to insert a button from the parent to load previous messages. What I see is that, when _scrollToBottom is invoked, instead of taking me to the bottom, it takes me to a bit higher. I suspect this is because of the button height.

I have read this autosize scroll strategy issue from Angular: https://github.com/angular/components/issues/10113; but I am not sure this will solve my problem.

Any idea of what I could do will be welcome.

Test

Codesandbox: https://codesandbox.io/s/angular-virtual-scroll-biwn6

  • While messages are being received, scroll up.
  • When the next message is loaded, instead of scrolling to bottom, the virtual-scroll stops a little higher.

Here is my code:

HTML

<cdk-virtual-scroll-viewport class="w-100 h-100" [itemSize]="65" #virtualScroll>
  <ng-content>
    <!-- HERE will go the load previous messages button -->
  </ng-content>

  <ul class="h-100 w-100">
    <li
      *cdkVirtualFor="
        let message of displayMessages$ | async;
        trackBy: messagesTrackByFn
      "
      [ngClass]="{
        'is-agent': isAgentMessage(message),
        'is-client': isCustomerMessage(message),
        'is-system': isSystemMessage(message),
      }"
    >
      <ng-container *ngIf="isSystemMessage(message)">
        <mat-card>
          <mat-icon [inline]="true">error_outline</mat-icon>
          <span [innerHTML]="message.body"></span>
        </mat-card>
      </ng-container>
      <!-- ... more types of messages in ng-container elements -->
    </li>
  </ul>
</cdk-virtual-scroll-viewport>

Typescript

export class MessageListComponent implements OnDestroy {
  @ViewChild("virtualScroll", { static: true })
  public virtualScrollViewport: CdkVirtualScrollViewport;
  private _lastMessageId = null;
  @Input() set messages$(value: Observable<Message[]>) {
    if (!!value) {
      this.displayMessages$ = value.pipe(
        takeUntil(this._destroy$),
        shareReplay({ refCount: true, bufferSize: 1 }),
        tap((messages) => {
          if (!messages || !messages.length) {
            return;
          }
          const lastMessage = messages[messages.length - 1];
          // Because of virtual-scroll, the number of items displayed is always the same
          // so to check if new elements have been added I compare the last message id
          if (!lastMessage.id || lastMessage.id !== this._lastMessageId) {
            this._scrollToBottom();
            this._lastMessageId = lastMessage.id;
          }
        })
      );
    }
  }

  //   ...

  private _scrollToBottom() {
    // I use setTimeout because this has to be executed after the view has rendered the elements
    setTimeout(
      () =>
        this.virtualScrollViewport.scrollTo({
          bottom: 0,
          behavior: "auto",
        }),
      0
    );
  }
}

Edit

package.json dependencies:

"dependencies": {
    "@angular/animations": "~8.2.14",
    "@angular/cdk": "^8.2.3",
    "@angular/common": "~8.2.14",
    "@angular/compiler": "~8.2.14",
    "@angular/core": "~8.2.14",
    "@angular/forms": "~8.2.14",
    "@angular/material": "^8.2.3",
    "@angular/material-moment-adapter": "^8.2.3",
    "@angular/platform-browser": "~8.2.14",
    "@angular/platform-browser-dynamic": "~8.2.14",
    "@angular/router": "~8.2.14",
    "matchmedia-polyfill": "^0.3.2",
    "rxjs": "~6.4.0",
    "zone.js": "~0.9.1"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "~0.803.25",
    "@angular/cli": "~8.3.26",
    "@angular/compiler-cli": "~8.2.14",
    "@angular/language-service": "~8.2.14",
    "@morphe/build-angular": "^3.1.0",
    "@morphe/schematic-angular": "^3.0.0-rc.2",
    "@types/jasmine": "~3.3.8",
    "@types/jasminewd2": "~2.0.3",
    "@types/node": "^8.9.5",
    "codelyzer": "^5.0.0",
    "jasmine-core": "~3.4.0",
    "jasmine-spec-reporter": "~4.2.1",
    "karma": "^4.1.0",
    "karma-chrome-launcher": "~2.2.0",
    "karma-coverage-istanbul-reporter": "~2.0.1",
    "karma-jasmine": "~2.0.1",
    "karma-jasmine-html-reporter": "^1.4.0",
    "protractor": "~5.4.0",
    "ts-node": "~7.0.0",
    "tslint": "~5.15.0",
    "tslint-sonarts": "^1.9.0",
    "typescript": "~3.5.3"
  }


Get this bounty!!!

#StackBounty: #angular #algolia #instantsearch.js How to get specific data from algolia in angular

Bounty: 50

I’m using algolia in angular.
I’m trying to get only a specific record from algolia.
I have a product_id and want record of that product_id
Here is what I have done so far –
.html file

    <ais-instantsearch [conproducts]>
        <ais-configure [searchParameters]="{ hitsPerPage: 1 }"></ais-configure>
        <ais-hits>
           <ng-template let-hits="hits">
           <div class="row">
                <div class="col-lg-2 col-md-3 col-sm-4 col-6"*ngFor="let hit of hits">
                    {{hit.name}}
                 </div>
           </div>
           </ng-template>
         </ais-hits>
    </ais-instantsearch>

.ts file

    const searchClient = algoliasearch(
      environment.algolia_application_id,
      environment.algolia_search_api_key
    );
    
    export class ProductsComponent implements OnInit {
      productsConfig = {
          indexName: 'products',
          searchClient
      };
    }

How to get specific data by id in the algolia?
Any help would be appreciated,
Thanks


Get this bounty!!!