Query Parameters Again

Hi,

I repeatedly run into issues with QueryParameters.

I have used them, I’ve even got them used in my project - not with the renderTemplate hook in a route admittedly, but they have worked in the normal case. But yet I’ll add another one and then get right back into issues again to the point of banging my head against the wall.

I have a route that overrides the renderTemplate() hook:

> this.render('user.companies.default.controller-submit', {
> 			  controller: 'user.companies.default.controller-submit',
> 			  model: model
> 			});

Inside controller-submit, (which does get called because I’ve tested that init() gets called as below), I have:

> queryParams: [
> 		'as2' 
> 	],
> 	as2:null ,
> 	init() {
> 		 this._super();
> 		 alert("in"+this.get('as2'));
> 	}

This in a child route of another route that also has its own query params declared.

Then in the browser, I try to go to this submit route:
http://localhost:4200/roomviewer-dreamdoors/159/submit?as2=37

Except the messagebox in the code above always says “null”. I have tried passing the as2 value to a component as well (binding to it) and the component also says:“null”. So its not that init() is trynig to access it too early necessarily.

Is it the rendertemplate() that is ruining my life here? Is it not set up properly to use query params

I did not realise the original default controller was still being used by the route. I had been thinking that the controller I specified in this.render would be the one it uses.

So I’ve fixed it using this in the submit route:

> setupController(controller, model) {
 		this._super(controller, model);
		var myController = this.controllerFor('user.companies.default.controller-submit');
		myController.set('as2', controller.get('as2'));		
> 	},

I wish there was some sort of getController hook that just lets you specify the controller you want to use for everything.

@Ben_Glancy in short, this architecture is not the best way to build your app, and you’ll likely find many issues with it when trying to connect things together.

Generally, the renderTemplate hook should be avoided. It’s overcomplicated and a holdover from older versions of ember that has pretty much no use that’s not better served by other methods.

What exactly are you trying to do that made you reach for renderTemplate in the first place?

1 Like

In short, I’m trying to use data to branch off different versions/customisations of the same application. I have so far settled on a kind of mix between using ENV.config and using data coming in from a parent route.

I tried at first to use different components using {{component}} but this is not suitable because then I am forced to use the same controller and template for multiple customers who have customised this application then I ran into issues regarding query parameters (again). Some query parameters are only relevant to certain customers so do not want them declared in the same controller. (would have been ok if the GET parameters could be accessed via a service instead of expecting them to be engraved into a controller as properties).

Yes I could have used different routes I suppose, which would have branched into a different controller and template. But then I’d have the problem of trying to establish a different route name for each customer which seems unwieldy as well - but could still be a sensible solution if RenderTemplate starts throwing me more curve balls - though…router.js would get awfully crowded. Being able to specify a controller name in data is very powerful. Hence my use of RenderTemplate. I haven’t experienced any issues yet.

So far it has worked ok once I started branching off into different controllers. It was just unexpected that the original controller was still being used for query parameters.

What would you do in this situation ?

The problem is, one application, multiple customers, flexible customization required for each. At any point a customer will turn around and need more customizations 6 month down the line.

I have had add-ons mentioned like ember-cli-deploy but I don’t have the time for a learning curve at the moment but have it mind to experiment with it when there’s a lull.

Query params are the real sticker here. It’s the only thing that you really need a controller for – anything else you’d do in a controller can be done in a component just as well. And as you’ve found, they don’t interact very well with trying to swap out your controller dynamically – by the time you’re picking a controller the query params already need to be known, because they need to be available to some of the Route methods.

Your options depend on how you determine which customer you’re dealing with. If you can know the customization as the app is first booting up, you can have one shared controller than establishes its set of query params based on the customer, at the point where the controller is first being created. And then it could always pass all of them down into a customer-specific component from there.

The basic idea would look like:

let MyController = Controller.extend({
  ... shared stuff ...
});

switch (customerId) {
case 1:
  MyController.reopen({ ... customized stuff ... })
  break;
case 2:
  MyController.reopen({ ... customized stuff ... })
  break;
}
export default MyController;

Another option – although I realize it’s a more dramatic architectural shift – is to do the customization at build time, and actually deploy many copies of the app. I used this strategy in a similar situation, where we had eight variants for different customers. In my case it was the full front- and back-end that was duplicated per customer, with their own subdomains. But it made life much easier when it came to being able to customize well, and even letting us rush out patches for one customer (who was clamoring for some feature) to try out before rolling it out to everyone else. Obviously this depends on having solid automated deployment / monitoring / etc because you don’t want to be hand-managing a herd of different apps.

They all shared the same codebase and almost always were running the same commit, unless we chose to temporarily pin one to a different branch.

Hi, thanks for your reply. It did not occur to me to ‘reopen’ the controller to define query properties. That could come in useful I think I’ll give that try. Do you think this could be done in setupController() of the route?

But it made life much easier when it came to being able to customize well, and even letting us rush out patches for one customer (who was clamoring for some feature) to try out before rolling it out to everyone else.

The above comment is the story of my life!

I didn’t fully understand what you mean by “deploy many copies”. ember.js is already deploying copies by default. when you do ember build -environment=production. I’d still have this master copy that needs configuring.

Did you mean just have a different build environment altogether for each project?

No, you should avoid reopening classes after they’ve been instantiated. It works (or at least it used to), but it’s imposes a bunch of overhead and we don’t want to keep supporting it.

Yes, different build targets that deploy to different places without overwriting each other. As one example, you can use ember-cli-deploy to manage many different targets so that you can run ember deploy customerA and ember deploy customerB. All of these builds would still be using the production environment, but they would get built with different configuration and deployed to different places (one way to separate the deploys is to give each build a different rootURL).

I suspect that being able to deploy to different folders doesn’t solve anything. You don’t need ember-cli-deploy to do that. I’m already doing it in config/environment.js with a few if’s and thens.

I still have the problem of managing/separating the actual code, separating out customer customisations. The query parameters are ruining my life by demanding to be put inside the controllers as properties.

In hindsight, I’m not sure a reopening of a class is the right way to do it either. I think the cleanest way is to actually just branch the entire project per customer.

That means that your ember-cli file watcher isn’t being bogged down by an ever increasing number of files , and it creates true separation leaving you free to hack bizarre changes in short time.

Sure, all I meant to suggest was what it sounds like you’re already doing – deploying separate apps per customer.

That enables you to do build-time customizations. For example, you could have a layout like this that lets you override specific files per customer:

.
├── app
│   └── controllers
│       └── something-shared.js
└── customizations
    ├── one
    │   └── controllers
    │       └── special.js
    └── two
        └── controllers
            └── special.js

You would make this work something like this (I’m paraphrasing here, I don’t have a tested example to point at):

// ember-cli-build.js
const WatchedDir = require('broccoli-source').WatchedDir;
const mergeTrees = require('broccoli-merge-trees');

let app = new EmberApp(defaults, {
  trees: {
    app() {
        let customer = figureOutWhichOne();
        return mergeTrees([
          this._super.apply(arguments), 
          new WatchedDir(`customers/${customer}`)
        ], { overwrite: true });
    }
  }
});

Something like that will overlay the customer-specific files over your regular app tree.

1 Like

But yeah, using branches per customer is also a totally valid strategy. It makes them even more independent, at the cost of spending more effort on merges. git is quite good at maintaining long-lived patch sets against an upstream source – that’s one of the things it was created for.

1 Like

Have you seen GitHub - offirgolan/ember-parachute: Improved Query Params for Ember ?

I’ve gone down the route of branching from a master version. Life got easier much quicker.