In EmberJS how can you use transition data in a controller? - javascript

I am getting the transition data in the route js file like so:
beforeModel(transition) { console.log(transition) }
And I want to use it in a function in my controller like this:
import Controller from '#ember/controller';
export default class ListingsController extends Controller {
get pageTitle() {
if (this.transition.targetName == 'foo') {
return 'page title';
}
}
}
And then I want to display the result like this:
<h1>{{this.pageTitle}}</h1>
I cannot find a way to pass the transition data from the route to the controller. Any ideas?

While you technically can leverage the beforeModel to get the controller via this.controllerFor as #KathirMagaesh suggests, I wouldn't actually advocate for this solution. It's definitely not the normal or expected Ember pattern. Furthermore, if you look at the transition api, there is no reference to transition.targetName. If this works, this is private api and thus brittle.
If you need to change a property based on the current route, you should be using the public router service which provides some useful properties for this very purpose!
For example, in your controller you could have a computed property that leverages the router service to determine what the page title should be
import Controller from '#ember/controller';
import { computed } from '#ember/object';
import { inject } from '#ember/service';
// this injects the router service into our component via Ember's DI framework
router: inject(),
export default Controller.extend({
pageTitle: computed('router.currentRouteName', function(){
let currentRoute = this.router.currentRouteName;
if(currentRoute === 'foo'){
return 'page title';
}
// do other stuff for other routes.
})
})
This leverages currentRouteName which is the period separated name like foo.bar. You can also also access the url via currentURL which would be /foo/bar
PS. Since I haven't used ES6 classes yet, I've provided the old style ember solution. You'll probably need to use the #computed decorator or #tracked where I'm using the computed function. I only know about the Octane ember style from RFCs and awesome blog posts but am not up to date with what's landed.
PPS. If you're on old ember, the current route name / URL properties are available on the application controller.

In the beforeModel hook use
this.controllerFor(currentrRouteName).set('transition', transition);
This will set transition property in controller of the current router.
For more on controllerFor()

Related

Angular Router pass data to component

Is it a good practice to pass data with the angular router to a component or should i use an service instead?
At the moment the component gets the data like this:
this.account = activatedRoute.snapshot.data.account
There are several ways to pass data to an angular component.
For objects like user account, I would use a provider (to have it ready on component init), a service (for sharing around app) or a guard (e.g. if you want to navigate out when not logged in).
When I want to reuse the same component in different routes and give it some hints about is behavior, I would use router data.
Another use case I met is to define a global app state using the activated route(s). Each route may define its data, a service listen for router events and stores the merged state.
It helps me with large apps to have a route-based configuration for title, metas, toolbar and menus visibility, etc...
If you want to pass data through a route, here is a simple example.
Make your route to look like this:
{ path: 'todo', component: TodoComponent, data: { id:'1', name:"Todo Title"} }
Then in your Component you can do something like this:
ngOnInit() {
this.activatedroute.data.subscribe(data => {
this.todo = data;
})
}
Was this helpful?

Ember passing an action closure through an outlet

I am building a simple Ember app, but I have run into difficulty passing an action closure to a child component when that component is rendered in the {{outlet}} of a navigable container.
For context, here is a quick look at the aesthetically-astonishing app I have been building:
I have a roles/role path that displays a component (the yellow section above) with the following markup. Note that the model for this component is an instance of a Role:
// file: app/components/role.hbs
<p>{{#role.name}}</p>
<div>
{{sel-nav-tabs items=this.tabConfig}}
<div class='route-content'>{{outlet}}</div>
</div>
(Where "sel" stands for "someone else's library".)
this.tabConfig is defines in the corresponding class:
// file: app/components.role.js
import Component from '#glimmer/component';
export default class RoleComponent extends Component {
get tabConfig() {
return [
{ label: 'Users', route: 'roles.role.users' },
{ label: 'Privileges', route: 'roles.role.privileges' },
];
}
}
Into the outlet in role.hbs will be rendered the appropriate list component, either users or privileges.
The users list is rendered by the following component. Note that the model is the list of User instances associated with the Role from its parent:
// file: app/components/role/user-list.hbs
<ul>
{{#each #users as |user|}}
<li>
{{user.name}}
{{#sel-button type="toolbar" onActivate=this.removeUser}}
{{sel-icon/remove-circle}}
{{/sel-button}}
</li>
{{/each}}
</ul>
and when the button is clicked it calls an action defined in the RoleUserListComponent class:
// file: app/components/role/user-list.js
import Component from '#glimmer/component';
import { action } from "#ember/object";
export default class RoleUserListComponent extends Component {
#action removeUser(user) {
// remove the user model from the role... but which role?
}
}
The catch is that the relationship between users and roles is many-to-many, so I can't simply unset the user's owner and let Ember Data take care of things. The obvious answer seemed like passing an action closure from the role component to its child user-list component.
Except, there seems to be no way to pass the action closure through the {{outlet}}. What I was hoping for was something like:
{{outlet onActivate=(action removeUser #role)}}
which would pass the closure to any component that was rendered there. I tried instead to use {{yield user}} in the child to let the parent render the delete button and give it the appropriate action, but that also hit the outlet wall.
I also tried to use controllers, which aren't documented that well, probably since their role seems to have been evolving dramatically over Ember's maturation. But while this brief explanation does mention passing down actions, it doesn't go into details, and the few up-to-date examples I found all seem to break when an outlet joins the party.
I'm suspecting that {{outlet}} just plain isn't closure-friendly.
While defining a service would probably work, that doesn't seem to be what services are intended for, and I'd be cluttering up my global space to solve a local problem.
What is the best practice (or, really, any practice) for dealing with getting messages through outlets? I looked for ways to query the earlier parts of the path, but I didn't find any that were defined in the relevant classes.
EDIT to add more detail:
The route template for /roles/role is simply:
// file app/templates/roles/role
{{role role=#model}}
Where the Role component is in the first listing above. (I also added the role.js file contents above.) My reasoning for doing that was that by making a component I created a logical place to put the config (rather than inline helper functions) and it just gave me a sense of tidiness to have all ui elements be in components.
If a refactor can be the anchor to a good solution (essentially copying the entire Role component into the route template), however, I'll happily do it.
{{outlet}} only supports one optional string argument for a named outlet and nothing else, so you won't be able to achieve this through the use of {{outlet}}!

How to access variable on template file which is set in route or component in ember

This is very basic question but i am not finding it any where.
As per my understanding before rendering template correspondin route or component.js's beforeModel() model() etc functions gets called.
What i want to do:
I want to show image of logged in user on my sidenav. User's data is stored in local storage.
My problems here
I am hoping that setting a variable in model and returning the same will solve my problem, but my model method is not being called at all.
My Code:
Template:
{{#paper-sidenav
class="md-whiteframe-z2"
name="right"
open=leftSideBarOpen2
lockedOpen=leftSideBarLockedOpen
position="right"
onToggle=(action (mut leftSideBarOpen2))}}
{{#paper-toolbar as |toolbar|}}
{{#paper-toolbar-tools}}
<img src="http://example.com/users/{{model.username}}.jpg" />
{{/paper-toolbar-tools}}
{{/paper-toolbar}}
{{#paper-content padding=true}}
Çup?
{{/paper-content}}
{{/paper-sidenav}}
Component.js
import Ember from 'ember';
export default Ember.Component.extend({
beforeModel(){
},
model(){
let user = localStorage.get('user');
console.log(user.username);
return user;
},
actions:{
toggle(propName) {
this.toggleProperty(propName);
}
}
});
In console i am getting error "GET http://example.com/users/.jpg 404 (Not Found)", which certainly says that user.username in my template evaluates to null. i tried debugging my model method in chrome's dev tool and found that model is not getting called
Could you please let me know where i'm mistaking?
Ember component does not have model() and afterModel() hooks, what you need to do, is first access the local storage data in afterModel() hook in route and pass the resolved model to component.
Main Route
export default Ember.Route.extend({
model(){
//load data
},
afterModel(model){
//access local store and set to model
model.set('users',data);
}
});
Main route hbs
{{component componetModel=model}}
inside component
import Ember from 'ember';
export default Ember.Component.extend({
init(){
this.set('users', componetModel.users);
}
});
component hbs
{{#each user in users}}
{{user.name}}
{{/each}}
You really have to read the documentation about component. It clearly state what are the lifecycle hooks for each stage,
init
update
delete
https://guides.emberjs.com/v2.11.0/components/the-component-lifecycle/
The only thing the template have access to from route is model so that you can use model directly inside the corresponding template OR pass it to the component(s).
If you need other property for your component you can either set them inside route's setupController(controller, model) hook OR create a controller for that route.
Illustration below is a sample for one specific route,
NOTE: Dashed line represents the runtime generate controller
See image above each "layer" only have access the property OR action direct inside the upper layer (special case is route auto inject model into runtime generated controller).
You do NOT need to specifically define your own controller because if the route cannot find one it will generate one at runtime.
The component is completed isolated from literally anything (unless you inject anything into it). The only way to work with the component is to pass data and action (usually used to handle events) to it.
Summary
(1) The component can only access the data you specifically passed to it (see code below).
{{component-name
internalName=externalName
}}
(2) The template can only access the data that is available inside the controller, either auto-generated one or via ember g controller controller-name. (model property is an exception)
(3) If you want to your template to have route data, using setupController hook.

Aurelia doesn't 'refresh' vm when navigating

Jolly good evening! In my Aurelia-App I'm using a viewModel to deal with various views via an navigationStrategy (reading out route-parameters and setting the view accordingly).
Navigation works baiscally well, there is one problem however:
When I keep navigating between routes that are based on the same viewModel, the viewModel doesn't 'refresh'. Only when navigating to a different route with a different viewModel first, and then back to the intended route, the contents are shown as expected.
It seems like the lifecycle-hooks of the component are not kicking in. Is there any way to trigger unbind() and detached() manually? Or is there a better way to do things generally?
Also the Route-Configuration seems a bit weird. When I'm taking away moduleId the app crashes, and when I'm taking away layoutViewModel the Data is not bound to the view. My Workaround for now is to assign an empty viewModel + an empty template. Am I using this wrong?
Big thanks!
configureRouter(config, Router) {
var getModelStrat = (instruction) => {
instruction.config.layoutView = "pages/templates/"+instruction.params.model+".html"
}
config.addAuthorizeStep(AuthorizeStep);
config.title = 'Aurelia';
config.map([
{
route: 'detail/:model/:id?',
name: 'detail',
moduleId: 'pages/empty',
layoutViewModel: 'pages/detail',
auth: true,
navigationStrategy: getModelStrat
},
{...}
]);
}
This is by design. Router will try to reuse existing view models.
If you need to override this per view model, then create determineActivationStrategy() method on it and return activationStrategy.replace:
import { activationStrategy } from 'aurelia-router';
export class SomeViewModel {
// ...
determineActivationStrategy() {
return activationStrategy.replace;
}
// ...
}
If you need to override this for each view model / route then take a look at Marton Sagi's answer for a similar question. Basically, all of your routes need to define activationStrategy: 'replace'.

Ember.js: redirect in router if certain condition is satisfied in controller?

Basically the objective is render the account page if user is logged in, otherwise redirect to a login page.
I have the following routes:
App.Router.map(function() {
this.resource('account', { path: '/'});
this.route('login', { path: '/login' });
})
My current code tries to access a loggedIn attribute in the account controller in the route:
App.AccountRoute = Ember.Route.extend({
renderTemplate: function(controller) {
var loggedIn = controller.get('loggedIn'); // ERROR: controller undefined
if (!loggedIn) {
this.transitionTo('login');
}
}
});
Should I implement this logic in the router? Why is the controller undefined in my route? Thanks!
Here are a couple ideas that might help you:
Your controller does not always exist. It is created by Ember when it needs it the first time. You can use the Chrome extension for Ember debugging to see which controllers are already created. In your case it should be available though since you are in the renderTemplate hook. In general, redirects should be done either in the beforeModel hook or the redirect hook:
redirect: function () {
if (!this.controller.get('loggedIn')) {
this.transitionTo('login');
}
}
Consider moving the authentication logic into an Ember service (example). A service in Ember is simply a class that extends Ember.Object. You will have the ability to inject that service into all your controllers and routes so it will be always available.
Even better: consider using the excellent ember-simple-auth that handles both authentication and authorization. It will create a session service available everywhere in your app, so you will be able to do things such as:
// Ensures the user is authenticated
if (!this.get('session.isAuthenticated')) {
this.transitionTo('login');
}
Or even better (since you don't want to copy paste that stuff everywhere):
// This route is now authenticated!
App.AccountRoute = Ember.Route.extend(AuthenticatedRouteMixin, {
...
}
And many other cool things!
Also, I see that you are not using Ember CLI yet. I'd recommend it once you feel more comfortable with Ember. Ember CLI is the future of Ember, it comes with a slightly different syntax but lot of great things.

Categories

Resources