I need some clarification on binding between service and component properties and data binding in angular2
assume i have a service(singleton) and a component
export class Service {
name = "Luke";
object = {id:1};
getName(){return this.name};
getObject(){return this.object};
}
export class Component implements OnInit{
name:string;
object:any;
constructor(private _service:Service){}
ngOnInit():any{
//Is this 2 way binding?
this.name = this._service.name;
this.object = this._service.object;
//Is this copying?
this.name = this._service.getName();
this.object = this._service.getObject();
}
}
If you update elements by reference (if you update something into the object property), you will see the updates in the view:
export class Service {
(...)
updateObject() {
this.object.id = 2;
}
}
If you update elements by value (if you update something into the name property), you won't see the updates in the view:
export class Service {
(...)
updateName() {
this.name = 'Luke1';
}
}
See this plunkr: https://plnkr.co/edit/w7bS0fAVjOc3utnpD39b?p=preview.
Angular binding only works for bindings declared in the view (HTML).
If you want properties in your component being updated when values in a service change, you need to take care of it yourself.
Observables make this easy. See detect change of nested property for component input for an example.
If you want properties in a component updates as soon as a value in change in a service changes:
Import DoCheck from #angular/core and your service into the
component.
Call the service functions affecting the component property in ngDoCheck(){...}
The component view will be updated automatically as soon as any changes
Something like this in your component:
ngDoCheck() {
this.qty = this.cartService.getTotalQtyInCart();
}
Related
I have a running Angular 9 application and I have created custom dialog box. I have also used ComponentFactoryResolver to dynamically load the component.
My custom dialog box looks like:
So, when I click on close button, the dialog box closes.
As per the current implementation, if I open multiple dialog box on the screen, then I am able to close only last opened dialog box by clicking on close button.
My expected behavior is to close all the dialog box. Please help me on this
Stackblitz demo: https://stackblitz.com/edit/dialog-box-overlay
Note: In this stackblitz demo, one modal opens on the top of another modal as I have not modified the css. So, please focus on Modal name to get to know which modal is opened
Instead of assigning the created modal component to the services dcRef property you need to manage all of your modal components, i.e. in a list. Your service's open() method
open(component: Type<any>, modalName: string) {
const factory = this.componentFactoryResolver.resolveComponentFactory(DialogComponent);
this.dcRef = factory.create(this.injector);
...
return this.dcRef;
}
returns the component reference. You could manage this reference from the caller and pass it as an argument to your close() method. When all of the component refs are managed by the service you can also "batch close" all modals (see closeAll()):
#Injectable()
export class DialogService {
refs: ComponentRef<DialogComponent>[] = [];
constructor(private componentFactoryResolver: ComponentFactoryResolver,
private applicationRef: ApplicationRef,
private injector: Injector
) { }
open(component: Type<any>, modalName: string) {
const factory = this.componentFactoryResolver.resolveComponentFactory(DialogComponent);
var ref = factory.create(this.injector);
this.applicationRef.attachView(ref.hostView);
const domElement = (ref.hostView as EmbeddedViewRef<any>).rootNodes[0] as HTMLElement;
document.body.appendChild(domElement);
ref.changeDetectorRef.detectChanges();
ref.instance.open(component, modalName);
this.refs.push(ref);
return ref;
}
close(ref) {
this.applicationRef.detachView(ref.hostView);
ref.instance.close();
ref.destroy();
// Remove ref from a list managed by the service
var i = this.refs.indexOf(ref);
this.refs.splice(i, 1);
}
closeAll()
{
this.refs.forEach(r => this.close(r));
}
}
This is not tested and might need to be tuned, but you should get the idea. Instead of using the ComponentRef as a handle you can also create some custom object to prevent the caller of the modal to interact with the component directly.
Developing a multiculture Angular5 app I've structured a class which contains a dictionary (custom ts class) to hold the translations. When the user changes the culture, anything related to it must change. It works but.... too much.
Since I use to console.log everytime the method which gets the right sentence according to the selected culture is called, I noticied that if I just click on a textbox, Angular updates everything and, incomprehensibly, if I click on nothing soon after just to leave the focus, again angular updates everything! I know there must be something related with ChangeDetectionStrategy but I tried to solve without any success.
When the app grows up, if the browser has to reload everything everytime... what a problem! The app looks like this:
a MainController.ts holds anything can be useful to the components so it is passed in every component constructor.
MainController looks like this:
#Injectable()
export class MainController extends BaseClass {
private currentCulture: string;
private platformLocalizedSentencies: KeyedCollection<LocalizedString>;
the class LocalizedString looks like this:
#Injectable()
export class LocalizedString extends BaseClass {
public DefaultText: string;
public IdTranslationIndex: number;
public Translations: KeyedCollection<string>;
constructor() {
super();
this.Translations = new KeyedCollection<string>();
}
public GetTranslation(culture: string) {
console.log('getting translation for ' + this.DefaultText + ' in culture ' + culture);
if (!this.Translations.ContainsKey(culture)) {
return '*' + this.DefaultText;
} else {
return this.Translations.Item(culture);
}
}
}
Now, there's a component (culture-selector.component.ts) which shows flags (changeDetection: ChangeDetectionStrategy.OnPush) and when the user select a flag this happens:
onCultureClick(menuItem: string) {
console.log(menuItem + ' clicked.');
this.mainController.CurrentCulture = menuItem; // this must be the thing which unleash the databinding on the other components I think and I hope
this.updateSelectedCultureUI(); // doesn't do anything special, just sets the right flag and culture name on the top of the control
this.mainController.trace(TraceType.Info, 'Culture ' + this.cultureName + ' clicked');
}
and the component where I noticied the hell wild by the databinding, the login component:
#Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css'],
})
export class LoginComponent extends BaseClass implements OnInit, AfterViewInit {
constructor(public mainController: MainController) {
super();
}
ngOnInit() {
this.usernamePlaceholder = this.mainController.PlatformLocalizedSentencies.Item('Username');
Now, "usernamePlaceholder" is used into the html like this:
<mat-form-field style="width: 100%;">
<input matInput [placeholder]="usernamePlaceholder.GetTranslation(mainController.CurrentCulture)" [(ngModel)]="loginInput.username" (keypress)="eventHandler($event.keyCode, 'txtUsername')" #txtUsername>
</mat-form-field>
in the following picture you can see into the console-window what happens if I just click into the username textbox.... I cleared the console before clicking. What is going on? I spent two days trying to undestand... sorry it's my first angular App.. I should have started with something easier :)
You can see username translation is get by ClassLibray.ts while the other sentencies by mainController.ts... this is because I tried different ways but same result... in mainController the code is almost the same:
.... and the method to retrieve the correct string
public GetPlatformSentence(key: string) {
console.log('getting translation for ' + key + ' in culture ' + this.CurrentCulture);
if (!this.PlatformLocalizedSentencies.ContainsKey(key)) {
return '[NOTRANSLATION]';
}
if (!this.PlatformLocalizedSentencies.Item(key).Translations.ContainsKey(this.CurrentCulture)) {
return '*' + this.PlatformLocalizedSentencies.Item(key).DefaultText;
} else {
return this.PlatformLocalizedSentencies.Item(key).Translations.Item(this.CurrentCulture);
}
}
Thank you anyway
here is what happens
Ok guys, I've tried anything without any success. Angular binding is... too pedantic :-)
The best result I was able to achieve was the refresh at first user-interaction. So I had to choose between refreshing everything everytime (which was too much) or refreshing as soon as the user clicked somewhere forcing the binding refresh (that was what I've been able to get playing with ChangeDetectionStrategy settings and some additional code).
So I solved (just for multicuture immediate binding) using a messageService. Every component subscribes and asks to a common maicontroller to update anything related on culture when it changes. Of course I needed to write e little bit more code but the result in terms of user experience, responsiveness and CPU load is very very nice. Also the executed code is the same for every component so it is not so bad.
Of course it isn't what I expected but, it works better for me.
I am trying to define an AngularJS rootscope variable in a .ASPX file to use in a TypeScript file, but I am unsure of how to do this. I am open to any way to be able to define a value in an .ASPX file and use it in TypeScript, so any other suggestion will work for me.
If you simply need to tell TypeScript that the property is there, you can extend the rootScope interface:
interface extendedRootScope extends ng.IRootScopeService {
myProp: number;
}
Then when you inject $rootScope in your controller, type it as your new interface:
export class MyController {
constructor(private $rootScope: extendedRootScope) { }
someMethod() {
// Access this.$rootScope.myProp
}
}
If you need to access $rootScope from outside the Angular world (like in your ASPX page) to add the property, you can do something like this:
<script>
var injector = angular.element('[ng-app]').injector();
var $rootScope = injector.get('$rootScope');
$rootScope.myProp = 1;
</script>
This assumes you are using ng-app to initialize your Angular app.
Similarly, you could create an Angular service on the fly in a script tag, inject $rootScope into that service, and add properties to it.
I am trying to make something work on angular2 and I am unable to find something about this behavior.
I have an application that implements a custom component like this one :
import {Component,Input} from 'angular2/core'
#Component({
selector:'my-comp',
template:`<input type="text" style="text-align:center; [(ngModel)]="inputText"> <p>{{inputText}}</p>`
})
export class MyComp{
#Input() inputText : string;
}
And I am trying to do a bidirectional databinding on my inputText variable from my component like this:
<my-comp [(inputText)]="testString"></my-comp>
Where the testString is a variable defined in the MyApp.ts which contains a string. I want my testString variable to be modified when my inputText is modified by the user.
Here is a Plunker with a simple sample code : https://plnkr.co/edit/zQiCQ3hxSSjCmhWJMJph?p=preview
Is there a way to make this works simply ? Do I have to implements an Angular2 class on my custom components and overload functions in order to make this works like an ngModel ? Do i necessarily have to create a inputTextChanged variable of EventEmitter type that emit my data when it's changed and do something like this :
<my-comp [inputText]="testString" (inputTextChanged)="testString = $event;"></my-comp>
Thank you in advance.
This is explained in the Template Syntax doc, in the Two-Way Binding with NgModel section:
<input [(ngModel)]="currentHero.firstName">
Internally, Angular maps the term, ngModel, to an ngModel input property and an ngModelChange output property. That’s a specific example of a more general pattern in which it matches [(x)] to an x input property for Property Binding and an xChange output property for Event Binding.
We can write our own two-way binding directive/component that follows this pattern if we're ever in the mood to do so.
Note also that [(x)] is just syntactic sugar for a property binding and an event binding:
[x]="someParentProperty" (xChange)="someParentProperty=$event"
In your case, you want
<my-comp [(inputText)]="testString"></my-comp>
so your component must have an inputText input property and an inputTextChange output property (which is an EventEmitter).
export class MyComp {
#Input() inputText: string;
#Output() inputTextChange: EventEmitter<string> = new EventEmitter();
}
To notify the parent of changes, whenever your component changes the value of inputText, emit an event:
inputTextChange.emit(newValue);
In your scenario, the MyComp component binds input property inputText using the [(x)] format to ngModel, so you used event binding (ngModelChange) to be notified of changes, and in that event handler you notified the parent component of the change.
In other scenarios where ngModel isn't used, the important thing is to emit() an event whenever the value of property inputText changes in the MyComp component.
I'll combine #pixelbits and #Günter Zöchbauer answers and comments to make a clear answer on my question if someone in the future is searching for this.
To make bidirectional data binding works on custom variables you need to creates your component based on the following.
MyComp.ts file :
import {Component,Input,Output,EventEmitter} from 'angular2/core'
#Component({
selector:'my-comp',
templateUrl:`<input type="text" style="text-align:center;"
[ngModel]="inputText" (ngModelChange)="inputText=$event;inputTextChange.emit($event);">`
})
export class MyComp{
#Input() inputText : string;
#Output() inputTextChange = new EventEmitter();
}
MyApp.ts file:
import {Component} from 'angular2/core'
import {MyComp} from './MyComp'
#Component({
selector:'my-app',
templateUrl:`<h1>Bidirectionnal Binding test </h1>
<my-comp [(inputText)]="testString"></my-comp><p>
<b>My Test String :</b> {{testString}}</p>`,
directives:[MyComp]
})
export class MyApp{
testString : string;
constructor(){
this.testString = "This is a test string";
}
}
There the bidirectional data binding to the inputText variable works correctly.
You can comment the answer for a more beautiful or simpler way to implement this code.
Your Plunker already contains the EventEmitter. The #Output() annotation is missing. To change the value call inputTextChanged.emit(newValue) (this also changes the value on inputText)
What I do is use a property, so when I change the data the change is emitted automatically
private _data: AnyType;
#Input() get data(): AnyType{
return this._data;
}
set data(value: AnyType) {
this._data = value;
this.dataChange.emit(this._data);
}
#Output() dataChange: EventEmitter<AnyType> = new EventEmitter();
In html you will bind the property using [(data)]
<comp [(data)]="getData()"></comp>
Not an easy question to decipher, so let me boil it down. I'm trying to convert an MXML component to an ActionScript Class. The component consists of a Form with a TextInput, TextArea, and two buttons - Save and Cancel, and a Validator for the TextInput, and other logic to handle events that occur. This component is currently extended by several other components.
Now, in the MXML component binding the TextInput text property to a property in an Object was very easy:
<mx:TextInput text="{_itemToEdit.name}" />
But in ActionScript, I'm creating the TextInput and setting the text property before the Object is set, and the TextInput is not being updated:
public var itemToEdit:Object = {};
private var nameInput:TextInput = new TextInput();
public function MyClass()
{
nameInput.text = itemToEdit.name;
}
How can I make sure that the TextInput text property is bound to the specified property in the Object?
Binding is all about firing change events. you'll need to modify your 'itemToEdit' class to be an EventDispatcher for this hack to work. here goes
//item to edit class
private var _name:String;
public function set name(value:String):void
{
_name = value;
dispatchEvent(new Event("NAME_CHANGED"));
}
//whatever the binding class is
private var _itemToEdit:ItemToEdit;
private var _textField:TextField;
public function set itemToEdit(value:ItemToEdit):void
{
if (_itemToEdit) removeEventListeners();
_itemToEdit = value;
if (_itemToEdit) addEventListeners();
}
private function addEventListeners():void
{
_itemToEdit.addEventListener("NAME_CHANGED", itemToEdit_nameChangedHandler);
itemToEditChangedHandler(null);
}
private function itemToEdit_nameChangedHandler(event:Event):void
{
_textField.text = _itemToEdit.name;
}
Obviously this was done just for speed, you'll need custom events and some better names etc, but this is the basic jist.
Apparently it's slightly more complex than a simple assignment to bind purely in AS, here's a couple tutorial/docs to show you how to pull it off.
http://cookbooks.adobe.com/index.cfm?event=showdetails&postId=6802
http://raghuonflex.wordpress.com/2007/08/30/binding-in-mxml-as/
Compile your MXML component with the -keep option. Examine the ActionScript code that was generated by mxmlc and do something similar.
You may also do it using the Proxy object - I blogged about it over here: http://flexblog.faratasystems.com/?p=433
If "itemToEdit" is a pure AS3 Object, then the binding probably doesn't work properly anyway. That is, it will work when the object is initially created, but any changes to "name" in the object won't be detected. (I could be wrong...haven't done extensive tests)
Anyway, your problem is easy to solve with getters/setters:
private var _itemToEdit:Object;
public function get itemToEdit():Object { return _itemToEdit; }
public function set itemToEdit(value:Objecy):void {
_itemToEdit = value;
nameInput.text = value.name;
}
Binding isn't necessary here.