list.component, which has an input variable tasks and listtype, which can be upcoming or completed. The #Output(s) are used if the task is checked (to become completed) and tasksChange, to change a task:
import {Component, EventEmitter, Input, Output} from '#angular/core';
import {Map, List} from 'immutable';
#Component({
selector: 'app-task-list',
templateUrl: 'task-list.component.html'
})
export class TaskListComponent {
#Input() tasks: string[] = [];
#Input() listType: 'upcoming' | 'completed' = 'upcoming';
#Output() itemChecked: EventEmitter<boolean> = new EventEmitter();
#Output() tasksChange: EventEmitter<string[]> = new EventEmitter();
constructor() { }
/**
* Is called when an item from the list is checked.
* #param selected---Value which indicates if the item is selected or deselected.
*/
onItemCheck(selected: boolean) {
this.itemChecked.emit(selected);
}
/**
* Is called when task list is changed.
* #param changedTasks---Changed task list value, which should be sent to the parent component.
*/
onTasksChanged(changedTasks: string[]) {
this.tasksChange.emit(changedTasks);
}
}
Now I wanted to display upcoming tasks with my upcoming-tasks.component, which has 3 mock tasks:
import { Component, OnInit } from '#angular/core';
import {TaskListComponent} from "../task-list/task-list.component";
#Component({
selector: 'app-upcoming-tasks',
templateUrl: './upcoming-tasks.component.html',
styleUrls: ['./upcoming-tasks.component.css']
})
export class UpcomingTasksComponent extends TaskListComponent {
upcomingTasks: string[] = ['bla', 'blub', 'bap'];
constructor() {
super();
}
ngOnInit(): void {
}
}
and the HTML of the upcoming-tasks.html:
<h1>Upcoming Tasks</h1>
<app-task-list [(tasks)]="upcomingTasks" [listType]="'upcoming'" (itemChecked)="onItemCheck($event)"></app-task-list>
So what am I missing to get these tasks on my UI?
Related
I am trying to show a list of Animals in my html page with their corresponding name and color.
My frontend gets the data from a spring backend that returns a list of Animals.
And I stumbled upon 2 questions that I have:
1)
I made the name and color properties private in the Animal class.
Code of the animal class:
interface AnimalJson {
name: string;
color: string;
}
export class Animal {
constructor(private name: string, private color: string) {}
static fromJSON(json: AnimalJson): Animal {
const a = new Animal(json.name, json.color);
return a;
}
}
code of my animal-component:
import { Component, OnInit } from '#angular/core';
import { Observable } from 'rxjs';
import { DataServiceService } from '../data-service.service';
import { Animal } from '../models/Animal';
#Component({
selector: 'app-animal',
templateUrl: './animal.component.html',
styleUrls: ['./animal.component.css'],
})
export class AnimalComponent implements OnInit {
public animals: Observable<Animal[]>;
constructor(private dataService: DataServiceService) {
this.animals = new Observable<Animal[]>();
}
ngOnInit(): void {
this.animals = this.dataService.getAnimals();
}
}
code of the service:
#Injectable({
providedIn: 'root',
})
export class DataServiceService {
constructor(private http: HttpClient) {}
getAnimals(): Observable<Animal[]> {
return this.http
.get<Animal[]>('http://localhost:8080/animals')
.pipe(map((animals: any[]): Animal[] => animals.map(Animal.fromJSON)));
}
}
code of the html-page:
<div *ngFor="let animal of animals | async">
<p>{{ animal.name }}</p>
</div>
Now when I try to get the animal.name, it gives an error that the name is private so I cant use it in my html page. How should I fix this? Should I just make it public? Or is there something I forget?
2)
Is this how you work with observables? Or am I using my observables in a wrong way?
Using the http get methode to get the observable and than call it in my animal-component and use async in my html-file to go over all the values in it?
If you use private then it should not be used in the html, am not sure why you are using a class for initializing the array. Just use a simple map statement.
If you are going to show it in the HTML then don't make the property private.
So the changes are.
interface Animal {
name: string;
color: string;
}
Service will be.
#Injectable({
providedIn: 'root',
})
export class DataServiceService {
constructor(private http: HttpClient) {}
getAnimals(): Observable<Animal[]> {
return this.http
.get<Animal[]>('http://localhost:8080/animals')
.pipe(map((animals: any[]): Animal[] => animals.map((item: Animal) => ({name: item.name, color: item.color}))));
}
}
Note: class can also be used as an interface, so when using animal you defined the properties as private, so you are unable to use in the HTML.
I have a component named RedirectUserToMobileAppComponent , I want to share a boolean property from it named enableLoginForm with app.component.
When I execute, I get this error :
enableLoginForm is undefined property on ngAfterViewInit in app.component
this is RedirectUserToMobileAppComponent component:
import {
Component,
ComponentFactoryResolver,
ComponentRef,
Inject,
Input,
OnInit,
Output,
ViewChild,
ViewContainerRef,
} from '#angular/core';
import { Observable, Subscription } from 'rxjs';
import { filter, map, pluck, tap } from 'rxjs/operators';
import { ActivatedRoute, Router } from '#angular/router';
import { MAT_DIALOG_SCROLL_STRATEGY_FACTORY } from '#angular/material/dialog';
#Component({
selector: 'redirect-user-to-mobile-app',
templateUrl: './redirect-user-to-mobile-app.component.html',
styleUrls: ['./redirect-user-to-mobile-app.component.sass'],
})
export class RedirectUserToMobileAppComponent implements OnInit {
constructor(
) {}
enableLoginForm = false;
ngOnInit(): void {}
OnLogin(): void {
this.enableLoginForm = true;
this.router.navigate(['../login']);
}
}
and this is app.component:
import {
Component,
HostListener,
OnDestroy,
OnInit,
ViewChild,
AfterViewInit,
} from '#angular/core';
import { MatIconRegistry } from '#angular/material/icon';
import { DomSanitizer } from '#angular/platform-browser';
import { FirebaseService } from './services/firebase/firebase.service';
import {
SnakeMessage,
SnakeMessageService,
} from './services/snakeMessage/snakeMessage.service';
import { MatSnackBar } from '#angular/material/snack-bar';
import { Subscription } from 'rxjs';
import { StorageService } from './services/storage/storage.service';
import { AuthService } from './services/auth/auth.service';
import { RedirectUserToMobileAppComponent } from './redirect-user-to-mobile-app/redirect-user-to-mobile-app.component';
#Component({
selector: 'app-component',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent implements OnInit, OnDestroy, AfterViewInit {
favIcon: HTMLLinkElement = document.querySelector('#appIcon');
private snakeMessageSub: Subscription;
isLoading = true;
isLogged: boolean;
#ViewChild(RedirectUserToMobileAppComponent)
redirectComponent!: RedirectUserToMobileAppComponent;
constructor(
private matIconRegistry: MatIconRegistry,
private firebaseService: FirebaseService,
private snakeMessageService: SnakeMessageService,
private _snackBar: MatSnackBar,
private storageService: StorageService,
private domSanitizer: DomSanitizer,
private authService: AuthService
) {
this.registerCustomIcons();
this.storageService.initDB();
this.storageService.onLoaded$.subscribe((loaded) => {
if (loaded) {
this.isLoading = false;
}
});
this.isLogged = this.authService.isLoggedIn;
}
ngAfterViewInit() {
if (this.redirectComponent.enableLoginForm) {
this._is = this.redirectComponent.enableLoginForm;
}
}
ngOnInit(): void {
this.snakeMessageSub = this.snakeMessageService.messageSub.subscribe(
(snakeMessage: SnakeMessage) => {
this._snackBar.open(snakeMessage.message, snakeMessage.action, {
duration: 3000,
horizontalPosition: 'center',
verticalPosition: 'top',
});
}
);
}
this is my app.component.html
<ng-container *ngIf="!isLoading">
<ng-container *ngIf="isMobileDevice() && !isLogged">
<redirect-user-to-mobile-app> </redirect-user-to-mobile-app>
<router-outlet
*ngIf="enableLoginForm"
></router-outlet>
</ng-container>
<router-outlet *ngIf="!isMobileDevice()"></router-outlet>
This is how you use ViewChild:
#ViewChild('templateId', { static: false }) redirectComponent: RedirectUserToMobileAppComponent;
You should have the templateId set in the template part :
<redirect-user-to-mobile-app #templateId> ... </redirect-user-to-mobile-app>
EDIT: Though I agree with skyBlue, you should use a service to shared data between components
ViewChild returns a reference to the HTML element.
I will quote from angular.io:
Property decorator that configures a view query. The change detector looks for the first element or the directive matching the selector in the view DOM. If the view DOM changes, and a new child matches the selector, the property is updated.
So you cant access it's controller variables with ViewChild.
My suggestion for you is to use a service for passing data.
I have changed the method, I have used #Output() component and it works fine:
this is RedirectUserToMobileAppComponent component after changing the method :
import {
Component,
ComponentFactoryResolver,
ComponentRef,
Inject,
Input,
OnChanges,
OnInit,
Output,
SimpleChanges,
ViewChild,
ViewContainerRef,
EventEmitter,
} from '#angular/core';
import { Observable, Subscription } from 'rxjs';
import { filter, map, pluck, tap } from 'rxjs/operators';
import { ActivatedRoute, Router } from '#angular/router';
import { MAT_DIALOG_SCROLL_STRATEGY_FACTORY } from '#angular/material/dialog';
#Component({
selector: 'yobi-redirect-user-to-mobile-app',
templateUrl: './redirect-user-to-mobile-app.component.html',
styleUrls: ['./redirect-user-to-mobile-app.component.sass'],
})
export class RedirectUserToMobileAppComponent implements OnInit {
constructor(
private router: Router,
private componentFactoryResolver: ComponentFactoryResolver
) {}
#Output() _enableLoginForm: EventEmitter<boolean> = new EventEmitter();
variable: any;
login: boolean;
enableLoginForm = false;
enableSignupForm = false;
ngOnInit(): void {}
sendDataToParent() {
this.enableLoginForm = true;
this._enableLoginForm.emit(this.enableLoginForm);
console.log(this.enableLoginForm + ' From redirect ');
}
I added this to RedirectUserToMobileAppComponent.html:
<a class="login-text" (click)="sendDataToParent()">
Login
</a>
I added this code to app.component :
receiveChildData($event) {
this.enableLoginForm = $event;
}
I added this code to the app.component.html :
<redirect-user-to-mobile-app
(_enableLoginForm)="receiveChildData($event)"
>
</redirect-user-to-mobile-app>
I want to change an HTML view via *ngIf, based on a local variable, which should change based on a variable delivered through an observable from a shared service.
HTML
<div class="login-container" *ngIf="!isAuthenticated">
TypeScript of same component:
export class LoginComponent implements OnInit {
authenticationsSubscription;
isAuthenticated: boolean;
constructor(
private authService: AuthServiceService,
private router: Router,
private route: ActivatedRoute){}
getAuth(): Observable<boolean>{
return this.authService.validation();
}
ngOnInit() {
this.authenticationsSubscription = this.authService.validation().subscribe(auth => this.isAuthenticated = auth);
}
}
TypeScript of shared service AuthService:
export class AuthServiceService {
isAuthenticated: boolean;
validation(): Observable<boolean>{
return of(this.isAuthenticated);
}
}
While debugging I found out, the variable isAuthenticated in the LoginComponent does not change, on changes of the variable isAuthenticated of the AuthService. I also tried using pipe() and tap(), which did not change anything.
What am I doing wrong?
Convert your AuthServiceService to have the authentication state as a BehaviorSubject and return it as Observable as described below.
import { Observable, BehaviorSubject } from "rxjs";
export class AuthServiceService {
private isAuthenticatedSub: BehaviorSubject<boolean> = new BehaviorSubject(false);
set isAuthenticated(isAuthenticated: boolean) {
this.isAuthenticatedSub.next(isAuthenticated);
}
get isAuthenticated(): boolean {
return this.isAuthenticatedSub.value;
}
validation(): Observable<boolean> {
return this.isAuthenticatedSub.asObservable();
}
}
The actual subscription of your observable will only happens once, when the OnInit lifecycle hook is triggered when the component is initialized.
You can subscribe to a BehaviorSubject in order to catch value changes.
Stackblitz example
AuthService
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs';
#Injectable()
export class AuthService {
isAuthenticated: BehaviorSubject<boolean>;
constructor() {
this.isAuthenticated = new BehaviorSubject<boolean>(false);
}
}
Component
import { Component, OnInit } from '#angular/core';
import { AuthService } from './auth.service';
import { Observable } from 'rxjs';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
isAuthenticated: Observable<boolean>;
constructor(private authService: AuthService) {}
ngOnInit() {
this.isAuthenticated = this.authService.isAuthenticated;
}
login() {
this.authService.isAuthenticated.next(true);
}
logout() {
this.authService.isAuthenticated.next(false);
}
}
Template
<div *ngIf="isAuthenticated | async; else notAuthenticated">
User is authenticated
</div>
<ng-template #notAuthenticated>
<div>User isn't authenticated</div>
</ng-template>
<button (click)="login()">Login</button>
<button (click)="logout()">Logout</button>
I can pass a class object like Person into a child component from parent component without any problems. But I would like to also manipulate that object in child component and pass it back to parent component.
This is the child component class:
export class ActionPanelComponent {
#Input('company') company: Company;
#Output() notify: EventEmitter = new EventEmitter();
constructor() {
}
public deleteCompany() {
console.log('display company');
console.log(this.company);
// FIXME: Implement Delete
this.company = new Company();
}
public onChange() {
this.notify.emit(this.company);
}
}
This is the html of this component (excerpt):
<div class="row" (change)="onChange()">
<div class="col-xs-2">
<button md-icon-button >
<md-icon>skip_previous</md-icon>
</button>
</div>
This is the parent component class (excerpt):
public onNotify(company: Company):void {
this.company = company;
}
And the parent component html (excerpt):
<action-panel [company]="company" (notify)="onNotify($event)"></action-panel>
I am doing something wrong because I cannot pass my company object inside the .emit and nothing works.
What is the correct way of achieving two way object binding between components?
Thanks in advance!
You were missing the type on the initialization of the EventEmitter.
You could use the Output binding to implement the two way object binding:
Child component (ts)
export class ActionPanelComponent {
#Input('company') company: Company;
#Output() companyChange: EventEmitter = new EventEmitter<Company>();
constructor() {
}
public deleteCompany() {
console.log('display company');
console.log(this.company);
// FIXME: Implement Delete
this.company = new Company();
}
public onChange() {
this.companyChange.emit(this.company);
}
}
Parent component (html)
<action-panel [(company)]="company"></action-panel>
So like this you don't need to declare an extra function onNotify. If you do need the onNotify function, use another name for the output binding:
export class ActionPanelComponent {
#Input('company') company: Company;
#Output() notify: EventEmitter = new EventEmitter<Company>();
constructor() {
}
public deleteCompany() {
console.log('display company');
console.log(this.company);
// FIXME: Implement Delete
this.company = new Company();
}
public onChange() {
this.notify.emit(this.company);
}
}
Change it like this to tell TS which Type the EventEmitter should emit:
export class ActionPanelComponent {
#Input('company') company: Company;
#Output() notify = new EventEmitter<Company>(); //<---- On this line!
constructor() {
}
public deleteCompany() {
console.log('display company');
console.log(this.company);
// FIXME: Implement Delete
this.company = new Company();
}
public onChange() {
this.notify.emit(this.company);
}
}
It is a workaround that worked for me, if it is helpful for anyone.
Your parent parent-component.ts would be like;
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'parent',
templateUrl:'./parent.component.html',
styleUrls: ['./parent.component.css']
})
export class Parent implements OnInit {
let parentInstance= this; //passing instance of the component to a variable
constructor() { }
parentMethod(var:<classtyepyourchoice>){
console.log(var);
}
ngOnInit() {
}
}
In you parent.component.html, you would have your child
<child [parent]="parentInstance" ></child>
This object will be available in the child component
Now, in your child component you will receive this like
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'child',
templateUrl:'./child.component.html',
styleUrls: ['./child.component.css']
})
export class Child implements OnInit {
#Input('parent') parent;
constructor() { }
childMethod(yourClassObject){
this.parent.parentMethod(yourClassObject);
}
ngOnInit() {
}
}
Thus, you can pass classobject from your child, like this, it worked for me.
I have an image element that I am trying to use ViewChild with:
<img class="postimage" #imagey [src]="">
My controller is this:
import { Component, ViewChild, ElementRef, Renderer } from '#angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
/**
* Generated class for the PostpagePage page.
*
* See http://ionicframework.com/docs/components/#navigation for more info
* on Ionic pages and navigation.
*/
#IonicPage()
#Component({
selector: 'page-postpage',
templateUrl: 'postpage.html',
})
export class PostpagePage {
#ViewChild('imagey') image:ElementRef;
imageHolder;
constructor(public myrenderer: Renderer, public navCtrl: NavController, public navParams: NavParams) {
}
ionViewDidLoad() {
this.imageHolder = this.navParams.get("path");
this.myrenderer.setElementAttribute(this.image.nativeElement, 'src', this.imageHolder);
console.log(JSON.stringify(this.image));
}
pushPage(){
// push another page on to the navigation stack
// causing the nav controller to transition to the new page
// optional data can also be passed to the pushed page.
//this.navCtrl.push(SignUpPage);
}
}
The result of the console message in ionViewDidLoad is:
{"nativeElement":{}}
It doesn't seem to be returning an element.
I had to remove the ion-item that was containing the image - then it worked. It is actually still loggin an empty object to the console.