Angular 2: Creating a global, settable, observable property? - javascript

I've got an Angular 2 app (using v2.1.0) with an authentication service to allow the user to login and logout of the app. I want to create a globally available boolean property that can be set when a user logs in or logs out so I can easily show and hide parts of the UI based on the state of the user. Something like isAuthenticated would be fine. However, I'm not entirely sure what the best method is to create a global class/service, or what the recommended method is. I found this question, which does address it, but all of the answers are from way before the final release came out, and I believe are outdated. Currently I have a property on every component where I need to keep track of the logged in state that goes back to the authentication service, but it seems inefficient to me:
IsAuthenticated: boolean = this.authService.isAuthenticated();

You can try use *ngIf to add or remove element | Visibility to hide or show element
<div *ngIf="IsAuthenticated">Content</div> //add or remove
<div [class.hidden]="!IsAuthenticated">Show with class</div> //show or hide
For more detail
https://angular.io/docs/ts/latest/guide/template-syntax.html#!#ngIf

Add a boolean property to your service and default it to false. When the user logs in set it to true. Create a function in your service to return the boolean. Then just call it like you call your login function.
Services are singletons and do just what you are asking for.

First you need to declare your authService on app.module provider. 1 instance of authService for all your application.
Next you can do that :
<a *ngIf="!authService.isAuthenticated()" [routerLink]="['/login']">Login</a>

Related

Click events on Ember

I'm discovering EmberJS and started to migrate an existing website to this framework. I was having an issue with a Bootstrap-based dropdown. This issue actually helped me understand Ember's concepts a bit better but I still have some questions.
I used the ember-bootstrap module to generate this dropdown (among other things) and here is what the code is supposed to be:
{{#bs-dropdown as |dd|}}
{{#dd.button}}
Sort by
{{/dd.button}}
{{#dd.menu as |ddm|}}
{{#ddm.item}}{{#ddm.link-to "index"}}Price low to high{{/ddm.link-to}}{{/ddm.item}}
{{#ddm.item}}{{#ddm.link-to "index"}}Price high to low{{/ddm.link-to}}{{/ddm.item}}
{{/dd.menu}}
{{/bs-dropdown}}
Now, I want some javascript code to be executed when the user clicks on one of the items. After checking the module's documentation, I found where the menu item component was defined and edited its code as follows:
export default Component.extend({
layout,
classNameBindings: ['containerClass'],
/* ... */
actions: {
// My addition
sortByPrice(param){
alert("sorting");
},
// End of the addition
toggleDropdown() {
if (this.get('isOpen')) {
this.send('closeDropdown');
} else {
this.send('openDropdown');
}
},
},
});
Then I updated the hbs file as follows:
{{#dd.menu as |ddm|}}
{{#ddm.item action "sortByPrice" low_to_high}}
{{#ddm.link-to "index" action "sortByPrice" low_to_high}}
Prix croissant
{{/ddm.link-to}}
{{/ddm.item}}
{{/dd.menu}}
This didn't work, and that's why you I added the *action* to the link-to element as well and declared similarly the action on its component file.
import LinkComponent from '#ember/routing/link-component';
export default LinkComponent.extend({
actions: {
sortByPrice(param){
alert("sorting");
console.log("sorting");
},
},
});
As you can see, the *link-to* component extends the LinkComponent one. I eventually understood that it wasn't possible for this element to handle click events natively, as explained in this thread.
Out of frustration, I ended up with a less elegant approach that still does the trick:
{{#bs-dropdown id="sort" as |dd|}}
{{#dd.button}}
Sort by
{{/dd.button}}
{{#dd.menu as |ddm|}}
{{#ddm.item action "sortByPrice" low_to_high}}
<a
class="dropdown-item"
onclick="sortByPrice('low_to_high'); return false;"
href="#"
>
Price low to high
</a>
{{/ddm.item}}
{{/dd.menu}}
{{/bs-dropdown}}
Now here are my questions:
Why is it that defining actions on both the Component file and the hbs one didn't change the result?
Why doesn't the LinkComponent handle click events natively? I get that a link is supposed to redirect users to a new page (which is still arguable), but the DOM event is still fired, so does Ember deliberately ignore it and choose not to let developers handle it? I want to know the logic behind this.
Is there a better approach than my solution?
Thanks.
Cheers for studying EmberJS and posting a beautiful, explicit question!
Your mistakes
Never modify the code inside node_modules/ and bower_components/ folders. If you really need to monkey-patch something, you can do it in an initializer. But your use case does not require monkey patching.
You attempted to define an action in the menu item component, but you apply it in a parent template. That action has to be defined in that parent's template component/controller.
This invocation is incorrect:
{{#ddm.link-to "index" action "sortByPrice" low_to_high}}
Here are the problems:
The ddm.link-to component is supposed to create a link to another route. It does not seem to support passing an action into it.
You're just passing a bunch of positional params to the component. If ddm.link-to did support accepting an action, the correct invocation would look like this:
{{#ddm.link-to "index" argName=(action "sortByPrice" low_to_high)}}
In this case, "index" is a position param and argName is a named param.
low_to_high without quotes is a reference to a property defined on the current scope. You probably meant a string instead: "low_to_high".
Never use JS code in template directly. This you should never do in Ember:
<a onclick="sortByPrice('low_to_high'); return false;">
Instead, pass an action (defined in the local scope: in a component or controller):
<a onclick={{action 'sortByPrice' 'low_to_high'}}>
The onclick property name is optional. An action defined without a property implies onclick (you only need to provide the property name if you need to attach the action to a different event):
<a {{action 'sortByPrice' 'low_to_high'}}>
For the link to be styled properly in a browser, a href attribute is required. But you don't have to pass a value '#' to it. The hash symbol was required in old-school apps to prevent the link from overwriting the URL. Ember overrides URL overwriting for you, so you can simply pass an empty href.
Here's the final correct usage:
<a href {{action 'sortByPrice' 'low_to_high'}}>
Answers to your questions
Why is it that defining actions on both the Component file and the hbs one didn't change the result?
Because you defined them in different scopes.
If you define an action in app/components/foo-bar.js, the action must be applied in app/templates/components/foo-bar.hbs.
If you define an action in app/controllers/index.js, the action must be applied in app/templates/index.hbs.
Why doesn't the LinkComponent handle click events natively? I get that a link is supposed to redirect users to a new page (which is still arguable), but the DOM event is still fired, so does Ember deliberately ignore it and choose not to let developers handle it? I want to know the logic behind this.
In a PWA, you do not do actual page redirects. Such a redirect would reload the whole app.
Instead, the LinkComponent overrides the click and tell the Ember's routing system to perform a transition. Routes must be set up properly and the route passed to the LinkComponent must exist.
It seems that your goal is not to perform a transition but to change a variable, so the LinkComponent is not applicable here. That's unless you wire the sort order property to an URL query param, in which case you can change the sort order by making a transition to a different query param.
Is there a better approach than my solution?
See below for the simplest approach that uses ember-bootstrap's dropdown.
A working example
Controller:
export default Ember.Controller.extend({
isSortAccending: true,
actions: {
changeSortDirection (isSortAccending) {
this.set('isSortAccending', isSortAccending);
}
}
});
Template:
<p>
Current sort order:
{{if isSortAccending "ascending" "descending"}}
</p>
{{#bs-dropdown as |dd|}}
{{#dd.button}}
Sort by
{{/dd.button}}
{{#dd.menu as |ddm|}}
{{#ddm.item}}
<a href {{action "changeSortDirection" true}}>
Price high to low
</a>
{{/ddm.item}}
{{#ddm.item}}
<a href {{action "changeSortDirection" false}}>
Price high to low
</a>
{{/ddm.item}}
{{/dd.menu}}
{{/bs-dropdown}}
Here's a working demo.

Angular.copy keeps giving me the same object

I have an AngularJS application that manages badges. In the application is a form to set the badge # and the name of the person it is assigned to, etc. This gets stored in $scope.badge.
When the user submits the form, I want to add the new badge to a list of badges, which is displayed below the form.
Partial code looks like this:
var badge = angular.copy($scope.badge); // make a copy so we don't keep adding the same object
$scope.badgeList.push(badge);
The first time I run this code, it adds the badge as expected.
Any subsequent time I run this code, the next badge REPLACES the previous badge in the badgeList. In other words, if I add 5 badges, the badgeList still only has 1 object in it because it just keeps getting replaced.
I'm thinking that this may be happening because the same object keeps getting added? Maybe I'm wrong? I am using angular.copy to try and avoid that happening, but it doesn't seem to be working.
Any thoughts on this?
$scope.badgeList.push(($scope.badge);
console.log($scope.badgeList)
no need to use angular.copy since you are ultimately storing all the badges in an array
angular.copy is used when you want to make a clone of object and not update the existing object and the clone's change are not reflected in main object.
If you just want to maintain a list of badges you can execute this block of code
like this
function addBadges(){
$scope.badgeList.push(($scope.badge);
console.log($scope.badgeList)
}
If you are refreshing the controller then obviously the variable will be reset and for such a case you need to make use of angular services.
Create a service and inside the service you need to define getter and setter method that will help in data persistence
and your bages array if saved in service will persist till the application is in foreground.
You could do something like this.
function addBadges(){
//initialize if undefined or null
if(!$scope.badgeList){
$scope.badgeList = [];
}
//Check if badge does not exists in the list
if ($scope.badgeList.indexOf($scope.badge) === -1) {
//Add to badge list
$scope.badgeList.push($scope.badge);
}
}

Proper way to bind to data object in Angular 2 service?

I am building an angular 2 application. The documentation has changed quite a bit since the released which has caused confusion. The best I can do is explain what I am trying to do (Which was easy in Angular 1) and hope someone can help me out.
I have created a login service using JWT's.
Once login is successful, I return a user object.
I have a loginComponent ( binds data to template ) and loginService ( which handles the https calls )
I have a userService which maintains the user object.
I have a userComponent which renders the user data.
The problem is, once the user has logged in, I am unclear on the best approach for letting the userService retrieve the new data in an object called "user", then the userComponent update its user object on the template. This was easy in angular 1 simply by putting a watcher on the userService.user object.
I tried Inputs and Outputs to no avail, eventEmitters, Observables and getters and setters. The getters and setters work, but force me to store everything in a "val()"
Can someone please tell me the best way to achieve this?
User Component renders template with user.firstName, user.lastName etc.
Initially user if an empty Object
The login service needs to set the UserService.user
The userComponent Needs to detect the change and update the DOM.
Thanks in ADVANCE!
If I'm not wrong, you are looking for a way to 'listen' to changes in your UserService.user to make appropriate updates in your UserComponent. It is fairly easy to do that with Subject (or BehaviorSubject).
-In your UserService, declare a property user with type Subject<User>.
user: Subject<User> = new Subject();
-Expose it to outside as observable:
user$: Observable<User>
...
this.user$ = this.user.asObservable();
-Login function will update the private user Subject.
login(userName: string, password: string) {
//...
this.user.next(new User("First name", "Last name"));
}
-In your UserComponent, subscribe to UserServive's user$ observable to update view.
this.userService.user$.subscribe((userData) => {this.user = userData;});
-In your view, simply use string interpolation:
{{user?.firstName}} {{user?.lastName}}
Here is the working plunker: http://plnkr.co/edit/qUR0spZL9hgZkBe8PHw4?p=preview
There are two rather different approaches you could take:
1. Share data via JavaScript reference types
If you create an object in your UserService
#Injectable()
export class UserService {
public user = new User();
you can then share that object just by virtue of it being a JavaScript reference type. Any other service or component that injects the UserService will have access to that user object. As long as you only modify the original object (i.e., you don't assign a new object) in your service,
updateUser(user:User) {
this.user.firstName = user.firstName;
this.user.lastName = user.lastName;
}
all of your views will automatically update and show the new data after it is changed (because of the way Angular change detection works). There is no need for any Angular 1-like watchers.
Here's an example plunker.
In the plunker, instead of a shared user object, it has a shared data object. There is a change data button that you can click that will call a changeData() method on the service. You can see that the AppComponent's view automatically updates when the service changes its data property. You don't have to write any code to make this work -- no getter, setter, Input, Output/EventEmitter, or Observable is required.
The view update automatically happens because (by default) Angular change detection checks all of the template bindings (like {{data.prop1}}) each time a monkey-patched asynchronous event fires (such as a button click).
2. "Push" data using RxJS
#HarryNinh covered this pretty well in his answer. See also Cookbook topic Parent and children communicate via a service. It shows how to use a Subject to facilitate communications "within a family".
I would suggest using a BehaviorSubject instead of a Subject because a BehaviorSubject has the notion of "the current value", which is likely applicable here. Consider, if you use routing and (based on some user action) you move to a new route and create a new component, you might want that new component to be able check the "current value" of the user. You'll need a BehaviorSubject to make that work. If you use a regular Subject, the new component will have no way to retrieve the current value, since subscribers to a Subject can only get newly emitted values.
So, should we use approach 1. or 2.? As usual, "it depends". Approach 1. is a lot less code, and you don't need to understand RxJS (but you do need to understand JavaScript reference types). Approach 2. is all the rage these days.
Approach 2. could also be more efficient than 1., but because Angular's default change detection strategy is to "check all components", you would need to use the OnPush change detection strategy and markForCheck() (I'm not going to get into how to use those here) to make it more efficient than approach 1.

How to change stored value in Value recipe of angular

From the angular documentation, I can see that a value recipe can be used to store some information that can be injected in different modules. So I wanted to use this for storing some user related configurations in my angular app.
What I am doing right now:
Set a value by default-
app.value('display', {
header: true,
switcher: true
})
I have a header and switcher in my views that I want to show or hide based on the value of header and switcher coming from above assignment.
This part of display and hiding works fine. What I want is that if some controller changes the value of header to false, header should then be hidden for that particular user. So from within my controller I just set the values to false. But on page refresh, these values are gone.
I am not sure what is going wrong here. Are we not supposed to change the value? If not, isn't that just a constant. In case we are not supposed to update values, what would be a better way to store some user related variables that will be available to entire app.
First make a factory like
(function() {
"use strict";
angular.module('dataModule',[])
.factory('datafactory',function(){
return {
};
});
})();
Now datafactory can be accessed any where in application just you need to inject this module in required module and factory in required controller
use like this
datafactory.myReusableVar ="something"
later on in someother controller
$scope.myLocalVar =datafactory.myReusableVar
//using session storage
var x ="value"// x can be any data type string,array, or object
sessionStorage.setItem("mySessionItem",x)
$scope.mysessionValue =sessionStorage.getItem("mySessionItem")
The problem is that on page refresh the current context is lost (including all the variables), you should resolve your issue by storing the data inside your browser's localStorge.
check this module
https://github.com/grevory/angular-local-storage

How to inject state parameter automatically

Abstract
Hi, I'm using angular + ui-router in my project, I have huge amount of nested states and different views that in turn contain huge amount of different inputs, a user fills these inputs incrementally step by step.
The problem
Sometimes users require additional info that is located on the previous step, and browsers "back" button helps the user to sneak peek into that data, but as soon as the user presses it, the info he already entered is lost due to state transition, which is obviously a bad thing.
Strategy
In order to overcome described problem I have the following plan:
Associate each user's "navigation" (guess this is a proper term) with a random id
To prevent scope-inheritance-and-serialization issues, instead of putting viewmodel into $scope use ordinary javascript object that will be storing immediate values that are bound to UI.
Add watcher to look for changes on that "storage object"
As soon as the change spotted, serialize the object and persist it
Explanations
Why do we need a random parameter in URL?
We don't want to store all data in URL, since there might be quite some amount of data that wont fit into URL. So in order to provide the guarantees the URL won't break, we put only small random GUID/UUID into it that later allows obtaining the data associated with current "navigation" by this random GUID/UUID.
The storage
There are multitude of storage scenarios available out there: LocalStorage, IndexedDB, WebSQL, Session Storage, you name it, but due to their cross-tab, cross-browser, browser-specific nature it would be hard to manipulate and manage all of the data that gets into the storage. The implementation will be buggy / might require server-side support.
So the most elegant storage strategy for this scenario would be storing data in special window.name variable which is capable of storing data in-between requests. So the data is safe until you close your tab.
The Question
On behalf of everything written above, I have the root view called "view" that has a state parameter id (this is the random GUID/UUID)
$stateProvider.state('view', {
url: '/view/{id}',
controller: 'view',
templateUrl: 'views/view.html'
});
All of the other views derive from this view, is there way to make ui-sref directive to automatically inject a random GUID/UUID into id state parameter of my root view, instead of writing each time ui-sref's like:
<a ui-sref="view({id:guid()}).someNestedView({someNestedParam: getParam()})"
I would like to have something like:
<a ui-sref="view.someNestedView({someNestedParam: getParam()})"
The AOP and Decorator pattern are the answer. The comprehensive description could be found here:
Experiment: Decorating Directives by Jesus Rodriguez
Similar solution as described below, could be observed:
Changing the default behavior of $state.go() in ui.router to reload by default
How that would work? There is a link to working example
In this case, we do not solve from which source the random GUID comes from. Let's just have it in runtime:
var guidFromSomeSource = '70F81249-2487-47B8-9ADF-603F796FF999';
Now, we can inject an Decorator like this:
angular
.module('MyApp')
.config(function ($provide) {
$provide.decorator('$state', function ($delegate) {
// let's locally use 'state' name
var state = $delegate;
// let's extend this object with new function
// 'baseGo', which in fact, will keep the reference
// to the original 'go' function
state.baseGo = state.go;
// here comes our new 'go' decoration
var go = function (to, params, options) {
params = params || {};
// only in case of missing 'id'
// append our random/constant 'GUID'
if (angular.isUndefined(params.id)) {
params.id = guidFromSomeSource;
}
// return processing to the 'baseGo' - original
this.baseGo(to, params, options);
};
// assign new 'go', right now decorating the old 'go'
state.go = go;
return $delegate;
});
})
Code should be self explanatory, check it in action here

Categories

Resources