Angular2: Directive variable change, update element - javascript

I'm currently trying to give classes to an wrapper that contains all of my app, i usually find this handy for giving certain states like when the header is fixed, or the menu's are opened etc.
So upon reading through some the docs of angular i should probably use an 'Directive'. Now i got this all set up and it looks like this:
constructor(private router:Router, #Inject(DOCUMENT) private document:Document, el:ElementRef, renderer:Renderer) {
this.setClasses(el, renderer);
}
setClasses(el:ElementRef, renderer:Renderer) {
renderer.setElementClass(el.nativeElement, 'header-fixed', this.headerFixed);
}
#HostListener("window:scroll", [])onWindowScroll() {
let number = this.document.body.scrollTop;
if (number > 100) {
this.headerFixed = true;
} else if (this.headerFixed && number < 10) {
this.headerFixed = false;
}
}
Now this is working perfectly but as you can see i'm toggling the headerFixed variable depending on scroll position. However i could of course run the function setClasses() again and it will work but is there anyway to subscribe/watch the variable and update automatically when changed?
Or is there even a better way of achieving wat i'm trying to do?

You can use #HostBinding like:
#HostBinding('class.header-fixed') get myClass() {
return someCondition;
}
Plunker Example

Related

unable to get methods of an object in javascript / reactjs

I'm working with ReactJS, but I think my question might be a general javascript question. I have two classes Url and QueryVars. I want Url to be able to call all the methods of QueryVars, as if I extended Url from QueryVars. So I did something like this:
export default class Url {
constructor()
{
this.queryVars = new QueryVars();
console.log(this.queryVars);
for(let x in this.queryVars)
{
console.log(x); // this never gets fired
if(typeof this.queryVars[x] == "function")
{
this[x] = this.queryVars[x].bind(this.queryVars);
}
}
}
}
default class QueryVars {
init()
{
/* do something */
}
getQS(key)
{
/* do something */
}
getPage(key)
{
/* do something */
}
}
The thing is that the console.log(x) does not get fired. I'm unable to retrieve the methods of this.queryVars. What am I doing wrong here?
Eventually, I want to assign a few more objects into the Url.constructor and bind more methods of those objects, so that it seems like I'm implementing multiple inheritance, or composing a new class out of other classes. So that I can:
do url.getQS() instead of url.queryVars.getQS()
do url.getIP() instead of url.ip.getIP()
etc...
My code above was based on what I learned in this question here:
Is there a way to print all methods of an object in javascript?

Handling Array Building via Toggling Checkboxes in Angular 2 App

I am trying to come up with a simple toggle functionality where an array is built based on selections sent via checkboxes in my Angular 2 app. I seem to remember where you can handle toggling by setting one function to be equal to it's opposite, or something like that. These are the two functions I have for adding and removing array elements:
private onItemSelected(item)
{
if (item)
{
this.itemsArray.push(item);
console.log(this.itemsArray);
return this.itemsArray;
}
}
private onItemRemoved(item)
{
if (item)
{
this.itemsArray.splice(item);
console.log(this.itemsArray);
return this.itemsArray;
}
}
And then in my view I'm trying:
<md-checkbox (change)="onItemSelected('A') === onItemRemoved('A')">A</md-checkbox>
<md-checkbox (change)="onItemSelected('B') === onItemRemoved('B')">B</md-checkbox>
<md-checkbox (change)="onItemSelected('C') === onItemRemoved('C')">C</md-checkbox>
But this is not correct in it's current implementation. However, I do remember seeing something like this, though I can't remember the specific syntax. I've also considered using the "checked" JavaScript property in the conditional check. Would that be a better way to accomplish this? Or is there a simpler method to handle an array when items are added and removed based on checkbox selections?
UPDATE: After a suggestion from # I am closer. With this implementation I can toggle of and off for one item, and the array will reflect that correctly. However, for more than one item selection it doesn't work:
private isChecked: boolean = false;
private onItemSelected(discipline)
{
this.isChecked = !this.isChecked;
if (item && this.isChecked)
{
this.itemsArray.push(item);
console.log(this.itemsArray);
}
else {
if (item && !this.isChecked)
{
this.itemsArray.splice(item);
console.log(this.itemsArray);
}
return this.itemsArray;
}
}
i have posted a more simplified version without adding any extra properties . please try the code below
private onItemSelected(item)
{
const index: number = this.itemsArray.indexOf(item);
if(index < 0){
this.itemsArray.push(item);
console.log(this.itemsArray);
}
else if(index != -1){
this.itemsArray.splice(index,1);
console.log(this.itemsArray);
}
return this.itemsArray;
}

Calculated value not being updated correctly using donejs

I have standard donejs project with a component and a model both created using the default generators like this:
donejs add component storyBoard story-board
donejs add supermodel story
I left a sample project here: https://github.com/riescorp/donejs-promise-iterate
If you run this example using donejs develop and go to http://localhost:8080 you'll see something like this:
The problem is that whenever you change a value of the number of pages (input boxes) for any story, the sum won't update its value. I tried almost everything, from promises to already-resolved-promises but nothing seems to do the trick.
Somehow it seems that when any of those values change, there is no update trigger at all. I also tried setting the results as a List (can.List) but with no luck.
The problem seems to be that elem.pages should be elem.attr("pages").
Here's a version of the code that works for me:
export const ViewModel = Map.extend({
define: {
storyPromise: {
get: function() {
return Story.getList({});
}
},
stories: {
get: function(last, resolve) {
this.attr("storyPromise").then(resolve);
}
},
sum2: {
get: function() {
var total = 0;
if (this.attr('stories') !== undefined) {
this.attr('stories').each(function(elem) {
total += parseInt( elem.attr("pages") );
});
}
return total;
}
}
}
});
You have to use .attr, otherwise, sum2 doesn't know when the pages property changes.

How to correctly wait for the elements to get their correct width before accessing it in Angular 2?

I'm trying to make buttons the same width but I'm having problems actually waiting for Angular 2 to finish all the rendering and stuff before accessing the width of the buttons.
I tried using DoCheck which "works", by "works" I mean that it does pick up the correct width value, but only the second time it runs.
Since NgDoCheck runs twice for some reason, as soon as I add the button.style.width = widest + 'px'; statement, the buttons will all get a width of 115px, so the second time ngDoCheck runs, it will go over all the buttons again, but this time all the buttons will be set to 115px, so widest can never become the correct value of 159px since we set the value the first time ngDoCheck ran.
What would be the correct way to go about this issue? Using DoCheck just seems wrong since it runs twice, and I'm not really checking input values which is DoCheck's use case when reading the docs.
export class ButtonGroupComponent implements DoCheck {
constructor(private _elementRef: ElementRef) {}
ngDoCheck() {
if (this.equalWidth) {
let buttons = this._elementRef.nativeElement.getElementsByTagName('button');
let widest = 0;
for (var button of buttons) {
if (button.getBoundingClientRect().width > widest) {
widest = button.getBoundingClientRect().width;
}
}
for (var button of buttons) {
button.style.width = widest + 'px';
}
}
}
}
Using AfterViewInit seems to do the trick, it only execute once resulting in the correct behavior and correct values. It's as simple as swapping DoCheck and ngDoCheck for AfterViewInit and ngAfterViewInit:
export class ButtonGroupComponent implements AfterViewInit {
constructor(private _elementRef: ElementRef) {}
ngAfterViewInit() {
if (this.equalWidth) {
let buttons = this._elementRef.nativeElement.getElementsByTagName('button');
let widest = 0;
for (var button of buttons) {
if (button.getBoundingClientRect().width > widest) {
widest = button.getBoundingClientRect().width;
}
}
for (var button of buttons) {
button.style.width = widest + 'px';
}
}
}
}
Hope this helps someone else doing DOM manipulation.

How do I update a value in an AngularJS (with TypeScript) controller when a value on an injected service updates

At the moment I am trying to learn Typescript and AngularJS coming from a background of Actionscript. I have been playing around with some code experiments and van generally achieve what I want to but not always in the way that I would like!
At the moment I have this code:
export class CountingService
{
countScope = { count : 0 };
public increment()
{
this.countScope.count++;
}
}
export class PageController
{
constructor( private service : CountingService )
{
this.countScope = service.countScope;
}
public localCount : number = 0;
countScope;
public increment()
{
this.service.increment();
this.localCount++;
}
}
and I have a few different views that have this code in:
<div ng-controller="PageController as page">
<Button ng-click="page.increment()">Count {{page.localCount}}/{{page.countScope.count}}</Button>
</div>
When I switch between the views the localCount always resets to zero but the count in the countScope from the service does not reset and is incremented by each different controller.
This works but it's a bit messy. I have to have the untyped localScope floating around and the views need to know about the internal structure of the countScope object and bind to page.countScope.count rather than something like page.globalCount.
In Actionscript I would have a read only getting on the controller. The service would dispatch an event each time the value changed and the controller would listen for this and then update it's own property which would then update the view.
My question is what is the best practice way to achieve what I am doing here that does not require the view to have knowledge of the internals of an untyped object.
I am pretty sure that public getters do not exist but can I dispatch and listen for an event?
Many Thanks
Finally answer three. I didn't expect this to work but it does and IMHO this is by far the nicest solution.
It enforces encapsulation with getters, it doesn't need $scope injected and there are no concerns about memory leaks.
I welcome any criticisms that point out issues that I am not aware of.
The only thing that I AM aware of is that you need to enable ECMAScript 5 which means that IE 7 and 8 are not supported. That's fine for me.
export class CountingService
{
private _serviceCount : number = 100;
public increment()
{
this._serviceCount++;
}
get serviceCount() : number
{
return this._serviceCount;
}
}
export class PageController
{
constructor( private countingService : CountingService )
{}
private _localCount : number = 0;
get localCount() : number
{
return this._localCount;
}
get serviceCount() : number
{
return this.countingService.serviceCount;
}
public increment()
{
this.countingService.increment();
this._localCount++;
}
}
This seems like the perfect fit for a reactive library and is exactly what we do. I highly recommend you check out RxJs which has some awesome features. https://github.com/Reactive-Extensions/RxJS/tree/master/doc
https://www.youtube.com/watch?v=XRYN2xt11Ek
You code using Rx
export class CountingService {
//Create a stream of changes. When caller subscribe always give them the most recent value.
private rawCountStream = new Rx.Subject<number>();
public countStream : Rx.Observable<number>;
private count = 0;
constructor() {
this.countStream = this.rawCountStream.replay(1);
}
public increment() {
this.count++;
//Pump the value to callers.
this.rawCountStream.onNext(this.count);
}
}
export class PageController {
constructor(private service: CountingService) {
//Listen to the stream of changes.
service.countStream.subscribe(value => {
//do something
//IMPORTANT If you want angular to update the ui you will need to call $apply on a scope.
//RXJS has a scheduler for this so you look at that here.
//https://github.com/Reactive-Extensions/rx.angular.js
}
);
}
public localCount: number = 0;
public increment() {
this.service.increment();
this.localCount++;
}
}
This is answer number 1 and I think is the approach that John Kurlak was talking about:
export class CountingService
{
serviceCount : number = 100;
public increment()
{
this.serviceCount++;
}
}
export class PageController
{
constructor( $scope, private service : CountingService )
{
this.serviceCount = service.serviceCount;
$scope.$watch( () => this.service.serviceCount, ( newValue : number, oldValue : number ) => this.updateLocal( newValue, oldValue ) );
}
localCount : number = 0;
serviceCount : number;
public increment()
{
this.service.increment();
this.localCount++;
}
private updateLocal( newValue : number, oldValue : number )
{
this.serviceCount = newValue;
}
}
This works and I think is the sort of solution that I was after. There are things that I don't like about it though.
I don't like having to inject the $scope into my service. It seems to be an extra dependency on something that I shouldn't need.
I also really don't like the public members that can be updated by anything and break encapsulation. I want to fix this with getters but I can't figure out how to update to ECMAScript 5 (question here: Targeting ES5 with TypeScript in IntelliJ IDEA 14).
John - is this what you were proposing? If anyone comments with Pros and Cons to this answer I'd be very grateful.
I will mark this as the answer as I think this is what I was after to start with.
This is answer number 2 and I think this is what PSL was getting at (or something similar)
interface CountingCallBack
{
parent : any;
callback : ( value : number ) => void;
}
export class CountingService
{
private _observerCallBacks : Array<CountingCallBack> = [];
private _serviceCount : number = 100;
public increment()
{
this._serviceCount++;
this.notifyObservers();
}
public registerObserverCallback( callbackParent : any, callbackFunction : ( value : number ) => void )
{
var callback : CountingCallBack = { parent : callbackParent, callback : callbackFunction };
this._observerCallBacks.push( callback );
this.updateObserver( callback );
}
private notifyObservers()
{
angular.forEach( this._observerCallBacks, this.updateObserver, this );
}
private updateObserver( callback : CountingCallBack )
{
callback.callback.apply( callback.parent, [ this._serviceCount ] );
}
}
export class PageController
{
constructor( private countingService : CountingService )
{
countingService.registerObserverCallback( this, this.updateLocal );
}
localCount : number = 0;
serviceCount : number;
public increment()
{
this.countingService.increment();
this.localCount++;
}
private updateLocal( newValue : number )
{
this.serviceCount = newValue;
}
}
I'm quite pleased that I managed to implement this as I now understand how the function interface syntax works (and I quite like it) and I managed to fix the issue with the 'this' scope issue in the updateLocal function on the controller.
This solution doesn't require and $scope to be injected which is nice but I find the callback implementation quite messy. I don't like having to pass the controller and the function on the controller to the service to add a callback.
I suppose I could create an interface, ICountListener or something, and just pass that to the service and then call updateCount on the controller. That would be a bit neater but still not that good.
Is there a neater way around setting up the callback than this?
The biggest problem with this code (and a reason that it should not be used) is that it creates a memory leak. If you keep switching views the controllers never get cleaned up so you have many controllers left in memory all responding to the updated count.
It would be relatively easy to clean up the callback and de-register a callback but that would presumably involve injecting a $scope and then listening for a destroy event (I don't know how to do this but I think this was under discussion in the comments above).
Short answer: Services are singletons i.e. the constructor is only called once. If you want it reset you can do that from the constructor of your controller.
this.service.countScope.count = 0
Notes
I see a lot of things I don't like about that code sample. But I am sure its just a sample so don't take it as a personal offence ;)
don't have a service called service. Call it countService.
don't have a member countScope on the service. Just use count. And don't copy countScope to the current controller. Just use this.countService.count
Update
The root of this question is how do I update a displayed value in html when a value in a service has changed
Since you have the controller as page and page.service is the service simply {{page.service.countScope.count}} would display teh count.

Categories

Resources