Angular View binding not updating with simple boolean - html

Goodday, This is probably a nooby question but I can't get it to work.
I have a simple service which toggles an boolean, if the boolean is true the class active should appear on my div and if false no class.. Simple as that. But the boolean gets updated, but my view doesn't react to it. Do I somehow have to notify my view that something has changed ?
Service:
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class ClassToggleService {
public menuActive = false;
toggleMenu() {
this.menuActive = !this.menuActive;
}
}
View (left menu component):
<div id="mainContainerRightTop" [class.active]="classToggleService.menuActive == true">
Toggle point (top menu component):
<a id="hamburgerIcon" (click)="classToggleService.toggleMenu()">

This because you are changing a value on a service not on the component, so Angular don't need to update the component, because it did not change. If you want to update the view of your component when a service element is modified, you have to use an Observables and Subjects, and subscribe to them. In this way when the element is changed, it automatically notify all the subscribed components.
#Injectable({
providedIn: 'root'
})
export class ClassToggleService {
public menuSubject: Subject<boolean> = new BehaviorSubject<boolean>(false);
public menuActive = this.menuSubject.asObservable();
toggleMenu(val : boolean) {
this.menuSubject.next(val);
}
}
And in your component just implement OnInit interface and subcribe to the observable in the your service:
public localBool = false;
ngOnInit() {
this._myService.menuActive.subscribe(value => this.localBool = value);
}
ComponentToggleMenu() {
this._myService.toggleMenu(!this.localBool);
}
Then your html:
<div id="mainContainerRightTop" [class.active]="localBool">
<a id="hamburgerIcon" (click)="ComponentToggleMenu()">

Why we need service, this should be integrated with component class. As a general rule, you are not supposed to call service method in template file.
export class TestComponent implements OnInit{
public menuActive = false;
toggleMenu() {
this.menuActive = !this.menuActive;
}
}
Template:
<div id="mainContainerRightTop" [class.active]="menuActive">

Related

How to use private properties in my html-file?

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.

How to pass a function from a parent to a deep nested child and use an #input value into the passed function in Angular 8?

I have 3 components in this situation:
-OuterComponent
--MiddleComponent
---InnerComponent
I need to pass a function from OuterComponent to InnerComponent through MiddleComponent.
It is important to mention that the function I need to pass does take an input: DoSomething(node)
I don't know if it is relevant but I am already passing a NodeTree from the OuterComponent to the MiddleComponent and then I am unpacking the NodeTree into a Node and passing it InnerComponent. This Node is what I need to use as an input for the function being passed.
So, I need to be able to use an #Input as the input for the function being passed to the InnerCompoenent, which I assume will need to be an #output.
Method 1:
You can call the parent component function(OuterComponent) from the child component(InnerComponent) using #Output.
OuterComponent HTML:
<MiddleComponent (updateOuterComponent)="parentFunction($event)" />
OuterComponent TS:
export class OuterComponent implements OnInit {
constructor() {}
ngOnInit() {}
parentFunction(para) {
console.log(para);
// operations you want to do in parent component
}
}
MiddleWare HTML:
<InnerComponent (updateMiddleComponent)="middleFunction($event)" />
MiddleComponent TS:
export class MiddleComponent implements OnInit {
#Output() updateOuterComponent = new EventEmitter();
constructor() {}
ngOnInit() {}
middleFunction(para) {
this.updateOuterComponent.emit(para);
}
}
InnerComponent HTML:
It can be whatever you want to write
InnerComponent TS:
export class InnerComponent implements OnInit {
#Output() updateMiddleComponent = new EventEmitter();
constructor() {}
ngOnInit() {}
updateMiddleAndParent(para) {
this.updateMiddleComponent.emit(para);
}
}
Once you call updateMiddleAndParent function form Inner component using emitter, that will trigger middleFunction in the MiddleComponent. After triggering middleFunction, Similarly middleFunction will trigger parentFunction with the help of emitter.
Method 2:
You need to create a service and use it to call the parent function:
DataService:
import {BehaviorSubject} from "rxjs/BehaviorSubject"
export class DataService {
private state$ = new BehaviorSubject<any>('initialState');
changeState(myChange) {
this.state$.next(myChange);
}
getState() {
return this.state$.asObservable();
}
}
call DataService in both InnerComponent and OuterComponent:
In the OuterComponent call DataService and call getState(), this will return an observable whenever the value changes you can any function using data passed in observable response.
In the InnerComponent call DataService and use the changeState() to change the value.
once the value is changed in DataService, then in parent Component the value will be change as you are subscribed to the observable, You will get the updated data from there you can call any function in parent.

null parameters from angular

I am trying to send parameters from my angular app using httpRequest.
I am getting back Null to my backend server.
I have checked with Postman and Fiddler both work with a json Object.
I have tried changing from Post to Get.
I am using Java RestAPI for the backend with apache Tomcat as the server.
This is my Service for login:
#Injectable({
providedIn: 'root'
})
export class LoginService {
private loginURL='http://localhost:8080/CouponSystemWeb/rest/loginpage/login'
constructor(private http:HttpClient) { }
public login(loginDetailes:LoginDetailes):Observable<LoginDetailes>{
return this.http.post<LoginDetailes>(this.loginURL,loginDetailes,{withCredentials:true})
}
}
This is my Login Component:
import { Component, OnInit } from '#angular/core';
import { LoginDetailes } from 'src/app/Entities/LoginDetailes';
import { LoginService } from 'src/app/services/login.service';
import { Router } from '#angular/router';
#Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
public loggedin:boolean;
public loggedClient:string;
public errormessage:string;
public loginDetailes = new LoginDetailes();
constructor(private loginservice:LoginService,private router:Router) { }
ngOnInit() {
this.loggedin=false;
}
public onLogin():void{
const observable=this.loginservice.login(this.loginDetailes);
observable.subscribe((returnedLoginDetailes:LoginDetailes)=>{
alert("Login Aquired");
this.loggedin=true;
if(this.loginDetailes.clientType=="ADMIN"){
this.router.navigate(['/crtComp']);
}
else if(this.loginDetailes.clientType=="COMPANY"){
this.router.navigate(['/login']);
}
else if(this.loginDetailes.clientType=="CUSTOMER"){
this.router.navigate(['/login']);
}else{
alert("Wrong Login Detailes");
}
}, err => {
this.errormessage=err.console.error("Wrong Detailes please Check Again!");
alert(this.errormessage);
}
)}}
This is the login Entity :
export class LoginDetailes{
public name:string
public password:string
public clientType:string
constructor(){
}
}
I have tried ngModel but that didn't fix the problem.
I have tried changing my backend from Post to Get.
The problem happends only in the angular App. I can send parameters with fiddler and Postman without problem.
Ok the answer was not in the component or the service.
the problem was in the HTML i was missing the ngModel two way data binding so my App was sending null's.

Angular 6 AOT issue - can't pass function as argument

I'm unable to pass a function as an argument to a base class from a child class when trying to build using ng build --prod. The build works fine without the --prod flag, which looks to indicate an issue with AOT. The error I get is:
ERROR in : Can't resolve all parameters for AppGridComponent in
/src/app/components/core/shared/app-grid.component.ts: (?, [object
Object])
I found this SO thread which has several different answers for solutions, but I haven't been able to get any to work. It appears that AOT wants to inject this argument as a service and can't resolve (which I don't need since I am passing the function as a value from the child).
Base Class - app-grid.component.ts
import { Component, OnDestroy, OnInit } from '#angular/core';
import { GlobalsService } from '../../../services/globals.service';
#Component({})
export class AppGridComponent implements OnInit, OnDestroy {
constructor(protected loadDataCallBack: any, protected globalsService: GlobalsService) {
}
ngOnInit() {
this.init();
}
// get api data
public init() {
this.loadDataCallBack()
.subscribe(result => result);
}
Child class - rules.component.ts
const loadApiData = function() {
return this.productRuleService.get();
};
#Component({
selector: 'app-rules',
template: `<div class="grid-wrapper">Data here...</div>`
})
export class RulesComponent extends AppGridComponent implements OnInit, OnDestroy {
constructor(protected globalsService: GlobalsService, protected productRuleService: ProductRelationshipRuleService) {
super(loadApiData, globalsService);
}
Any suggestions on how to get this to build would be appreciated.
I was able to get this to work by creating a class that extends Function and then using this class as the provider in the component.
// Base Class - app-grid.component.ts
export class LoadDataCallBack extends Function {
}
#Component({
template: '',
providers: [{provide: LoadDataCallBack, useValue: () => {}}]
})
This ultimately satisfied the compiler and it is able to identify the type to inject into the first argument: loadDataCallBack.

Angular (4): pass a class object from child to parent component

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.