Angular2 Calling exposed method from outside app and loses change binding - javascript

I have a public method that I exposed to window. This method talks to a Component and modifies a variable I am watching in my template. But when I change the value, the *ngIf() does not get triggered.
app.component
constructor(private _public: PublicService,) {
window.angular = {methods: this._public};
}
PublicService
export class PublicService {
constructor(
private _viewManager: ViewManagerComponent,
) {}
CallMe(){
this._viewManager.renderView('page1')
}
}
LayoutManagerComponent
#Component({
selector: 'view-manager',
template: `<page *ngIf="view == 'page1'"></page>`
})
export class ViewManagerComponent {
//This is the variable being watched
view = "page";
renderView = function(type){
console.log(type)
this.view = type;
console.log(this.view)
};
}
So the idea is that when the view initially loads, the view is blank. Then when I type angular.methods.CallMe() it modifies the view variable to page1 which should then show the html for the Component. If I console renderView function it is successfully getting called, just the view does not change.
----Update - Still not working -------
export class ViewManagerComponent {
constructor(private zone:NgZone,private cdRef:ChangeDetectorRef) {
}
view = "page";
#Output() renderView(type){
// type is 'page'
console.log(this.view)
this.zone.run(() => {
// type is 'page'
console.log(this.view)
this.view = type;
// type is 'page1'
console.log(this.view)
});
// type is 'page1'
console.log(this.view)
//cdRef errors:
//view-manager.component.ts:36 Uncaught TypeError: this.cdRef.detectChanges is not a function(…)
this.cdRef.detectChanges();
};
}

In this case Angular2 doesn't know that it needs to run change detection because the change is caused by code that runs outside Angulars zone.
Run change detection explicitely
contructor(private cdRef:ChangeDetectorRef) {}
someMethodCalledFromOutside() {
// code that changes properties in this component
this.cdRef.detectChanges();
}
Run the code that modifies the components properties inside Angulars zone explicitely
contructor(private zone:NgZone) {}
someMethodCalledFromOutside() {
this.zone.run(() => {
// code that changes properties in this component
});
}
The zone method is a better fit when // code that changes properties in this component not only changes properties of the current component, but also causes changes to other components (like this.router.navigate(), call method references of methods of other components) because zone.run() executes the code inside Angulars zone, and you don't need to explicitely take care of change detection in every component where a change might happen because of this call.
If you use function(...) instead of () => it's likely you'll get unexpected behavior with this in code inside the Angular component.
See also my answer to this similar question for more details Angular 2 - communication of typescript functions with external js libraries
update
export class ViewManagerComponent {
constructor(private zone:NgZone,private cdRef:ChangeDetectorRef) {
self = this;
}
view = "page";
#Output() renderView(type){
// type is 'page'
console.log(self.view)
self.zone.run(() => {
// type is 'page'
console.log(self.view)
self.view = type;
// type is 'page1'
console.log(self.view)
});
// type is 'page1'
console.log(self.view)
self.cdRef.detectChanges();
};
}

Related

Angular Component Class Variable Set in one Function is Undefined in Another

I have a component with a viewChild() binding to a child component that I use to call a function in the child component. The function takes an argument of true or false and is meant to set a class variable in the child component to the argument's value. The class variable is then meant to be read in an if statement in another function in the child component.
So far, I have successfully called the child component function from the parent component, passed the boolean argument to it, set the class variable, and printed it to the console. I have verified that the class function is included in the 'this' of the component.
IN THE PARENT COMPONENT:
if (res[0] === undefined) {
this.typeAhead.badRequest(true);
}
IN THE CHILD COMPONENT:
The console log in the onSubmit() function only returns the value of _badRequestFlag set when the variable was declared, not the value assigned in badRequest().
private _badRequestFlag: boolean = false;
badRequest (res: boolean) {
this._badPlaRequestFlag = res;
}
onSubmit (): void {
console.log('BAD PLA REQUEST:, ', this);
if (this._badRequestFlag === true) {
alert('Does Not Exist');
throw (new Error('Does Not Exist'));
}
When I try to use the class variable in the onSubmit() function, it's value is only the value that it was declared with, and not the value set in the badRequest() function. I'm assuming that I'm running into a scoping issue, but I cannot determine what and how to resolve it.
I would recommend setting the badRequestFlag using an input instead:
class ChildComponent {
#Input() badRequestFlag: boolean;
}
#Component({
selector: 'app',
template: `
<child-component [badRequestFlag]="true"></child-component>
`
})
You can bind this to a variable in the parent controller: [badRequestFlag]="requestFlag"
probably you should review the lifecycle-hooks, i try to understand you problem in stackblitz but i can't replay your decribed error. i hope help you

Angular Two-Way Data Binding and Watching for Changes in Parent Component

It seems there is no way to watch changes in the parent component when using two-way data binding.
I have a custom input component for collecting a tag list. Two-way data binding is setup and working between this component and its parent.
// the parent component is just a form
// here is how I'm adding the child component
<input-tags formControlName="skillField" [(tags)]='skillTags' (ngModelChange)="skillTagUpdate($event)">
</input-tags>
In the parent component how do you watch the bound variable for changes? While it's always up to date (I've confirmed this) I cannot find any guidance on reacting to changes.
I've tried:
ngOnChanges(changes: SimpleChanges) {
if (changes['skillTags']) {
console.log(this.skillTags); // nothing
}
}
And
skillTagUpdate(event){
console.log(event); // nothing
}
UPDATE:
TWDB IMHO is not what it is advertised to be. Whenever I arrive at this place where TWDB seems to be a solution I rearchitect for a service and or observable communication instead.
When you implement a two way binding of your own, you have to implement an event Emitter. The syntax for that is mandatory.
this means that you have a hook to listen to if the value changes.
Here is a demo :
<hello [(name)]="name" (nameChange)="doSomething()"></hello>
_name: string;
#Output() nameChange = new EventEmitter();
set name(val) {
this._name = val;
this.nameChange.emit(this._name);
}
#Input()
get name() {
return this._name;
}
counter = 0;
ngOnInit() {
setInterval(() => {
this.name = this.name + ', ' + this.counter++;
}, 1000);
}
Stackblitz
From what I know, this seems the less annoying way to use it, and any two way binding will follow the same rule no matter what, i.e. it ends with the Change word !
Your implementation is actually not two-way databinding, the parent and child component are just sharing a reference on the same skillTags variable.
The syntax [(tags)]='skillTags' is syntaxic sugar for [tags]='skillTags' (tagsChange)='skillTags = $event'
You need to implement tagsChange in the child component like this: #Output('tagsChange') tagsChange = new EventEmitter<any>();, then any time you want to modify tags into the children component, dont do it directly, but use this.tagsChange.emit(newValue) instead.
At this point, you'll have real two-way databinding and the parent component is the unique owner of the variable (responsible for applying changes on it and broadcasting changes to the children).
Now in your parent component, if you want to do more than skillTags = $event (implicitly done with [(tags)]='skillTags'), then just add another listener with (tagsChange)='someFunction($event)'.
StackBlitz Demo
Don't know if this is what you're looking for, but have you tried using #Input()?
In child component
#Input() set variableName(value: valueType) {
console.log(value);
}
In parent component
<input-tags formControlName="skillField" [(tags)]='skillTags'
[variableName]="skillTagUpdate($event)"></input-tags>
The input function is called every time the object binded to the function is changed.
you could listen to the change:
<input-tags formControlName="skillField" [tags]='skillTags' (tagsChange)='skillTags=$event; skillTagUpdate();'></input-tags>
or use getter and setter:
get skillTags(): string {
return ...
}
set skillTags(value) {
variable = value;
}
another approach:
export class Test implements DoCheck {
differ: KeyValueDiffer<string, any>;
public skillTags: string[] = [];
ngDoCheck() {
const change = this.differ.diff(this.skillTags);
if (change) {
change.forEachChangedItem(item => {
doSomething();
});
}
}
constructor(private differs: KeyValueDiffers) {
this.differ = this.differs.find({}).create();
}
}}
1.you can use output(eventemitter)
2.easiest solution is rxjs/subject. it can be observer and observable in same time
Usage:
1.Create Subject Property in service:
import { Subject } from 'rxjs';
export class AuthService {
loginAccures: Subject<boolean> = new Subject<boolean>();
}
2.When event happend in child page/component use :
logout(){
this.authService.loginAccures.next(false);
}
3.And subscribe to subject in parent page/component:
constructor(private authService: AuthService) {
this.authService.loginAccures.subscribe((isLoggedIn: boolean) => {this.isLoggedIn = isLoggedIn;})
}
Update
for two-way binding you can use viewchild to access to your child component items and properties
<input-tags #test></<input-tags>
and in ts file
#ViewChild('test') inputTagsComponent : InputTagsComponent;
save()
{
var childModel = this.inputTagsComponent.Model;
}

How to make service in angular 2+ accessible in javascript

I have the following scenario.
I have a simple angular 2 app with a service which I add to providers in app.module. When I click on a button the app should load a javascript file and execute a function e.g function A defined in this javascript file. So the question is how can I access the service within this function A. My consideration is to append the service to the global window variable.
Are there better ways to achieve it?
Code:
export class MyService{
public editProjectDone = new Subject<string>();
}
app.module.ts
{
...providers: [MyService]
}
app.component.html
<button (click)="editProject()">Edit project</button>
app.component.ts
function editProject(){
... load Javascript file
call javascript file
js.editProject("projectId") // call function defined in javascript file
}
javascript file
{
function editProject(projectId)
{
//do calculation
// fire event that calculation is done
// the calcuation is not done in typescript, but here
MyService.editProjectDone.next()
// The question is here how to access the event and fire it
}
}
So you want to access angular service method in javascript function A().
For example:
Your service class:
export class SettingsService{
getLanguage() {
return 'en-GB';
}
}
Your javascript file
function A() {
settingsService.getLanguage();
}
Solution: Custom Event.
Basically you define a custom event handler in javascript file. And define the Custom Event and dispatchEvent the Custom Event in Angular click event function.
app.component.html:
<input type="button" value='log' (click)="logclick($event)">
app.component.ts
constructor(private settings: SettingsService){}
logclick(event){
// define custom event
var customevent = new CustomEvent(
"newMessage",
{
detail: {
message: "Hello World!",
time: new Date(),
myservice: this.settings //passing SettingsService reference
},
bubbles: true,
cancelable: true
}
);
event.target.dispatchEvent(customevent); //dispatch custom event
}
javascript file:
// event handler function
function newMessageHandler(e) {
console.log(
"Event subscriber on "+e.currentTarget.nodeName+", "
+e.detail.time.toLocaleString()+": "+e.detail.message
);
//calling SettingsService.getLanguage()
console.log(e.detail.myservice.getLanguage());
}
//adding listener to custom event.
document.addEventListener("newMessage", newMessageHandler, false);
Example output:
Event subscriber on #document, 9/11/2018, 11:31:36 AM: Hello World!
en-GB
Note: I have not added section for dynamically loading javascript file. I assume you are already able to do that from your comments.
Declare variable as public using window object but in proper way. export only some functions not whole service and in some standard way like below.
In Angular
export class AbcService {
constructor() {
const exportFunctions = {
xyzFunction: this.xyzFunction.bind(this),
pqrFunction: this.pqrFunction.bind(this)
}; // must use .bind(this)
window['ngLib']['abcService'] = exportFunctions;
}
xyzFunction(param1, param2) {
// code
}
pqrFunction() {
// code
}
private oneFunction() {
// code
}
private twoFunction() {
// code
}
}
In Javascript
ngLib.abcService.xyzFunction(value1, value2);
ngLib.abcService.pqrFunction();
Firstly you need to import the js file before calling the service and that can be done by:
TS
let js: any = require('JSFileNAME')['default']; //Something lik this in constructer
then once the file is imported you need to create a instantiate of the js in your Ts
something like
this.newObjectOfJs = new js(); // pass any paramater if its required.
hereafter you will be able to access the methods and service from the JSfile.
You have to use service variable to access functions and variable of particular services.
Here I demonstrate how you can do it.
export class AppComponent {
name = 'Angular';
constructor(private myServices: MyServices){
this.myServices.sayHello();
}
}
Whole code available here : https://stackblitz.com/edit/angular-services-example

Call Variable from Constructor of Another Class

Say I have a .js file where the class is exported and the constructor is built like this:
constructor(info) {
this.info = info;
}
And on another .js file, I want to call that variable and change it, and I want the changes to be reflected in the original .js file the variable comes from, so I import the class well and inject it:
#inject(ContactGateway, Router, TestElement)
export class LoginNormal {
constructor(contactGateway, router, testelement) {
this.contactGateway = contactGateway;
this.router = router;
this.testelement = testelement;
}
And then on this same .js file, inside a function, I change the original variable:
TestInfo() {
let testinfo = this.testelement;
testinfo.info= true;
}
Upon further testing, I see that the original variable isn't being changed at all, what am I doing wrong while trying to change the original variable's boolean through a function in another file?
Thanks in advance.
You're probably just getting a different instance of TestElement injected. Custom Elements are usually scoped to a specific child container or view (depending on the context).
If you want to ensure you get the same instance everywhere, and you're sure you'll only ever need one instance, you could manually register that custom element's class as a singleton on the root container.
It's better though to simply have a separate service / state class where you keep that info property. Register that state class as a singleton / instance on the root container, and inject it in both your TestElement and LoginNormal classes. That's the recommended way to pass information around for reading/modifying between different html resources.
Something like this should typically do the trick (split/move/rename accordingly):
#inject(ApplicationState)
export class TestElement {
constructor(state) {
this.state = state;
}
}
#inject(ContactGateway, Router, ApplicationState)
export class LoginNormal {
constructor(contactGateway, router, state) {
this.contactGateway = contactGateway;
this.router = router;
this.state = state;
}
TestInfo() {
this.state.info = true; // will be changed in your TestElement too
}
}
// the hard way
export class ApplicationState {
constructor(info) {
this.info = info;
}
}
export function configure(config) {
config.instance(ApplicationState, new ApplicationState(false))
}
// or the easy way (no configure required, but needs parameterless constructor)
#singleton()
export class ApplicationState {
constructor() {
this.info = false;
}
}

angular2 #Input EventEmitter update view

iam trying to make a simple grid component and i have a trouble with updating view after emitting event !
Please explain who knows, why after updating simple component, view do not re-rendered ? whats wrong with this code ?
export class GridComponent implements OnInit {
#Input() resource: any [];
#Output() saveModel = new EventEmitter();
#Output() deleteModel = new EventEmitter();
attributes: any[];
isUpdating: boolean = false;
updatingID: number;
constructor() {}
ngOnInit() {
this.attributes = Object.keys(this.resource[0]);
}
toggleUpdate(id, flag = true) {
this.isUpdating = !this.isUpdating;
this.updatingID = flag ? id : undefined;
}
destroy(id) {
this.deleteModel.emit(id);
}
save(model) {
this.saveModel.emit(model);
this.toggleUpdate(model.id, false);
}
cancel(id) {
this.toggleUpdate(id, false);
}
}
Full example here https://plnkr.co/edit/InxsHu9GwCtMplYoocsS?p=preview
The resource data is updated properly in parent and child components, just the form doesn't show the update.
I think you need to change the values pipe to only return the keys but not the values and then access the values using the *ngFor variables with the keys to get the values in the view directly.
EDITED:
Günter Zöchbauer, thank you saved my time !
I think you need to change the values pipe to only return the keys but not the values and then access the values using the *ngFor variables with the keys to get the values in the view directly.
this was root of evil

Categories