Use this function to flatten the response returned from strapi on version 4. Helps you get rid of data and attributes properties
This will give you the same response structure as version 3 of strapi. This would help you migrate to version 4 from version 3 easily.
How to use it?
import the file.
const flattnedData = flattenObj({...data})
NOTE: The data here is the response returned from strapi version 4.
export const flattenObj = (data) => {
const isObject = (data) =>
Object.prototype.toString.call(data) === "[object Object]";
const isArray = (data) =>
Object.prototype.toString.call(data) === "[object Array]";
const flatten = (data) => {
if (!data.attributes) return data;
return {
id: data.id,
...data.attributes,
};
};
if (isArray(data)) {
return data.map((item) => flattenObj(item));
}
if (isObject(data)) {
if (isArray(data.data)) {
data = [...data.data];
} else if (isObject(data.data)) {
data = flatten({ ...data.data });
} else if (data.data === null) {
data = null;
} else {
data = flatten(data);
}
for (const key in data) {
data[key] = flattenObj(data[key]);
}
return data;
}
return data;
};
In my case I created a new middleware "flatten-response.js" in "middlewares" folder.
function flattenArray(obj) {
return obj.map(e => flatten(e));
}
function flattenData(obj) {
return flatten(obj.data);
}
function flattenAttrs(obj) {
let attrs = {};
for (var key in obj.attributes) {
attrs[key] = flatten(obj.attributes[key]);
}
return {
id: obj.id,
...attrs
};
}
function flatten(obj) {
if(Array.isArray(obj)) {
return flattenArray(obj);
}
if(obj && obj.data) {
return flattenData(obj);
}
if(obj && obj.attributes) {
return flattenAttrs(obj);
}
for (var k in obj) {
if(typeof obj[k] == "object") {
obj[k] = flatten(obj[k]);
}
}
return obj;
}
async function respond(ctx, next) {
await next();
if (!ctx.url.startsWith('/api')) {
return;
}
ctx.response.body = flatten(ctx.response.body.data)
}
module.exports = () => respond;
And I called it in "config/middlewares.js"
module.exports = [
/* ... Strapi middlewares */
'global::flatten-response' // <- your middleware,
'strapi::favicon',
'strapi::public',
];
The routing is not working for the index.html. It is even giving a compiler error. Index.html is my startup page. Through Header details link the Add Header.html page should open. I have added the whole code in plunkr ["https://plnkr.co/edit/w9eWiHKvSDrf0viERgoX?p=preview"]
app.js
var MyApp = angular.module('MyApp', ['ngRoute']);
// configure our routes
MyApp.config(function ($routeProvider) {
$routeProvider
// route for the home page
.when('/', {
templateUrl: 'AddHeader.html',
controller: 'headerCtrl'
})
.when('/AddHeader', {
templateUrl: 'AddHeader.html',
controller: 'headerCtrl'
})
// route for the about page
.when('/ProjectIDCreation', {
templateUrl: '/ProjectIDCreation.html',
controller: 'headerCtrl'
})
});
HeaderCtrl.js
var app = angular.module('MyApp');
var baseAddress = 'http://localhost:49754/api/TimeSheet/';
var url = "";
//var app = angular.module('MyApp');
//app.controller('mainController', function ($scope) {
// console.log('mainController');
//});
app.factory('userFactory', function ($http) {
return {
getHeadersList: function () {
url = baseAddress + "FetchHeaderDetails";
return $http.get(url);
},
addHeader: function (user) {
url = baseAddress + "InsertHeaderDetails";
return $http.post(url, user);
},
updateHeader: function (user) {
url = baseAddress + "UpdateHeaderDetails";
return $http.put(url, user);
}
};
});
//var app = angular.module('MyApp');
app.controller('headerCtrl', function PostController($scope, userFactory) {
$scope.users = [];
$scope.user = null;
$scope.editMode = false;
//Fetch all Headers
$scope.getAll = function () {
userFactory.getHeadersList().success(function (data) {
$scope.users = data;
}).error(function (data) {
$scope.error = "An Error has occured while Loading users! " + data.ExceptionMessage;
});
};
//Add Header
$scope.add = function () {
var currentUser = this.user;
userFactory.addHeader(currentUser).success(function (data) {
$scope.addMode = false;
currentUser.HeaderID = data;
$scope.users.push(currentUser);
$scope.user = null;
$('#userModel').modal('hide');
}).error(function (data) {
$scope.error = "An Error has occured while Adding user! " + data.ExceptionMessage;
});
};
//Edit Header
$scope.edit = function () {
$scope.user = this.user;
$scope.editMode = true;
$('#userModel').modal('show');
};
//Update Header
$scope.update = function () {
var currentUser = this.user;
userFactory.updateHeader(currentUser).success(function (data) {
currentUser.editMode = false;
$('#userModel').modal('hide');
}).error(function (data) {
$scope.error = "An Error has occured while Updating user! " + data.ExceptionMessage;
});
};
//Model popup events
$scope.showadd = function () {
$scope.user = null;
$scope.editMode = false;
$('#userModel').modal('show');
};
$scope.showedit = function () {
$('#userModel').modal('show');
};
$scope.cancel = function () {
$scope.user = null;
$('#userModel').modal('hide');
}
// initialize your users data
$scope.getAll();
});
Make sure the file path which you have used in script tags are correct. Which in the plnkr were not correct. Also i found you had two modules defined avoid doing that. Also you are importing angular, jquery, bootstrap more than once dont do that.
Below is the corrected code
Edited plnkr
var app = angular.module('MyApp', ['ngRoute']);
// configure our routes
app.config(function ($routeProvider) {
$routeProvider
// route for the home page
.when('/', {
templateUrl: 'AddHeader.html',
controller: 'headerCtrl'
})
.when('/AddHeader', {
templateUrl: 'AddHeader.html',
controller: 'headerCtrl'
})
// route for the about page
.when('/ProjectIDCreation', {
templateUrl: 'ProjectIDCreation.html',
controller: 'headerCtrl'
})
});
var baseAddress = 'http://localhost:49754/api/TimeSheet/';
var url = "";
app.factory('userFactory', function ($http) {
return {
getHeadersList: function () {
url = baseAddress + "FetchHeaderDetails";
return $http.get(url);
},
addHeader: function (user) {
url = baseAddress + "InsertHeaderDetails";
return $http.post(url, user);
},
updateHeader: function (user) {
url = baseAddress + "UpdateHeaderDetails";
return $http.put(url, user);
}
};
});
//var app = angular.module('MyApp');
app.controller('headerCtrl', function PostController($scope, userFactory) {
$scope.users = [];
$scope.user = null;
$scope.editMode = false;
//Fetch all Headers
$scope.getAll = function () {
userFactory.getHeadersList().success(function (data) {
$scope.users = data;
}).error(function (data) {
$scope.error = "An Error has occured while Loading users! " + data.ExceptionMessage;
});
};
//Add Header
$scope.add = function () {
var currentUser = this.user;
userFactory.addHeader(currentUser).success(function (data) {
$scope.addMode = false;
currentUser.HeaderID = data;
$scope.users.push(currentUser);
$scope.user = null;
$('#userModel').modal('hide');
}).error(function (data) {
$scope.error = "An Error has occured while Adding user! " + data.ExceptionMessage;
});
};
//Edit Header
$scope.edit = function () {
$scope.user = this.user;
$scope.editMode = true;
$('#userModel').modal('show');
};
//Update Header
$scope.update = function () {
var currentUser = this.user;
userFactory.updateHeader(currentUser).success(function (data) {
currentUser.editMode = false;
$('#userModel').modal('hide');
}).error(function (data) {
$scope.error = "An Error has occured while Updating user! " + data.ExceptionMessage;
});
};
//Model popup events
$scope.showadd = function () {
$scope.user = null;
$scope.editMode = false;
$('#userModel').modal('show');
};
$scope.showedit = function () {
$('#userModel').modal('show');
};
$scope.cancel = function () {
$scope.user = null;
$('#userModel').modal('hide');
}
// initialize your users data
$scope.getAll();
});
<li></i>AddHeader</li>
<li></i>ProjectIDCreation</li>
(in .html don't forget / )
.config(
[ '$locationProvider', '$routeProvider',
function config($locationProvider, $routeProvider) {
$locationProvider.hashPrefix('!');
.when('/AddHeader', {
templateUrl: 'AddHeader.html',
controller: 'headerCtrl'
})
.when('/ProjectIDCreation', {
templateUrl: '/ProjectIDCreation.html',
controller: 'headerCtrl'
})
.otherwise('/', {
templateUrl: 'AddHeader.html',
controller: 'headerCtrl'
})
}
]);
I am getting closer in my quest for a JSON response. In this example, the events variable gets populated AFTER it's returned, causing a blank output. I need it to wait. I have read that a promise is the way to go... but not sure how that would work... in my console.log you can see the array but Events.all(); returns null.
.factory('Events', function($http) {
var events="";
$http.get('http://appserver.falconinet.com/events.lasso').then(function(resp) {
events = resp.data;
console.log(events);
}, function(err) {
console.error('ERR', err);
// err.status will contain the status code
})
return {
all: function() {
return events;
},
get: function(eventId) {
for (var i = 0; i < events.length; i++) {
if (events[i].id === parseInt(eventId)) {
return events[i];
}
}
return null;
}
}
})
and here is my controller:
// events
.controller('EventsCtrl', function($scope, Events) {
$scope.events = Events.all();
})
.controller('EventDetailCtrl', function($scope, $stateParams, Events) {
$scope.event = Events.get($stateParams.eventId);
})
Following will return the promise created by $http as well as caches the loading of all events.
.factory('Events', function ($http, $q) {
function loadEvents(id) {
var promise = $http.get('http://appserver.falconinet.com/events.lasso', {cache: true});
// return the promise
return promise.then(function (resp) {
var events = resp.data;
if (id) {
// returns item or promise rejection
return getEventById(id, events);
} else {
return events;
}
}).catch (function (err) {
console.log('Events error ', err);
});
}
// helper function , returns event or promise rejection
function getEventById(id, events) {
for (var i = 0; i < events.length; i++) {
if (events[i].id === parseInt(eventId)) {
return events[i];
}
}
return $q.reject('None found');
}
return {
all: function () {
return loadEvents();
},
get: function (eventId) {
return loadEvents(eventId);
}
}
});
Then in controllers you need to resove your data in the promise then callback
.controller('EventsCtrl', function($scope, Events) {
Events.all().then(function(events){
$scope.events = events;
});
})
.controller('EventDetailCtrl', function($scope, $stateParams, Events) {
Events.get($stateParams.eventId).then(function(event){
$scope.event = event;
});
})
As you mentioned, wrapping your actual process in a new promise is the way to go. In order to do so, the usage of this factory needs some tweaks. Lemme try to write a sample from your script, but I don't promise to get it working on the first try :)
.factory('Events', function($http, $q) {
return {
all: function() {
var myPromise = $q.defer();
$http.get('http://appserver.falconinet.com/events.lasso')
.then(function(resp) {
myPromise.resolve(resp.data);
}, function(err) {
myPromise.reject(err);
});
return myPromise.promise;
},
get: function(eventId) {
var myPromise = $q.defer();
$http.get('http://appserver.falconinet.com/events.lasso')
.then(function(resp) {
var events = resp.data;
for (var i = 0; i < events.length; i++) {
if (events[i].id === parseInt(eventId)) {
myPromise.resolve(events[i]);
}
}
}, function(err) {
myPromise.reject(err);
});
return myPromise.promise;
}
}
});
Besides everything, this can be improved as you placer but I found pretty straightforward to do so after knowing how to handle promises.
Using them would be like this:
// Get all
Events.all().then(function(events){
$scope.events = events;
});
// Get one
Events.get(eventId).then(function(event){
$scope.events = event;
});
Basically I am testing to see how a PROD version of my app is looking; I proceeded to run it through some gulp tasks (minify, strip unused css etc.) and got this error:
Error: [$injector:unpr] Unknown provider: tProvider <- t <- myActiveLinkDirective
Can anyone help with what's going on here?
This is some my angular code:
var rustyApp = angular.module('rustyApp', [
'ngAnimate',
'ngRoute',
'viewController',
'mm.foundation',
'angular-flexslider',
'ui.router']).config(['$routeProvider', '$locationProvider', function($routeProvider, $locationProvider) {
$routeProvider.when('/', {
title: 'home',
templateUrl: '/partials/home.html',
controller: 'HomeController'
}).when('/work', {
title: 'my work',
templateUrl: '/partials/work.html',
controller: 'WorkController'
}).when('/contact', {
title: 'contact',
templateUrl: '/partials/contact.html',
controller: 'ContactController'
}).otherwise({redirectTo: '/'});
// configure html5 to get links working
$locationProvider.html5Mode(true);
}]);
rustyApp.controller('BasicSliderCtrl', function($scope) {
$scope.slides = [
'../images/sliderContent/1.jpg',
'../images/sliderContent/2.jpg',
'../images/sliderContent/3.jpg',
'../images/sliderContent/4.jpg'
];
});
rustyApp.run(function() {
FastClick.attach(document.body);
});
rustyApp.run(['$location', '$rootScope', function($location, $rootScope) {
$rootScope.$on('$routeChangeSuccess', function(event, current, previous) {
$rootScope.title = current.$$route.title;
});
}]);
rustyApp.controller('HomeController', function($scope) {
$scope.pageClass = 'home';
});
rustyApp.controller('WorkController', function($scope) {
$scope.pageClass = 'work';
});
rustyApp.controller('ContactController', function($scope) {
$scope.pageClass = 'contact';
});
rustyApp.controller('OffCanvasDemoCtrl', function($scope) {});
var OffCanvasDemoCtrl = function($scope) {};
rustyApp.controller('ContactController', function($scope, $http) {
$scope.result = 'hidden'
$scope.resultMessage;
$scope.formData; //formData is an object holding the name, email, subject, and message
$scope.submitButtonDisabled = false;
$scope.submitted = false; //used so that form errors are shown only after the form has been submitted
$scope.submit = function(contactform) {
$scope.submitted = true;
$scope.submitButtonDisabled = true;
if (contactform.$valid) {
$http({
method: 'POST',
url: '../partials/mailer.php',
data: $.param($scope.formData), //param method from jQuery
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
} //set the headers so angular passing info as form data (not request payload)
}).success(function(data) {
console.log(data);
if (data.success) { //success comes from the return json object
$scope.submitButtonDisabled = true;
$scope.resultMessage = data.message;
$scope.result = 'bg-success';
if ($scope.result === 'bg-success') {
$scope.class = "bg-success";
}
// if($scope.result){setTimeout(window.location.reload(true),4000);}
if ($scope.result) {
setTimeout(function() {
window.location.reload(true)
}, 4000);
}
} else {
$scope.submitButtonDisabled = false;
$scope.resultMessage = data.message;
$scope.result = 'bg-danger';
}
});
} else {
$scope.submitButtonDisabled = false;
if ($scope.submitButtonDisabled) {
$scope.class = "bg-danger";
}
$scope.resultMessage = 'Failed Please fill out all the fields.';
$scope.result = 'bg-danger';
}
}
});
var viewController = angular.module('viewController', []);
rustyApp.directive('myActiveLink', function($location) {
return {
restrict: 'A',
scope: {
path: "#myActiveLink"
},
link: function(scope, element, attributes) {
scope.$on('$locationChangeSuccess', function() {
if ($location.path() === scope.path) {
element.addClass('uk-active');
} else {
element.removeClass('uk-active');
}
});
}
};
});
// var $j = jQuery.noConflict();
// $j(function() {
// $j('#Container').mixItUp();
// });
rustyApp.directive('mixItUp', function() {
var directive = {
restrict: 'A',
link: link
};
return directive;
function link(scope, element, attrs) {
var $j = jQuery.noConflict();
var mixContainer = $j('#Container');
mixContainer.mixItUp();
mixContainer.on('$destroy', function() {
mixContainer.mixItUp('destroy');
});
}
});
rustyApp.directive('share', function() {
var directive = {
restrict: 'A',
link: link
};
return directive;
function link(scope, element, attrs) {
var $s = jQuery.noConflict();
// mixContainer.on('$destroy', function() {
// mixContainer.mixItUp('destroy');
// });
var $s = new Share(".share-button", {
networks: {
facebook: {
app_id: "602752456409826",
}
}
});
}
});
rustyApp.directive('animationOverlay', function() {
var directive = {
restrict: 'A',
link: link
};
return directive;
function link(scope, element, attrs) {
var modal = $.UIkit.modal(".modalSelector");
if (modal.isActive()) {
modal.hide();
} else {
modal.show();
}
}
});
UPDATED CODE
var rustyApp = angular.module('rustyApp', [
'ngAnimate',
'ngRoute',
'viewController',
'mm.foundation',
'angular-flexslider',
'ui.router'
]).config(['$routeProvider', '$locationProvider', function($routeProvider, $locationProvider) {
$routeProvider.when('/', {
title: 'home',
templateUrl: '/partials/home.html',
controller: 'HomeController'
}).when('/work', {
title: 'my work',
templateUrl: '/partials/work.html',
controller: 'WorkController'
}).when('/contact', {
title: 'contact',
templateUrl: '/partials/contact.html',
controller: 'ContactController'
}).otherwise({redirectTo: '/'});
// configure html5 to get links working
$locationProvider.html5Mode(true);
}]);
rustyApp.controller('BasicSliderCtrl', ['$scope',
function($scope) {
$scope.slides = [
'../images/sliderContent/1.jpg',
'../images/sliderContent/2.jpg',
'../images/sliderContent/3.jpg',
'../images/sliderContent/4.jpg'
];
}]);
rustyApp.run(function() {
FastClick.attach(document.body);
});
rustyApp.run(['$location', '$rootScope', function($location, $rootScope) {
$rootScope.$on('$routeChangeSuccess', function(event, current, previous) {
$rootScope.title = current.$$route.title;
});
}]);
rustyApp.controller('HomeController', ['$scope', function($scope) {
$scope.pageClass = 'home';
}]);
rustyApp.controller('WorkController', ['$scope', function($scope) {
$scope.pageClass = 'work';
}]);
rustyApp.controller('ContactController', ['$scope', function($scope) {
$scope.pageClass = 'contact';
}]);
rustyApp.controller('OffCanvasDemoCtrl', ['$scope', function($scope) {}]);
var OffCanvasDemoCtrl = function($scope) {};
rustyApp.controller('ContactController', ['$scope', function($scope, $http) {
$scope.result = 'hidden'
$scope.resultMessage;
$scope.formData; //formData is an object holding the name, email, subject, and message
$scope.submitButtonDisabled = false;
$scope.submitted = false; //used so that form errors are shown only after the form has been submitted
$scope.submit = function(contactform) {
$scope.submitted = true;
$scope.submitButtonDisabled = true;
if (contactform.$valid) {
$http({
method: 'POST',
url: '../partials/mailer.php',
data: $.param($scope.formData), //param method from jQuery
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
} //set the headers so angular passing info as form data (not request payload)
}).success(function(data) {
console.log(data);
if (data.success) { //success comes from the return json object
$scope.submitButtonDisabled = true;
$scope.resultMessage = data.message;
$scope.result = 'bg-success';
if ($scope.result === 'bg-success') {
$scope.class = "bg-success";
}
// if($scope.result){setTimeout(window.location.reload(true),4000);}
if ($scope.result) {
setTimeout(function() {
window.location.reload(true)
}, 4000);
}
} else {
$scope.submitButtonDisabled = false;
$scope.resultMessage = data.message;
$scope.result = 'bg-danger';
}
});
} else {
$scope.submitButtonDisabled = false;
if ($scope.submitButtonDisabled) {
$scope.class = "bg-danger";
}
$scope.resultMessage = 'Failed Please fill out all the fields.';
$scope.result = 'bg-danger';
}
}
}]);
var viewController = angular.module('viewController', []);
rustyApp.directive('myActiveLink', ['$location', function($location) {
return {
restrict: 'A',
scope: {
path: "#myActiveLink"
},
link: function(scope, element, attributes) {
scope.$on('$locationChangeSuccess', function() {
if ($location.path() === scope.path) {
element.addClass('uk-active');
} else {
element.removeClass('uk-active');
}
});
}
};
}]);
// var $j = jQuery.noConflict();
// $j(function() {
// $j('#Container').mixItUp();
// });
rustyApp.directive('mixItUp', function() {
var directive = {
restrict: 'A',
link: link
};
return directive;
function link(scope, element, attrs) {
var $j = jQuery.noConflict();
var mixContainer = $j('#Container');
mixContainer.mixItUp();
mixContainer.on('$destroy', function() {
mixContainer.mixItUp('destroy');
});
}
});
rustyApp.directive('share', function() {
var directive = {
restrict: 'A',
link: link
};
return directive;
function link(scope, element, attrs) {
var $s = jQuery.noConflict();
// mixContainer.on('$destroy', function() {
// mixContainer.mixItUp('destroy');
// });
var $s = new Share(".share-button", {
networks: {
facebook: {
app_id: "602752456409826",
}
}
});
}
});
rustyApp.directive('animationOverlay', function() {
var directive = {
restrict: 'A',
link: link
};
return directive;
function link(scope, element, attrs) {
var modal = $.UIkit.modal(".modalSelector");
if (modal.isActive()) {
modal.hide();
} else {
modal.show();
}
}
});
UPDATE
So I wound using gulp-ng-annotateand it appears to add the syntax which was suggested below :) However when I try a PROD build I don't get any errors or anything, it just fails silently. Can anyone help?
I posted the generic answer below before I had a chance to see the rest of the code you posted. Yes, indeed you have some controllers and directives that use inference. Change your code to use inline annotation, the $inject property; or less intrusively, use a tool like ng-annotate (thanks #deitch for the pointer).
If you're minifying your code, don't use the inference style of dependency annotation. Use the $inject property or use inline annotation. See https://docs.angularjs.org/api/auto/service/$injector.
Example
Don't rely on inference:
function ($scope, $timeout, myFooService) {
}
Use either inline annotation:
[ '$scope', '$timeout', 'myFooService', function ($scope, $rootScope, myFooService) {
}]
Or the $inject property:
function MyFactory($scope, $timeout, myFooService) {
}
MyFactory.$inject = [ '$scope', '$timeout', 'myFooService' ];
This is because the inference flavour relies on the argument names of the function to be preserved (and match to existing services). You might be losing the original names during minification.
Set mangle: false.
This setting solved same problem that I had.
var $ = require('gulp-load-plugins')();
$.uglify({
mangle: false,
compress:true,
output: {
beautify: false
}
});
Do I have to move my getTemplates function out of the return or what ?
Example : I dont know what to replace "XXXXXXX" with (I have tried "this/self/templateFactory" etc... ) :
.factory('templateFactory', [
'$http',
function($http) {
var templates = [];
return {
getTemplates : function () {
$http
.get('../api/index.php/path/templates.json')
.success ( function (data) {
templates = data;
});
return templates;
},
delete : function (id) {
$http.delete('../api/index.php/path/templates/' + id + '.json')
.success(function() {
templates = XXXXXXX.getTemplates();
});
}
};
}
])
By doing templates = this.getTemplates(); you are referring to an object property that is not yet instantiated.
Instead you can gradually populate the object:
.factory('templateFactory', ['$http', function($http) {
var templates = [];
var obj = {};
obj.getTemplates = function(){
$http.get('../api/index.php/path/templates.json')
.success ( function (data) {
templates = data;
});
return templates;
}
obj.delete = function (id) {
$http.delete('../api/index.php/path/templates/' + id + '.json')
.success(function() {
templates = obj.getTemplates();
});
}
return obj;
}]);
How about this?
.factory('templateFactory', [
'$http',
function($http) {
var templates = [];
var some_object = {
getTemplates: function() {
$http
.get('../api/index.php/path/templates.json')
.success(function(data) {
templates = data;
});
return templates;
},
delete: function(id) {
$http.delete('../api/index.php/path/templates/' + id + '.json')
.success(function() {
templates = some_object.getTemplates();
});
}
};
return some_object
}
])