Cannot read property 'nativeElement' of undefined - ngAfterViewInit - html

I'm trying to add a "clipboard" directive using this example. The example is now outdated so I have had to update how it is getting the nativeElement.
I'm getting the error
Cannot read property 'nativeElement' of undefined
I have marked the error in the code with <===== error here:
clipboard.directive.js
import {Directive,ElementRef,Input,Output,EventEmitter, ViewChild, AfterViewInit} from "#angular/core";
import Clipboard from "clipboard";
#Directive({
selector: "[clipboard]"
})
export class ClipboardDirective implements AfterViewInit {
clipboard: Clipboard;
#Input("clipboard")
elt:ElementRef;
#ViewChild("bar") el;
#Output()
clipboardSuccess:EventEmitter<any> = new EventEmitter();
#Output()
clipboardError:EventEmitter<any> = new EventEmitter();
constructor(private eltRef:ElementRef) {
}
ngAfterViewInit() {
this.clipboard = new Clipboard(this.el.nativeElement, { <======error here
target: () => {
return this.elt;
}
} as any);
this.clipboard.on("success", (e) => {
this.clipboardSuccess.emit();
});
this.clipboard.on("error", (e) => {
this.clipboardError.emit();
});
}
ngOnDestroy() {
if (this.clipboard) {
this.clipboard.destroy();
}
}
}
html
<div class="website" *ngIf="xxx.website !== undefined"><a #foo href="{{formatUrl(xxx.website)}}" target="_blank" (click)="someclickmethod()">{{xxx.website}}</a></div>
<button #bar [clipboard]="foo" (clipboardSuccess)="onSuccess()">Copy</button>
How do I get rid of that error?
Updated to not use AfterViewInit because it is not a view...same error:
#Directive({
selector: "[clipboard]"
})
export class ClipboardDirective implements OnInit {
clipboard: Clipboard;
#Input("clipboard")
elt:ElementRef;
#ViewChild("bar") el;
#Output()
clipboardSuccess:EventEmitter<any> = new EventEmitter();
#Output()
clipboardError:EventEmitter<any> = new EventEmitter();
constructor(private eltRef:ElementRef) {
}
ngOnInit() {
this.clipboard = new Clipboard(this.el.nativeElement, {
target: () => {
return this.elt;
}
} as any);
I think I need to not use #viewChild because it is not a component but am unsure how to populate el or eltRef. el is only there to replace eltRef because I couldn't populate eltRef.

You name ElementRef as eltRef but try to use this.el in ngAfterViewInit. You need to use the same name.
this will work:
constructor(private el:ElementRef) {
}
ngAfterViewInit() {
this.clipboard = new Clipboard(this.el.nativeElement, {
target: () => {
return this.elt;
}
}

Related

#viewChild and #ViewChildern gives undefined

I'm working on Angular 9 and want to access an input field after clicking on a button. right now it gives me undefined. I have tried #ViewChild and #viewChildern because I'm using ngIf.
Template.html file
<div class="search-input" #searchDiv *ngIf="serachActive">
<input
#searched
autofocus
type="text"
class="serach-term"
placeholder="Search"
[(ngModel)]="searchTerms"
(ngModelChange)="applySearch()"
/>
<button (click)="toggleSearch(!serachActive)">
<span class="material-icons"> search </span>
</button>
<ul class="search-list">
<li *ngFor="let result of results">
<a [routerLink]="['/', 'video', 'details', result._id]">{{
result.title ? result.title : ''
}}</a>
</li>
</ul>
</div>
Template.ts file
import { Component, OnInit,AfterViewInit,ElementRef,ViewChild,ViewChildren } from '#angular/core';
import { UserService } from '../../../user.service';
import { VideoService } from '../../../services/video.service';
import { Subject } from 'rxjs';
import { distinctUntilChanged, debounceTime } from 'rxjs/operators';
import { Router } from '#angular/router';
#Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.css'],
})
export class HeaderComponent implements OnInit,AfterViewInit{
serachActive: boolean = false;
#ViewChildren('searched') searchElement: ElementRef;
#ViewChildren("searched") input: ElementRef;
user;
subject = new Subject<string>();
results = [];
searchTerms;
loggedIn: Boolean = false;
constructor(
private userService: UserService,
private videoService: VideoService,
private router: Router
) {
this.user = this.userService.getUser();
this.loggedIn = this.userService.isAuthenticated();
}
ngOnInit() {
console.log('on init', this.input); //undefined
this.subject
.pipe(debounceTime(400), distinctUntilChanged())
.subscribe((value) => {
this.router.navigate(['search'], { queryParams: { term: value } });
});
}
ngAfterViewInit() {
console.log('on after', this.input); //undefined
}
toggleSearch(toggledata) {
this.serachActive = toggledata;
this.results = [];
this.searchTerms = '';
console.log(this.input) //undefined
console.log(this.searchElement.nativeElement) //undefined
}
applySearch() {
const searchText = this.searchTerms;
this.subject.next(searchText);
this.searchElement.nativeElement.focus(); //undefined
}
menuButtonClick(button){
if(button === "history"){
this.router.navigate(['history'])
}
}
}
Use ViewChild since you're only searching for 1 element ID.
If adding { static: true } or { static: false } in your ViewChild options doesn't work as what is stipulated on Angular Static Query Migration Documentation
Use ChangeDetectorRef instead:
#Component({...})
export class AppComponent {
#ViewChild('searchInput') input: ElementRef;
isShow: boolean = false;
constructor(private cdr: ChangeDetectorRef) {}
toggle(): void {
this.isShow = !this.isShow;
this.cdr.detectChanges(); // Detects changes which this.isShow is responsible on showing / hiding
// the element you're referencing to in ViewChild
if (this.isShow) // If element is shown, console the referenced element
console.log(this.input);
}
}
Have created a Stackblitz Demo for your reference

triggering ngOnInit of children components angular

I have a father component and several child components displayed as labels. I'm having trouble to control the behavior of the children ngOnInit as when changing from one label to another, ngOnInit of children won't trigger.
The father is app-management and its html is:
<mat-tab-group dynamicHeight="true">
<mat-tab label="Application Loader">
<app-loader [deviceId]="deviceId"></app-loader>
</mat-tab>
<mat-tab label="Application Config">
<app-config [deviceId]="deviceId"></app-config>
</mat-tab>
<mat-tab label="Docker">
<app-docker [deviceId]="deviceId"></app-docker>
</mat-tab>
<mat-tab label="Application Logs">
<app-logs [deviceId]="deviceId"></app-logs>
</mat-tab>
<mat-tab label="Ack Logs">
<exec-logs [deviceId]="deviceId"></exec-logs>
</mat-tab>
<mat-tab label="Docker Logs">
<docker-logs [deviceId]="deviceId"></docker-logs>
</mat-tab>
</mat-tab-group>
When introducing a console.log in ngOnint of app-docker doesn't work. I need to implemement a subscription in app-docker from the event in app-loader as when the application changes that change is displayed in app-docker, but, as ngOninit won't trigger when navigating to that label, I have no idea how to solve this.
The structure in VC it's aa follows:
>app-config
>app-docker
>app-loader
>app-logs
>docker-logs
>exec-logs
app-management.component.html
app-management.component.scss
app-management.component.ts
app-management.service.ts
I have tried this: on my app-docker.ts
import { Component, OnInit, Input, ChangeDetectorRef } from '#angular/core';
import { AppManagementService } from "../app-management.service";
import { SnackMessageService } from "app/main/common/services/snackMessage.service";
import { Subscription } from 'rxjs';
#Component({
selector: "app-docker",
templateUrl: "./app-docker.component.html",
styleUrls: ["./app-docker.component.scss"],
})
export class AppDockerComponent implements OnInit {
#Input() deviceId: string;
configErrorMessage: string;
dataContainer: any;
application: any;
private appChangeSub: Subscription;
constructor(
private appManagementService: AppManagementService,
private snackMessageService: SnackMessageService, private cd: ChangeDetectorRef) {
this.dataContainer = {
containerInfo: [],
dockerInfo: [],
timestamp: ""
}
this.application = {
lastUpdate: new Date(0),
registryId: "",
applicationId: ""
}
}
ngOnInit() {
this.appManagementService.getDevicepplication(this.deviceId).then(response => {
this.application = response
if (this.application.applicationId !== "") {
this.postContainerInfo();
}
this.appChangeSub = this.appManagementService.onSelectedApplicationsChanged.subscribe(app => {
this.application.applicationId = app
})
}).catch(()=> this.configErrorMessage = "Oops, could not get application info")
}
public postContainerInfo() {
this.appManagementService.postContainerInfo(this.deviceId).then(() => { this.getDockerContainerInfo() }).catch(() => this.configErrorMessage = "Oops, could not send container info message.")
}
public getDockerContainerInfo() {
this.appManagementService.getDockerContainerInfo(this.deviceId).then(response => {
this.dataContainer = response
}).catch(() => this.configErrorMessage = "Oops, could not get docker data.");
}
the app-loader loads a new application, and every time this application changes I need to display these changes when going to app-docker label, without pressing any button. Is that possible?
my app-loader component .ts
import { Component, OnInit, Input, OnDestroy } from "#angular/core";
import { FormGroup, FormBuilder, Validators } from "#angular/forms";
import { RegistryModel } from "app/main/registry/registry.model";
import { ApplicationModel } from "app/main/registry/application.model";
import { AppManagementService } from "../app-management.service";
import { SnackMessageService } from "app/main/common/services/snackMessage.service";
import { MatDialogRef, MatDialog } from "#angular/material";
import { FuseConfirmDialogComponent } from "#fuse/components/confirm-dialog/confirm-dialog.component";
#Component({
selector: "app-loader",
templateUrl: "./app-loader.component.html",
styleUrls: ["./app-loader.component.scss"]
})
export class AppLoaderComponent implements OnInit, OnDestroy {
#Input() deviceId: string;
ngOnInit() {}
public sendRegistryForm(): void {
this.confirmDialogRef = this.matDialog.open(FuseConfirmDialogComponent, {
disableClose: false
});
this.confirmDialogRef.componentInstance.confirmMessage = "Are you sure you want to send this application to the device?";
this.confirmDialogRef.afterClosed()
.subscribe(result => {
if (result) {
this.appManagementService.sendApplicationToDevice(this.deviceId, this.registryForm.value.registryId, this.registryForm.value.applicationId).then(() => {
this.loaderErrorMessage = null;
this.snackMessageService.sendMessage("Application sent");
}).catch(() => this.loaderErrorMessage = "Oops, could not send the application to the device.");
}
this.confirmDialogRef = null;
});
}
}
In the appmanagementservice, I'm trying to implement an emitter with next each time a new application is loaded, but, I'm not sure this is right. The method postContainerInfo in app-docker is sending a message to an mqtt service, updating a ddbb, and then getting the info updated through getContainerInfo().
My app-management.service.ts:
import { Injectable } from "#angular/core";
import { RegistryModel } from "app/main/registry/registry.model";
import { BackEndCommunicationService } from "app/main/common/services/beComm.service";
import { BehaviorSubject, Subject } from "rxjs";
#Injectable({
providedIn: "root"
})
export class AppManagementService {
onSelectedApplicationsChanged: Subject<any>;
constructor(private backEndCommunicationService: BackEndCommunicationService) {
this.onSelectedApplicationsChanged = new Subject();
}
public getDevicepplication(deviceId: string): Promise<any> {
return new Promise((resolve, reject) => {
this.backEndCommunicationService.getResource("/devices/" + deviceId).then((response: any) => {
this.onSelectedApplicationsChanged.next()
resolve(response.response.application);
}).catch(error => {
console.log(error);
reject("Error getting persistant size")
})
})
}
public sendApplicationToDevice(deviceId: string, registryId: string, applicationId: string): Promise<void> {
return new Promise((resolve, reject) => {
const sendObject = {
data: {
registryId: registryId,
applicationId: applicationId
}
};
this.backEndCommunicationService.postResource("/devices/" + deviceId + "/application", sendObject).then(() => {
resolve();
}).catch(error => {
console.log(error);
reject("Error sending app to device");
});
});
}
The ngOnInit won't run, because the DOM: the tab labels and the content, is built once, not rebuilt on every click.
You can use the selectedTabChange event to react to the tab change. What exactly are you trying to achieve?

Problem withAngular when I want to add elements in array

I have an animals array. I want to put in another array just the field "espece" of each animal.
I push all the espece of animals in especeAnimalPresenteTmp and then I remove the duplicates and save the next array in especeAnimalPresente.
I have this angular code :
import { Component, OnInit } from '#angular/core';
import { AnimalService } from "./animal.service";
import { Animal } from "./animal";
#Component({
selector: 'app-animal',
templateUrl: './animal.component.html',
styleUrls: ['./animal.component.css']
})
export class AnimalComponent implements OnInit {
private animaux:Array<Animal>;
private especeAnimalPresente:Array<string>;
private especeAnimalPresenteTmp:Array<string>;
constructor(private animalService: AnimalService) { }
ngOnInit() {
this.recupAllAnimals();
}
recupAllAnimals(){
this.animalService.getAllAnimaux().subscribe(data => {
this.animaux = data;
this.recupEspecePresent();
})
}
recupEspecePresent(){
// if (this.animaux){
for (let animal of this.animaux) {
this.especeAnimalPresenteTmp.push(animal.espece);
}
this.especeAnimalPresente = this.removeDuplicates(this.especeAnimalPresenteTmp);
// }
}
removeDuplicates(array) {
let unique = {};
array.forEach(function(i) {
if(!unique[i]) {
unique[i] = true;
}
});
return Object.keys(unique);
}
}
But I have this error in my console :
ERROR TypeError: "this.especeAnimalPresenteTmp is undefined"
recupEspecePresent animal.component.ts:32
recupAllAnimals animal.component.ts:24
RxJS 11
Angular 8
Someone can help me please ?
You have to initialize the array, for example in the constructor:
constructor(private animalService: AnimalService) {
this.especeAnimalPresenteTmp = [];
}

How can I get the real image value from each item in my list and subscribe it to another list?

I have a list of services that have multiple property like serviceId, serviceName and photoProfile called from a database using a spring REST API.
The 'photoProfile' property only has the id of the profile picture which if you use the 'localhost:8082/downloadFile/'+photoProfile would get you the image which is in turn is stored in a folder in the spring project.
After looking for a while online, I've found how I can actually get the real image to display on my website but now I'm stuck since I need to do this for the whole list.
Here's my angular code:
import { Component, OnInit } from '#angular/core';
import { Router } from '#angular/router';
import { LoginComponent } from '../login/login.component';
import { UserService } from '../user.service';
import { Observable, forkJoin } from 'rxjs';
import { HttpHeaders, HttpClient } from '#angular/common/http';
import { combineLatest } from 'rxjs/operators';
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {
loggedIn: boolean;
services: any[] = [];
imgSrc: any;
newList: any[] = [];
constructor(private router: Router, private service: UserService, private http: HttpClient) {
}
ngOnInit() {
this.service.getServices().subscribe(res => {
this.services = res;
console.log('services: ', this.services);
});
for (let i = 0; i < this.services.length; i++) {
const element = this.services[i];
this.getImage('http://localhost:4200/downloadFile/' + element.photoProfile).subscribe(data => {
this.createImageFromBlob(data);
});
this.newList.push(this.imgSrc);
console.log(this.newList);
//I want to add the element from the services list and the image value after being converted to the new list
}
}
getImage(imageUrl: string): Observable<Blob> {
return this.http.get(imageUrl, {responseType: 'blob'});
}
createImageFromBlob(image: Blob) {
const reader = new FileReader();
reader.addEventListener('load', () => {
this.imgSrc = reader.result;
}, false);
if (image) {
reader.readAsDataURL(image);
}
}
}
Thank you for your help.
You need to add the new list inside the ngOnInit after you are subscribing to the services list. Because currently. You don't have the services when the for loop runs. You need to run the for loop after you have the result from services. Like this:
ngOnInit() {
this.service.getServices().subscribe(res => {
this.services = res;
console.log('services: ', this.services);
for (let i = 0; i < this.services.length; i++) {
const element = this.services[i];
this.getImage('http://localhost:4200/downloadFile/' + element.photoProfile).subscribe(data => {
this.createImageFromBlob(data);
element.imgSrc = this.imgSrc;
this.newList.push(element);
});
console.log(this.newList);
}
}
});
I had similar situation, and method of Muhammad Kamran work particularry for me, because images loaded into array absolutely randomly. As i understand, the speed of FOR cycle is faster than picture download speed. The solution is - pushing into array in createImageFromBlob (in case of i'mgnome). In my case it was like this:
export interface File {
...
original_name: name of file;
linkToPicture: string;
image: any;
}
...
files: File[] = [];//array for incoming data with link to pictures
this.getTable(...)
...
getTable(sendingQueryForm: QueryForm){
this.queryFormService.getTable(sendingQueryForm)
.subscribe(
(data) => {
this.files=data as File[];
for (let i = 0; i < this.files.length; i++) {
this.getImage('/api/auth/getFileImage/' + this.files[i].linkToPicture).subscribe(blob => {
this.createImageFromBlob(blob,i);
});
}
},
error => console.log(error)
);
}
getImage(imageUrl: string): Observable<Blob> {
return this.http.get(imageUrl, {responseType: 'blob'});
}
createImageFromBlob(image: Blob, index:number) {
const reader = new FileReader();
reader.addEventListener('load', () => {
this.files[index].image = reader.result;
}, false);
if (image) {
reader.readAsDataURL(image);
}
}
and in HTML:
<div *ngFor="let block of files; let i = index" >
<mat-card class="grid-card">
<div>
<img [src]="block.image" width=120>
<p>{{block.original_name}}</p>
</div>
</mat-card>
</div>
I hope it will useful for someone and thanks for topic!

ngAfterViewInit local variables undefined angular 6

I am getting this.objectsCount is undefined in addcomponent function
what am I doing wrong? if I debug this has only "div,el,sidenav,subscription".
export class SidenavComponent implements OnInit {
subscription: Subscription;
tog: { compact: boolean, tag: string };
objectsCount: { tag: any, ctrl: Object }[];
compact: boolean;
tag: string;
#ViewChild('sidebar') div;
objects: any;
constructor(private sidenav: SidenavService, private el: ElementRef) {
}
ngOnInit() {
this.subscription = this.sidenav.onToggle().subscribe((toggle) => {
this.tog = toggle;
if (toggle.tag) {
let nav = this.el.nativeElement;
//nav.attributes.
}
});
}
addcomponent(elem) {
this.objectsCount.push({ tag: elem.nativeElement("tag").value, ctrl:
elem.nativeElement });
}
ngAfterViewInit() {
this.addcomponent(this.div);
}
}
You never initialize the objectsCount Array, set it in the OnInit function
this.objectsCount = [];